difftreelog
feat(evaluator) ArrComp support
in: master
7 files changed
crates/jsonnet-evaluator/src/ctx.rsdiffbeforeafterboth--- a/crates/jsonnet-evaluator/src/ctx.rs
+++ b/crates/jsonnet-evaluator/src/ctx.rs
@@ -1,4 +1,7 @@
-use crate::{future_wrapper, rc_fn_helper, LazyBinding, LazyVal, ObjValue};
+use crate::{
+ future_wrapper, lazy_binding, lazy_val, rc_fn_helper, LazyBinding, LazyVal, ObjValue, Val,
+};
+use closure::closure;
use std::{cell::RefCell, collections::HashMap, fmt::Debug, rc::Rc};
rc_fn_helper!(
@@ -62,6 +65,17 @@
ctx.unwrap()
}
+ pub fn with_var(&self, name: String, value: Val) -> Context {
+ let mut new_bindings: HashMap<_, LazyBinding> = HashMap::new();
+ new_bindings.insert(
+ name,
+ lazy_binding!(
+ closure!(clone value, |_t, _s|lazy_val!(closure!(clone value, ||value.clone())))
+ ),
+ );
+ self.extend(new_bindings, None, None, None)
+ }
+
pub fn extend(
&self,
new_bindings: HashMap<String, LazyBinding>,
crates/jsonnet-evaluator/src/evaluate.rsdiffbeforeafterboth--- a/crates/jsonnet-evaluator/src/evaluate.rs
+++ b/crates/jsonnet-evaluator/src/evaluate.rs
@@ -5,8 +5,9 @@
};
use closure::closure;
use jsonnet_parser::{
- ArgsDesc, BinaryOpType, BindSpec, Expr, FieldMember, LiteralType, LocExpr, Member, ObjBody,
- ParamsDesc, UnaryOpType, Visibility,
+ el, Arg, ArgsDesc, AssertStmt, BinaryOpType, BindSpec, CompSpec, Expr, FieldMember,
+ ForSpecData, IfSpecData, LiteralType, LocExpr, Member, ObjBody, ParamsDesc, UnaryOpType,
+ Visibility,
};
use std::{
collections::{BTreeMap, HashMap},
@@ -94,29 +95,64 @@
}
}
-pub fn evaluate_binary_op(a: &Val, op: BinaryOpType, b: &Val) -> Val {
+pub fn evaluate_add_op(a: &Val, b: &Val) -> Val {
+ match (a, b) {
+ (Val::Str(v1), Val::Str(v2)) => Val::Str(v1.to_owned() + &v2),
+ (Val::Str(v1), Val::Num(v2)) => Val::Str(format!("{}{}", v1, v2)),
+ (Val::Num(v1), Val::Str(v2)) => Val::Str(format!("{}{}", v1, v2)),
+ (Val::Obj(v1), Val::Obj(v2)) => Val::Obj(v2.with_super(v1.clone())),
+ (Val::Arr(a), Val::Arr(b)) => Val::Arr([&a[..], &b[..]].concat()),
+ (Val::Num(v1), Val::Num(v2)) => Val::Num(v1 + v2),
+ _ => panic!("can't add: {:?} and {:?}", a, b),
+ }
+}
+
+pub fn evaluate_binary_op(
+ context: Context,
+ eval_state: EvaluationState,
+ a: &Val,
+ op: BinaryOpType,
+ b: &Val,
+) -> Val {
match (a, op, b) {
- (Val::Lazy(a), o, b) => evaluate_binary_op(&a.evaluate(), o, b),
- (a, o, Val::Lazy(b)) => evaluate_binary_op(a, o, &b.evaluate()),
+ (Val::Lazy(a), o, b) => evaluate_binary_op(context, eval_state, &a.evaluate(), o, b),
+ (a, o, Val::Lazy(b)) => evaluate_binary_op(context, eval_state, a, o, &b.evaluate()),
+
+ (a, BinaryOpType::Add, b) => evaluate_add_op(a, b),
- (Val::Str(v1), BinaryOpType::Add, Val::Str(v2)) => Val::Str(v1.to_owned() + &v2),
(Val::Str(v1), BinaryOpType::Ne, Val::Str(v2)) => bool_val(v1 != v2),
- (Val::Str(v1), BinaryOpType::Add, Val::Num(v2)) => Val::Str(format!("{}{}", v1, v2)),
(Val::Str(v1), BinaryOpType::Mul, Val::Num(v2)) => Val::Str(v1.repeat(*v2 as usize)),
+ (Val::Str(format), BinaryOpType::Mod, args) => evaluate(
+ context
+ .with_var("__tmp__format__".to_owned(), Val::Str(format.to_owned()))
+ .with_var(
+ "__tmp__args__".to_owned(),
+ match args {
+ Val::Arr(v) => Val::Arr(v.clone()),
+ v => Val::Arr(vec![v.clone()]),
+ },
+ ),
+ eval_state,
+ &el!(Expr::Apply(
+ el!(Expr::Index(
+ el!(Expr::Var("std".to_owned())),
+ el!(Expr::Str("format".to_owned()))
+ )),
+ ArgsDesc(vec![
+ Arg(None, el!(Expr::Var("__tmp__format__".to_owned()))),
+ Arg(None, el!(Expr::Var("__tmp__args__".to_owned())))
+ ])
+ )),
+ ),
(Val::Bool(a), BinaryOpType::And, Val::Bool(b)) => Val::Bool(*a && *b),
(Val::Bool(a), BinaryOpType::Or, Val::Bool(b)) => Val::Bool(*a || *b),
- (Val::Obj(v1), BinaryOpType::Add, Val::Obj(v2)) => Val::Obj(v2.with_super(v1.clone())),
-
- (Val::Arr(a), BinaryOpType::Add, Val::Arr(b)) => Val::Arr([&a[..], &b[..]].concat()),
-
(Val::Num(v1), BinaryOpType::Mul, Val::Num(v2)) => Val::Num(v1 * v2),
(Val::Num(v1), BinaryOpType::Div, Val::Num(v2)) => Val::Num(v1 / v2),
(Val::Num(v1), BinaryOpType::Mod, Val::Num(v2)) => Val::Num(v1 % v2),
- (Val::Num(v1), BinaryOpType::Add, Val::Num(v2)) => Val::Num(v1 + v2),
(Val::Num(v1), BinaryOpType::Sub, Val::Num(v2)) => Val::Num(v1 - v2),
(Val::Num(v1), BinaryOpType::Lhs, Val::Num(v2)) => {
@@ -152,6 +188,42 @@
future_wrapper!(HashMap<String, LazyBinding>, FutureNewBindings);
future_wrapper!(ObjValue, FutureObjValue);
+pub fn evaluate_comp(
+ context: Context,
+ eval_state: EvaluationState,
+ value: &LocExpr,
+ specs: &[CompSpec],
+) -> Option<Vec<Val>> {
+ match specs.get(0) {
+ None => Some(vec![evaluate(context, eval_state, &value)]),
+ Some(CompSpec::IfSpec(IfSpecData(cond))) => {
+ match evaluate(context.clone(), eval_state.clone(), &cond).unwrap_if_lazy() {
+ Val::Bool(false) => None,
+ Val::Bool(true) => evaluate_comp(context, eval_state, value, &specs[1..]),
+ _ => panic!("if expression evaluated to non-boolean value"),
+ }
+ }
+ Some(CompSpec::ForSpec(ForSpecData(var, expr))) => {
+ match evaluate(context.clone(), eval_state.clone(), &expr).unwrap_if_lazy() {
+ Val::Arr(list) => {
+ let mut out = Vec::new();
+ for item in list {
+ let item = item.clone();
+ out.push(evaluate_comp(
+ context.with_var(var.clone(), item),
+ eval_state.clone(),
+ value,
+ &specs[1..],
+ ));
+ }
+ Some(out.iter().flatten().flatten().cloned().collect())
+ }
+ _ => panic!("for expression evaluated to non-iterable value"),
+ }
+ }
+ }
+}
+
// TODO: Asserts
pub fn evaluate_object(context: Context, eval_state: EvaluationState, object: ObjBody) -> ObjValue {
match object {
@@ -272,11 +344,18 @@
Parened(e) => evaluate(context, eval_state.clone(), e),
Str(v) => Val::Str(v.clone()),
Num(v) => Val::Num(*v),
- BinaryOp(v1, o, v2) => evaluate_binary_op(
- &evaluate(context.clone(), eval_state.clone(), v1),
- *o,
- &evaluate(context, eval_state.clone(), v2),
- ),
+ BinaryOp(v1, o, v2) => {
+ let a = evaluate(context.clone(), eval_state.clone(), v1).unwrap_if_lazy();
+ let op = *o;
+ let b = evaluate(context.clone(), eval_state.clone(), v2).unwrap_if_lazy();
+ evaluate_binary_op(
+ context,
+ eval_state,
+ &a,
+ op,
+ &b,
+ )
+ },
UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(context, eval_state, v)),
Var(name) => Val::Lazy(context.binding(&name)).unwrap_if_lazy(),
Index(value, index) => {
@@ -286,7 +365,7 @@
) {
(Val::Obj(v), Val::Str(s)) => v
.get(&s)
- .unwrap_or_else(closure!(clone context, || {
+ .unwrap_or_else(closure!(clone context, clone eval_state, || {
if let Some(n) = v.get("__intristic_namespace__") {
if let Val::Str(n) = n.unwrap_if_lazy() {
Val::Intristic(n, s)
@@ -335,12 +414,16 @@
}
Val::Arr(out)
}
+ ArrComp(expr, compspecs) => {
+ Val::Arr(evaluate_comp(context, eval_state, expr, compspecs).unwrap())
+ }
Obj(body) => Val::Obj(evaluate_object(context, eval_state, body.clone())),
Apply(value, ArgsDesc(args)) => {
let value = evaluate(context.clone(), eval_state.clone(), value).unwrap_if_lazy();
match value {
// TODO: Capture context of application
Val::Intristic(ns, name) => match (&ns as &str, &name as &str) {
+ // arr/string/function
("std", "length") => {
assert_eq!(args.len(), 1);
let expr = &args.get(0).unwrap().1;
@@ -350,11 +433,13 @@
v => panic!("can't get length of {:?}", v),
}
}
+ // any
("std", "type") => {
assert_eq!(args.len(), 1);
let expr = &args.get(0).unwrap().1;
Val::Str(evaluate(context, eval_state, expr).type_of().to_owned())
}
+ // length, idx=>any
("std", "makeArray") => {
assert_eq!(args.len(), 2);
if let (Val::Num(v), Val::Func(d)) = (
@@ -371,6 +456,7 @@
panic!("bad makeArray call");
}
}
+ // string
("std", "codepoint") => {
assert_eq!(args.len(), 1);
if let Val::Str(s) = evaluate(context, eval_state, &args[0].1) {
@@ -383,6 +469,19 @@
panic!("bad codepoint call");
}
}
+ // object, includeHidden
+ ("std", "objectFieldsEx") => {
+ assert_eq!(args.len(), 2);
+ if let (Val::Obj(body), Val::Bool(_include_hidden)) = (
+ evaluate(context.clone(), eval_state.clone(), &args[0].1),
+ evaluate(context, eval_state, &args[1].1),
+ ) {
+ // TODO: handle visibility (_include_hidden)
+ Val::Arr(body.fields().into_iter().map(Val::Str).collect())
+ } else {
+ panic!("bad objectFieldsEx call");
+ }
+ }
(ns, name) => panic!("Intristic not found: {}.{}", ns, name),
},
Val::Func(f) => f.evaluate(
@@ -402,6 +501,17 @@
}
}
Function(params, body) => evaluate_method(context, eval_state, body, params.clone()),
+ AssertExpr(AssertStmt(value, msg), returned) => {
+ if evaluate(context.clone(), eval_state.clone(), &value).try_cast_bool() {
+ evaluate(context, eval_state, returned)
+ }else {
+ if let Some(msg) = msg {
+ panic!("assertion failed ({:?}): {}", value, evaluate(context, eval_state, msg).try_cast_str());
+ } else {
+ panic!("assertion failed ({:?}): no message", value);
+ }
+ }
+ },
Error(e) => panic!("error: {}", evaluate(context, eval_state, e)),
IfElse {
cond,
crates/jsonnet-evaluator/src/lib.rsdiffbeforeafterboth--- a/crates/jsonnet-evaluator/src/lib.rs
+++ b/crates/jsonnet-evaluator/src/lib.rs
@@ -359,7 +359,12 @@
#[test]
fn json() {
- println!("{:?}", eval_stdlib!(r#"std.manifestJson({a:3, b:4, c:6})"#));
+ println!("{:?}", eval_stdlib!(r#"std.manifestJsonEx({a:3, b:4, c:6},"")"#));
+ }
+
+ #[test]
+ fn test() {
+ assert_json_stdlib!(r#"[[a, b] for a in [1,2,3] for b in [4,5,6]]"#, "");
}
#[test]
crates/jsonnet-evaluator/src/obj.rsdiffbeforeafterboth--- a/crates/jsonnet-evaluator/src/obj.rs
+++ b/crates/jsonnet-evaluator/src/obj.rs
@@ -1,5 +1,5 @@
-use crate::{evaluate_binary_op, Binding, Val};
-use jsonnet_parser::{BinaryOpType, Visibility};
+use crate::{evaluate_add_op, Binding, Val};
+use jsonnet_parser::Visibility;
use std::{
cell::RefCell,
collections::{BTreeMap, BTreeSet, HashMap},
@@ -90,9 +90,8 @@
(Some(k), Some(s)) => {
let our = k.invoke.0(Some(real_this.clone()), self.0.super_obj.clone());
if k.add {
- s.get_raw(key, real_this).map_or(Some(our.clone()), |v| {
- Some(evaluate_binary_op(&v, BinaryOpType::Add, &our))
- })
+ s.get_raw(key, real_this)
+ .map_or(Some(our.clone()), |v| Some(evaluate_add_op(&v, &our)))
} else {
Some(our)
}
crates/jsonnet-evaluator/src/val.rsdiffbeforeafterboth--- a/crates/jsonnet-evaluator/src/val.rs
+++ b/crates/jsonnet-evaluator/src/val.rs
@@ -117,6 +117,18 @@
Intristic(String, String),
}
impl Val {
+ pub fn try_cast_bool(self) -> bool {
+ match self.unwrap_if_lazy() {
+ Val::Bool(v) => v,
+ v => panic!("expected bool, got {:?}", v),
+ }
+ }
+ pub fn try_cast_str(self) -> String {
+ match self.unwrap_if_lazy() {
+ Val::Str(v) => v,
+ v => panic!("expected bool, got {:?}", v),
+ }
+ }
pub fn unwrap_if_lazy(self) -> Self {
if let Val::Lazy(v) = self {
v.evaluate().unwrap_if_lazy()
crates/jsonnet-parser/src/expr.rsdiffbeforeafterboth--- a/crates/jsonnet-parser/src/expr.rs
+++ b/crates/jsonnet-parser/src/expr.rs
@@ -165,7 +165,7 @@
/// ]
/// ],
/// ```
- ArrComp(LocExpr, ForSpecData, Vec<CompSpec>),
+ ArrComp(LocExpr, Vec<CompSpec>),
/// Object: {a: 2}
Obj(ObjBody),
crates/jsonnet-parser/src/lib.rsdiffbeforeafterboth1#![feature(box_syntax)]23use peg::parser;4use std::rc::Rc;5mod expr;6pub use expr::*;78enum Suffix {9 String(String),10 Slice(SliceDesc),11 Expression(LocExpr),12 Apply(expr::ArgsDesc),13 Extend(expr::ObjBody),14}15struct LocSuffix(Suffix, ExprLocation);1617pub struct ParserSettings {18 pub loc_data: bool,19 pub file_name: String,20}2122parser! {23 grammar jsonnet_parser() for str {24 use peg::ParseLiteral;2526 /// Standard C-like comments27 rule comment() = "//" (!['\n'][_])* "\n" / "/*" ((!("*/")[_][_])/("\\" "*/"))* "*/"28 rule _() = ([' ' | '\n' | '\t'] / comment())*2930 /// For comma-delimited elements31 rule comma() = quiet!{_ "," _} / expected!("<comma>")32 rule alpha() -> char = c:$(['_' | 'a'..='z' | 'A'..='Z']) {c.chars().next().unwrap()}33 rule digit() -> char = d:$(['0'..='9']) {d.chars().next().unwrap()}34 rule end_of_ident() = !['0'..='9' | '_' | 'a'..='z' | 'A'..='Z']35 /// Sequence of digits36 rule uint() -> u32 = a:$(digit()+) { a.parse().unwrap() }37 /// Number in scientific notation format38 rule number() -> f64 = quiet!{a:$(uint() ("." uint())? (['e'|'E'] (s:['+'|'-'])? uint())?) { a.parse().unwrap() }} / expected!("<number>")3940 /// Reserved word followed by any non-alphanumberic41 rule reserved() = ("assert" / "else" / "error" / "false" / "for" / "function" / "if" / "import" / "importstr" / "in" / "local" / "null" / "tailstrict" / "then" / "self" / "super" / "true") end_of_ident()42 rule id() -> String = quiet!{ !reserved() s:$(alpha() (alpha() / digit())*) {s.to_owned()}} / expected!("<identifier>")4344 rule keyword(id: &'static str) = ##parse_string_literal(id) end_of_ident()45 rule l(s: &ParserSettings, x: rule<Expr>) -> LocExpr = start:position!() v:x() end:position!() {loc_expr!(v, s.loc_data, (s.file_name.clone(), start, end))}4647 pub rule param(s: &ParserSettings) -> expr::Param = name:id() expr:(_ "=" _ expr:expr(s){expr})? { expr::Param(name, expr) }48 pub rule params(s: &ParserSettings) -> expr::ParamsDesc49 = params:(param(s) ** comma()) {50 let mut defaults_started = false;51 for param in ¶ms {52 defaults_started = defaults_started || param.1.is_some();53 assert_eq!(defaults_started, param.1.is_some(), "defauld parameters should be used after all positionals");54 }55 expr::ParamsDesc(params)56 }57 / { expr::ParamsDesc(Vec::new()) }5859 pub rule arg(s: &ParserSettings) -> expr::Arg60 = name:id() _ "=" _ expr:expr(s) {expr::Arg(Some(name), expr)}61 / expr:expr(s) {expr::Arg(None, expr)}62 pub rule args(s: &ParserSettings) -> expr::ArgsDesc63 = args:arg(s) ** comma() comma()? {64 let mut named_started = false;65 for arg in &args {66 named_started = named_started || arg.0.is_some();67 assert_eq!(named_started, arg.0.is_some(), "named args should be used after all positionals");68 }69 expr::ArgsDesc(args)70 }71 / { expr::ArgsDesc(Vec::new()) }7273 pub rule bind(s: &ParserSettings) -> expr::BindSpec74 = name:id() _ "=" _ expr:expr(s) {expr::BindSpec{name, params: None, value: expr}}75 / name:id() _ "(" _ params:params(s) _ ")" _ "=" _ expr:expr(s) {expr::BindSpec{name, params: Some(params), value: expr}}76 pub rule assertion(s: &ParserSettings) -> expr::AssertStmt = keyword("assert") _ cond:expr(s) msg:(_ ":" _ e:expr(s) {e})? { expr::AssertStmt(cond, msg) }77 pub rule string() -> String78 = "\"" str:$(("\\\"" / !['"'][_])*) "\"" {str.to_owned()}79 / "'" str:$((!['\''][_])*) "'" {str.to_owned()}80 pub rule field_name(s: &ParserSettings) -> expr::FieldName81 = name:id() {expr::FieldName::Fixed(name)}82 / name:string() {expr::FieldName::Fixed(name)}83 / "[" _ expr:expr(s) _ "]" {expr::FieldName::Dyn(expr)}84 pub rule visibility() -> expr::Visibility85 = ":::" {expr::Visibility::Unhide}86 / "::" {expr::Visibility::Hidden}87 / ":" {expr::Visibility::Normal}88 pub rule field(s: &ParserSettings) -> expr::FieldMember89 = name:field_name(s) _ plus:"+"? _ visibility:visibility() _ value:expr(s) {expr::FieldMember{90 name,91 plus: plus.is_some(),92 params: None,93 visibility,94 value,95 }}96 / name:field_name(s) _ "(" _ params:params(s) _ ")" _ visibility:visibility() _ value:expr(s) {expr::FieldMember{97 name,98 plus: false,99 params: Some(params),100 visibility,101 value,102 }}103 pub rule obj_local(s: &ParserSettings) -> BindSpec104 = keyword("local") _ bind:bind(s) {bind}105 pub rule member(s: &ParserSettings) -> expr::Member106 = bind:obj_local(s) {expr::Member::BindStmt(bind)}107 / assertion:assertion(s) {expr::Member::AssertStmt(assertion)}108 / field:field(s) {expr::Member::Field(field)}109 pub rule objinside(s: &ParserSettings) -> expr::ObjBody110 = pre_locals:(b: obj_local(s) comma() {b})* "[" _ key:expr(s) _ "]" _ ":" _ value:expr(s) post_locals:(comma() b:obj_local(s) {b})* _ first:forspec(s) rest:(_ rest:compspec(s) {rest})? {111 expr::ObjBody::ObjComp {112 pre_locals,113 key,114 value,115 post_locals,116 first,117 rest: rest.unwrap_or_default(),118 }119 }120 / members:(member(s) ** comma()) comma()? {expr::ObjBody::MemberList(members)}121 pub rule ifspec(s: &ParserSettings) -> IfSpecData = keyword("if") _ expr:expr(s) {IfSpecData(expr)}122 pub rule forspec(s: &ParserSettings) -> ForSpecData = keyword("for") _ id:id() _ keyword("in") _ cond:expr(s) {ForSpecData(id, cond)}123 pub rule compspec(s: &ParserSettings) -> Vec<expr::CompSpec> = s:(i:ifspec(s) { expr::CompSpec::IfSpec(i) } / f:forspec(s) {expr::CompSpec::ForSpec(f)} )+ {s}124 pub rule local_expr(s: &ParserSettings) -> LocExpr = l(s,<keyword("local") _ binds:bind(s) ** comma() _ ";" _ expr:expr(s) { Expr::LocalExpr(binds, expr) }>)125 pub rule string_expr(s: &ParserSettings) -> LocExpr = l(s, <s:string() {Expr::Str(s)}>)126 pub rule obj_expr(s: &ParserSettings) -> LocExpr = l(s,<"{" _ body:objinside(s) _ "}" {Expr::Obj(body)}>)127 pub rule array_expr(s: &ParserSettings) -> LocExpr = l(s,<"[" _ elems:(expr(s) ** comma()) _ comma()? "]" {Expr::Arr(elems)}>)128 pub rule array_comp_expr(s: &ParserSettings) -> LocExpr = l(s,<"[" _ expr:expr(s) _ comma()? _ forspec:forspec(s) _ others:(others: compspec(s) _ {others})? "]" {Expr::ArrComp(expr, forspec, others.unwrap_or_default())}>)129 pub rule number_expr(s: &ParserSettings) -> LocExpr = l(s,<n:number() { expr::Expr::Num(n) }>)130 pub rule var_expr(s: &ParserSettings) -> LocExpr = l(s,<n:id() { expr::Expr::Var(n) }>)131 pub rule if_then_else_expr(s: &ParserSettings) -> LocExpr = l(s,<cond:ifspec(s) _ keyword("then") _ cond_then:expr(s) cond_else:(_ keyword("else") _ e:expr(s) {e})? {Expr::IfElse{132 cond,133 cond_then,134 cond_else,135 }}>)136137 pub rule literal(s: &ParserSettings) -> LocExpr138 = l(s,<v:(139 keyword("null") {LiteralType::Null}140 / keyword("true") {LiteralType::True}141 / keyword("false") {LiteralType::False}142 / keyword("self") {LiteralType::This}143 / keyword("$") {LiteralType::Dollar}144 / keyword("super") {LiteralType::Super}145 ) {Expr::Literal(v)}>)146147 pub rule expr_basic(s: &ParserSettings) -> LocExpr148 = literal(s)149150 / string_expr(s) / number_expr(s)151 / array_expr(s)152 / obj_expr(s)153 / array_expr(s)154 / array_comp_expr(s)155156 / var_expr(s)157 / local_expr(s)158 / if_then_else_expr(s)159160 / l(s,<keyword("function") _ "(" _ params:params(s) _ ")" _ expr:expr(s) {Expr::Function(params, expr)}>)161 / l(s,<assertion:assertion(s) _ ";" _ expr:expr(s) { Expr::AssertExpr(assertion, expr) }>)162163 / l(s,<keyword("error") _ expr:expr(s) { Expr::Error(expr) }>)164165 rule expr_basic_with_suffix(s: &ParserSettings) -> LocExpr166 = a:expr_basic(s) suffixes:(_ suffix:l_expr_suffix(s) {suffix})* {167 let mut cur = a;168 for suffix in suffixes {169 let LocSuffix(suffix, location) = suffix;170 cur = LocExpr(Rc::new(match suffix {171 Suffix::String(index) => Expr::Index(cur, loc_expr!(Expr::Str(index), s.loc_data, (s.file_name.clone(), location.1, location.2))),172 Suffix::Slice(desc) => Expr::Slice(cur, desc),173 Suffix::Expression(index) => Expr::Index(cur, index),174 Suffix::Apply(args) => Expr::Apply(cur, args),175 Suffix::Extend(body) => Expr::ObjExtend(cur, body),176 }), if s.loc_data { Some(Rc::new(location)) } else { None })177 }178 cur179 }180181 pub rule slice_desc(s: &ParserSettings) -> SliceDesc182 = start:expr(s)? _ ":" _ pair:(end:expr(s)? _ step:(":" _ e:expr(s) {e})? {(end, step)})? {183 if let Some((end, step)) = pair {184 SliceDesc { start, end, step }185 }else{186 SliceDesc { start, end: None, step: None }187 }188 }189190 rule expr_suffix(s: &ParserSettings) -> Suffix191 = "." _ s:id() { Suffix::String(s) }192 / "[" _ s:slice_desc(s) _ "]" { Suffix::Slice(s) }193 / "[" _ s:expr(s) _ "]" { Suffix::Expression(s) }194 / "(" _ args:args(s) _ ")" (_ keyword("tailstrict"))? { Suffix::Apply(args) }195 / "{" _ body:objinside(s) _ "}" { Suffix::Extend(body) }196 rule l_expr_suffix(s: &ParserSettings) -> LocSuffix197 = start:position!() suffix:expr_suffix(s) end:position!() {LocSuffix(suffix, ExprLocation(s.file_name.clone(), start, end))}198199 rule expr(s: &ParserSettings) -> LocExpr200 = start:position!() a:precedence! {201 a:(@) _ "||" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Or, b))}202 --203 a:(@) _ "&&" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::And, b))}204 --205 a:(@) _ "|" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::BitOr, b))}206 --207 a:@ _ "^" _ b:(@) {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::BitXor, b))}208 --209 a:(@) _ "&" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::BitAnd, b))}210 --211 a:(@) _ "==" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Eq, b))}212 a:(@) _ "!=" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Ne, b))}213 --214 a:(@) _ "<" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Lt, b))}215 a:(@) _ ">" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Gt, b))}216 a:(@) _ "<=" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Lte, b))}217 a:(@) _ ">=" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Gte, b))}218 --219 a:(@) _ "<<" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Lhs, b))}220 a:(@) _ ">>" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Rhs, b))}221 --222 a:(@) _ "+" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Add, b))}223 a:(@) _ "-" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Sub, b))}224 --225 a:(@) _ "*" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Mul, b))}226 a:(@) _ "/" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Div, b))}227 a:(@) _ "%" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Mod, b))}228 --229 e:expr_basic_with_suffix(s) {e}230 "-" _ expr:expr_basic_with_suffix(s) { loc_expr_todo!(Expr::UnaryOp(UnaryOpType::Minus, expr)) }231 "!" _ expr:expr_basic_with_suffix(s) { loc_expr_todo!(Expr::UnaryOp(UnaryOpType::Not, expr)) }232 "(" _ e:expr(s) _ ")" {loc_expr_todo!(Expr::Parened(e))}233 } end:position!() {234 let LocExpr(e, _) = a;235 LocExpr(e, if s.loc_data {236 Some(Rc::new(ExprLocation(s.file_name.to_owned(), start, end)))237 } else {238 None239 })240 }241 / e:expr_basic_with_suffix(s) {e}242243 pub rule jsonnet(s: &ParserSettings) -> LocExpr = _ e:expr(s) _ {e}244 }245}246247// TODO: impl FromStr from Expr248pub fn parse(249 str: &str,250 settings: &ParserSettings,251) -> Result<LocExpr, peg::error::ParseError<peg::str::LineCol>> {252 jsonnet_parser::jsonnet(str, settings)253}254255#[cfg(test)]256pub mod tests {257 use super::{expr::*, parse};258 use crate::ParserSettings;259 macro_rules! el {260 ($expr:expr) => {261 LocExpr(std::rc::Rc::new($expr), None)262 };263 }264 macro_rules! parse {265 ($s:expr) => {266 parse(267 $s,268 &ParserSettings {269 loc_data: false,270 file_name: "test.jsonnet".to_owned(),271 },272 )273 .unwrap()274 };275 }276277 mod expressions {278 use super::*;279280 pub fn basic_math() -> LocExpr {281 el!(Expr::BinaryOp(282 el!(Expr::Num(2.0)),283 BinaryOpType::Add,284 el!(Expr::BinaryOp(285 el!(Expr::Num(2.0)),286 BinaryOpType::Mul,287 el!(Expr::Num(2.0)),288 )),289 ))290 }291 }292293 #[test]294 fn empty_object() {295 assert_eq!(parse!("{}"), el!(Expr::Obj(ObjBody::MemberList(vec![]))));296 }297298 #[test]299 fn basic_math() {300 assert_eq!(301 parse!("2+2*2"),302 el!(Expr::BinaryOp(303 el!(Expr::Num(2.0)),304 BinaryOpType::Add,305 el!(Expr::BinaryOp(306 el!(Expr::Num(2.0)),307 BinaryOpType::Mul,308 el!(Expr::Num(2.0))309 ))310 ))311 );312 }313314 #[test]315 fn basic_math_with_indents() {316 assert_eq!(parse!("2 + 2 * 2 "), expressions::basic_math());317 }318319 #[test]320 fn basic_math_parened() {321 assert_eq!(322 parse!("2+(2+2*2)"),323 el!(Expr::BinaryOp(324 el!(Expr::Num(2.0)),325 BinaryOpType::Add,326 el!(Expr::Parened(expressions::basic_math())),327 ))328 );329 }330331 /// Comments should not affect parsing332 #[test]333 fn comments() {334 assert_eq!(335 parse!("2//comment\n+//comment\n3/*test*/*/*test*/4"),336 el!(Expr::BinaryOp(337 el!(Expr::Num(2.0)),338 BinaryOpType::Add,339 el!(Expr::BinaryOp(340 el!(Expr::Num(3.0)),341 BinaryOpType::Mul,342 el!(Expr::Num(4.0))343 ))344 ))345 );346 }347348 /// Comments should be able to be escaped349 #[test]350 fn comment_escaping() {351 assert_eq!(352 parse!("2/*\\*/+*/ - 22"),353 el!(Expr::BinaryOp(354 el!(Expr::Num(2.0)),355 BinaryOpType::Sub,356 el!(Expr::Num(22.0))357 ))358 );359 }360361 #[test]362 fn suffix_comparsion() {363 use Expr::*;364 assert_eq!(365 parse!("std.type(a) == \"string\""),366 el!(BinaryOp(367 el!(Apply(368 el!(Index(369 el!(Var("std".to_owned())),370 el!(Str("type".to_owned()))371 )),372 ArgsDesc(vec![Arg(None, el!(Var("a".to_owned())))])373 )),374 BinaryOpType::Eq,375 el!(Str("string".to_owned()))376 ))377 );378 }379380 #[test]381 fn array_comp() {382 use Expr::*;383 assert_eq!(384 parse!("[std.deepJoin(x) for x in arr]"),385 el!(ArrComp(386 el!(Apply(387 el!(Index(388 el!(Var("std".to_owned())),389 el!(Str("deepJoin".to_owned()))390 )),391 ArgsDesc(vec![Arg(None, el!(Var("x".to_owned())))])392 )),393 ForSpecData("x".to_owned(), el!(Var("arr".to_owned()))),394 vec![]395 )),396 )397 }398399 #[test]400 fn array_comp_with_ifs() {401 use Expr::*;402 assert_eq!(403 parse!("[k for k in std.objectFields(patch) if patch[k] == null]"),404 el!(ArrComp(405 el!(Var("k".to_owned())),406 ForSpecData(407 "k".to_owned(),408 el!(Apply(409 el!(Index(410 el!(Var("std".to_owned())),411 el!(Str("objectFields".to_owned()))412 )),413 ArgsDesc(vec![Arg(None, el!(Var("patch".to_owned())))])414 ))415 ),416 vec![CompSpec::IfSpec(IfSpecData(el!(BinaryOp(417 el!(Index(418 el!(Var("patch".to_owned())),419 el!(Var("k".to_owned()))420 )),421 BinaryOpType::Eq,422 el!(Literal(LiteralType::Null))423 ))))]424 ))425 );426 }427428 #[test]429 fn reserved() {430 use Expr::*;431 assert_eq!(parse!("null"), el!(Literal(LiteralType::Null)));432 assert_eq!(parse!("nulla"), el!(Var("nulla".to_owned())));433 }434435 #[test]436 fn multiple_args_buf() {437 parse!("a(b, null_fields)");438 }439440 #[test]441 fn infix_precedence() {442 use Expr::*;443 assert_eq!(444 parse!("!a && !b"),445 el!(BinaryOp(446 el!(UnaryOp(UnaryOpType::Not, el!(Var("a".to_owned())))),447 BinaryOpType::And,448 el!(UnaryOp(UnaryOpType::Not, el!(Var("b".to_owned()))))449 ))450 );451 }452453 #[test]454 fn can_parse_stdlib() {455 parse!(jsonnet_stdlib::STDLIB_STR);456 }457}