diff --git a/src/interpreter.rs b/src/interpreter.rs index 31d477d..a259bee 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -474,6 +474,7 @@ impl<'source> Interpreter<'source> { panic!(); } } + Literal::NotExpr { expr, .. } => matches!(self.eval_expr(expr)?, Value::Bool(false)), _ => unimplemented!(), }); @@ -1347,6 +1348,108 @@ impl<'source> Interpreter<'source> { } } + fn check_default_value(&self, expr: &'source Expr<'source>) -> Result<()> { + use Expr::*; + let (kind, span) = match expr { + // Scalars are supported + String(_) | RawString(_) | Number(_) | True(_) | False(_) | Null(_) => return Ok(()), + + // Uminus of number is treated as a single expression, + UnaryExpr { expr, .. } if matches!(expr.as_ref(), Number(_)) => return Ok(()), + + Var(span) => ("var", span), + + // Check each item in array/set. + Array { items, .. } | Set { items, .. } => { + for item in items { + self.check_default_value(item)?; + } + return Ok(()); + } + + // Check each field in object + Object { fields, .. } => { + for (_, key, value) in fields { + self.check_default_value(key)?; + self.check_default_value(value)?; + } + return Ok(()); + } + + // Check each statement in comprehensions + ArrayCompr { term, query, .. } | SetCompr { term, query, .. } => { + self.check_default_value(term)?; + for stmt in &query.stmts { + self.check_default_value_in_stmt(stmt)?; + } + return Ok(()); + } + + ObjectCompr { + key, value, query, .. + } => { + self.check_default_value(key)?; + self.check_default_value(value)?; + for stmt in &query.stmts { + self.check_default_value_in_stmt(stmt)?; + } + return Ok(()); + } + + Call { span, .. } => ("call", span), + UnaryExpr { span, .. } => ("unaryexpr", span), + RefDot { span, .. } => ("ref", span), + RefBrack { span, .. } => ("ref", span), + BinExpr { span, .. } => ("binexpr", span), + BoolExpr { span, .. } => ("boolexpr", span), + ArithExpr { span, .. } => ("arithexpr", span), + AssignExpr { span, .. } => ("assignexpr", span), + Membership { span, .. } => ("membership", span), + }; + + Err(span.error(format!("invalid `{kind}` in default value").as_str())) + } + + fn check_default_value_in_stmt(&self, stmt: &'source LiteralStmt<'source>) -> Result<()> { + for m in &stmt.with_mods { + self.check_default_value(&m.refr)?; + self.check_default_value(&m.r#as)?; + } + + match &stmt.literal { + Literal::SomeVars { span, .. } => { + Err(span.error("invalid `some vars` in default value")) + } + Literal::SomeIn { + key, + value, + collection, + .. + } => { + self.check_default_value(key)?; + if let Some(value) = &value { + self.check_default_value(value)?; + } + self.check_default_value(collection) + } + Literal::Expr { expr, .. } | Literal::NotExpr { expr, .. } => { + self.check_default_value(expr) + } + Literal::Every { span, .. } => Err(span.error("invalid `every` in default value")), + } + } + + fn check_default_rules(&self) -> Result<()> { + for module in &self.modules { + for rule in &module.policy { + if let Rule::Default { value, .. } = rule { + self.check_default_value(value)?; + } + } + } + Ok(()) + } + fn eval_default_rule(&mut self, rule: &'source Rule<'source>) -> Result<()> { // Skip reprocessing rule. if self.processed.contains(rule) { @@ -1368,12 +1471,7 @@ impl<'source> Interpreter<'source> { Parser::get_path_ref_components_into(refr, &mut path)?; let paths: Vec<&str> = path.iter().map(|s| s.text()).collect(); - if matches!( - value, - Expr::Var(_) | Expr::RefBrack { .. } | Expr::RefDot { .. } - ) { - bail!("illegal default rule (value contains a variable or reference)"); - } + self.check_default_value(value)?; let value = self.eval_expr(value)?; // Assume at this point that all the non-default rules have been evaluated. @@ -1495,6 +1593,7 @@ impl<'source> Interpreter<'source> { self.data = data.clone(); } + self.check_default_rules()?; self.update_function_table()?; self.gather_rules()?; diff --git a/src/lexer.rs b/src/lexer.rs index cfa479f..a646485 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -61,6 +61,14 @@ impl<'source> Span<'source> { pub fn text(&self) -> &'source str { &self.source.contents[self.start as usize..self.end as usize] } + + pub fn message(&self, kind: &str, msg: &str) -> String { + self.source.message(self.line, self.col, kind, msg) + } + + pub fn error(&self, msg: &str) -> anyhow::Error { + self.source.error(self.line, self.col, msg) + } } impl<'source> Debug for Span<'source> { diff --git a/src/parser.rs b/src/parser.rs index db7449d..b7f1668 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -15,7 +15,6 @@ pub struct Parser<'source> { line: u16, end: u16, future_keywords: BTreeMap<&'source str, Span<'source>>, - in_default_value: bool, } const FUTURE_KEYWORDS: [&str; 4] = ["contains", "every", "if", "in"]; @@ -31,7 +30,6 @@ impl<'source> Parser<'source> { line: 0, end: 0, future_keywords: BTreeMap::new(), - in_default_value: false, }) } @@ -550,22 +548,6 @@ impl<'source> Parser<'source> { } } - if self.in_default_value { - if let Some((kind, span)) = match &term { - Expr::Var(v) => Some(("var", v)), - Expr::RefDot { span, .. } => Some(("ref", span)), - Expr::Call { span, .. } => Some(("call", span)), - Expr::RefBrack { span, .. } => Some(("ref", span)), - _ => None, - } { - return Err(self.source.error( - span.line, - span.col, - format!("invalid {kind} in default value").as_str(), - )); - } - } - Ok(term) } @@ -1418,9 +1400,7 @@ impl<'source> Parser<'source> { // todo: Rego errors for binary expressions here, but they are // somehow valid in a comprehension - self.in_default_value = true; let value = self.parse_term()?; - self.in_default_value = false; span.end = self.end; Ok(Rule::Default { span, diff --git a/tests/interpreter/cases/default/basic.yaml b/tests/interpreter/cases/default/basic.yaml index b3a9c5a..fb1ab57 100644 --- a/tests/interpreter/cases/default/basic.yaml +++ b/tests/interpreter/cases/default/basic.yaml @@ -12,7 +12,7 @@ cases: a = b - default b = 6 + default b = -6 c = d @@ -34,11 +34,23 @@ cases: complex["hello"] = "world" + default empty_array = [] + default empty_set = set() + default empty_object = {} + default null_value = null + default true_value = true + default false_value = false + default string_value = "abc" + default composite_value = [ 1, {2}, {"a": 5}] + default array_compr = [5 | true] + default set_compr = {5 | true} + default object_compr = {"a":5 | true} + default empty_array_1 = [1 | not 1] query: data.test want_result: x: 5 - a: 6 - b: 6 + a: -6 + b: -6 c: "has_default" d: "has_default" object: @@ -53,4 +65,284 @@ cases: value: "bool_false" - key: "hello" value: "world" - + empty_array: [] + empty_set: + set!: [] + empty_object: + object!: [] + null_value: null + true_value: true + false_value: false + string_value: "abc" + composite_value: + - 1 + - set!: [2] + - a: 5 + array_compr: [5] + set_compr: + set!: [5] + object_compr: + a: 5 + empty_array_1: [] + + - note: invalid-var + data: {} + modules: + - | + package t + default a = x + query: data + error: invalid `var` in default value + + - note: invalid-var-in-set-aray + data: {} + modules: + - | + package t + default a = [ {x} ] + query: data + error: invalid `var` in default value + + - note: invalid-var-in-object-key + data: {} + modules: + - | + package t + default a = { x : 5 } + query: data + error: invalid `var` in default value + + - note: invalid-var-in-object-value + data: {} + modules: + - | + package t + default a = { "x" : x } + query: data + error: invalid `var` in default value + + - note: invalid-var-in-array-compr-output + data: {} + modules: + - | + package t + default a = [ x | 1 ] + query: data + error: invalid `var` in default value + + - note: invalid-var-in-array-compr-query + data: {} + modules: + - | + package t + default a = [ 1 | true; x ] + query: data + error: invalid `var` in default value + + - note: invalid-var-in-set-compr-output + data: {} + modules: + - | + package t + default a = { x | 1 } + query: data + error: invalid `var` in default value + + - note: invalid-var-in-array-compr-query + data: {} + modules: + - | + package t + default a = { 1 | true; x } + query: data + error: invalid `var` in default value + + - note: invalid-var-in-object-compr-key + data: {} + modules: + - | + package t + default a = { x : 5 | true } + query: data + error: invalid `var` in default value + + - note: invalid-var-in-object-compr-value + data: {} + modules: + - | + package t + default a = { "x" : x | true } + query: data + error: invalid `var` in default value + + - note: invalid-var-in-object-compr-query + data: {} + modules: + - | + package t + default a = { "a" : 5 | x } + query: data + error: invalid `var` in default value + + - note: invalid-call + data: {} + modules: + - | + package t + default a = incr(5) + query: data + error: invalid `call` in default value + + - note: invalid-unaryexpr + data: {} + modules: + - | + package t + default a = -b + query: data + error: invalid `unaryexpr` in default value + + - note: invalid-refdot + data: {} + modules: + - | + package t + default a = {"b" : 5}.b + query: data + error: invalid `ref` in default value + + - note: invalid-refbrack + data: {} + modules: + - | + package t + default a = ["abc"][0] + query: data + error: invalid `ref` in default value + + - note: invalid-binexpr + data: {} + modules: + - | + package t + default a = ({5} | {6}) + query: data + error: invalid `binexpr` in default value + + - note: invalid-boolexpr + data: {} + modules: + - | + package t + default a = (5 > 6) + query: data + error: invalid `boolexpr` in default value + + - note: invalid-arithexpr + data: {} + modules: + - | + package t + default a = (5 + 6) + query: data + error: invalid `arithexpr` in default value + + - note: invalid-assignexpr + data: {} + modules: + - | + package t + # This is rejected by parser. + default a = (x = 5) + query: data + error: expecting `)` + + - note: invalid-membership + data: {} + modules: + - | + package t + import future.keywords + # Following is rejected by parser + default a = 5 in {5} + query: data + error: unexpected keyword `in` + + - note: invalid-some-vars + data: {} + modules: + - | + package t + default a = [5 | some a] + query: data + error: invalid `some vars` in default value + + - note: invalid-some-vars + data: {} + modules: + - | + package t + default a = [5 | some a] + query: data + error: invalid `some vars` in default value + + - note: invalid-every + data: {} + modules: + - | + package t + import future.keywords + default a = [5 | every x in [1,2,3] { true }] + query: data + error: invalid `every` in default value + + - note: invalid-var-in-some-in-key + data: {} + modules: + - | + package t + import future.keywords + default a = [5 | some x in {5}] + query: data + error: invalid `var` in default value + + - note: invalid-var-in-some-in-value + data: {} + modules: + - | + package t + import future.keywords + default a = [5 | some 5, x in {5}] + query: data + error: invalid `var` in default value + + - note: invalid-var-in-some-in-collection + data: {} + modules: + - | + package t + import future.keywords + default a = [5 | some 5, 5 in x] + query: data + error: invalid `var` in default value + + - note: invalid-var-in-not-expr + data: {} + modules: + - | + package t + import future.keywords + default a = [5 | not x] + query: data + error: invalid `var` in default value + + - note: invalid-var-in-expr + data: {} + modules: + - | + package t + import future.keywords + default a = [5 | x] + query: data + error: invalid `var` in default value +