///|
pub suberror ConditionError {
InvalidCondition(String)
} derive(Debug)
///|
pub extend ConditionError with @debug.Debug::{to_repr}
///|
priv struct ConditionParser {
chars : Array[Char]
symbols : Array[String]
mut pos : Int
}
///|
/// Evaluate $NAME, !, &&, || and parentheses. Symbols are explicit and ASCII
/// case-insensitive; unknown symbols are false. No host platform is inferred.
pub fn evaluate_condition(
expression : String,
symbols : Array[String],
) -> Bool raise ConditionError {
if expression.char_length() > 4096 {
raise InvalidCondition("Expression exceeds 4096 characters")
}
let p = ConditionParser::{ chars: expression.to_array(), symbols, pos: 0, }
let value = p.read_or(0)
p.space()
if p.pos != p.chars.length() {
raise InvalidCondition("Unexpected conditional token")
}
value
}
///|
fn ConditionParser::space(self : ConditionParser) -> Unit {
while self.chars.get(self.pos) is Some(c) && is_space(c) {
self.pos += 1
}
}
///|
fn ConditionParser::take(self : ConditionParser, c : Char) -> Bool {
self.space()
if self.chars.get(self.pos) == Some(c) {
self.pos += 1
true
} else {
false
}
}
///|
fn ConditionParser::read_or(
self : ConditionParser,
depth : Int,
) -> Bool raise ConditionError {
let mut value = self.read_and(depth)
while self.take('|') {
if self.chars.get(self.pos) != Some('|') {
raise InvalidCondition("Expected ||")
}
self.pos += 1
let rhs = self.read_and(depth)
value = value || rhs
}
value
}
///|
fn ConditionParser::read_and(
self : ConditionParser,
depth : Int,
) -> Bool raise ConditionError {
let mut value = self.read_atom(depth)
while self.take('&') {
if self.chars.get(self.pos) != Some('&') {
raise InvalidCondition("Expected &&")
}
self.pos += 1
let rhs = self.read_atom(depth)
value = value && rhs
}
value
}
///|
fn ConditionParser::read_atom(
self : ConditionParser,
depth : Int,
) -> Bool raise ConditionError {
if depth > 64 {
raise InvalidCondition("Conditional nesting exceeds 64")
}
if self.take('!') {
return !self.read_atom(depth + 1)
}
if self.take('(') {
let value = self.read_or(depth + 1)
if !self.take(')') {
raise InvalidCondition("Missing closing parenthesis")
}
return value
}
if !self.take('$') {
raise InvalidCondition("Expected $NAME")
}
let name = StringBuilder()
while self.chars.get(self.pos) is Some(c) &&
(
(c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') ||
c == '_'
) {
name.write_char(c)
self.pos += 1
}
let text = name.to_string()
if text.is_empty() {
raise InvalidCondition("Empty symbol")
}
self.symbols.any(s => {
s.equal_ignore_ascii_case(text) || s.equal_ignore_ascii_case("$" + text)
})
}