mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
Implement every statement (#4)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
@@ -188,8 +188,8 @@ pub enum Literal<'source> {
|
||||
},
|
||||
Every {
|
||||
span: Span<'source>,
|
||||
key: Span<'source>,
|
||||
value: Option<Span<'source>>,
|
||||
key: Option<Span<'source>>,
|
||||
value: Span<'source>,
|
||||
domain: Expr<'source>,
|
||||
query: Query<'source>,
|
||||
},
|
||||
|
||||
@@ -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<Value> {
|
||||
@@ -423,6 +426,69 @@ impl<'source> Interpreter<'source> {
|
||||
Ok(Value::Bool(true))
|
||||
}
|
||||
|
||||
fn eval_every(
|
||||
&mut self,
|
||||
_span: &'source Span<'source>,
|
||||
key: &'source Option<Span<'source>>,
|
||||
value: &'source Span<'source>,
|
||||
domain: &'source Expr<'source>,
|
||||
query: &'source Query<'source>,
|
||||
) -> Result<bool> {
|
||||
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<bool> {
|
||||
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();
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
130
tests/interpreter/cases/every/tests.yaml
Normal file
130
tests/interpreter/cases/every/tests.yaml
Normal file
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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``"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user