From 31dfb91f8ba375b1e36be855f8ec823de8da5c2c Mon Sep 17 00:00:00 2001 From: Лач Date: Sat, 16 May 2020 04:33:52 +0000 Subject: [PATCH] chore(parser): initial parser commit --- --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,59 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "jsonnet-parser" +version = "0.1.0" +dependencies = [ + "peg", +] + +[[package]] +name = "peg" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9075875c14bb21f25f11cad4b6ad2e4dd443b8fb83900b2fbdd6ebd744b82e97" +dependencies = [ + "peg-macros", + "peg-runtime", +] + +[[package]] +name = "peg-macros" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c24c165fd39e995246140cc78df55c56c6733ba87e6658cb3e197b8856c62852" +dependencies = [ + "peg-runtime", + "proc-macro2", + "quote", +] + +[[package]] +name = "peg-runtime" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c1a2897e69d986c7986747ebad425cf03746ec5e3e09bb3b2600f91301ba864" + +[[package]] +name = "proc-macro2" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8872cf6f48eee44265156c111456a700ab3483686b3f96df4cf5481c89157319" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42934bc9c8ab0d3b273a16d8551c8f0fcff46be73276ca083ec2414c15c4ba5e" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "unicode-xid" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["crates/jsonnet-parser"] --- /dev/null +++ b/crates/jsonnet-parser/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "jsonnet-parser" +version = "0.1.0" +authors = ["Лач "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +peg = "0.6.2" --- /dev/null +++ b/crates/jsonnet-parser/src/expr.rs @@ -0,0 +1,206 @@ +#[derive(Debug, Clone, PartialEq)] +pub enum FieldName { + /// {fixed: 2} + Fixed(String), + /// {["dyn"+"amic"]: 3} + Dyn(Box), +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Visibility { + /// : + Normal, + /// :: + Hidden, + /// ::: + Unhide, +} + +#[derive(Debug, Clone, PartialEq)] +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: Params, + visibility: Visibility, + value: Expr, + }, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Member { + Field(FieldMember), + BindStmt(Bind), + AssertStmt(AssertStmt), +} + +#[derive(Debug, Clone, PartialEq)] +pub enum UnaryOp { + Plus, + Minus, + BitNot, + Not, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum BinaryOp { + Mul, + Div, + Mod, + + Add, + Sub, + + Lhs, + Rhs, + + Lt, + Gt, + Lte, + Gte, + + In, + + Eq, + Ne, + + BitAnd, + BitOr, + And, + Or, + + BitXor, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Param { + Positional(String), + Named(String, Box), +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Params(pub Vec); + +#[derive(Debug, Clone, PartialEq)] +pub enum Arg { + Positional(Box), + Named(String, Box), +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Args(pub Vec); + +#[derive(Debug, Clone, PartialEq)] +pub enum Bind { + Value(String, Box), + Function(String, Params, Box), +} + +#[derive(Debug, Clone, PartialEq)] +pub struct IfSpec(pub Box); +#[derive(Debug, Clone, PartialEq)] +pub struct ForSpec(pub String, pub Vec); + +#[derive(Debug, Clone, PartialEq)] +pub enum CompSpec { + IfSpec(IfSpec), + ForSpec(ForSpec), +} + +#[derive(Debug, Clone, PartialEq)] +pub enum ObjBody { + MemberList(Vec), + ObjComp { + pre_locals: Vec, + key: Box, + value: Box, + post_locals: Vec, + first: ForSpec, + rest: Vec, + }, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Literal { + Null, + True, + False, + This, + Super, + Dollar, +} + +/// Syntax base +#[derive(Debug, Clone, PartialEq)] +pub enum Expr { + /// Plain value: null/true/false + Literal(Literal), + + /// String value: "hello" + Str(String), + /// Number: 1, 2.0, 2e+20 + Num(f64), + /// Variable name: test + Var(String), + + /// Array of expressions: [1, 2, "Hello"] + Arr(Vec), + /// Array comprehension: + /// ```jsonnet + /// ingredients: [ + /// { kind: kind, qty: 4 / 3 } + /// for kind in [ + /// 'Honey Syrup', + /// 'Lemon Juice', + /// 'Farmers Gin', + /// ] + /// ], + /// ``` + ArrComp(Box, Vec), + + /// Object: {a: 2} + Obj(ObjBody), + /// Object extension: var1 {b: 2} + ObjExtend(Box, ObjBody), + + /// (obj) + Parened(Box), + + Params(Params), + Args(Args), + + UnaryOp(UnaryOp, Box), + BinaryOp(Box, BinaryOp, Box), + AssertExpr(AssertStmt, Box), + LocalExpr(Vec, Box), + + Bind(Bind), + Import(String), + ImportStr(String), + Error(Box), + Apply(Box, Args), + Select(Box, String), + Index(Box, Box), + Slice { + value: Box, + start: Option>, + end: Option>, + step: Option>, + }, + Function(Params, Box), + IfElse { + cond: IfSpec, + cond_then: Box, + cond_else: Option>, + }, + IfSpec(IfSpec), + ForSpec(ForSpec), +} --- /dev/null +++ b/crates/jsonnet-parser/src/lib.rs @@ -0,0 +1,208 @@ +use peg::parser; + +pub mod expr; +use expr::{Expr, Literal}; + +enum Suffix { + String(String), + Expression(Expr), + Apply(expr::Args), +} + +parser! { + grammar jsonnet_parser() for str { + rule delimiter() = quiet!{__() "," __()} / expected!("") + rule _() = quiet!{[' ' | '\n' | '\t']+} / expected!("") + rule __() = quiet!{[' ' | '\n' | '\t']*} + 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") s:$(alpha() (alpha() / digit())*) {s.to_owned()}} / expected!("") + + 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 params() -> expr::Params + = positionals:(positional_param() ** delimiter()) delimiter() named:(named_param() ** delimiter()) { + expr::Params([&positionals[..], &named[..]].concat()) + } + / named:(named_param() ** delimiter()) {expr::Params(named)} + / positionals:(positional_param() ** delimiter()) {expr::Params(positionals)} + / {expr::Params(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 args() -> expr::Args + = positionals:(positional_arg() ** delimiter()) delimiter() named:(named_arg() ** delimiter()) { + expr::Args([&positionals[..], &named[..]].concat()) + } + / named:(named_arg() ** delimiter()) {expr::Args(named)} + / positionals:(positional_arg() ** delimiter()) {expr::Args(positionals)} + / {expr::Args(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 string() -> String + = "\"" 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)} + 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, + plus: plus.is_some(), + visibility, + value, + }} + / name:field_name() __() "(" __() params:params() __() ")" __() visibility:visibility() __() value:expr() {expr::FieldMember::Function{ + name, + params, + visibility, + value, + }} + pub rule member() -> expr::Member + = "local" _() bind:bind() {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 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 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 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{ + cond, + cond_then, + cond_else, + }} + pub rule expr_basic() -> Expr + = "null" {Expr::Literal(Literal::Null)} + / "true" {Expr::Literal(Literal::True)} / "false" {Expr::Literal(Literal::False)} + / "self" {Expr::Literal(Literal::This)} / "$" {Expr::Literal(Literal::Dollar)} + / "super" {Expr::Literal(Literal::Super)} + + / string_expr() / number_expr() + / array_expr() + / array_comp_expr() + / obj_expr() + / array_expr() + / array_comp_expr() + + / var_expr() + / if_then_else_expr() + / local_expr() + + rule expr_suffix() -> Suffix + = "." __() s:id() { Suffix::String(s) } + / "[" __() s:expr() __() "]" { Suffix::Expression(s) } + / "(" __() args:args() __() ")" { Suffix::Apply(args) } + + rule expr() -> Expr + = a:precedence! { + a:(@) __() "||" __() b:@ {Expr::BinaryOp(Box::new(a), expr::BinaryOp::Or, Box::new(b))} + -- + a:(@) __() "&&" __() b:@ {Expr::BinaryOp(Box::new(a), expr::BinaryOp::And, Box::new(b))} + -- + a:(@) __() "|" __() b:@ {Expr::BinaryOp(Box::new(a), expr::BinaryOp::BitOr, Box::new(b))} + -- + a:@ __() "^" __() b:(@) {Expr::BinaryOp(Box::new(a), expr::BinaryOp::BitXor, Box::new(b))} + -- + a:(@) __() "&" __() b:@ {Expr::BinaryOp(Box::new(a), expr::BinaryOp::BitAnd, Box::new(b))} + -- + a:(@) __() "==" __() b:@ {Expr::BinaryOp(Box::new(a), expr::BinaryOp::Eq, Box::new(b))} + a:(@) __() "!=" __() b:@ {Expr::BinaryOp(Box::new(a), expr::BinaryOp::Ne, Box::new(b))} + -- + a:(@) __() "<" __() b:@ {Expr::BinaryOp(Box::new(a), expr::BinaryOp::Lt, Box::new(b))} + a:(@) __() ">" __() b:@ {Expr::BinaryOp(Box::new(a), expr::BinaryOp::Gt, Box::new(b))} + a:(@) __() "<=" __() b:@ {Expr::BinaryOp(Box::new(a), expr::BinaryOp::Lte, Box::new(b))} + a:(@) __() ">=" __() b:@ {Expr::BinaryOp(Box::new(a), expr::BinaryOp::Gte, Box::new(b))} + -- + a:(@) __() "<<" __() b:@ {Expr::BinaryOp(Box::new(a), expr::BinaryOp::Lhs, Box::new(b))} + a:(@) __() ">>" __() b:@ {Expr::BinaryOp(Box::new(a), expr::BinaryOp::Rhs, Box::new(b))} + -- + a:(@) __() "+" __() b:@ {Expr::BinaryOp(Box::new(a), expr::BinaryOp::Add, Box::new(b))} + a:(@) __() "-" __() b:@ {Expr::BinaryOp(Box::new(a), expr::BinaryOp::Sub, Box::new(b))} + -- + a:(@) __() "*" __() b:@ {Expr::BinaryOp(Box::new(a), expr::BinaryOp::Mul, Box::new(b))} + a:(@) __() "/" __() b:@ {Expr::BinaryOp(Box::new(a), expr::BinaryOp::Div, Box::new(b))} + a:(@) __() "%" __() b:@ {Expr::BinaryOp(Box::new(a), expr::BinaryOp::Mod, Box::new(b))} + -- + e:expr_basic() {e} + "(" __() e:expr_basic() __() ")" {Expr::Parened(Box::new(e))} + } 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 + } + / e:expr_basic() {e} + + pub rule boxed_expr() -> Box = e:expr() {Box::new(e)} + pub rule jsonnet() -> Expr = __() e:expr() __() {e} + } +} + +// TODO: impl FromStr from Expr +pub fn parse(str: &str) -> Result> { + jsonnet_parser::jsonnet(str) +} + +#[cfg(test)] +pub mod tests { + use super::{expr::*, parse}; + #[test] + fn empty_object() { + assert_eq!(parse("{}").unwrap(), Expr::Obj(ObjBody::MemberList(vec![])),); + } + #[test] + fn basic_math() { + assert_eq!( + parse("2+2*2").unwrap(), + Expr::BinaryOp( + Box::new(Expr::Num(2.0)), + BinaryOp::Add, + Box::new(Expr::BinaryOp( + Box::new(Expr::Num(2.0)), + BinaryOp::Mul, + Box::new(Expr::Num(2.0)) + )) + ) + ); + } +} -- gitstuff