///|
fn is_comprehension_macro(name : String) -> Bool {
name == "all" ||
name == "exists" ||
name == "exists_one" ||
name == "map" ||
name == "filter"
}
///|
fn macro_element_type(collection : CelType) -> CelType? {
match collection {
CelType::List(element) => Some(element)
CelType::Map(key, _) => Some(key)
CelType::Dyn => Some(CelType::Dyn)
_ => None
}
}
///|
fn macro_variable(argument : Expr) -> String? {
match argument.kind {
ExprKind::Identifier(name) => Some(name)
_ => None
}
}
///|
fn Checker::infer_has_macro(
self : Checker,
expr : Expr,
arguments : Array[Expr],
) -> CelType {
if arguments.length() != 1 {
self.error(
"CEL-CHECK-MACRO-ARITY",
"has requires exactly one field selection or index",
expr.span,
)
for argument in arguments {
ignore(self.infer(argument))
}
return self.record(expr, CelType::Bool)
}
let argument = arguments[0]
match argument.kind {
ExprKind::Select(_, _) | ExprKind::Index(_, _) =>
ignore(self.infer(argument))
_ => {
ignore(self.infer(argument))
self.error(
"CEL-CHECK-HAS",
"has argument must be a field selection or index",
argument.span,
)
}
}
self.record(expr, CelType::Bool)
}
///|
fn Checker::with_macro_variable(
self : Checker,
name : String,
cel_type : CelType,
body : Expr,
) -> CelType {
let previous = self.env
self.env = self.env.with_variable(name, cel_type)
let result = self.infer(body)
self.env = previous
result
}
///|
fn Checker::macro_predicate(
self : Checker,
variable : String,
element : CelType,
predicate : Expr,
) -> Unit {
let predicate_type = self.with_macro_variable(variable, element, predicate)
ignore(
self.require(
predicate_type,
CelType::Bool,
predicate.span,
"macro predicate",
),
)
}
///|
fn Checker::infer_map_macro(
self : Checker,
expr : Expr,
element_type : CelType,
variable : String,
arguments : Array[Expr],
) -> CelType {
let result = if arguments.length() == 2 {
self.with_macro_variable(variable, element_type, arguments[1])
} else if arguments.length() == 3 {
self.macro_predicate(variable, element_type, arguments[1])
self.with_macro_variable(variable, element_type, arguments[2])
} else {
self.error(
"CEL-CHECK-MACRO-ARITY",
"map requires (variable, transform) or (variable, predicate, transform)",
expr.span,
)
CelType::Dyn
}
self.record(expr, CelType::List(result))
}
///|
fn Checker::infer_comprehension_macro(
self : Checker,
expr : Expr,
receiver : Expr,
name : String,
arguments : Array[Expr],
) -> CelType {
let collection_type = self.infer(receiver)
let element_type = match macro_element_type(collection_type) {
Some(value) => value
None => {
self.error(
"CEL-CHECK-MACRO-TARGET",
"\{name} requires a list or map receiver, found \{collection_type.display()}",
receiver.span,
)
CelType::Dyn
}
}
if arguments.is_empty() {
self.error(
"CEL-CHECK-MACRO-ARITY",
"\{name} requires an iteration variable and body",
expr.span,
)
return self.record(expr, CelType::Dyn)
}
let variable = match macro_variable(arguments[0]) {
Some(value) => value
None => {
self.error(
"CEL-CHECK-MACRO-VARIABLE",
"first \{name} argument must be an identifier",
arguments[0].span,
)
"__invalid_macro_variable"
}
}
if name == "map" {
return self.infer_map_macro(expr, element_type, variable, arguments)
}
if arguments.length() != 2 {
self.error(
"CEL-CHECK-MACRO-ARITY",
"\{name} requires exactly an iteration variable and predicate",
expr.span,
)
return self.record(expr, CelType::Dyn)
}
self.macro_predicate(variable, element_type, arguments[1])
let result = if name == "filter" { collection_type } else { CelType::Bool }
self.record(expr, result)
}
///|
fn macro_items(
value : Value,
span : SourceSpan,
) -> Array[Value] raise EvalError {
match value {
Value::List(items) => items
Value::Map(entries) => entries.map(entry => entry.key)
other => {
ignore(type_mismatch("list or map", other, span))
[]
}
}
}
///|
fn macro_binding(
arguments : Array[Expr],
span : SourceSpan,
) -> String raise EvalError {
if arguments.is_empty() {
raise InvalidArgument(message="macro requires an iteration variable", span~)
}
match arguments[0].kind {
ExprKind::Identifier(name) => name
_ =>
raise InvalidArgument(
message="macro variable must be an identifier",
span~,
)
}
}
///|
fn Evaluator::eval_scoped(
self : Evaluator,
variable : String,
value : Value,
body : Expr,
) -> Value raise EvalError {
let previous = self.activation
self.activation = self.activation.bind(variable, value)
let result = self.eval_expr(body)
self.activation = previous
result
}
///|
fn Evaluator::eval_has(
self : Evaluator,
arguments : Array[Expr],
span : SourceSpan,
) -> Value raise EvalError {
if arguments.length() != 1 {
raise InvalidArgument(message="has requires one argument", span~)
}
let present = match arguments[0].kind {
ExprKind::Select(target, field) =>
match self.eval_expr(target) {
Value::Map(entries) =>
entries.any(entry => entry.key == Value::String(field))
_ => false
}
ExprKind::Index(target, index) => {
let key = self.eval_expr(index)
match self.eval_expr(target) {
Value::List(items) =>
match key {
Value::Int(position) => {
let position = position.to_int()
position >= 0 && position < items.length()
}
_ => false
}
Value::Map(entries) => entries.any(entry => entry.key == key)
_ => false
}
}
_ => false
}
Value::Bool(present)
}
///|
fn Evaluator::eval_quantifier(
self : Evaluator,
name : String,
variable : String,
items : Array[Value],
predicate : Expr,
) -> Value raise EvalError {
let mut matches = 0
for item in items {
self.charge_iteration(predicate.span)
let accepted = require_bool(
self.eval_scoped(variable, item, predicate),
predicate.span,
)
if name == "all" && !accepted {
return Value::Bool(false)
}
if name == "exists" && accepted {
return Value::Bool(true)
}
if accepted {
matches = matches + 1
if name == "exists_one" && matches > 1 {
return Value::Bool(false)
}
}
}
match name {
"all" => Value::Bool(true)
"exists" => Value::Bool(false)
"exists_one" => Value::Bool(matches == 1)
_ => raise UnknownFunction(name~, span=predicate.span)
}
}
///|
fn Evaluator::eval_filter(
self : Evaluator,
variable : String,
items : Array[Value],
predicate : Expr,
) -> Value raise EvalError {
let selected : Array[Value] = []
for item in items {
self.charge_iteration(predicate.span)
if require_bool(self.eval_scoped(variable, item, predicate), predicate.span) {
selected.push(item)
}
}
Value::List(selected)
}
///|
fn Evaluator::eval_map_macro(
self : Evaluator,
variable : String,
items : Array[Value],
arguments : Array[Expr],
span : SourceSpan,
) -> Value raise EvalError {
if arguments.length() != 2 && arguments.length() != 3 {
raise InvalidArgument(
message="map macro expects two or three arguments",
span~,
)
}
let output : Array[Value] = []
for item in items {
self.charge_iteration(span)
if arguments.length() == 3 &&
!require_bool(
self.eval_scoped(variable, item, arguments[1]),
arguments[1].span,
) {
continue
}
let transform = arguments[arguments.length() - 1]
output.push(self.eval_scoped(variable, item, transform))
}
Value::List(output)
}
///|
fn Evaluator::eval_comprehension(
self : Evaluator,
target : Expr,
name : String,
arguments : Array[Expr],
span : SourceSpan,
) -> Value raise EvalError {
let variable = macro_binding(arguments, span)
let items = macro_items(self.eval_expr(target), target.span)
if name == "map" {
return self.eval_map_macro(variable, items, arguments, span)
}
if arguments.length() != 2 {
raise InvalidArgument(message="\{name} expects two arguments", span~)
}
if name == "filter" {
self.eval_filter(variable, items, arguments[1])
} else {
self.eval_quantifier(name, variable, items, arguments[1])
}
}