///|
/// Dynamic values exposed to custom functions and filters.
pub(all) enum Value {
Undefined
Null
IntValue(Int)
DoubleValue(Double)
BoolValue(Bool)
StrValue(String)
SafeStr(String)
ListValue(Array[Value])
MapValue(Map[String, Value])
}
///|
fn escape_html(source : String) -> String {
let output = StringBuilder(size_hint=source.length())
for char in source {
match char {
'&' => output.write_string("&")
'<' => output.write_string("<")
'>' => output.write_string(">")
'"' => output.write_string(""")
'\'' => output.write_string("'")
_ => output.write_char(char)
}
}
output.to_string()
}
///|
fn utf8_length(source : String) -> Int {
let mut length = 0
for char in source {
length += utf8_char_size(char)
}
length
}
///|
fn display_string(value : Value, autoescape? : Bool = false) -> String {
match value {
SafeStr(text) => text
StrValue(text) => if autoescape { escape_html(text) } else { text }
_ => {
let text = value_to_string(value)
if autoescape {
escape_html(text)
} else {
text
}
}
}
}
///|
fn is_ws(char : Char) -> Bool {
char == ' ' || char == '\t' || char == '\n' || char == '\r'
}
///|
fn trim_ws(source : String) -> String {
let chars = source.to_array()
let mut left = 0
let mut right = chars.length()
while left < right && is_ws(chars[left]) {
left += 1
}
while left < right && is_ws(chars[right - 1]) {
right -= 1
}
String::from_array(chars[left:right].to_owned())
}
///|
fn trim_ws_start(source : String) -> String {
let chars = source.to_array()
let mut left = 0
while left < chars.length() && is_ws(chars[left]) {
left += 1
}
String::from_array(chars[left:].to_owned())
}
///|
fn trim_ws_end(source : String) -> String {
let chars = source.to_array()
let mut right = chars.length()
while right > 0 && is_ws(chars[right - 1]) {
right -= 1
}
String::from_array(chars[:right].to_owned())
}
///|
fn value_to_string(value : Value) -> String {
match value {
Null | Undefined => ""
IntValue(number) => number.to_string()
DoubleValue(number) => number.to_string()
BoolValue(flag) => if flag { "true" } else { "false" }
StrValue(text) | SafeStr(text) => text
ListValue(items) => items.map(value_to_string).join(", ")
MapValue(_) => "[object]"
}
}
///|
fn is_truthy(value : Value) -> Bool {
match value {
Null | Undefined => false
BoolValue(flag) => flag
StrValue(text) | SafeStr(text) => text != ""
IntValue(number) => number != 0
DoubleValue(number) => number != 0.0
ListValue(items) => !items.is_empty()
MapValue(entries) => !entries.is_empty()
}
}
///|
fn apply_binop(
lhs : Value,
operator : BinOp,
rhs : Value,
) -> Value raise JinjaError {
if operator == In || operator == NotIn {
let included = contains_value(rhs, lhs)
return BoolValue(if operator == NotIn { !included } else { included })
}
if operator == Concat {
return StrValue(value_to_string(lhs) + value_to_string(rhs))
}
match (lhs, rhs) {
(IntValue(left), IntValue(right)) =>
match operator {
Add => IntValue(left + right)
Sub => IntValue(left - right)
Mul => IntValue(left * right)
Div =>
if right == 0 {
raise RenderError("Division by zero")
} else {
DoubleValue(left.to_double() / right.to_double())
}
FloorDiv =>
if right == 0 {
raise RenderError("Division by zero")
} else {
let quotient = left / right
let remainder = left % right
IntValue(
if remainder != 0 && (remainder < 0) != (right < 0) {
quotient - 1
} else {
quotient
},
)
}
Mod =>
if right == 0 {
raise RenderError("Modulo by zero")
} else {
let remainder = left % right
IntValue(
if remainder != 0 && (remainder < 0) != (right < 0) {
remainder + right
} else {
remainder
},
)
}
Pow => apply_integer_pow(left, right)
Eq => BoolValue(left == right)
Ne => BoolValue(left != right)
Lt => BoolValue(left < right)
Le => BoolValue(left <= right)
Gt => BoolValue(left > right)
Ge => BoolValue(left >= right)
_ => raise RenderError("Unsupported numeric operation")
}
(DoubleValue(left), DoubleValue(right)) =>
apply_double_binop(left, operator, right)
(IntValue(left), DoubleValue(right)) =>
apply_double_binop(left.to_double(), operator, right)
(DoubleValue(left), IntValue(right)) =>
apply_double_binop(left, operator, right.to_double())
(StrValue(left), StrValue(right)) =>
match operator {
Add => StrValue(left + right)
Eq => BoolValue(left == right)
Ne => BoolValue(left != right)
_ => raise RenderError("Unsupported string operation")
}
(BoolValue(left), BoolValue(right)) =>
match operator {
And => BoolValue(left && right)
Or => BoolValue(left || right)
Eq => BoolValue(left == right)
Ne => BoolValue(left != right)
_ => raise RenderError("Unsupported boolean operation")
}
(Null, Null) =>
match operator {
Eq => BoolValue(true)
Ne => BoolValue(false)
_ => raise RenderError("Unsupported null operation")
}
_ => raise RenderError("Type mismatch in binary expression")
}
}
///|
fn apply_integer_pow(base : Int, exponent : Int) -> Value {
if exponent < 0 {
return DoubleValue(@math.pow(base.to_double(), exponent.to_double()))
}
let mut result = 1
let mut factor = base
let mut remaining = exponent
while remaining > 0 {
if remaining % 2 == 1 {
result *= factor
}
remaining /= 2
if remaining > 0 {
factor *= factor
}
}
IntValue(result)
}
///|
fn contains_value(container : Value, needle : Value) -> Bool raise JinjaError {
match (container, needle) {
(StrValue(text) | SafeStr(text), StrValue(part) | SafeStr(part)) =>
text.contains(part)
(MapValue(entries), StrValue(key) | SafeStr(key)) => entries.contains(key)
(ListValue(items), value) =>
items.any(item => primitive_values_equal(item, value))
_ => raise RenderError("Right operand of 'in' is not iterable")
}
}
///|
fn primitive_values_equal(left : Value, right : Value) -> Bool {
match (left, right) {
(Undefined, Undefined) | (Null, Null) => true
(IntValue(a), IntValue(b)) => a == b
(DoubleValue(a), DoubleValue(b)) => a == b
(IntValue(a), DoubleValue(b)) => a.to_double() == b
(DoubleValue(a), IntValue(b)) => a == b.to_double()
(BoolValue(a), BoolValue(b)) => a == b
(StrValue(a) | SafeStr(a), StrValue(b) | SafeStr(b)) => a == b
_ => false
}
}
///|
fn apply_double_binop(
left : Double,
operator : BinOp,
right : Double,
) -> Value raise JinjaError {
match operator {
Add => DoubleValue(left + right)
Sub => DoubleValue(left - right)
Mul => DoubleValue(left * right)
Div =>
if right == 0.0 {
raise RenderError("Division by zero")
} else {
DoubleValue(left / right)
}
FloorDiv =>
if right == 0.0 {
raise RenderError("Division by zero")
} else {
DoubleValue((left / right).floor())
}
Mod =>
if right == 0.0 {
raise RenderError("Modulo by zero")
} else {
DoubleValue(left - (left / right).floor() * right)
}
Pow => DoubleValue(@math.pow(left, right))
Eq => BoolValue(left == right)
Ne => BoolValue(left != right)
Lt => BoolValue(left < right)
Le => BoolValue(left <= right)
Gt => BoolValue(left > right)
Ge => BoolValue(left >= right)
_ => raise RenderError("Unsupported numeric operation")
}
}