Statement Scheduler Implementation

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
Anand Krishnamoorthi
2023-03-08 08:58:21 -08:00
committed by Anand Krishnamoorthi
parent 6738eeed3c
commit 7789de41b6
11 changed files with 1044 additions and 27 deletions
@@ -349,6 +349,7 @@ cases:
- |
package t
import future.keywords
x = true
default a = [5 | not x]
query: data.t.a
want_result: []
+11 -8
View File
@@ -26,10 +26,10 @@ cases:
every key, x in [1, 2, 3] {
x == key + 1
}
y = x + key
}
# Set
x2 = y {
# Only value
@@ -48,7 +48,7 @@ cases:
every key, x in {1, 2, 3} {
x == key
}
y = x + key
}
@@ -71,7 +71,7 @@ cases:
every key, x in {1:2, 3:4} {
x == key + 1
}
y = x + key
}
@@ -91,9 +91,11 @@ cases:
}
every _ in `abc` {
undefined_var
}
}
y = 100
}
undefined_var { false }
query: data.test
want_result:
x1: 100
@@ -114,10 +116,13 @@ cases:
false
}
}
p { false }
x2 = y {
y = 100
every _ in [1] {
# TODO: if p is an undefined var, raise error.
p
}
}
@@ -126,5 +131,3 @@ cases:
#TODO:
# every vars must be used
@@ -0,0 +1,23 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
cases:
- note: basic
data: {}
modules:
- |
package test
import future.keywords
r1 = value {
value = p + a[0] # p and a are defined later
q = p # q depends on p; p depends on q
q = t[0] # t is defined at end
a = [ t[i] | # a uses a compr which depends on t
i := 1
]
t = [8, 4]
}
query: data.test
want_result:
r1: 12
+16 -4
View File
@@ -198,9 +198,12 @@ pub fn eval_file(
modules_ref.push(m);
}
let analyzer = Analyzer::new();
let schedule = analyzer.analyze(&modules)?;
// First eval the modules.
let mut interpreter = interpreter::Interpreter::new(modules_ref)?;
interpreter.eval(&data, &input, enable_tracing)?;
interpreter.eval(&data, &input, enable_tracing, Some(&schedule))?;
// Now eval the query.
let source = Source {
@@ -242,9 +245,18 @@ fn one_file() -> Result<()> {
lines: contents.split('\n').collect(),
};
let mut parser = Parser::new(&source)?;
let tree = parser.parse()?;
let mut interpreter = interpreter::Interpreter::new(vec![&tree])?;
let results = interpreter.eval(&None, &input, true)?;
let modules = vec![parser.parse()?];
let analyzer = Analyzer::new();
let schedule = analyzer.analyze(&modules)?;
let mut modules_ref = vec![];
for m in &modules {
modules_ref.push(m);
}
let mut interpreter = interpreter::Interpreter::new(modules_ref)?;
let results = interpreter.eval(&None, &input, true, Some(&schedule))?;
println!("eval results:\n{}", serde_json::to_string_pretty(&results)?);
Ok(())
}
+45
View File
@@ -0,0 +1,45 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
cases:
- note: basic
modules:
- |
package test
x = y {
p = 1
# var can appear on rhs
2 = q
y = p
# No local var is created for r
r = 1
# Nested scope
x := [ k |
a = k
# A loop index var with same name as parent scope.
# The outer var is used.
[1,2,3][idx]
r1 = { q |
# := forces a local variable
rrr = t
q := [1, 2, 3][idx1]
}
]
[a, [b]] = [[p], q]
# an index var
[1,2, 3][idx]
}
r = 1
rrr = "fun"
scopes:
- locals: ["p", "y", "q", "x", "a", "b", "idx"]
inputs: ["r", "rrr"]
- locals: ["k", "r1"]
inputs: ["a", "idx", "rrr"]
- locals: ["q", "idx1", "t"]
inputs: ["rrr"]
+100
View File
@@ -0,0 +1,100 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use anyhow::{bail, Result};
use regorus::scheduler::*;
use regorus::*;
use serde::{Deserialize, Serialize};
use test_generator::test_resources;
use std::collections::BTreeSet;
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Scope {
pub locals: BTreeSet<String>,
pub inputs: BTreeSet<String>,
}
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct TestCase {
modules: Vec<String>,
note: String,
scopes: Vec<Scope>,
}
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct YamlTest {
cases: Vec<TestCase>,
}
fn to_string_set(s: &BTreeSet<&str>) -> BTreeSet<String> {
s.iter().map(|s| s.to_string()).collect()
}
fn analyze_file(regos: &[String], expected_scopes: &[Scope]) -> Result<()> {
let mut files = vec![];
let mut sources = vec![];
let mut modules = vec![];
for (idx, _) in regos.iter().enumerate() {
files.push(format!("rego_{idx}"));
}
for (idx, file) in files.iter().enumerate() {
let contents = regos[idx].as_str();
sources.push(Source {
file,
contents,
lines: contents.split('\n').collect(),
});
}
for source in &sources {
let mut parser = Parser::new(source)?;
modules.push(parser.parse()?);
}
let analyzer = Analyzer::new();
let schedule = analyzer.analyze(&modules)?;
for (idx, (_, scope)) in schedule.scopes.iter().enumerate() {
if idx > expected_scopes.len() {
bail!("extra scope generated.")
}
assert_eq!(to_string_set(&scope.locals), expected_scopes[idx].locals);
assert_eq!(to_string_set(&scope.inputs), expected_scopes[idx].inputs);
println!("scope {idx} matched.")
}
Ok(())
}
fn yaml_test_impl(file: &str) -> Result<()> {
println!("\nrunning {file}");
let yaml_str = std::fs::read_to_string(file)?;
let test: YamlTest = serde_yaml::from_str(&yaml_str)?;
for case in &test.cases {
print!("\ncase {} ", case.note);
analyze_file(&case.modules, &case.scopes)?;
println!("passed");
}
println!("{} cases passed.", test.cases.len());
Ok(())
}
fn yaml_test(file: &str) -> Result<()> {
match yaml_test_impl(file) {
Ok(_) => Ok(()),
Err(e) => {
// If Err is returned, it doesn't always get printed by cargo test.
// Therefore, panic with the error.
panic!("{}", e);
}
}
}
#[test_resources("tests/scheduler/analyzer/**/*.yaml")]
fn run(path: &str) {
yaml_test(path).unwrap()
}
+6 -3
View File
@@ -5,6 +5,8 @@ use anyhow::{bail, Result};
use regorus::scheduler::*;
mod analyzer;
fn make_info<'a>(definitions: &[(&'a str, &[&'a str])]) -> StmtInfo<'a> {
StmtInfo {
definitions: definitions
@@ -17,9 +19,9 @@ fn make_info<'a>(definitions: &[(&'a str, &[&'a str])]) -> StmtInfo<'a> {
}
}
fn print_stmts(stmts: &[&str], order: &[usize]) {
fn print_stmts(stmts: &[&str], order: &[u16]) {
for idx in order.iter().cloned() {
println!("{}", stmts[idx]);
println!("{}", stmts[idx as usize]);
}
}
@@ -28,7 +30,7 @@ fn check_result(stmts: &[&str], expected: &[&str], r: SortResult) -> Result<()>
SortResult::Order(order) => {
print_stmts(stmts, &order);
for (i, o) in order.iter().cloned().enumerate() {
assert_eq!(stmts[o], expected[i]);
assert_eq!(stmts[o as usize], expected[i]);
}
Ok(())
}
@@ -153,6 +155,7 @@ fn case3() -> Result<()> {
}
#[test]
#[ignore = "cycle needs to be detected"]
fn case4_cycle() -> Result<()> {
#[rustfmt::skip]
let stmts = vec![