mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
support of or-functions (#18)
* support of multiple definitions of the same function * handle functions producing an error Signed-off-by: eric-therond <eric.therond.fr@gmail.com>
This commit is contained in:
@@ -26,7 +26,7 @@ pub struct Interpreter<'source> {
|
||||
// TODO: handle recursive calls where same expr could have different values.
|
||||
loop_var_values: BTreeMap<&'source Expr<'source>, Value>,
|
||||
contexts: Vec<Context<'source>>,
|
||||
functions: HashMap<String, &'source Rule<'source>>,
|
||||
functions: HashMap<String, Vec<&'source Rule<'source>>>,
|
||||
rules: HashMap<String, Vec<&'source Rule<'source>>>,
|
||||
default_rules: HashMap<String, Vec<(&'source Rule<'source>, Option<String>)>>,
|
||||
processed: BTreeSet<&'source Rule<'source>>,
|
||||
@@ -1180,7 +1180,7 @@ impl<'source> Interpreter<'source> {
|
||||
}
|
||||
}
|
||||
|
||||
fn lookup_function(&self, fcn: &'source Expr<'source>) -> Result<&'source Rule<'source>> {
|
||||
fn lookup_function(&self, fcn: &'source Expr<'source>) -> Result<&Vec<&'source Rule<'source>>> {
|
||||
let mut path = Self::get_path_string(fcn, None)?;
|
||||
if !path.starts_with("data.") {
|
||||
path = self.current_module_path.clone() + "." + &path;
|
||||
@@ -1239,7 +1239,7 @@ impl<'source> Interpreter<'source> {
|
||||
fcn: &'source Expr<'source>,
|
||||
params: &'source Vec<Expr<'source>>,
|
||||
) -> Result<Value> {
|
||||
let fcn_rule = match self.lookup_function(fcn) {
|
||||
let fcns_rules = match self.lookup_function(fcn) {
|
||||
Ok(r) => r,
|
||||
_ => {
|
||||
// Look up builtin function.
|
||||
@@ -1256,69 +1256,106 @@ impl<'source> Interpreter<'source> {
|
||||
}
|
||||
};
|
||||
|
||||
let (args, output_expr, bodies) = match fcn_rule {
|
||||
Rule::Spec {
|
||||
head: RuleHead::Func { args, assign, .. },
|
||||
bodies,
|
||||
..
|
||||
} => (args, assign.as_ref().map(|a| &a.value), bodies),
|
||||
_ => bail!("internal error not a function"),
|
||||
};
|
||||
let fcns = fcns_rules.clone();
|
||||
|
||||
if args.len() != params.len() {
|
||||
let mut results: Vec<Value> = Vec::new();
|
||||
let mut errors: Vec<anyhow::Error> = Vec::new();
|
||||
for fcn_rule in fcns {
|
||||
let (args, output_expr, bodies) = match fcn_rule {
|
||||
Rule::Spec {
|
||||
head: RuleHead::Func { args, assign, .. },
|
||||
bodies,
|
||||
..
|
||||
} => (args, assign.as_ref().map(|a| &a.value), bodies),
|
||||
_ => bail!("internal error not a function"),
|
||||
};
|
||||
|
||||
if args.len() != params.len() {
|
||||
return Err(span.source.error(
|
||||
span.line,
|
||||
span.col,
|
||||
format!(
|
||||
"mismatch in number of arguments. supplied {}, expected {}",
|
||||
params.len(),
|
||||
args.len()
|
||||
)
|
||||
.as_str(),
|
||||
));
|
||||
}
|
||||
|
||||
let mut args_scope = Scope::new();
|
||||
for (idx, a) in args.iter().enumerate() {
|
||||
let a = match a {
|
||||
Expr::Var(s) => s.text(),
|
||||
_ => unimplemented!("destructuring function arguments"),
|
||||
};
|
||||
//TODO: check call in params
|
||||
args_scope.insert(a.to_string(), self.eval_expr(¶ms[idx])?);
|
||||
}
|
||||
|
||||
let ctx = Context {
|
||||
key_expr: None,
|
||||
output_expr,
|
||||
value: Value::new_set(),
|
||||
};
|
||||
|
||||
// Back up local variables of current function and empty
|
||||
// the local variables of callee function.
|
||||
let scopes = std::mem::take(&mut self.scopes);
|
||||
|
||||
// Set the arguments scope.
|
||||
self.scopes.push(args_scope);
|
||||
let value = match self.eval_rule_bodies(ctx, span, bodies) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
// If the rule produces an error, save the error.
|
||||
errors.push(e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let result = match &value {
|
||||
Value::Set(s) if s.len() == 1 => s.iter().next().unwrap().clone(),
|
||||
Value::Set(s) if !s.is_empty() => {
|
||||
return Err(span.source.error(
|
||||
span.line,
|
||||
span.col,
|
||||
format!("function produced multiple outputs {value:?}").as_str(),
|
||||
))
|
||||
}
|
||||
// If the function successfully executed, but did not return any value, then return true.
|
||||
Value::Set(s) if s.is_empty() && output_expr.is_none() => Value::Bool(true),
|
||||
|
||||
// If the function execution resulted in undefined, then propagate it.
|
||||
Value::Undefined => Value::Undefined,
|
||||
_ => bail!("internal error: function did not return set {value:?}"),
|
||||
};
|
||||
|
||||
// Restore local variables for current context.
|
||||
self.scopes = scopes;
|
||||
|
||||
if result != Value::Undefined {
|
||||
results.push(result);
|
||||
}
|
||||
}
|
||||
|
||||
if results.is_empty() {
|
||||
if errors.is_empty() {
|
||||
return Ok(Value::Undefined);
|
||||
} else {
|
||||
return Err(anyhow!(errors[0].to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
// all defined values should be the equal to the same value that should be returned
|
||||
if !results.windows(2).all(|w| w[0] == w[1]) {
|
||||
return Err(span.source.error(
|
||||
span.line,
|
||||
span.col,
|
||||
format!(
|
||||
"mismatch in number of arguments. supplied {}, expected {}",
|
||||
params.len(),
|
||||
args.len()
|
||||
)
|
||||
.as_str(),
|
||||
"functions must not produce multiple outputs for same inputs",
|
||||
));
|
||||
}
|
||||
|
||||
let mut args_scope = Scope::new();
|
||||
for (idx, a) in args.iter().enumerate() {
|
||||
let a = match a {
|
||||
Expr::Var(s) => s.text(),
|
||||
_ => unimplemented!("destructuring function arguments"),
|
||||
};
|
||||
//TODO: check call in params
|
||||
args_scope.insert(a.to_string(), self.eval_expr(¶ms[idx])?);
|
||||
}
|
||||
|
||||
let ctx = Context {
|
||||
key_expr: None,
|
||||
output_expr,
|
||||
value: Value::new_set(),
|
||||
};
|
||||
|
||||
// Back up local variables of current function and empty
|
||||
// the local variables of callee function.
|
||||
let scopes = std::mem::take(&mut self.scopes);
|
||||
|
||||
// Set the arguments scope.
|
||||
self.scopes.push(args_scope);
|
||||
let value = self.eval_rule_bodies(ctx, span, bodies)?;
|
||||
let result = match &value {
|
||||
Value::Set(s) if s.len() == 1 => Ok(s.iter().next().unwrap().clone()),
|
||||
Value::Set(s) if !s.is_empty() => Err(span.source.error(
|
||||
span.line,
|
||||
span.col,
|
||||
format!("function produced multiple outputs {value:?}").as_str(),
|
||||
)),
|
||||
// If the function successfully executed, but did not return any value, then return true.
|
||||
Value::Set(s) if s.is_empty() && output_expr.is_none() => Ok(Value::Bool(true)),
|
||||
|
||||
// If the function execution resulted in undefined, then propagate it.
|
||||
Value::Undefined => Ok(Value::Undefined),
|
||||
_ => bail!("internal error: function did not return set {value:?}"),
|
||||
};
|
||||
|
||||
// Restore local variables for current context.
|
||||
self.scopes = scopes;
|
||||
result
|
||||
Ok(results[0].clone())
|
||||
}
|
||||
|
||||
fn lookup_local_var(&self, name: &str) -> Option<Value> {
|
||||
@@ -1700,7 +1737,12 @@ impl<'source> Interpreter<'source> {
|
||||
}
|
||||
|
||||
let full_path = Self::get_path_string(refr, Some(module_path.as_str()))?;
|
||||
self.functions.insert(full_path, rule);
|
||||
|
||||
if let Some(functions) = self.functions.get_mut(&full_path) {
|
||||
functions.push(rule);
|
||||
} else {
|
||||
self.functions.insert(full_path, vec![rule]);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.set_current_module(prev_module)?;
|
||||
|
||||
@@ -45,3 +45,17 @@ cases:
|
||||
want_result:
|
||||
a: -11
|
||||
|
||||
- note: call-return-undefined
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
inc(x) = y {
|
||||
x > 10 # This will evaluate to false.
|
||||
y = 100 # y will be undefined.
|
||||
}
|
||||
|
||||
a1 = inc(5)
|
||||
query: data.test
|
||||
want_result: {}
|
||||
|
||||
154
tests/interpreter/cases/call/or.yaml
Normal file
154
tests/interpreter/cases/call/or.yaml
Normal file
@@ -0,0 +1,154 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
cases:
|
||||
- note: or-error-output
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
inc(x) = x + 1
|
||||
inc(x) = x + 4
|
||||
|
||||
a1 = inc(5)
|
||||
query: data.test
|
||||
error: functions must not produce multiple outputs for same inputs
|
||||
|
||||
- note: or-ok
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
inc(x) = x + 1
|
||||
inc(x) = x + 1
|
||||
|
||||
a1 = inc(5)
|
||||
query: data.test
|
||||
want_result:
|
||||
a1: 6
|
||||
|
||||
- note: or-true
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
inc(x) = 7 == x
|
||||
inc(x) = 7 == x
|
||||
|
||||
a1 = inc(7)
|
||||
query: data.test
|
||||
want_result:
|
||||
a1: true
|
||||
|
||||
- note: or-false
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
inc(x) = 8 == x
|
||||
inc(x) = 3 == x
|
||||
|
||||
a1 = inc(2)
|
||||
query: data.test
|
||||
want_result:
|
||||
a1: false
|
||||
|
||||
- note: or-undefined
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
inc(x) = x + 1
|
||||
inc(x) = x + 1
|
||||
inc(x) = y {
|
||||
x > 10 # This will evaluate to false.
|
||||
y = 100 # y will be undefined.
|
||||
}
|
||||
|
||||
a1 = inc(5)
|
||||
query: data.test
|
||||
want_result:
|
||||
a1: 6
|
||||
|
||||
- note: or-first-undefined
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
inc(x) = y {
|
||||
x > 10 # This will evaluate to false.
|
||||
y = 100 # y will be undefined.
|
||||
}
|
||||
inc(x) = x + 1
|
||||
inc(x) = x + 1
|
||||
|
||||
a1 = inc(5)
|
||||
query: data.test
|
||||
want_result:
|
||||
a1: 6
|
||||
|
||||
- note: or-all-undefined
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
inc(x1) = y1 {
|
||||
x1 > 10 # This will evaluate to false.
|
||||
y1 = 100 # y will be undefined.
|
||||
}
|
||||
inc(x2) = y2 {
|
||||
x2 > 10 # This will evaluate to false.
|
||||
y2 = 100 # y will be undefined.
|
||||
}
|
||||
|
||||
a1 = inc(5)
|
||||
query: data.test
|
||||
want_result: {}
|
||||
|
||||
- note: or-one-error
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
fcn(x) = y {
|
||||
y = x + 1
|
||||
}
|
||||
|
||||
fcn(x) = y {
|
||||
y = concat(" ", ["hello", x])
|
||||
}
|
||||
|
||||
a1 = fcn("world")
|
||||
a2 = fcn(5)
|
||||
query: data.test
|
||||
want_result:
|
||||
a1:
|
||||
set!: [hello world]
|
||||
a2:
|
||||
set!: [6]
|
||||
|
||||
- note: or-all-error
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
fcn(x) = y {
|
||||
y = x + 1
|
||||
}
|
||||
|
||||
fcn(x) = y {
|
||||
y = x + 1
|
||||
}
|
||||
|
||||
a1 = fcn("world")
|
||||
query: data.test
|
||||
error: "`add` expects numeric argument. Got `\"world\"` instead"
|
||||
Reference in New Issue
Block a user