Skip to content

Commit

Permalink
More OPA conformance; in-progress: ability to trace interpreter (#63)
Browse files Browse the repository at this point in the history
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
  • Loading branch information
anakrish authored Dec 17, 2023
1 parent 73ee18f commit 577e1aa
Show file tree
Hide file tree
Showing 19 changed files with 429 additions and 173 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ wax = { version = "0.6.0", features = [], default-features = false, optional = t

[dev-dependencies]
clap = { version = "4.4.7", features = ["derive"] }
colored-diff = "0.2.3"
serde_yaml = "0.9.16"
test-generator = "0.3.1"
walkdir = "2.3.2"
Expand Down
5 changes: 5 additions & 0 deletions examples/regorus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,11 @@ struct Cli {
}

fn main() -> Result<()> {
env_logger::builder()
.format_level(false)
.format_timestamp(None)
.init();

// Parse and dispatch command.
let cli = Cli::parse();
match cli.command {
Expand Down
2 changes: 1 addition & 1 deletion scripts/pre-push
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ if [ -f Cargo.toml ]; then
fi

# Ensure that OPA conformance tests don't regress.
cargo test --test opa -- $(tr '\n' ' ' < tests/opa.passing)
cargo test -r --test opa -- $(tr '\n' ' ' < tests/opa.passing)
fi
2 changes: 2 additions & 0 deletions src/builtins/deprecated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ fn cast_array(span: &Span, params: &[Ref<Expr>], args: &[Value], strict: bool) -
ensure_args_count(span, name, params, args, 1)?;
match &args[0] {
Value::Array(_) => Ok(args[0].clone()),
Value::Set(s) => Ok(Value::from_array(s.iter().cloned().collect())),
_ if strict => bail!(params[0].span().error("array required")),
_ => Ok(Value::Undefined),
}
Expand Down Expand Up @@ -113,6 +114,7 @@ fn cast_set(span: &Span, params: &[Ref<Expr>], args: &[Value], strict: bool) ->
ensure_args_count(span, name, params, args, 1)?;
match &args[0] {
Value::Set(_) => Ok(args[0].clone()),
Value::Array(a) => Ok(Value::from_set(a.iter().cloned().collect())),
_ if strict => bail!(params[0].span().error("set required")),
_ => Ok(Value::Undefined),
}
Expand Down
93 changes: 73 additions & 20 deletions src/builtins/numbers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
m.insert("ceil", (ceil, 1));
m.insert("floor", (floor, 1));
m.insert("numbers.range", (range, 2));
m.insert("numbers.range_step", (range_step, 3));
m.insert("rand.intn", (intn, 2));
m.insert("round", (round, 1));
}
Expand All @@ -29,6 +30,7 @@ pub fn arithmetic_operation(
expr2: &Expr,
v1: Value,
v2: Value,
strict: bool,
) -> Result<Value> {
let op_name = format!("{:?}", op).to_lowercase();
let v1 = ensure_numeric(op_name.as_str(), expr1, &v1)?;
Expand All @@ -38,9 +40,11 @@ pub fn arithmetic_operation(
ArithOp::Add => v1.add(&v2)?,
ArithOp::Sub => v1.sub(&v2)?,
ArithOp::Mul => v1.mul(&v2)?,
ArithOp::Div if v2 == Number::from(0u64) => bail!(span.error("divide by zero")),
ArithOp::Div if strict && v2 == Number::from(0u64) => bail!(span.error("divide by zero")),
ArithOp::Div if v2 == Number::from(0u64) => return Ok(Value::Undefined),
ArithOp::Div => v1.divide(&v2)?,
ArithOp::Mod if v2 == Number::from(0u64) => bail!(span.error("modulo by zero")),
ArithOp::Mod if strict && v2 == Number::from(0u64) => bail!(span.error("modulo by zero")),
ArithOp::Mod if v2 == Number::from(0u64) => return Ok(Value::Undefined),
ArithOp::Mod if !v1.is_integer() || !v2.is_integer() => {
bail!(span.error("modulo on floating-point number"))
}
Expand Down Expand Up @@ -69,25 +73,33 @@ fn floor(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Re
))
}

fn range(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
ensure_args_count(span, "numbers.range", params, args, 2)?;
let v1 = ensure_numeric("numbers.range", &params[0], &args[0].clone())?;
let v2 = ensure_numeric("numbers.range", &params[1], &args[1].clone())?;

let (incr, num_elements) = match (v1.as_i64(), v2.as_i64()) {
(Some(v1), Some(v2)) if v2 > v1 => (1, v2 + 1 - v1),
(Some(v1), Some(v2)) => (-1, v1 + 1 - v2),
_ => {
// TODO: OPA returns undefined here.
// Can we emit a warning?
return Ok(Value::Undefined);
}
};
fn range(span: &Span, params: &[Ref<Expr>], args: &[Value], strict: bool) -> Result<Value> {
let name = "numbers.range";
ensure_args_count(span, name, params, args, 2)?;
let v1 = ensure_numeric(name, &params[0], &args[0].clone())?;
let v2 = ensure_numeric(name, &params[1], &args[1].clone())?;

let mut values = Vec::with_capacity(num_elements as usize);
match v1.is_integer() {
false if strict => bail!(params[0].span().error("operand must be integer")),
false => return Ok(Value::Undefined),
_ => (),
}

match v2.is_integer() {
false if strict => bail!(params[1].span().error("operand must be integer")),
false => return Ok(Value::Undefined),
_ => (),
}

let (incr, num_elements) = match v2.sub(&v1)?.as_i64() {
Some(v) if v >= 0 => (1i64, v as usize + 1),
Some(v) => (-1i64, (-v + 1) as usize),
_ => bail!(span.error("could not determine number of elements")),
};

let mut values = Vec::with_capacity(num_elements);
let mut v = v1;
let incr = Number::from(incr as i64);
let incr = Number::from(incr);
while v != v2 {
values.push(Value::from(v.clone()));
v.add_assign(&incr)?;
Expand All @@ -96,10 +108,51 @@ fn range(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Re
Ok(Value::from_array(values))
}

fn range_step(span: &Span, params: &[Ref<Expr>], args: &[Value], strict: bool) -> Result<Value> {
let name = "numbers.range_step";
ensure_args_count(span, name, params, args, 3)?;
let v1 = ensure_numeric(name, &params[0], &args[0].clone())?;
let v2 = ensure_numeric(name, &params[1], &args[1].clone())?;
let incr = ensure_numeric(name, &params[2], &args[2].clone())?;

match v1.is_integer() {
false if strict => bail!(params[0].span().error("operand must be integer")),
false => return Ok(Value::Undefined),
_ => (),
}

match v2.is_integer() {
false if strict => bail!(params[1].span().error("operand must be integer")),
false => return Ok(Value::Undefined),
_ => (),
}

if strict && (!incr.is_integer() || incr <= Number::from(0u64)) {
bail!(params[2].span().error("step must be a positive integer"))
}

let (incr, num_elements) = match (v2.sub(&v1)?.as_i64(), incr.as_i64()) {
(Some(v), Some(incr)) if v >= 0 => (incr, v / incr + 1),
(Some(v), Some(incr)) => (-incr, -v / incr + 1),
_ => bail!(span.error("could not determine number of elements")),
};

let mut values = Vec::with_capacity(num_elements as usize);
let incr = Number::from(incr);
let mut v = v1;
while (v <= v2 && incr.is_positive()) || (v >= v2 && !incr.is_positive()) {
values.push(Value::from(v.clone()));
v.add_assign(&incr)?;
}

Ok(Value::from_array(values))
}

fn round(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
ensure_args_count(span, "round", params, args, 1)?;
let name = "round";
ensure_args_count(span, name, params, args, 1)?;
Ok(Value::from(
ensure_numeric("round", &params[0], &args[0])?.round(),
ensure_numeric(name, &params[0], &args[0])?.round(),
))
}

Expand Down
15 changes: 12 additions & 3 deletions src/builtins/semver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,18 @@ fn compare(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) ->
Ok(Value::from(result as i64))
}

fn is_valid(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
fn is_valid(span: &Span, params: &[Ref<Expr>], args: &[Value], strict: bool) -> Result<Value> {
let name = "semver.is_valid";
ensure_args_count(span, name, params, args, 1)?;
let v = ensure_string(name, &params[0], &args[0])?;
Ok(Value::Bool(Version::parse(&v).is_ok()))
Ok(Value::Bool(
Version::parse(&if strict {
ensure_string(name, &params[0], &args[0])?
} else {
match &args[0] {
Value::String(s) => s.clone(),
_ => return Ok(Value::Bool(false)),
}
})
.is_ok(),
))
}
43 changes: 38 additions & 5 deletions src/builtins/strings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,41 @@ fn split(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Re
))
}

fn to_string(v: &Value, unescape: bool) -> String {
match v {
Value::Null => "null".to_owned(),
Value::Bool(b) => b.to_string(),
Value::String(s) if unescape => serde_json::to_string(&s).unwrap_or(s.as_ref().to_string()),
Value::String(s) => s.as_ref().to_string(),
Value::Number(n) => n.format_decimal(),
Value::Array(a) => {
"[".to_owned()
+ &a.iter()
.map(|e| to_string(e, true))
.collect::<Vec<String>>()
.join(", ")
+ "]"
}
Value::Set(s) => {
"{".to_owned()
+ &s.iter()
.map(|e| to_string(e, true))
.collect::<Vec<String>>()
.join(", ")
+ "}"
}
Value::Object(o) => {
"{".to_owned()
+ &o.iter()
.map(|(k, v)| to_string(k, true) + ": " + &to_string(v, true))
.collect::<Vec<String>>()
.join(", ")
+ "}"
}
Value::Undefined => "#undefined".to_string(),
}
}

fn sprintf(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "sprintf";
ensure_args_count(span, name, params, args, 2)?;
Expand Down Expand Up @@ -219,6 +254,9 @@ fn sprintf(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) ->
// Handle Golang printing verbs.
// https://pkg.go.dev/fmt
match (verb, arg) {
('s', Value::String(sv)) => s += sv.as_ref(),
('s', v) => s += &to_string(v, false),

('v', _) => s += format!("{arg}").as_str(),
('b', Value::Number(f)) if f.is_integer() => {
let (sign, v) = get_sign_value(f);
Expand Down Expand Up @@ -315,11 +353,6 @@ fn sprintf(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) ->
bail!(args_span.error("floating-point number specified for format verb {verb}."));
}

('s', Value::String(sv)) => s += sv.as_ref(),
('s', _) => {
bail!(args_span.error("invalid non string argument specified for format verb %s"));
}

('+', _) if chars.next() == Some('v') => {
bail!(args_span.error("Go-syntax fields names format verm %#v is not supported."));
}
Expand Down
1 change: 1 addition & 0 deletions src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ impl Engine {
self.interpreter.set_current_module(prev_module)?;
}

self.interpreter.create_rule_prefixes()?;
Ok(self.interpreter.get_data_mut().clone())
}

Expand Down
Loading

0 comments on commit 577e1aa

Please sign in to comment.