1#![feature(box_syntax)]23use peg::parser;45mod expr;6pub use expr::*;78enum Suffix {9 String(String),10 Slice(SliceDesc),11 Expression(Expr),12 Apply(expr::ArgsDesc),13 Extend(expr::ObjBody),14}1516parser! {17 grammar jsonnet_parser() for str {18 use peg::ParseLiteral;1920 21 rule comment() = "//" (!['\n'][_])* "\n" / "/*" ((!("*/")[_][_])/("\\" "*/"))* "*/"22 rule _() = ([' ' | '\n' | '\t'] / comment())*2324 25 rule comma() = quiet!{_ "," _} / expected!("<comma>")26 rule alpha() -> char = c:$(['_' | 'a'..='z' | 'A'..='Z']) {c.chars().nth(0).unwrap()}27 rule digit() -> char = d:$(['0'..='9']) {d.chars().nth(0).unwrap()}28 rule end_of_ident() = !['0'..='9' | '_' | 'a'..='z' | 'A'..='Z']29 30 rule uint() -> u32 = a:$(digit()+) { a.parse().unwrap() }31 32 rule number() -> f64 = quiet!{a:$(uint() ("." uint())? (['e'|'E'] (s:['+'|'-'])? uint())?) { a.parse().unwrap() }} / expected!("<number>")3334 35 rule reserved() = ("assert" / "else" / "error" / "false" / "for" / "function" / "if" / "import" / "importstr" / "in" / "local" / "null" / "tailstrict" / "then" / "self" / "super" / "true") end_of_ident()36 rule id() -> String = quiet!{ !reserved() s:$(alpha() (alpha() / digit())*) {s.to_owned()}} / expected!("<identifier>")37 rule keyword(id: &'static str) = ##parse_string_literal(id) end_of_ident()3839 pub rule param() -> expr::Param = name:id() expr:(_ "=" _ expr:boxed_expr(){expr})? { expr::Param(name, expr) }40 pub rule params() -> expr::ParamsDesc41 = params:(param() ** comma()) {42 let mut defaults_started = false;43 for param in ¶ms {44 defaults_started = defaults_started || param.1.is_some();45 assert_eq!(defaults_started, param.1.is_some(), "defauld parameters should be used after all positionals");46 }47 expr::ParamsDesc(params)48 }49 / { expr::ParamsDesc(Vec::new()) }5051 pub rule arg() -> expr::Arg52 = name:id() _ "=" _ expr:boxed_expr() {expr::Arg(Some(name), expr)}53 / expr:boxed_expr() {expr::Arg(None, expr)}54 pub rule args() -> expr::ArgsDesc55 = args:arg() ** comma() comma()? {56 let mut named_started = false;57 for arg in &args {58 named_started = named_started || arg.0.is_some();59 assert_eq!(named_started, arg.0.is_some(), "named args should be used after all positionals");60 }61 expr::ArgsDesc(args)62 }63 / { expr::ArgsDesc(Vec::new()) }6465 pub rule bind() -> expr::BindSpec66 = name:id() _ "=" _ expr:boxed_expr() {expr::BindSpec{name, params: None, value: expr}}67 / name:id() _ "(" _ params:params() _ ")" _ "=" _ expr:boxed_expr() {expr::BindSpec{name, params: Some(params), value: expr}}68 pub rule assertion() -> expr::AssertStmt = keyword("assert") _ cond:boxed_expr() msg:(_ ":" _ e:boxed_expr() {e})? { expr::AssertStmt(cond, msg) }69 pub rule string() -> String70 = "\"" str:$(("\\\"" / !['"'][_])*) "\"" {str.to_owned()}71 / "'" str:$((!['\''][_])*) "'" {str.to_owned()}72 pub rule field_name() -> expr::FieldName73 = name:id() {expr::FieldName::Fixed(name)}74 / name:string() {expr::FieldName::Fixed(name)}75 / "[" _ expr:boxed_expr() _ "]" {expr::FieldName::Dyn(expr)}76 pub rule visibility() -> expr::Visibility77 = ":::" {expr::Visibility::Unhide}78 / "::" {expr::Visibility::Hidden}79 / ":" {expr::Visibility::Normal}80 pub rule field() -> expr::FieldMember81 = name:field_name() _ plus:"+"? _ visibility:visibility() _ value:expr() {expr::FieldMember{82 name,83 plus: plus.is_some(),84 params: None,85 visibility,86 value,87 }}88 / name:field_name() _ "(" _ params:params() _ ")" _ visibility:visibility() _ value:expr() {expr::FieldMember{89 name,90 plus: false,91 params: Some(params),92 visibility,93 value,94 }}95 pub rule obj_local() -> BindSpec96 = keyword("local") _ bind:bind() {bind}97 pub rule member() -> expr::Member98 = bind:obj_local() {expr::Member::BindStmt(bind)}99 / assertion:assertion() {expr::Member::AssertStmt(assertion)}100 / field:field() {expr::Member::Field(field)}101 pub rule objinside() -> expr::ObjBody102 = 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})? {103 expr::ObjBody::ObjComp {104 pre_locals,105 key,106 value,107 post_locals,108 first,109 rest: rest.unwrap_or(Vec::new()),110 }111 }112 / members:(member() ** comma()) comma()? {expr::ObjBody::MemberList(members)}113 pub rule ifspec() -> IfSpecData = keyword("if") _ expr:boxed_expr() {IfSpecData(expr)}114 pub rule forspec() -> ForSpecData = keyword("for") _ id:id() _ keyword("in") _ cond:boxed_expr() {ForSpecData(id, cond)}115 pub rule compspec() -> Vec<expr::CompSpec> = s:(i:ifspec() { expr::CompSpec::IfSpec(i) } / f:forspec() {expr::CompSpec::ForSpec(f)} )+ {s}116 pub rule bind_expr() -> Expr = bind:bind() {Expr::Bind(bind)}117 pub rule local_expr() -> Expr = keyword("local") _ binds:bind() ** comma() _ ";" _ expr:boxed_expr() { Expr::LocalExpr(binds, expr) }118 pub rule string_expr() -> Expr = s:string() {Expr::Str(s)}119 pub rule parened_expr() -> Expr = "(" e:boxed_expr() ")" {Expr::Parened(e)}120 pub rule obj_expr() -> Expr = "{" _ body:objinside() _ "}" {Expr::Obj(body)}121 pub rule array_expr() -> Expr = "[" _ elems:(expr() ** comma()) _ comma()? "]" {Expr::Arr(elems)}122 pub rule array_comp_expr() -> Expr = "[" _ expr:boxed_expr() _ comma()? _ forspec:forspec() _ others:(others: compspec() _ {others})? "]" {Expr::ArrComp(expr, forspec, others.unwrap_or(vec![]))}123 pub rule index_expr() -> Expr124 = val:boxed_expr() "." idx:id() {Expr::Index(val, Box::new(Expr::Str(idx)))}125 / val:boxed_expr() "[" key:boxed_expr() "]" {Expr::Index(val, key)}126 pub rule number_expr() -> Expr = n:number() { expr::Expr::Num(n) }127 pub rule var_expr() -> Expr = n:id() { expr::Expr::Var(n) }128 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{129 cond,130 cond_then,131 cond_else,132 }}133134 pub rule literal() -> Expr135 = v:(136 keyword("null") {LiteralType::Null}137 / keyword("true") {LiteralType::True}138 / keyword("false") {LiteralType::False}139 / keyword("self") {LiteralType::This}140 / keyword("$") {LiteralType::Dollar}141 / keyword("super") {LiteralType::Super}142 ) {Expr::Literal(v)}143144 pub rule expr_basic() -> Expr145 = literal()146147 / string_expr() / number_expr()148 / array_expr()149 / obj_expr()150 / array_expr()151 / array_comp_expr()152153 / var_expr()154 / local_expr()155 / if_then_else_expr()156157 / keyword("function") _ "(" _ params:params() _ ")" _ expr:boxed_expr() {Expr::Function(params, expr)}158 / assertion:assertion() _ ";" _ expr:boxed_expr() { Expr::AssertExpr(assertion, expr) }159160 / keyword("error") _ expr:boxed_expr() { Expr::Error(expr) }161162 rule expr_basic_with_suffix() -> Expr163 = a:expr_basic() suffixes:(_ suffix:expr_suffix() {suffix})* {164 let mut cur = a;165 for suffix in suffixes {166 cur = match suffix {167 Suffix::String(index) => Expr::Index(Box::new(cur), Box::new(Expr::Str(index))),168 Suffix::Slice(desc) => Expr::Slice(Box::new(cur), desc),169 Suffix::Expression(index) => Expr::Index(Box::new(cur), Box::new(index)),170 Suffix::Apply(args) => Expr::Apply(Box::new(cur), args),171 Suffix::Extend(body) => Expr::ObjExtend(box cur, body),172 }173 }174 cur175 }176177 pub rule slice_desc() -> SliceDesc178 = start:boxed_expr()? _ ":" _ pair:(end:boxed_expr()? _ step:(":" _ e:boxed_expr() {e})? {(end, step)})? {179 if let Some((end, step)) = pair {180 SliceDesc { start, end, step }181 }else{182 SliceDesc { start, end: None, step: None }183 }184 }185186 rule expr_suffix() -> Suffix187 = "." _ s:id() { Suffix::String(s) }188 / "[" _ s:slice_desc() _ "]" { Suffix::Slice(s) }189 / "[" _ s:expr() _ "]" { Suffix::Expression(s) }190 / "(" _ args:args() _ ")" (_ keyword("tailstrict"))? { Suffix::Apply(args) }191 / "{" _ body:objinside() _ "}" { Suffix::Extend(body) }192193 rule expr() -> Expr194 = a:precedence! {195 a:(@) _ "||" _ b:@ {Expr::BinaryOp(Box::new(a), BinaryOpType::Or, Box::new(b))}196 --197 a:(@) _ "&&" _ b:@ {Expr::BinaryOp(Box::new(a), BinaryOpType::And, Box::new(b))}198 --199 a:(@) _ "|" _ b:@ {Expr::BinaryOp(Box::new(a), BinaryOpType::BitOr, Box::new(b))}200 --201 a:@ _ "^" _ b:(@) {Expr::BinaryOp(Box::new(a), BinaryOpType::BitXor, Box::new(b))}202 --203 a:(@) _ "&" _ b:@ {Expr::BinaryOp(Box::new(a), BinaryOpType::BitAnd, Box::new(b))}204 --205 a:(@) _ "==" _ b:@ {Expr::BinaryOp(Box::new(a), BinaryOpType::Eq, Box::new(b))}206 a:(@) _ "!=" _ b:@ {Expr::BinaryOp(Box::new(a), BinaryOpType::Ne, Box::new(b))}207 --208 a:(@) _ "<" _ b:@ {Expr::BinaryOp(Box::new(a), BinaryOpType::Lt, Box::new(b))}209 a:(@) _ ">" _ b:@ {Expr::BinaryOp(Box::new(a), BinaryOpType::Gt, Box::new(b))}210 a:(@) _ "<=" _ b:@ {Expr::BinaryOp(Box::new(a), BinaryOpType::Lte, Box::new(b))}211 a:(@) _ ">=" _ b:@ {Expr::BinaryOp(Box::new(a), BinaryOpType::Gte, Box::new(b))}212 --213 a:(@) _ "<<" _ b:@ {Expr::BinaryOp(Box::new(a), BinaryOpType::Lhs, Box::new(b))}214 a:(@) _ ">>" _ b:@ {Expr::BinaryOp(Box::new(a), BinaryOpType::Rhs, Box::new(b))}215 --216 a:(@) _ "+" _ b:@ {Expr::BinaryOp(Box::new(a), BinaryOpType::Add, Box::new(b))}217 a:(@) _ "-" _ b:@ {Expr::BinaryOp(Box::new(a), BinaryOpType::Sub, Box::new(b))}218 --219 a:(@) _ "*" _ b:@ {Expr::BinaryOp(Box::new(a), BinaryOpType::Mul, Box::new(b))}220 a:(@) _ "/" _ b:@ {Expr::BinaryOp(Box::new(a), BinaryOpType::Div, Box::new(b))}221 a:(@) _ "%" _ b:@ {Expr::BinaryOp(Box::new(a), BinaryOpType::Mod, Box::new(b))}222 --223 e:expr_basic_with_suffix() {e}224 "-" _ expr:expr_basic_with_suffix() { Expr::UnaryOp(UnaryOpType::Minus, box expr) }225 "!" _ expr:expr_basic_with_suffix() { Expr::UnaryOp(UnaryOpType::Not, box expr) }226 "(" _ e:boxed_expr() _ ")" {Expr::Parened(e)}227 }228 / e:expr_basic_with_suffix() {e}229230 pub rule boxed_expr() -> Box<Expr> = e:expr() {Box::new(e)}231 pub rule jsonnet() -> Expr = _ e:expr() _ {e}232 }233}234235236pub fn parse(str: &str) -> Result<Expr, peg::error::ParseError<peg::str::LineCol>> {237 jsonnet_parser::jsonnet(str)238}239240#[cfg(test)]241pub mod tests {242 use super::{expr::*, parse};243244 mod expressions {245 use super::*;246247 pub fn basic_math() -> Expr {248 Expr::BinaryOp(249 Box::new(Expr::Num(2.0)),250 BinaryOpType::Add,251 Box::new(Expr::BinaryOp(252 Box::new(Expr::Num(2.0)),253 BinaryOpType::Mul,254 Box::new(Expr::Num(2.0)),255 )),256 )257 }258 }259260 #[test]261 fn empty_object() {262 assert_eq!(parse("{}").unwrap(), Expr::Obj(ObjBody::MemberList(vec![])));263 }264265 #[test]266 fn basic_math() {267 assert_eq!(268 parse("2+2*2").unwrap(),269 Expr::BinaryOp(270 Box::new(Expr::Num(2.0)),271 BinaryOpType::Add,272 Box::new(Expr::BinaryOp(273 Box::new(Expr::Num(2.0)),274 BinaryOpType::Mul,275 Box::new(Expr::Num(2.0))276 ))277 )278 );279 }280281 #[test]282 fn basic_math_with_indents() {283 assert_eq!(parse("2 + 2 * 2 ").unwrap(), expressions::basic_math());284 }285286 #[test]287 fn basic_math_parened() {288 assert_eq!(289 parse("2+(2+2*2)").unwrap(),290 Expr::BinaryOp(291 Box::new(Expr::Num(2.0)),292 BinaryOpType::Add,293 Box::new(Expr::Parened(Box::new(expressions::basic_math()))),294 )295 );296 }297298 299 #[test]300 fn comments() {301 assert_eq!(302 parse("2//comment\n+//comment\n3/*test*/*/*test*/4").unwrap(),303 Expr::BinaryOp(304 box Expr::Num(2.0),305 BinaryOpType::Add,306 box Expr::BinaryOp(box Expr::Num(3.0), BinaryOpType::Mul, box Expr::Num(4.0))307 )308 );309 }310311 312 #[test]313 fn comment_escaping() {314 assert_eq!(315 parse("2/*\\*/+*/ - 22").unwrap(),316 Expr::BinaryOp(box Expr::Num(2.0), BinaryOpType::Sub, box Expr::Num(22.0))317 );318 }319320 #[test]321 fn suffix_comparsion() {322 use Expr::*;323 assert_eq!(324 parse("std.type(a) == \"string\"").unwrap(),325 BinaryOp(326 box Apply(327 box Index(box Var("std".to_owned()), box Str("type".to_owned())),328 ArgsDesc(vec![Arg(None, box Var("a".to_owned()))])329 ),330 BinaryOpType::Eq,331 box Str("string".to_owned())332 )333 );334 }335336 #[test]337 fn array_comp() {338 use Expr::*;339 assert_eq!(340 parse("[std.deepJoin(x) for x in arr]").unwrap(),341 ArrComp(342 box Apply(343 box Index(box Var("std".to_owned()), box Str("deepJoin".to_owned())),344 ArgsDesc(vec![Arg(None, box Var("x".to_owned()))])345 ),346 ForSpecData("x".to_owned(), box Var("arr".to_owned())),347 vec![]348 ),349 )350 }351352 #[test]353 fn array_comp_with_ifs() {354 use Expr::*;355 assert_eq!(356 parse("[k for k in std.objectFields(patch) if patch[k] == null]").unwrap(),357 ArrComp(358 box Var("k".to_owned()),359 ForSpecData(360 "k".to_owned(),361 box Apply(362 box Index(363 box Var("std".to_owned()),364 box Str("objectFields".to_owned())365 ),366 ArgsDesc(vec![Arg(None, box Var("patch".to_owned()))])367 )368 ),369 vec![CompSpec::IfSpec(IfSpecData(box BinaryOp(370 box Index(box Var("patch".to_owned()), box Var("k".to_owned())),371 BinaryOpType::Eq,372 box Literal(LiteralType::Null)373 )))]374 ),375 );376 }377378 #[test]379 fn reserved() {380 use Expr::*;381 assert_eq!(parse("null").unwrap(), Literal(LiteralType::Null));382 assert_eq!(parse("nulla").unwrap(), Var("nulla".to_owned()));383 }384385 #[test]386 fn multiple_args_buf() {387 parse("a(b, null_fields)").unwrap();388 }389390 #[test]391 fn infix_precedence() {392 use Expr::*;393 assert_eq!(parse("!a && !b").unwrap(), BinaryOp(394 box UnaryOp(UnaryOpType::Not, box Var("a".to_owned())),395 BinaryOpType::And,396 box UnaryOp(UnaryOpType::Not, box Var("b".to_owned()))397 ));398 }399400 #[test]401 fn can_parse_stdlib() {402 parse(jsonnet_stdlib::STDLIB_STR).unwrap();403 }404}