--- a/crates/jsonnet-parser/Cargo.toml +++ b/crates/jsonnet-parser/Cargo.toml @@ -11,3 +11,6 @@ [dependencies] peg = "0.6.2" + +[dev-dependencies] +jsonnet-stdlib = { version = "0.1.0", path = "../jsonnet-stdlib" } --- /dev/null +++ b/crates/jsonnet-parser/README.md @@ -0,0 +1,3 @@ +# jsonnet-parser + +Parser for jsonnet language --- a/crates/jsonnet-parser/src/expr.rs +++ b/crates/jsonnet-parser/src/expr.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + #[derive(Debug, Clone, PartialEq)] pub enum FieldName { /// {fixed: 2} @@ -20,25 +22,18 @@ pub struct AssertStmt(pub Box, pub Option>); #[derive(Debug, Clone, PartialEq)] -pub enum FieldMember { - Value { - name: FieldName, - plus: bool, - visibility: Visibility, - value: Expr, - }, - Function { - name: FieldName, - params: ParamsDesc, - visibility: Visibility, - value: Expr, - }, +pub struct FieldMember { + pub name: FieldName, + pub plus: bool, + pub params: Option, + pub visibility: Visibility, + pub value: Expr, } #[derive(Debug, Clone, PartialEq)] pub enum Member { Field(FieldMember), - BindStmt(Bind), + BindStmt(BindSpec), AssertStmt(AssertStmt), } @@ -50,7 +45,7 @@ Not, } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq)] pub enum BinaryOpType { Mul, Div, @@ -80,73 +75,90 @@ Or, } +/// name, default value #[derive(Debug, Clone, PartialEq)] -pub enum Param { - Positional(String), - Named(String, Box), -} - +pub struct Param(pub String, pub Option>); +/// Defined function parameters #[derive(Debug, Clone, PartialEq)] pub struct ParamsDesc(pub Vec); - -#[derive(Debug, Clone, PartialEq)] -pub enum Arg { - Positional(Box), - Named(String, Box), +impl ParamsDesc { + pub fn with_defaults(&self) -> Vec { + self.0 + .iter() + .filter(|e| e.1.is_some()) + .map(|e| e.clone()) + .collect() + } } #[derive(Debug, Clone, PartialEq)] +pub struct Arg(pub Option, pub Box); +#[derive(Debug, Clone, PartialEq)] pub struct ArgsDesc(pub Vec); #[derive(Debug, Clone, PartialEq)] -pub enum Bind { - Value(String, Box), - Function(String, ParamsDesc, Box), +pub struct BindSpec { + pub name: String, + pub params: Option, + pub value: Box, } #[derive(Debug, Clone, PartialEq)] -pub struct IfSpec(pub Box); +pub struct IfSpecData(pub Box); #[derive(Debug, Clone, PartialEq)] -pub struct ForSpec(pub String, pub Vec); +pub struct ForSpecData(pub String, pub Box); #[derive(Debug, Clone, PartialEq)] pub enum CompSpec { - IfSpec(IfSpec), - ForSpec(ForSpec), + IfSpec(IfSpecData), + ForSpec(ForSpecData), } #[derive(Debug, Clone, PartialEq)] pub enum ObjBody { MemberList(Vec), ObjComp { - pre_locals: Vec, + pre_locals: Vec, key: Box, value: Box, - post_locals: Vec, - first: ForSpec, + post_locals: Vec, + first: ForSpecData, rest: Vec, }, } #[derive(Debug, Clone, PartialEq)] -pub enum ValueType { +pub enum LiteralType { + This, + Super, + Dollar, Null, True, False, } +impl Display for LiteralType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use LiteralType::*; + match self { + This => write!(f, "this"), + Null => write!(f, "null"), + True => write!(f, "true"), + False => write!(f, "false"), + _ => panic!("non printable item"), + } + } +} #[derive(Debug, Clone, PartialEq)] -pub enum LiteralType { - This, - Super, - Dollar, +pub struct SliceDesc { + pub start: Option>, + pub end: Option>, + pub step: Option>, } /// Syntax base #[derive(Debug, Clone, PartialEq)] pub enum Expr { - Value(ValueType), - /// Plain value: null/true/false Literal(LiteralType), /// String value: "hello" @@ -169,7 +181,7 @@ /// ] /// ], /// ``` - ArrComp(Box, Vec), + ArrComp(Box, ForSpecData, Vec), /// Object: {a: 2} Obj(ObjBody), @@ -179,33 +191,48 @@ /// (obj) Parened(Box), + /// Params in function definition + /// hello, world, test = 2 Params(ParamsDesc), + /// Args in function call + /// 2 + 2, 3, named = 6 Args(ArgsDesc), + /// -2 UnaryOp(UnaryOpType, Box), + /// 2 - 2 BinaryOp(Box, BinaryOpType, Box), + /// assert 2 == 2 : "Math is broken" AssertExpr(AssertStmt, Box), - LocalExpr(Vec, Box), + /// local a = 2; { b: a } + LocalExpr(Vec, Box), - Bind(Bind), + /// a = 3 + Bind(BindSpec), + /// import "hello" Import(String), + /// importStr "file.txt" ImportStr(String), + /// error "I'm broken" Error(Box), + /// a(b, c) Apply(Box, ArgsDesc), + /// Select(Box, String), + /// a[b] Index(Box, Box), - Slice { - value: Box, - start: Option>, - end: Option>, - step: Option>, - }, + /// a[1::2] + Slice(Box, SliceDesc), + /// function(x) x Function(ParamsDesc, Box), + /// if true == false then 1 else 2 IfElse { - cond: IfSpec, + cond: IfSpecData, cond_then: Box, cond_else: Option>, }, - IfSpec(IfSpec), - ForSpec(ForSpec), + /// if 2 = 3 + IfSpec(IfSpecData), + /// for elem in array + ForSpec(ForSpecData), } --- a/crates/jsonnet-parser/src/lib.rs +++ b/crates/jsonnet-parser/src/lib.rs @@ -7,188 +7,228 @@ enum Suffix { String(String), + Slice(SliceDesc), Expression(Expr), Apply(expr::ArgsDesc), + Extend(expr::ObjBody), } parser! { grammar jsonnet_parser() for str { - rule delimiter() = quiet!{__() "," __()} / expected!("") - rule _() = quiet!{[' ' | '\n' | '\t']+} / expected!("") - rule __() = quiet!{[' ' | '\n' | '\t']*} + use peg::ParseLiteral; + + /// Standard C-like comments + rule comment() = "//" (!['\n'][_])* "\n" / "/*" ((!("*/")[_][_])/("\\" "*/"))* "*/" + rule _() = ([' ' | '\n' | '\t'] / comment())* + + /// For comma-delimited elements + rule comma() = quiet!{_ "," _} / expected!("") rule alpha() -> char = c:$(['_' | 'a'..='z' | 'A'..='Z']) {c.chars().nth(0).unwrap()} rule digit() -> char = d:$(['0'..='9']) {d.chars().nth(0).unwrap()} - rule int() -> u32 = a:$(digit()+) { a.parse().unwrap() } - rule number() -> f64 = quiet!{a:$((['-'|'+'])? int() ("." int())? (['e'|'E'] (s:['+'|'-'])? int())?) { a.parse().unwrap() }} / expected!("") - rule id() -> String = quiet!{ !("local" / "super" / "self" / "true" / "false" / "null" / "$" / "if" / "then" / "else" / "function") s:$(alpha() (alpha() / digit())*) {s.to_owned()}} / expected!("") + rule end_of_ident() = !['0'..='9' | '_' | 'a'..='z' | 'A'..='Z'] + /// Sequence of digits + rule uint() -> u32 = a:$(digit()+) { a.parse().unwrap() } + /// Number in scientific notation format + rule number() -> f64 = quiet!{a:$(uint() ("." uint())? (['e'|'E'] (s:['+'|'-'])? uint())?) { a.parse().unwrap() }} / expected!("") + + /// Reserved word followed by any non-alphanumberic + rule reserved() = ("assert" / "else" / "error" / "false" / "for" / "function" / "if" / "import" / "importstr" / "in" / "local" / "null" / "tailstrict" / "then" / "self" / "super" / "true") end_of_ident() + rule id() -> String = quiet!{ !reserved() s:$(alpha() (alpha() / digit())*) {s.to_owned()}} / expected!("") + rule keyword(id: &'static str) = ##parse_string_literal(id) end_of_ident() - pub rule positional_param() -> expr::Param = name:id() {expr::Param::Positional(name)} - pub rule named_param() -> expr::Param = name:id() __() "=" __() expr:boxed_expr() {expr::Param::Named(name, expr)} + pub rule param() -> expr::Param = name:id() expr:(_ "=" _ expr:boxed_expr(){expr})? { expr::Param(name, expr) } pub rule params() -> expr::ParamsDesc - = positionals:(positional_param() ** delimiter()) named: (delimiter() named:(named_param() ** delimiter()) {named})? { - if named.is_some() { - expr::ParamsDesc([&positionals[..], &named.unwrap()[..]].concat()) - } else { - expr::ParamsDesc(positionals) + = params:(param() ** comma()) { + let mut defaults_started = false; + for param in ¶ms { + defaults_started = defaults_started || param.1.is_some(); + assert_eq!(defaults_started, param.1.is_some(), "defauld parameters should be used after all positionals"); } + expr::ParamsDesc(params) } - / named:(named_param() ** delimiter()) {expr::ParamsDesc(named)} - / {expr::ParamsDesc(Vec::new())} + / { expr::ParamsDesc(Vec::new()) } - pub rule positional_arg() -> expr::Arg = quiet!{name:boxed_expr() {expr::Arg::Positional(name)}}/expected!("") - pub rule named_arg() -> expr::Arg = quiet!{name:id() __() "=" __() expr:boxed_expr() {expr::Arg::Named(name, expr)}}/expected!("") + pub rule arg() -> expr::Arg + = name:id() _ "=" _ expr:boxed_expr() {expr::Arg(Some(name), expr)} + / expr:boxed_expr() {expr::Arg(None, expr)} pub rule args() -> expr::ArgsDesc - = positionals:(positional_arg() ** delimiter()) named: (delimiter() named:(named_arg() ** delimiter()) {named})? { - if named.is_some() { - expr::ArgsDesc([&positionals[..], &named.unwrap()[..]].concat()) - } else { - expr::ArgsDesc(positionals) + = args:arg() ** comma() comma()? { + let mut named_started = false; + for arg in &args { + named_started = named_started || arg.0.is_some(); + assert_eq!(named_started, arg.0.is_some(), "named args should be used after all positionals"); } + expr::ArgsDesc(args) } - / named:(named_arg() ** delimiter()) {expr::ArgsDesc(named)} - / {expr::ArgsDesc(Vec::new())} + / { expr::ArgsDesc(Vec::new()) } - pub rule bind() -> expr::Bind - = name:id() __() "=" __() expr:boxed_expr() {expr::Bind::Value(name, expr)} - / name:id() __() "(" __() params:params() __() ")" __() "=" __() expr:boxed_expr() {expr::Bind::Function(name, params, expr)} - pub rule assertion() -> expr::AssertStmt = "assert" _() cond:boxed_expr() msg:(__() ":" __() e:boxed_expr() {e})? { expr::AssertStmt(cond, msg) } + pub rule bind() -> expr::BindSpec + = name:id() _ "=" _ expr:boxed_expr() {expr::BindSpec{name, params: None, value: expr}} + / name:id() _ "(" _ params:params() _ ")" _ "=" _ expr:boxed_expr() {expr::BindSpec{name, params: Some(params), value: expr}} + pub rule assertion() -> expr::AssertStmt = keyword("assert") _ cond:boxed_expr() msg:(_ ":" _ e:boxed_expr() {e})? { expr::AssertStmt(cond, msg) } pub rule string() -> String - = "\"" str:$((!['"'][_])*) "\"" {str.to_owned()} + = "\"" str:$(("\\\"" / !['"'][_])*) "\"" {str.to_owned()} / "'" str:$((!['\''][_])*) "'" {str.to_owned()} pub rule field_name() -> expr::FieldName = name:id() {expr::FieldName::Fixed(name)} / name:string() {expr::FieldName::Fixed(name)} - / "[" __() expr:boxed_expr() __() "]" {expr::FieldName::Dyn(expr)} + / "[" _ expr:boxed_expr() _ "]" {expr::FieldName::Dyn(expr)} pub rule visibility() -> expr::Visibility = ":::" {expr::Visibility::Unhide} / "::" {expr::Visibility::Hidden} / ":" {expr::Visibility::Normal} pub rule field() -> expr::FieldMember - = name:field_name() __() plus:"+"? __() visibility:visibility() __() value:expr() {expr::FieldMember::Value{ + = name:field_name() _ plus:"+"? _ visibility:visibility() _ value:expr() {expr::FieldMember{ name, plus: plus.is_some(), + params: None, visibility, value, }} - / name:field_name() __() "(" __() params:params() __() ")" __() visibility:visibility() __() value:expr() {expr::FieldMember::Function{ + / name:field_name() _ "(" _ params:params() _ ")" _ visibility:visibility() _ value:expr() {expr::FieldMember{ name, - params, + plus: false, + params: Some(params), visibility, value, }} + pub rule obj_local() -> BindSpec + = keyword("local") _ bind:bind() {bind} pub rule member() -> expr::Member - = "local" _() bind:bind() {expr::Member::BindStmt(bind)} + = bind:obj_local() {expr::Member::BindStmt(bind)} / assertion:assertion() {expr::Member::AssertStmt(assertion)} / field:field() {expr::Member::Field(field)} - pub rule obj_body() -> expr::ObjBody = members:(member() ** delimiter()) delimiter()? {expr::ObjBody::MemberList(members)} - pub rule ifspec() -> expr::IfSpec = "if" _() expr:boxed_expr() {expr::IfSpec(expr)} - pub rule forspec() -> expr::ForSpec = "for" _() id:id() _() "in" _() ifs:ifspec()* {expr::ForSpec(id, ifs)} + pub rule objinside() -> expr::ObjBody + = pre_locals:(b: obj_local() comma() {b})* "[" _ key:boxed_expr() _ "]" _ ":" _ value:boxed_expr() post_locals:(comma() b:obj_local() {b})* _ first:forspec() rest:(_ rest:compspec() {rest})? { + expr::ObjBody::ObjComp { + pre_locals, + key, + value, + post_locals, + first, + rest: rest.unwrap_or(Vec::new()), + } + } + / members:(member() ** comma()) comma()? {expr::ObjBody::MemberList(members)} + pub rule ifspec() -> IfSpecData = keyword("if") _ expr:boxed_expr() {IfSpecData(expr)} + pub rule forspec() -> ForSpecData = keyword("for") _ id:id() _ keyword("in") _ cond:boxed_expr() {ForSpecData(id, cond)} + pub rule compspec() -> Vec = s:(i:ifspec() { expr::CompSpec::IfSpec(i) } / f:forspec() {expr::CompSpec::ForSpec(f)} )+ {s} pub rule bind_expr() -> Expr = bind:bind() {Expr::Bind(bind)} - pub rule local_expr() -> Expr = "local" _() binds:(bind() ** delimiter()) __() ";" __() expr:boxed_expr() { Expr::LocalExpr(binds, expr) } + pub rule local_expr() -> Expr = keyword("local") _ binds:bind() ** comma() _ ";" _ expr:boxed_expr() { Expr::LocalExpr(binds, expr) } pub rule string_expr() -> Expr = s:string() {Expr::Str(s)} pub rule parened_expr() -> Expr = "(" e:boxed_expr() ")" {Expr::Parened(e)} - pub rule obj_expr() -> Expr = "{" __() body:obj_body() __() "}" {Expr::Obj(body)} - pub rule array_expr() -> Expr = "[" __() elems:(expr() ** delimiter()) __() delimiter()? "]" {Expr::Arr(elems)} - pub rule array_comp_expr() -> Expr = "[" __() expr:boxed_expr() delimiter()? fors:forspec()+ __() "]" {Expr::ArrComp(expr, fors)} + pub rule obj_expr() -> Expr = "{" _ body:objinside() _ "}" {Expr::Obj(body)} + pub rule array_expr() -> Expr = "[" _ elems:(expr() ** comma()) _ comma()? "]" {Expr::Arr(elems)} + pub rule array_comp_expr() -> Expr = "[" _ expr:boxed_expr() _ comma()? _ forspec:forspec() _ others:(others: compspec() _ {others})? "]" {Expr::ArrComp(expr, forspec, others.unwrap_or(vec![]))} pub rule index_expr() -> Expr = val:boxed_expr() "." idx:id() {Expr::Index(val, Box::new(Expr::Str(idx)))} / val:boxed_expr() "[" key:boxed_expr() "]" {Expr::Index(val, key)} - pub rule slice_expr() -> Expr - = value:boxed_expr() "[" start:boxed_expr()? ":" pair:(end:boxed_expr()? step:(":" e:boxed_expr() {e})? {(end, step)})? "]" { - if let Some((end, step)) = pair { - Expr::Slice { value, start, end, step } - }else{ - Expr::Slice{ value, start, end: None, step: None } - } - } pub rule number_expr() -> Expr = n:number() { expr::Expr::Num(n) } pub rule var_expr() -> Expr = n:id() { expr::Expr::Var(n) } - pub rule if_then_else_expr() -> Expr = cond:ifspec() _() "then" _() cond_then:boxed_expr() cond_else:(_() "else" _() e:boxed_expr() {e})? {Expr::IfElse{ + pub rule if_then_else_expr() -> Expr = cond:ifspec() _ keyword("then") _ cond_then:boxed_expr() cond_else:(_ keyword("else") _ e:boxed_expr() {e})? {Expr::IfElse{ cond, cond_then, cond_else, }} + + pub rule literal() -> Expr + = v:( + keyword("null") {LiteralType::Null} + / keyword("true") {LiteralType::True} + / keyword("false") {LiteralType::False} + / keyword("self") {LiteralType::This} + / keyword("$") {LiteralType::Dollar} + / keyword("super") {LiteralType::Super} + ) {Expr::Literal(v)} + pub rule expr_basic() -> Expr - = "null" {Expr::Value(ValueType::Null)} - / "true" {Expr::Value(ValueType::True)} / "false" {Expr::Value(ValueType::False)} + = literal() - / "self" {Expr::Literal(LiteralType::This)} / "$" {Expr::Literal(LiteralType::Dollar)} - / "super" {Expr::Literal(LiteralType::Super)} - / string_expr() / number_expr() / array_expr() - / array_comp_expr() / obj_expr() / array_expr() / array_comp_expr() / var_expr() + / local_expr() / if_then_else_expr() - / local_expr() + / "-" _ expr:boxed_expr() { Expr::UnaryOp(UnaryOpType::Minus, expr) } + / "!" _ expr:boxed_expr() { Expr::UnaryOp(UnaryOpType::Not, expr) } - / "function" __() "(" __() params:params() __() ")" __() expr:boxed_expr() {Expr::Function(params, expr)} + / keyword("function") _ "(" _ params:params() _ ")" _ expr:boxed_expr() {Expr::Function(params, expr)} + / assertion:assertion() _ ";" _ expr:boxed_expr() { Expr::AssertExpr(assertion, expr) } + / keyword("error") _ expr:boxed_expr() { Expr::Error(expr) } + rule expr_basic_with_suffix() -> Expr - = a:expr_basic() suffixes:(__() suffix:expr_suffix() {suffix})* { + = a:expr_basic() suffixes:(_ suffix:expr_suffix() {suffix})* { let mut cur = a; for suffix in suffixes { - match suffix { - Suffix::String(index) => { - cur = Expr::Index(Box::new(cur), Box::new(Expr::Str(index))) - }, - Suffix::Expression(index) => { - cur = Expr::Index(Box::new(cur), Box::new(index)) - }, - Suffix::Apply(args) => { - cur = Expr::Apply(Box::new(cur), args) - } + cur = match suffix { + Suffix::String(index) => Expr::Index(Box::new(cur), Box::new(Expr::Str(index))), + Suffix::Slice(desc) => Expr::Slice(Box::new(cur), desc), + Suffix::Expression(index) => Expr::Index(Box::new(cur), Box::new(index)), + Suffix::Apply(args) => Expr::Apply(Box::new(cur), args), + Suffix::Extend(body) => Expr::ObjExtend(box cur, body), } } cur } + pub rule slice_desc() -> SliceDesc + = start:boxed_expr()? _ ":" _ pair:(end:boxed_expr()? _ step:(":" _ e:boxed_expr() {e})? {(end, step)})? { + if let Some((end, step)) = pair { + SliceDesc { start, end, step } + }else{ + SliceDesc { start, end: None, step: None } + } + } + rule expr_suffix() -> Suffix - = "." __() s:id() { Suffix::String(s) } - / "[" __() s:expr() __() "]" { Suffix::Expression(s) } - / "(" __() args:args() __() ")" { Suffix::Apply(args) } + = "." _ s:id() { Suffix::String(s) } + / "[" _ s:slice_desc() _ "]" { Suffix::Slice(s) } + / "[" _ s:expr() _ "]" { Suffix::Expression(s) } + / "(" _ args:args() _ ")" (_ keyword("tailstrict"))? { Suffix::Apply(args) } + / "{" _ body:objinside() _ "}" { Suffix::Extend(body) } rule expr() -> Expr = a:precedence! { - a:(@) __() "||" __() b:@ {Expr::BinaryOp(Box::new(a), expr::BinaryOpType::Or, Box::new(b))} + a:(@) _ "||" _ b:@ {Expr::BinaryOp(Box::new(a), BinaryOpType::Or, Box::new(b))} -- - a:(@) __() "&&" __() b:@ {Expr::BinaryOp(Box::new(a), expr::BinaryOpType::And, Box::new(b))} + a:(@) _ "&&" _ b:@ {Expr::BinaryOp(Box::new(a), BinaryOpType::And, Box::new(b))} -- - a:(@) __() "|" __() b:@ {Expr::BinaryOp(Box::new(a), expr::BinaryOpType::BitOr, Box::new(b))} + a:(@) _ "|" _ b:@ {Expr::BinaryOp(Box::new(a), BinaryOpType::BitOr, Box::new(b))} -- - a:@ __() "^" __() b:(@) {Expr::BinaryOp(Box::new(a), expr::BinaryOpType::BitXor, Box::new(b))} + a:@ _ "^" _ b:(@) {Expr::BinaryOp(Box::new(a), BinaryOpType::BitXor, Box::new(b))} -- - a:(@) __() "&" __() b:@ {Expr::BinaryOp(Box::new(a), expr::BinaryOpType::BitAnd, Box::new(b))} + a:(@) _ "&" _ b:@ {Expr::BinaryOp(Box::new(a), BinaryOpType::BitAnd, Box::new(b))} -- - a:(@) __() "==" __() b:@ {Expr::BinaryOp(Box::new(a), expr::BinaryOpType::Eq, Box::new(b))} - a:(@) __() "!=" __() b:@ {Expr::BinaryOp(Box::new(a), expr::BinaryOpType::Ne, Box::new(b))} + a:(@) _ "==" _ b:@ {Expr::BinaryOp(Box::new(a), BinaryOpType::Eq, Box::new(b))} + a:(@) _ "!=" _ b:@ {Expr::BinaryOp(Box::new(a), BinaryOpType::Ne, Box::new(b))} -- - a:(@) __() "<" __() b:@ {Expr::BinaryOp(Box::new(a), expr::BinaryOpType::Lt, Box::new(b))} - a:(@) __() ">" __() b:@ {Expr::BinaryOp(Box::new(a), expr::BinaryOpType::Gt, Box::new(b))} - a:(@) __() "<=" __() b:@ {Expr::BinaryOp(Box::new(a), expr::BinaryOpType::Lte, Box::new(b))} - a:(@) __() ">=" __() b:@ {Expr::BinaryOp(Box::new(a), expr::BinaryOpType::Gte, Box::new(b))} + a:(@) _ "<" _ b:@ {Expr::BinaryOp(Box::new(a), BinaryOpType::Lt, Box::new(b))} + a:(@) _ ">" _ b:@ {Expr::BinaryOp(Box::new(a), BinaryOpType::Gt, Box::new(b))} + a:(@) _ "<=" _ b:@ {Expr::BinaryOp(Box::new(a), BinaryOpType::Lte, Box::new(b))} + a:(@) _ ">=" _ b:@ {Expr::BinaryOp(Box::new(a), BinaryOpType::Gte, Box::new(b))} -- - a:(@) __() "<<" __() b:@ {Expr::BinaryOp(Box::new(a), expr::BinaryOpType::Lhs, Box::new(b))} - a:(@) __() ">>" __() b:@ {Expr::BinaryOp(Box::new(a), expr::BinaryOpType::Rhs, Box::new(b))} + a:(@) _ "<<" _ b:@ {Expr::BinaryOp(Box::new(a), BinaryOpType::Lhs, Box::new(b))} + a:(@) _ ">>" _ b:@ {Expr::BinaryOp(Box::new(a), BinaryOpType::Rhs, Box::new(b))} -- - a:(@) __() "+" __() b:@ {Expr::BinaryOp(Box::new(a), expr::BinaryOpType::Add, Box::new(b))} - a:(@) __() "-" __() b:@ {Expr::BinaryOp(Box::new(a), expr::BinaryOpType::Sub, Box::new(b))} + a:(@) _ "+" _ b:@ {Expr::BinaryOp(Box::new(a), BinaryOpType::Add, Box::new(b))} + a:(@) _ "-" _ b:@ {Expr::BinaryOp(Box::new(a), BinaryOpType::Sub, Box::new(b))} -- - a:(@) __() "*" __() b:@ {Expr::BinaryOp(Box::new(a), expr::BinaryOpType::Mul, Box::new(b))} - a:(@) __() "/" __() b:@ {Expr::BinaryOp(Box::new(a), expr::BinaryOpType::Div, Box::new(b))} - a:(@) __() "%" __() b:@ {Expr::BinaryOp(Box::new(a), expr::BinaryOpType::Mod, Box::new(b))} + a:(@) _ "*" _ b:@ {Expr::BinaryOp(Box::new(a), BinaryOpType::Mul, Box::new(b))} + a:(@) _ "/" _ b:@ {Expr::BinaryOp(Box::new(a), BinaryOpType::Div, Box::new(b))} + a:(@) _ "%" _ b:@ {Expr::BinaryOp(Box::new(a), BinaryOpType::Mod, Box::new(b))} -- e:expr_basic_with_suffix() {e} - "(" __() e:boxed_expr() __() ")" {Expr::Parened(e)} + "(" _ e:boxed_expr() _ ")" {Expr::Parened(e)} } / e:expr_basic_with_suffix() {e} pub rule boxed_expr() -> Box = e:expr() {Box::new(e)} - pub rule jsonnet() -> Expr = __() e:expr() __() {e} + pub rule jsonnet() -> Expr = _ e:expr() _ {e} } } @@ -220,7 +260,29 @@ ); } + /// Comments should not affect parsing #[test] + fn comments() { + assert_eq!( + parse("2//comment\n+//comment\n3/*test*/*/*test*/4").unwrap(), + Expr::BinaryOp( + box Expr::Num(2.0), + BinaryOpType::Add, + box Expr::BinaryOp(box Expr::Num(3.0), BinaryOpType::Mul, box Expr::Num(4.0)) + ) + ); + } + + /// Comments should be able to be escaped + #[test] + fn comment_escaping() { + assert_eq!( + parse("2/*\\*/+*/ - 22").unwrap(), + Expr::BinaryOp(box Expr::Num(2.0), BinaryOpType::Sub, box Expr::Num(22.0)) + ); + } + + #[test] fn suffix_comparsion() { use Expr::*; assert_eq!( @@ -228,11 +290,70 @@ BinaryOp( box Apply( box Index(box Var("std".to_owned()), box Str("type".to_owned())), - ArgsDesc(vec![Arg::Positional(box Var("a".to_owned()))]) + ArgsDesc(vec![Arg(None, box Var("a".to_owned()))]) ), BinaryOpType::Eq, box Str("string".to_owned()) ) ); } + + #[test] + fn array_comp() { + use Expr::*; + assert_eq!( + parse("[std.deepJoin(x) for x in arr]").unwrap(), + ArrComp( + box Apply( + box Index(box Var("std".to_owned()), box Str("deepJoin".to_owned())), + ArgsDesc(vec![Arg(None, box Var("x".to_owned()))]) + ), + ForSpecData("x".to_owned(), box Var("arr".to_owned())), + vec![] + ), + ) + } + + #[test] + fn array_comp_with_ifs() { + use Expr::*; + assert_eq!( + parse("[k for k in std.objectFields(patch) if patch[k] == null]").unwrap(), + ArrComp( + box Var("k".to_owned()), + ForSpecData( + "k".to_owned(), + box Apply( + box Index( + box Var("std".to_owned()), + box Str("objectFields".to_owned()) + ), + ArgsDesc(vec![Arg(None, box Var("patch".to_owned()))]) + ) + ), + vec![CompSpec::IfSpec(IfSpecData(box BinaryOp( + box Index(box Var("patch".to_owned()), box Var("k".to_owned())), + BinaryOpType::Eq, + box Literal(LiteralType::Null) + )))] + ), + ); + } + + #[test] + fn reserved() { + use Expr::*; + assert_eq!(parse("null").unwrap(), Literal(LiteralType::Null)); + assert_eq!(parse("nulla").unwrap(), Var("nulla".to_owned())); + } + + #[test] + fn multiple_args_buf() { + parse("a(b, null_fields)").unwrap(); + } + + #[test] + fn can_parse_stdlib() { + parse(jsonnet_stdlib::STDLIB_STR).unwrap(); + } }