diff --git a/cel-rs/Cargo.toml b/cel-rs/Cargo.toml index eb8525b..72382ee 100644 --- a/cel-rs/Cargo.toml +++ b/cel-rs/Cargo.toml @@ -15,6 +15,7 @@ ordered_hash_map = "0.4.0" regex = "1.4.2" lalrpop-util = "0.19.1" lazy_static = "1.4.0" +unescape = "0.1.0" [dev-dependencies] cel-spec = {path = "../cel-spec"} diff --git a/cel-rs/src/eval.rs b/cel-rs/src/eval.rs index c27f2a2..fb34991 100644 --- a/cel-rs/src/eval.rs +++ b/cel-rs/src/eval.rs @@ -1,5 +1,8 @@ +use std::collections::HashMap; +use std::rc::Rc; + use crate::parser::{Atom, Expression, RelationOp}; -use crate::value::value::{Val}; +use crate::value::value::Val; use crate::Context; #[derive(Default)] @@ -47,24 +50,7 @@ impl Eval { // crate::parser::Member::Fields(_) => todo!("Fields"), // } // } - // fn eval_map(&self, entries: Vec<(Expression, Expression)>, ctx: &mut Context) -> Value { - // let mut map = OrderedHashMap::with_capacity(entries.len()); - // for (kexpr, vexpr) in entries { - // let k = self.eval(kexpr, ctx).unpack(); - // let v = self.eval(vexpr, ctx).unpack(); - // map.insert(k, v); - // } - // Value::Map(Rc::new(map)) - // } - // fn eval_list(&self, elems: Vec, ctx: &mut Context) -> Value { - // let mut list = Vec::with_capacity(elems.len()); - // for expr in elems { - // let v = self.eval(expr, ctx).unpack(); - // list.push(v); - // } - // Value::List(Rc::new(list)) - // } pub fn eval(&self, expr: Expression, ctx: &mut Context) -> Val { match expr { @@ -88,8 +74,8 @@ impl Eval { Expression::Unary(_, _) => todo!(), Expression::Member(_, _) => todo!(), Expression::FunctionCall(_) => todo!(), - Expression::List(_) => todo!(), - Expression::Map(_) => todo!(), + Expression::List(values) => self.eval_list(values, ctx), + Expression::Map(entries) => self.eval_map(entries, ctx), Expression::Atom(atom) => self.eval_atom(atom, ctx), Expression::Ident(ident) => ctx .resolve(&ident) @@ -98,6 +84,26 @@ impl Eval { } } + fn eval_map(&self, entries: Vec<(Expression, Expression)>, ctx: &mut Context) -> Val { + let mut map = HashMap::with_capacity(entries.len()); + for (kexpr, vexpr) in entries { + let k = self.eval(kexpr, ctx); + let v = self.eval(vexpr, ctx); + map.insert(k, v); + } + Val::new_map(Rc::new(map)) + } + + + fn eval_list(&self, elems: Vec, ctx: &mut Context) -> Val { + let mut list = Vec::with_capacity(elems.len()); + for expr in elems { + let v = self.eval(expr, ctx); + list.push(v); + } + Val::new_list(Rc::new(list)) + } + pub fn eval_atom(&self, atom: Atom, ctx: &mut Context) -> Val { match atom { Atom::Int(i) => Val::new_int(i), diff --git a/cel-rs/src/lib.rs b/cel-rs/src/lib.rs index 673d7ed..a7268ba 100644 --- a/cel-rs/src/lib.rs +++ b/cel-rs/src/lib.rs @@ -6,4 +6,5 @@ mod parser; // public api pub use crate::program::Program; -pub use crate::context::Context; \ No newline at end of file +pub use crate::context::Context; +pub use value::value::{Val, Value}; \ No newline at end of file diff --git a/cel-rs/src/parser/cel.lalrpop b/cel-rs/src/parser/cel.lalrpop index 1a3c04e..137b980 100644 --- a/cel-rs/src/parser/cel.lalrpop +++ b/cel-rs/src/parser/cel.lalrpop @@ -1,4 +1,5 @@ use crate::parser::{RelationOp, ArithmeticOp, Expression, UnaryOp, Member, Atom}; +use crate::parser::parse; use std::rc::Rc; grammar; @@ -90,11 +91,15 @@ RelationOp: RelationOp = { Atom: Atom = { // Integer literals - r"-?[0-9]+" => Atom::Int(<>.parse().unwrap()), - r"-?0[xX]([0-9a-fA-F]+)" => Atom::Int(i64::from_str_radix(<>, 16).unwrap()), + + r"-?[0-9]+" => Atom::Int(<>.parse().expect("failed to parse int")), + + r"0[xX]([0-9a-fA-F]+)" => Atom::Int(i64::from_str_radix(<>.trim_start_matches("0x").trim_start_matches("0X"), 16).unwrap()), + r"-0[xX]([0-9a-fA-F]+)" => Atom::Int(-i64::from_str_radix(<>.trim_start_matches("-0x").trim_start_matches("-0x"), 16).unwrap()), + // LALRPOP does not support regex capture groups. https://github.com/lalrpop/lalrpop/issues/575 r"-?[0-9]+[uU]" => Atom::UInt(<>.trim_end_matches(|c| c == 'u' || c == 'U').parse().unwrap()), - r"-?0[xX]([0-9a-fA-F]+)[uU]" => Atom::UInt(u64::from_str_radix(<>.trim_end_matches(|c| c == 'u' || c == 'U'), 16).unwrap()), + r"0[xX]([0-9a-fA-F]+)[uU]" => Atom::UInt(u64::from_str_radix(<>.trim_start_matches("0x").trim_start_matches("0X").trim_end_matches(|c| c == 'u' || c == 'U'), 16).expect("heyo")), // Float with decimals and optional exponent r"([-+]?[0-9]*\.[0-9]+([eE][-+]?[0-9]+)?)" => Atom::Float(<>.parse().unwrap()), @@ -102,12 +107,12 @@ Atom: Atom = { r"[-+]?[0-9]+[eE][-+]?[0-9]+" => Atom::Float(<>.parse().unwrap()), // Double quoted string - "r"? => Atom::String(Rc::new(s[1..s.len()-1].into())), - "r"? => Atom::String(Rc::new(s[3..s.len()-3].into())), + "r"? => Atom::String(Rc::new(parse::parse_str(&s[1..s.len()-1]))), + "r"? => Atom::String(Rc::new(parse::parse_str(&s[3..s.len()-3]))), // Single quoted string - "r"? => Atom::String(Rc::new(s[1..s.len()-1].into())), - "r"? => Atom::String(Rc::new(s[3..s.len()-3].into())), + "r"? => Atom::String(Rc::new(parse::parse_str(&s[1..s.len()-1]))), + "r"? => Atom::String(Rc::new(parse::parse_str(&s[3..s.len()-3]))), // Double quoted bytes diff --git a/cel-rs/src/parser/mod.rs b/cel-rs/src/parser/mod.rs index 3d5750a..c2cc842 100644 --- a/cel-rs/src/parser/mod.rs +++ b/cel-rs/src/parser/mod.rs @@ -1,6 +1,7 @@ use lalrpop_util::lalrpop_mod; pub mod ast; +pub mod parse; pub use ast::*; lalrpop_mod!( diff --git a/cel-rs/src/parser/parse.rs b/cel-rs/src/parser/parse.rs new file mode 100644 index 0000000..9644cae --- /dev/null +++ b/cel-rs/src/parser/parse.rs @@ -0,0 +1,5 @@ +use unescape::unescape; + +pub fn parse_str(str: &str) -> String { + unescape(str).unwrap_or(String::new()) +} \ No newline at end of file diff --git a/cel-rs/src/program.rs b/cel-rs/src/program.rs index 0d3bbf3..7cd5566 100644 --- a/cel-rs/src/program.rs +++ b/cel-rs/src/program.rs @@ -113,6 +113,18 @@ pub mod tests { assert_eq!(eval_program!(r#"2 == 2"#), Val::new_bool(true)); } + #[test] + fn self_eval_int_hex_negative() { + let expr = r#"-0x55555555"#; + let program = crate::Program::new(expr); + assert!(program.is_ok(), "failed to parse '{}'", expr); + let program = program.unwrap(); + let mut ctx = crate::Context::default(); + let value = program.eval(&mut ctx); + let expected_value = crate::Val::new_int(-1431655765); + assert_eq!(value, expected_value, r#""{:?}" did not match "{:?}""#, value, expected_value); + } + // fn calc_string_string(args: Vec) -> Value { // println!("{:?}", args); diff --git a/cel-rs/src/value/bool.rs b/cel-rs/src/value/bool.rs index 1cc1546..e7082b1 100644 --- a/cel-rs/src/value/bool.rs +++ b/cel-rs/src/value/bool.rs @@ -32,12 +32,13 @@ impl Value for Bool { } fn compare(&self, other: &Val) -> Option { - other.as_bool().map(|ob| { - (&self.0).cmp(ob).into() - }) + other.as_bool().map(|ob| (&self.0).cmp(ob).into()) } - fn equals(&self, other: &Val) -> Option { - other.as_bool().map(|f| Val::new_bool(&self.0 == f)) + fn equals(&self, other: &Val) -> Val { + other + .as_bool() + .map(|f| Val::new_bool(&self.0 == f)) + .unwrap_or(Val::new_bool(false)) } } diff --git a/cel-rs/src/value/error.rs b/cel-rs/src/value/error.rs index bb27bbf..fef55d7 100644 --- a/cel-rs/src/value/error.rs +++ b/cel-rs/src/value/error.rs @@ -1,3 +1,5 @@ +use core::fmt; + use super::{value::{Value, Val}, ty::Ty}; #[derive(Eq, PartialEq)] @@ -6,6 +8,12 @@ pub struct Error { error: String, } +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "error(id = {:?}, message = {})", self.id, self.error) + } +} + impl Error { pub fn new(error: String) -> Val { return Val::new(Self { id: None, error }); diff --git a/cel-rs/src/value/map.rs b/cel-rs/src/value/map.rs new file mode 100644 index 0000000..30d1349 --- /dev/null +++ b/cel-rs/src/value/map.rs @@ -0,0 +1,51 @@ +use std::{collections::HashMap, rc::Rc}; +use crate::{Val, Value}; +use super::ty::Ty; + +pub struct Map(Rc>); + +impl Map { + pub fn new(h: Rc>) -> Self { + Self(h) + } +} + +impl Value for Map { + fn ty(&self) -> super::ty::Ty { + Ty::Map + } + + fn native_value(&self) -> &dyn std::any::Any { + &self.0 + } + + fn equals(&self, other: &Val) -> Val { + other + .native_value() + .downcast_ref::>>() + .map(|other| { + if other.len() != self.0.len() { + return Val::new_bool(false); + } + + for (k, v) in self.0.iter() { + let ov = other.get(k); + if !ov.is_none() { + return Val::new_bool(false); + } + + // TODO: use value.equals once all types support it. + if !ov + .unwrap() + .compare(v) + .is_some_and(|o| o.as_int() == Some(&0)) + { + return Val::new_bool(false); + } + } + + return Val::new_bool(true); + }) + .unwrap_or(Val::new_bool(false)) + } +} diff --git a/cel-rs/src/value/mod.rs b/cel-rs/src/value/mod.rs index 2a77172..608f5ae 100644 --- a/cel-rs/src/value/mod.rs +++ b/cel-rs/src/value/mod.rs @@ -12,3 +12,4 @@ pub mod bytes; pub mod double; pub mod uint; pub mod int; +pub mod map; diff --git a/cel-rs/src/value/value.rs b/cel-rs/src/value/value.rs index 7f8453d..02d9565 100644 --- a/cel-rs/src/value/value.rs +++ b/cel-rs/src/value/value.rs @@ -1,4 +1,5 @@ use std::cmp; +use std::collections::HashMap; use std::{fmt, rc::Rc}; use crate::value::{error::Error, ty::Ty}; @@ -7,6 +8,7 @@ use super::bool::Bool; use super::bytes::Bytes; use super::double::Double; use super::int::Int; +use super::map::Map; use super::null::Null; use super::string::String as CELString; use super::uint::Uint; @@ -28,7 +30,7 @@ pub trait Value { unimplemented!("compare {:?} {:?}", self.ty(), other.ty()) } - fn equals(&self, other: &Val) -> Option { + fn equals(&self, other: &Val) -> Val { unimplemented!("equals {:?} {:?}", self.ty(), other.ty()) } } @@ -41,19 +43,71 @@ impl cmp::PartialOrd for Val { } } +impl std::hash::Hash for Val { + fn hash(&self, state: &mut H) { + state.write(format!("TODO:{:?}", &self).as_bytes()); + } +} + +impl Eq for Val {} + impl PartialEq for Val { fn eq(&self, other: &Self) -> bool { - self.partial_cmp(other).is_some_and(|ord| ord == cmp::Ordering::Equal) + // TODO: switch other types to use equals instead. + if other.ty() == Ty::Map { + return self.equals(other).as_bool().unwrap_or(&false).to_owned(); + } + self.partial_cmp(other) + .is_some_and(|ord| ord == cmp::Ordering::Equal) } } impl fmt::Debug for Val { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Val(ty = {:?}", self.ty())?; + // TODO: maybe replace this with a call to self.to_type(Ty::String).as_string()? match self.ty() { Ty::Bool => write!(f, ", value = {}", self.as_bool().unwrap()), - Ty::Int => write!(f, ", value = {}", self.native_value().downcast_ref::().unwrap()), - _ => Ok(()) + Ty::Int => write!( + f, + ", value = {}", + self.native_value().downcast_ref::().unwrap() + ), + Ty::UInt => write!( + f, + ", value = {}", + self.native_value().downcast_ref::().unwrap() + ), + Ty::Double => write!( + f, + ", value = {}", + self.native_value().downcast_ref::().unwrap() + ), + Ty::String => write!( + f, + ", value = {}", + self.native_value().downcast_ref::().unwrap() + ), + Ty::Bytes => write!( + f, + ", value = {:?}", + self.native_value().downcast_ref::>().unwrap() + ), + Ty::List => write!(f, ", value = TODO"), + Ty::Map => write!(f, ", value = {:?}", self.native_value().downcast_ref::>>().unwrap()), + Ty::Null => write!(f, ", value = null"), + Ty::Type => write!( + f, + ", value = {:?}", + self.native_value().downcast_ref::().unwrap() + ), + Ty::Unknown => write!(f, ", value = ?"), + Ty::Error => write!( + f, + ", value = {}", + self.native_value().downcast_ref::().unwrap() + ), + Ty::Dyn => write!(f, ", value = dyn"), }?; write!(f, ")") } @@ -65,7 +119,6 @@ impl Clone for Val { } } - impl Val { pub fn new(v: impl Value + 'static) -> Self { Self(Rc::new(v)) @@ -95,10 +148,19 @@ impl Val { pub fn new_int(i: i64) -> Self { Self::new(Int::new(i)) } + pub fn new_map(h: Rc>) -> Self { + Self::new(Map::new(h)) + } + pub fn new_list(b: Rc>) -> Self { + Self::new(Null::new()) + } pub fn as_bool(&self) -> Option<&bool> { return self.0.native_value().downcast_ref::(); } + pub fn as_int(&self) -> Option<&i64> { + return self.0.native_value().downcast_ref::(); + } } impl Value for Val { @@ -114,5 +176,15 @@ impl Value for Val { fn compare(&self, other: &Val) -> Option { self.0.compare(other) - } + } + + fn equals(&self, other: &Val) -> Val { + self.0.equals(other) + } + fn to_bool(&self) -> Val { + self.0.to_bool() + } + fn to_type(&self, ty: Ty) -> Val { + self.0.to_type(ty) + } } diff --git a/cel-rs/tests/test.rs b/cel-rs/tests/test.rs index 6239304..a0a9f73 100644 --- a/cel-rs/tests/test.rs +++ b/cel-rs/tests/test.rs @@ -1,11 +1,309 @@ -// use cel_spec; +use cel_spec; // cel_spec::suite!( // name = "basic", -// include = "self_eval_zeroish", +// // include = "self_eval_zeroish", // include = "self_eval_nonzeroish", -// include = "variables", +// // include = "variables", // // include = "ffunctions", // // include = "reserved_const", // ); +pub mod self_eval_nonzeroish { + #[test] + fn self_eval_int_nonzero() { + let expr = r#"42"#; + let program = cel_rs::Program::new(expr); + assert!(program.is_ok(), "failed to parse '{}'", expr); + let program = program.unwrap(); + let mut ctx = cel_rs::Context::default(); + let value = program.eval(&mut ctx); + let expected_value = cel_rs::Val::new_int(42); + assert_eq!( + value, expected_value, + r#""{:?}" did not match "{:?}""#, + value, expected_value + ); + } + #[test] + fn self_eval_uint_nonzero() { + let expr = r#"123456789u"#; + let program = cel_rs::Program::new(expr); + assert!(program.is_ok(), "failed to parse '{}'", expr); + let program = program.unwrap(); + let mut ctx = cel_rs::Context::default(); + let value = program.eval(&mut ctx); + let expected_value = cel_rs::Val::new_uint(123456789); + assert_eq!( + value, expected_value, + r#""{:?}" did not match "{:?}""#, + value, expected_value + ); + } + #[test] + fn self_eval_uint_alias_nonzero() { + let expr = r#"123456789U"#; + let program = cel_rs::Program::new(expr); + assert!(program.is_ok(), "failed to parse '{}'", expr); + let program = program.unwrap(); + let mut ctx = cel_rs::Context::default(); + let value = program.eval(&mut ctx); + let expected_value = cel_rs::Val::new_uint(123456789); + assert_eq!( + value, expected_value, + r#""{:?}" did not match "{:?}""#, + value, expected_value + ); + } + #[test] + fn self_eval_int_negative_min() { + let expr = r#"-9223372036854775808"#; + let program = cel_rs::Program::new(expr); + assert!(program.is_ok(), "failed to parse '{}'", expr); + let program = program.unwrap(); + let mut ctx = cel_rs::Context::default(); + let value = program.eval(&mut ctx); + let expected_value = cel_rs::Val::new_int(-9223372036854775808); + assert_eq!( + value, expected_value, + r#""{:?}" did not match "{:?}""#, + value, expected_value + ); + } + #[test] + fn self_eval_float_negative_exp() { + let expr = r#"-2.3e+1"#; + let program = cel_rs::Program::new(expr); + assert!(program.is_ok(), "failed to parse '{}'", expr); + let program = program.unwrap(); + let mut ctx = cel_rs::Context::default(); + let value = program.eval(&mut ctx); + let expected_value = cel_rs::Val::new_double((-23).into()); + assert_eq!( + value, expected_value, + r#""{:?}" did not match "{:?}""#, + value, expected_value + ); + } + #[test] + fn self_eval_string_excl() { + let expr = r#""!""#; + let program = cel_rs::Program::new(expr); + assert!(program.is_ok(), "failed to parse '{}'", expr); + let program = program.unwrap(); + let mut ctx = cel_rs::Context::default(); + let value = program.eval(&mut ctx); + let expected_value = + cel_rs::Val::new_string(std::rc::Rc::new(String::from_utf8([33].to_vec()).unwrap())); + assert_eq!( + value, expected_value, + r#""{:?}" did not match "{:?}""#, + value, expected_value + ); + } + #[test] + fn self_eval_string_escape() { + let expr = r#"'\''"#; + let program = cel_rs::Program::new(expr); + assert!(program.is_ok(), "failed to parse '{}'", expr); + let program = program.unwrap(); + let mut ctx = cel_rs::Context::default(); + let value = program.eval(&mut ctx); + let expected_value = + cel_rs::Val::new_string(std::rc::Rc::new(String::from_utf8([39].to_vec()).unwrap())); + assert_eq!( + value, expected_value, + r#""{:?}" did not match "{:?}""#, + value, expected_value + ); + } + #[test] + fn self_eval_bytes_escape() { + let expr = r#"b'ΓΏ'"#; + let program = cel_rs::Program::new(expr); + assert!(program.is_ok(), "failed to parse '{}'", expr); + let program = program.unwrap(); + let mut ctx = cel_rs::Context::default(); + let value = program.eval(&mut ctx); + let expected_value = cel_rs::Val::new_bytes(std::rc::Rc::new(Vec::from([195, 191]))); + assert_eq!( + value, expected_value, + r#""{:?}" did not match "{:?}""#, + value, expected_value + ); + } + #[test] + fn self_eval_bytes_invalid_utf8() { + let expr = r#"b'\000\xff'"#; + let program = cel_rs::Program::new(expr); + assert!(program.is_ok(), "failed to parse '{}'", expr); + let program = program.unwrap(); + let mut ctx = cel_rs::Context::default(); + let value = program.eval(&mut ctx); + let expected_value = cel_rs::Val::new_bytes(std::rc::Rc::new(Vec::from([0, 255]))); + assert_eq!( + value, expected_value, + r#""{:?}" did not match "{:?}""#, + value, expected_value + ); + } + #[test] + fn self_eval_list_singleitem() { + let expr = r#"[-1]"#; + let program = cel_rs::Program::new(expr); + assert!(program.is_ok(), "failed to parse '{}'", expr); + let program = program.unwrap(); + let mut ctx = cel_rs::Context::default(); + let value = program.eval(&mut ctx); + let expected_value = cel_rs::Val::new_list(Vec::new().into()); + assert_eq!( + value, expected_value, + r#""{:?}" did not match "{:?}""#, + value, expected_value + ); + } + #[test] + fn self_eval_map_singleitem() { + let expr = r#"{"k":"v"}"#; + let program = cel_rs::Program::new(expr); + assert!(program.is_ok(), "failed to parse '{}'", expr); + let program = program.unwrap(); + let mut ctx = cel_rs::Context::default(); + let value = program.eval(&mut ctx); + let expected_value = cel_rs::Val::new_map( + std::collections::HashMap::::new().into(), + ); + assert_eq!( + value, expected_value, + r#""{:?}" did not match "{:?}""#, + value, expected_value + ); + } + #[test] + fn self_eval_bool_true() { + let expr = r#"true"#; + let program = cel_rs::Program::new(expr); + assert!(program.is_ok(), "failed to parse '{}'", expr); + let program = program.unwrap(); + let mut ctx = cel_rs::Context::default(); + let value = program.eval(&mut ctx); + let expected_value = cel_rs::Val::new_bool(true); + assert_eq!( + value, expected_value, + r#""{:?}" did not match "{:?}""#, + value, expected_value + ); + } + #[test] + fn self_eval_int_hex() { + let expr = r#"0x55555555"#; + let program = cel_rs::Program::new(expr); + assert!(program.is_ok(), "failed to parse '{}'", expr); + let program = program.unwrap(); + let mut ctx = cel_rs::Context::default(); + let value = program.eval(&mut ctx); + let expected_value = cel_rs::Val::new_int(1431655765); + assert_eq!( + value, expected_value, + r#""{:?}" did not match "{:?}""#, + value, expected_value + ); + } + #[test] + fn self_eval_int_hex_negative() { + let expr = r#"-0x55555555"#; + let program = cel_rs::Program::new(expr); + assert!(program.is_ok(), "failed to parse '{}'", expr); + let program = program.unwrap(); + let mut ctx = cel_rs::Context::default(); + let value = program.eval(&mut ctx); + let expected_value = cel_rs::Val::new_int(-1431655765); + assert_eq!( + value, expected_value, + r#""{:?}" did not match "{:?}""#, + value, expected_value + ); + } + #[test] + fn self_eval_uint_hex() { + let expr = r#"0x55555555u"#; + let program = cel_rs::Program::new(expr); + assert!(program.is_ok(), "failed to parse '{}'", expr); + let program = program.unwrap(); + let mut ctx = cel_rs::Context::default(); + let value = program.eval(&mut ctx); + let expected_value = cel_rs::Val::new_uint(1431655765); + assert_eq!( + value, expected_value, + r#""{:?}" did not match "{:?}""#, + value, expected_value + ); + } + #[test] + fn self_eval_uint_alias_hex() { + let expr = r#"0x55555555U"#; + let program = cel_rs::Program::new(expr); + assert!(program.is_ok(), "failed to parse '{}'", expr); + let program = program.unwrap(); + let mut ctx = cel_rs::Context::default(); + let value = program.eval(&mut ctx); + let expected_value = cel_rs::Val::new_uint(1431655765); + assert_eq!( + value, expected_value, + r#""{:?}" did not match "{:?}""#, + value, expected_value + ); + } + + #[test] + fn self_eval_unicode_escape_four() { + let expr = r#""\u270c""#; + let program = cel_rs::Program::new(expr); + assert!(program.is_ok(), "failed to parse '{}'", expr); + let program = program.unwrap(); + let mut ctx = cel_rs::Context::default(); + let value = program.eval(&mut ctx); + let expected_value = cel_rs::Val::new_string(std::rc::Rc::new( + String::from_utf8([226, 156, 140].to_vec()).unwrap(), + )); + assert_eq!( + value, expected_value, + r#""{:?}" did not match "{:?}""#, + value, expected_value + ); + } + #[test] + fn self_eval_unicode_escape_eight() { + let expr = r#""\U0001f431""#; + let program = cel_rs::Program::new(expr); + assert!(program.is_ok(), "failed to parse '{}'", expr); + let program = program.unwrap(); + let mut ctx = cel_rs::Context::default(); + let value = program.eval(&mut ctx); + let expected_value = cel_rs::Val::new_string(std::rc::Rc::new( + String::from_utf8([240, 159, 144, 177].to_vec()).unwrap(), + )); + assert_eq!( + value, expected_value, + r#""{:?}" did not match "{:?}""#, + value, expected_value + ); + } + #[test] + fn self_eval_ascii_escape_seq() { + let expr = r#""\a\b\f\n\r\t\v\"\'\\""#; + let program = cel_rs::Program::new(expr); + assert!(program.is_ok(), "failed to parse '{}'", expr); + let program = program.unwrap(); + let mut ctx = cel_rs::Context::default(); + let value = program.eval(&mut ctx); + let expected_value = cel_rs::Val::new_string(std::rc::Rc::new( + String::from_utf8([7, 8, 12, 10, 13, 9, 11, 34, 39, 92].to_vec()).unwrap(), + )); + assert_eq!( + value, expected_value, + r#""{:?}" did not match "{:?}""#, + value, expected_value + ); + } +} diff --git a/cel-spec/Cargo.toml b/cel-spec/Cargo.toml index d511d0f..ffc3d18 100644 --- a/cel-spec/Cargo.toml +++ b/cel-spec/Cargo.toml @@ -13,6 +13,9 @@ prost-reflect = { version = "0.13.0", features = ["text-format"] } syn = { version = "2.0.55", features = ["parsing"] } static_init = "1.0.3" darling = "0.20.8" +lazy_static = "1.4.0" -[build_dependencies] +[build-dependencies] prost-build = "0.12.3" +protoc = "2.28.0" +protoc-prebuilt = "0.3.0" diff --git a/cel-spec/build.rs b/cel-spec/build.rs index e755986..669912e 100644 --- a/cel-spec/build.rs +++ b/cel-spec/build.rs @@ -1,13 +1,77 @@ -use std::{env, io::Result, path::PathBuf}; +use std::{ + env, + fmt::format, + fs::{self, File}, + io::{Result, Write}, + path::{Path, PathBuf}, + process::{Command, Stdio}, +}; fn main() -> Result<()> { - let out = env::var("OUT_DIR").expect("OUT_DIR environment variable not set"); - let out = PathBuf::from(out).join("cel.bin"); + let out = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR environment variable not set")); + let mut config = prost_build::Config::new(); config.disable_comments(&["."]); - config.file_descriptor_set_path(out); + config.file_descriptor_set_path(out.join("cel.bin")); config.compile_protos( &["cel-spec/proto/test/v1/simple.proto"], &["cel-spec/proto/", "googleapis/"], - ) + )?; + + let protoc = protoc_prebuilt::init("27.0").expect("failed to download protoc"); + + let mut stamp = String::from("use std::collections::HashMap;\n"); + + let mut items = String::new(); + + for s in fs::read_dir("cel-spec/tests/simple/testdata").unwrap() { + let filename = PathBuf::from("cel-spec/tests/simple/testdata") + .join(s.unwrap().file_name().to_str().unwrap()); + + if !filename.is_file() { + continue; + } + if !filename.extension().is_some_and(|f| f == "textproto") { + continue; + } + + let output = File::create(out.join(filename.with_extension("bin").file_name().unwrap()))?; + let name = filename.with_extension(""); + let name = name.file_name().unwrap().to_string_lossy(); + stamp.push_str("\n"); + stamp.push_str( + format!( + r#"const {name}: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/{out}.bin"));"#, + name = name.to_uppercase(), + out = name + ) + .as_str(), + ); + + items.push_str("\n\t"); + items.push_str(format!(r#"("{name}", {var}),"#, name = name, var = name.to_uppercase()).as_str()); + + let input = File::open(filename)?; + + Command::new(&protoc.0) + .arg("-Icel-spec/proto/") + .arg("-Igoogleapis/") + .arg("--encode=google.api.expr.test.v1.SimpleTestFile") + .arg("cel-spec/proto/test/v1/simple.proto") + .stdin(Stdio::from(input)) + .stdout(Stdio::from(output)) + .stderr(Stdio::inherit()) + .spawn()? + .wait()?; + } + + stamp.push_str("\n"); + stamp.push_str("lazy_static::lazy_static! {\n"); + stamp.push_str("static ref TESTS: HashMap<&'static str, &'static [u8]> = HashMap::from(["); + + stamp.extend(items.chars().into_iter()); + stamp.push_str("\n]);\n}"); + File::create(out.join("tests.rs"))?.write_all(stamp.as_bytes())?; + + Ok(()) } diff --git a/cel-spec/src/lib.rs b/cel-spec/src/lib.rs index 103e943..2510d3f 100644 --- a/cel-spec/src/lib.rs +++ b/cel-spec/src/lib.rs @@ -1,49 +1,60 @@ use std::io::Read; use proc_macro::TokenStream; -use static_init::dynamic; use darling::{Error, FromMeta}; use darling::ast::NestedMeta; -use prost_reflect::{DescriptorPool, MessageDescriptor, DynamicMessage}; +use prost::Message; + +mod google { + mod rpc { + include!(concat!(env!("OUT_DIR"), "/google.rpc.rs")); + } + pub mod api { + pub mod expr { + mod v1alpha1 { + include!(concat!(env!("OUT_DIR"), "/google.api.expr.v1alpha1.rs")); + } + pub mod test { + pub mod v1 { + include!(concat!(env!("OUT_DIR"), "/google.api.expr.test.v1.rs")); + } + } + } + } +} + +include!(concat!(env!("OUT_DIR"), "/tests.rs")); -const BYTES: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/cel.bin")); -#[dynamic] -static MESSAGE_DESCRIPTOR: MessageDescriptor = { - let pool = DescriptorPool::decode(BYTES.as_ref()).unwrap(); - pool - .get_message_by_name("google.api.expr.test.v1.SimpleTestFile") - .unwrap() -}; fn get_expected_value(value: &DynamicMessage) -> String { let field = value.fields().next().expect("get expected value"); let (m, v, c) = match field.0.name() { "string_value" => ( - "String(std::rc::Rc::new(String::from_utf8(", + "new_string(std::rc::Rc::new(String::from_utf8(", format!("{:?}", field.1.as_str().unwrap().as_bytes().to_vec()), ".to_vec()).unwrap()))", ), - "bool_value" => ("Bool(", format!("{}", field.1.as_bool().unwrap()), ")"), - "int64_value" => ("Int(", format!("{}", field.1.as_i64().unwrap()), ")"), - "uint64_value" => ("UInt(", format!("{}", field.1.as_u64().unwrap()), ")"), + "bool_value" => ("new_bool(", format!("{}", field.1.as_bool().unwrap()), ")"), + "int64_value" => ("new_int(", format!("{}", field.1.as_i64().unwrap()), ")"), + "uint64_value" => ("new_uint(", format!("{}", field.1.as_u64().unwrap()), ")"), "double_value" => ( - "Float(", + "new_double(", format!("({}).into()", field.1.as_f64().unwrap()), ")", ), "map_value" => ( - "Map(", - String::from("ordered_hash_map::OrderedHashMap::new().into()"), + "new_map(", + String::from("std::collections::HashMap::::new().into()"), ")", ), - "list_value" => ("List(", String::from("Vec::new().into()"), ")"), + "list_value" => ("new_list(", String::from("Vec::new().into()"), ")"), "bytes_value" => ( - "Bytes(std::rc::Rc::new(Vec::from(", + "new_bytes(std::rc::Rc::new(Vec::from(", format!("{:?}", field.1.as_bytes().unwrap().to_vec()), ")))", ), - _ => ("Null", String::new(), ""), + _ => ("new_null(", String::new(), ")"), }; - format!("cel_rs::value::Value::{}{}{}", m, v, c) + format!("cel_rs::Val::{}{}{}", m, v, c) } #[derive(Debug, FromMeta)] @@ -74,34 +85,22 @@ pub fn suite(rargs: TokenStream) -> TokenStream { )) .expect("could not find the suite"); - let mut content = String::new(); - file.read_to_string(&mut content) - .expect("can not read the suite file"); - let suite = DynamicMessage::parse_text_format(MESSAGE_DESCRIPTOR.to_owned(), &content).unwrap(); + let testfile = google::api::expr::test::v1::SimpleTestFile::decode(file).expect("msg"); let mut ast = String::new(); - for section in suite - .get_field_by_name("section") - .unwrap() - .as_list() - .unwrap() + for section in testfile.section { - let section = section.as_message().unwrap(); - let sname = section.get_field_by_name("name").unwrap(); - let sname = sname.as_str().unwrap(); let includes = &args.includes; - if includes.into_iter().find(|p| p.as_str() == sname).is_none(){ - println!("skip {}", &sname); + if includes.into_iter().find(|p| p == &§ion.name).is_none(){ + println!("skip {}", §ion.name); continue; } - ast.push_str(format!("pub mod {}{{", sname).as_str()); + ast.push_str("pub mod "); + ast.push_str(section.name.as_str()); + ast.push_str("{"); - for case in section - .get_field_by_name("test") - .expect("test") - .as_list() - .expect("test as list") + for test in section.test { let case = case.as_message().expect("message as case"); let name = case.get_field_by_name("name").expect("expected name"); @@ -111,7 +110,7 @@ pub fn suite(rargs: TokenStream) -> TokenStream { let name = name.as_str().expect("name as str"); let expr = expr.as_str().expect("expr as str"); let value = value.as_message().expect("value as message"); - let expected_value = get_expected_value(value); + let expected_value = get_expected_value(test.); ast.push_str(&format!(r##" #[test] @@ -120,12 +119,12 @@ pub fn suite(rargs: TokenStream) -> TokenStream { let program = cel_rs::Program::new(expr); assert!(program.is_ok(), "failed to parse '{{}}'", expr); let program = program.unwrap(); - let mut ctx = cel_rs::context::Context::default(); + let mut ctx = cel_rs::Context::default(); let value = program.eval(&mut ctx); let expected_value = {expected_value}; - assert_eq!(value, expected_value, r#""{{}}" did not match "{{}}""#, value, expected_value); + assert_eq!(value, expected_value, r#""{{:?}}" did not match "{{:?}}""#, value, expected_value); }} - "##, name = name, expr = expr, expected_value = expected_value ).to_string()); + "##, name = test.name, expr = test.expr, expected_value = expected_value ).to_string()); } ast.push_str("}");