git.delta.rocks / jrsonnet / refs/commits / 26c294dc66fa

difftreelog

source

crates/jsonnet-parser/src/lib.rs13.5 KiBsourcehistory
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		/// Standard C-like comments21		rule comment() = "//" (!['\n'][_])* "\n" / "/*" ((!("*/")[_][_])/("\\" "*/"))* "*/"22		rule _() = ([' ' | '\n' | '\t'] / comment())*2324		/// For comma-delimited elements25		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		/// Sequence of digits30		rule uint() -> u32 = a:$(digit()+) { a.parse().unwrap() }31		/// Number in scientific notation format32		rule number() -> f64 = quiet!{a:$(uint() ("." uint())? (['e'|'E'] (s:['+'|'-'])? uint())?) { a.parse().unwrap() }} / expected!("<number>")3334		/// Reserved word followed by any non-alphanumberic35		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 &params {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}234235// TODO: impl FromStr from Expr236pub 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	/// Comments should not affect parsing299	#[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	/// Comments should be able to be escaped312	#[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}