diff --git a/src/ast.rs b/src/ast.rs index 31f11eb..6036195 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -188,8 +188,8 @@ pub enum Literal<'source> { }, Every { span: Span<'source>, - key: Span<'source>, - value: Option>, + key: Option>, + value: Span<'source>, domain: Expr<'source>, query: Query<'source>, }, diff --git a/src/interpreter.rs b/src/interpreter.rs index f5e2daa..860c052 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -74,18 +74,27 @@ impl<'source> Interpreter<'source> { } } + fn current_scope(&mut self) -> Result<&Scope> { + match self.scopes.last() { + Some(scope) => Ok(scope), + _ => bail!("internal error: no active scope"), + } + } + + fn current_scope_mut(&mut self) -> Result<&mut Scope> { + match self.scopes.last_mut() { + Some(scope) => Ok(scope), + _ => bail!("internal error: no active scope"), + } + } + #[inline(always)] fn add_variable(&mut self, name: &str, value: Value) -> Result<()> { let name = name.to_string(); // Only add the variable if the key is not "_" if name != "_" { - match self.scopes.last_mut() { - Some(scope) => { - scope.insert(name, value); - } - _ => bail!("internal error: no active scope"), - } + self.current_scope_mut()?.insert(name, value); } Ok(()) @@ -104,18 +113,12 @@ impl<'source> Interpreter<'source> { // TODO: optimize this fn variables_assignment(&mut self, name: &str, value: &Value) -> Result<()> { - match self.scopes.last_mut() { - Some(scope) => { - if let Some(variable) = scope.get_mut(name) { - *variable = value.clone(); - } else { - return Err(anyhow!("variable {} is undefined", name)); - } - } - _ => bail!("internal error: no active scope"), + if let Some(variable) = self.current_scope_mut()?.get_mut(name) { + *variable = value.clone(); + Ok(()) + } else { + Err(anyhow!("variable {} is undefined", name)) } - - Ok(()) } fn eval_chained_ref_dot_or_brack(&mut self, mut expr: &'source Expr<'source>) -> Result { @@ -423,6 +426,69 @@ impl<'source> Interpreter<'source> { Ok(Value::Bool(true)) } + fn eval_every( + &mut self, + _span: &'source Span<'source>, + key: &'source Option>, + value: &'source Span<'source>, + domain: &'source Expr<'source>, + query: &'source Query<'source>, + ) -> Result { + let domain = self.eval_expr(domain)?; + + self.scopes.push(Scope::new()); + self.contexts.push(Context { + key_expr: None, + output_expr: None, + value: Value::new_set(), + }); + let mut r = true; + match domain { + Value::Array(a) => { + for (idx, v) in a.iter().enumerate() { + self.add_variable(value.text(), v.clone())?; + if let Some(key) = key { + self.add_variable(key.text(), Value::from_float(idx as Float))?; + } + if !self.eval_query(query)? { + r = false; + break; + } + } + } + Value::Set(s) => { + for v in s.iter() { + self.add_variable(value.text(), v.clone())?; + if let Some(key) = key { + self.add_variable(key.text(), v.clone())?; + } + if !self.eval_query(query)? { + r = false; + break; + } + } + } + Value::Object(o) => { + for (k, v) in o.iter() { + self.add_variable(value.text(), v.clone())?; + if let Some(key) = key { + self.add_variable(key.text(), k.clone())?; + } + if !self.eval_query(query)? { + r = false; + break; + } + } + } + // Other types cause every to evaluate to true even though + // it is supposed to happen only for empty domain. + _ => (), + }; + self.contexts.pop(); + self.scopes.pop(); + Ok(r) + } + fn eval_stmt(&mut self, stmt: &'source LiteralStmt<'source>) -> Result { let mut to_restore = vec![]; for wm in &stmt.with_mods { @@ -496,7 +562,13 @@ impl<'source> Interpreter<'source> { } } Literal::NotExpr { expr, .. } => matches!(self.eval_expr(expr)?, Value::Bool(false)), - _ => unimplemented!(), + Literal::Every { + span, + key, + value, + domain, + query, + } => self.eval_every(span, key, value, domain, query)?, }); for (path, value) in to_restore.into_iter().rev() { @@ -532,10 +604,7 @@ impl<'source> Interpreter<'source> { // Save the current scope and restore it after evaluating the statements so // that the effects of the current loop iteration are cleared. - let scope_saved = match self.scopes.last() { - Some(scope) => scope.clone(), - _ => bail!("internal error: missing scope"), - }; + let scope_saved = self.current_scope()?.clone(); match self.eval_expr(loop_expr.value)? { Value::Array(items) => { @@ -544,9 +613,7 @@ impl<'source> Interpreter<'source> { self.add_variable(loop_expr.index, Value::from_float(idx as Float))?; result = self.eval_stmts_in_loop(stmts, &loops[1..])? || result; - if let Some(s) = self.scopes.last_mut() { - *s = scope_saved.clone(); - } + *self.current_scope_mut()? = scope_saved.clone(); } } Value::Set(items) => { @@ -555,9 +622,7 @@ impl<'source> Interpreter<'source> { // For sets, index is also the value. self.add_variable(loop_expr.index, v.clone())?; result = self.eval_stmts_in_loop(stmts, &loops[1..])? || result; - if let Some(s) = self.scopes.last_mut() { - *s = scope_saved.clone(); - } + *self.current_scope_mut()? = scope_saved.clone(); } } Value::Object(obj) => { @@ -566,9 +631,7 @@ impl<'source> Interpreter<'source> { // For objects, index is key. self.add_variable(loop_expr.index, k.clone())?; result = self.eval_stmts_in_loop(stmts, &loops[1..])? || result; - if let Some(s) = self.scopes.last_mut() { - *s = scope_saved.clone(); - } + *self.current_scope_mut()? = scope_saved.clone(); } } _ => { diff --git a/src/parser.rs b/src/parser.rs index f9110db..11edddb 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -780,12 +780,12 @@ impl<'source> Parser<'source> { let context = "Failed to parse `every` statement."; self.parse_future_keyword("every", false, context)?; - let key = self.parse_var()?; - let value = match self.tok.1.text() { + let ident = self.parse_var()?; + let (key, value) = match self.tok.1.text() { "," => { self.next_token()?; match self.parse_var() { - Ok(v) => Some(v), + Ok(v) => (Some(ident), v), Err(e) => { return Err(self.source.error( span.line, @@ -795,7 +795,7 @@ impl<'source> Parser<'source> { } } } - _ => None, + _ => (None, ident), }; self.parse_future_keyword("in", false, context)?; diff --git a/tests/interpreter/cases/every/tests.yaml b/tests/interpreter/cases/every/tests.yaml new file mode 100644 index 0000000..1a72306 --- /dev/null +++ b/tests/interpreter/cases/every/tests.yaml @@ -0,0 +1,130 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +cases: + - note: all + data: {} + modules: + - | + package test + import future.keywords + + # Array + x1 = y { + # Only value + every x in [1, 2, 3] { + x > 0 + } + + # Key and value + every key, x in [1, 2, 3] { + x == key + 1 + } + + key = 5 + x = 95 + # Key and value can shadow (local) variables. + every key, x in [1, 2, 3] { + x == key + 1 + } + + y = x + key + } + + # Set + x2 = y { + # Only value + every x in {1, 2, 3} { + x > 0 + } + + # Key and value are same. + every key, x in {1, 2, 3} { + x == key + } + + key = 5 + x = 95 + # Key and value can shadow (local) variables. + every key, x in {1, 2, 3} { + x == key + } + + y = x + key + } + + # Object + x3 = y { + # Only value + every x in {1:2, 3:4} { + x >= 2 + x % 2 == 0 + } + + # Key and value. + every key, x in {1:2, 3:4} { + x == key + 1 + } + + key = 5 + x = 95 + # Key and value can shadow (local) variables. + every key, x in {1:2, 3:4} { + x == key + 1 + } + + y = x + key + } + + # Non aggregate types + x4 = y { + every _, _ in 1 { + false + } + every _, _ in null { + false + } + every _, _ in false { + false + } + every _ in "abc" { + false + } + every _ in `abc` { + undefined_var + } + y = 100 + } + query: data.test + want_result: + x1: 100 + x2: 100 + x3: 100 + x4: 100 + + - note: negative + data: {} + modules: + - | + package test + import future.keywords + + x1 = y { + y = 100 + every _ in [1] { + false + } + } + + x2 = y { + y = 100 + every _ in [1] { + p + } + } + query: data.test + want_result: {} + + #TODO: + # every vars must be used + + diff --git a/tests/parser/cases/every/every.yaml b/tests/parser/cases/every/every.yaml index 50a7075..b81bab0 100644 --- a/tests/parser/cases/every/every.yaml +++ b/tests/parser/cases/every/every.yaml @@ -1,5 +1,5 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. +# Copyright (c) Microsoft Corporation. Licensed under the MIT +# License. cases: - note: basic @@ -28,7 +28,7 @@ cases: stmts: - literal: every: - key: x + value: x domain: array: - number: 2 diff --git a/tests/parser/mod.rs b/tests/parser/mod.rs index aada829..d4a924b 100644 --- a/tests/parser/mod.rs +++ b/tests/parser/mod.rs @@ -525,19 +525,19 @@ fn match_literal(l: &Literal, v: &Value) -> Result<()> { query, } => { match_span_opt(span, &v["every"]["span"])?; - match_span(key, &v["every"]["key"])?; - match value { - Some(s) => match_span(s, &v["every"]["value"])?, + match_span(value, &v["every"]["value"])?; + match key { + Some(s) => match_span(s, &v["every"]["key"])?, None => { my_assert_eq!( &Value::Undefined, - &v["value"], + &v["key"], "{}", span.source.message( span.line, span.col, "mismatch-error", - "could not match `value``" + "could not match `key``" ) ); }