///|
fn variable_name(name : String) -> Bool {
let chars = name.to_array()
!chars.is_empty() && name_start(chars[0]) && chars.all(name_char)
}
///|
fn Runtime::expand(
self : Runtime,
text : String,
span : Span,
) -> String raise SieveError {
if !self.variables_enabled {
return text
}
let chars = text.to_array()
let out = StringBuilder()
let mut size = 0
let mut i = 0
while i < chars.length() {
self.budget.step(span)
if chars[i] == '$' && i + 1 < chars.length() && chars[i + 1] == '{' {
let mut end = i + 2
while end < chars.length() && chars[end] != '}' {
self.budget.step(span)
end += 1
}
if end < chars.length() {
let name = chars_text(chars, i + 2, end)
let numeric = !name.is_empty() && name.all(c => c.is_ascii_digit())
if variable_name(name) || numeric {
let value = if numeric {
let mut index = 0
for c in name {
index = index * 10 + c.to_int() - 48
if index > 9 {
fail(
"variable.capture", "only match variables 0 through 9 are supported",
span,
)
}
}
self.captures[index]
} else {
self.variables.get(ascii_lower(name)).unwrap_or("")
}
size += value.length()
if size > self.budget.limits.string_chars {
fail("limit.expansion", "expanded string too long", span)
}
out.write_string(value)
i = end + 1
continue
}
}
}
size += chars[i].utf16_len()
if size > self.budget.limits.string_chars {
fail("limit.expansion", "expanded string too long", span)
}
out.write_char(chars[i])
i += 1
}
out.to_string()
}
///|
fn Runtime::expanded_strings(
self : Runtime,
arg : Argument,
span : Span,
) -> Array[String] raise SieveError {
let out : Array[String] = []
for text in strings_arg(arg, span) {
out.push(self.expand(text, span))
}
out
}
///|
fn modify_variable(value : String, flags : Array[String]) -> String {
let mut result = value
if flags.contains("lower") {
result = result.to_lower()
}
if flags.contains("upper") {
result = result.to_upper()
}
if flags.contains("lowerfirst") || flags.contains("upperfirst") {
let chars = result.to_array()
if !chars.is_empty() {
let first = chars_text(chars, 0, 1)
result = (if flags.contains("lowerfirst") {
first.to_lower()
} else {
first.to_upper()
}) +
chars_text(chars, 1, chars.length())
}
}
if flags.contains("quotewildcard") {
let out = StringBuilder()
for c in result {
if c == '*' || c == '?' || c == '\\' {
out.write_char('\\')
}
out.write_char(c)
}
result = out.to_string()
}
if flags.contains("length") {
result = result.char_length().to_string()
}
result
}
///|
fn check_set(
a : Arguments,
caps : Array[String],
span : Span,
) -> Unit raise SieveError {
needs(caps, "variables", span)
allowed_flags(
a,
["lower", "upper", "lowerfirst", "upperfirst", "quotewildcard", "length"],
span,
)
exclusive(a, ["lower", "upper"], span)
exclusive(a, ["lowerfirst", "upperfirst"], span)
count_args(a, 2, span)
if !variable_name(single_arg(a.values[0], span)) {
fail(
"check.variable", "set target must be an identifier, not a match variable",
span,
)
}
ignore(single_arg(a.values[1], span))
}
///|
fn Runtime::set_variable(
self : Runtime,
a : Arguments,
span : Span,
) -> Unit raise SieveError {
let name = ascii_lower(single_arg(a.values[0], span))
let value = modify_variable(
self.expand(single_arg(a.values[1], span), span),
a.flags,
)
if value.length() > self.budget.limits.string_chars {
fail("limit.expansion", "variable value exceeds limit", span)
}
if !self.variables.contains(name) &&
self.variables.length() >= self.budget.limits.variables {
fail("limit.variables", "variable count exhausted", span)
}
self.variables[name] = value
self.record("variable", name, span, None)
}
///|
fn Runtime::matching(
self : Runtime,
value : String,
key : String,
a : Arguments,
span : Span,
) -> Bool raise SieveError {
if !self.variables_enabled || mode_from(a) != Matches {
return compare_text(
value,
key,
mode_from(a),
comparator_from(a),
self.budget,
span,
)
}
if value.length() > self.budget.limits.string_chars ||
key.length() > self.budget.limits.string_chars {
fail("limit.string", "matching input too long", span)
}
let text = value.to_array()
let units = glob_units(key, comparator_from(a))
let width = text.length() + 1
if units.length() + 1 >
(self.budget.limits.comparisons - self.budget.comparisons) / width {
fail("limit.comparisons", "capture table exceeds matching budget", span)
}
self.budget.compare(width * (units.length() + 1), span)
self.budget.step(span)
let table = Array::make(width * (units.length() + 1), false)
table[units.length() * width + text.length()] = true
let mut row = units.length() - 1
while row >= 0 {
let mut col = text.length()
while col >= 0 {
table[row * width + col] = match units[row] {
Many =>
table[(row + 1) * width + col] ||
(col < text.length() && table[row * width + col + 1])
One => col < text.length() && table[(row + 1) * width + col + 1]
Exact(c) =>
col < text.length() &&
fold_char(text[col], comparator_from(a)) == c &&
table[(row + 1) * width + col + 1]
}
col -= 1
}
row -= 1
}
if !table[0] {
return false
}
for n = 0; n <= 9; n = n + 1 {
self.captures[n] = ""
}
self.captures[0] = value
let mut pos = 0
let mut capture = 1
for i = 0; i < units.length(); i = i + 1 {
match units[i] {
Exact(_) => pos += 1
One => {
if capture <= 9 {
self.captures[capture] = chars_text(text, pos, pos + 1)
}
capture += 1
pos += 1
}
Many => {
let mut end = text.length()
while end > pos && !table[(i + 1) * width + end] {
end -= 1
}
if capture <= 9 {
self.captures[capture] = chars_text(text, pos, end)
}
capture += 1
pos = end
}
}
}
true
}