--- a/Cargo.lock +++ b/Cargo.lock @@ -609,7 +609,7 @@ "jrsonnet-cli", "jrsonnet-evaluator", "jrsonnet-gcmodule", - "jrsonnet-parser", + "jrsonnet-ir", "mimallocator", "serde", "serde_json", @@ -623,7 +623,7 @@ "clap", "jrsonnet-evaluator", "jrsonnet-gcmodule", - "jrsonnet-parser", + "jrsonnet-ir", "jrsonnet-stdlib", ] @@ -637,8 +637,9 @@ "hi-doc", "jrsonnet-gcmodule", "jrsonnet-interner", + "jrsonnet-ir", "jrsonnet-macros", - "jrsonnet-parser", + "jrsonnet-peg-parser", "jrsonnet-types", "num-bigint", "pathdiff", @@ -704,6 +705,17 @@ ] [[package]] +name = "jrsonnet-ir" +version = "0.5.0-pre97" +dependencies = [ + "insta", + "jrsonnet-gcmodule", + "jrsonnet-interner", + "peg", + "static_assertions", +] + +[[package]] name = "jrsonnet-macros" version = "0.5.0-pre97" dependencies = [ @@ -714,14 +726,12 @@ ] [[package]] -name = "jrsonnet-parser" +name = "jrsonnet-peg-parser" version = "0.5.0-pre97" dependencies = [ "insta", - "jrsonnet-gcmodule", - "jrsonnet-interner", + "jrsonnet-ir", "peg", - "static_assertions", ] [[package]] @@ -746,8 +756,8 @@ "base64", "jrsonnet-evaluator", "jrsonnet-gcmodule", + "jrsonnet-ir", "jrsonnet-macros", - "jrsonnet-parser", "lru", "md5", "num-bigint", @@ -812,7 +822,7 @@ "jrsonnet-evaluator", "jrsonnet-gcmodule", "jrsonnet-interner", - "jrsonnet-parser", + "jrsonnet-ir", "jrsonnet-stdlib", ] --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,8 @@ [workspace.dependencies] jrsonnet-evaluator = { path = "./crates/jrsonnet-evaluator", version = "0.5.0-pre97" } jrsonnet-macros = { path = "./crates/jrsonnet-macros", version = "0.5.0-pre97" } -jrsonnet-parser = { path = "./crates/jrsonnet-parser", version = "0.5.0-pre97" } +jrsonnet-ir = { path = "./crates/jrsonnet-ir", version = "0.5.0-pre97" } +jrsonnet-peg-parser = { path = "./crates/jrsonnet-peg-parser", version = "0.5.0-pre97" } jrsonnet-rowan-parser = { path = "./crates/jrsonnet-rowan-parser", version = "0.5.0-pre97" } jrsonnet-interner = { path = "./crates/jrsonnet-interner", version = "0.5.0-pre97" } jrsonnet-stdlib = { path = "./crates/jrsonnet-stdlib", version = "0.5.0-pre97" } --- a/bindings/jsonnet/Cargo.toml +++ b/bindings/jsonnet/Cargo.toml @@ -20,7 +20,7 @@ [dependencies] jrsonnet-evaluator.workspace = true -jrsonnet-parser.workspace = true +jrsonnet-ir.workspace = true jrsonnet-stdlib.workspace = true jrsonnet-gcmodule.workspace = true jrsonnet-interner.workspace = true --- a/bindings/jsonnet/src/import.rs +++ b/bindings/jsonnet/src/import.rs @@ -17,7 +17,7 @@ AsPathLike, ImportResolver, ResolvePath, }; use jrsonnet_gcmodule::Acyclic; -use jrsonnet_parser::{SourceDirectory, SourceFile, SourcePath}; +use jrsonnet_ir::{SourceDirectory, SourceFile, SourcePath}; use crate::VM; --- a/bindings/jsonnet/src/lib.rs +++ b/bindings/jsonnet/src/lib.rs @@ -31,7 +31,7 @@ AsPathLike, FileImportResolver, IStr, ImportResolver, Result, State, Val, }; use jrsonnet_gcmodule::Acyclic; -use jrsonnet_parser::SourcePath; +use jrsonnet_ir::SourcePath; use jrsonnet_stdlib::ContextInitializer; /// WASM stub --- a/cmds/jrsonnet/Cargo.toml +++ b/cmds/jrsonnet/Cargo.toml @@ -37,7 +37,7 @@ # obj?.field, obj?.['field'] exp-null-coaelse = [ "jrsonnet-evaluator/exp-null-coaelse", - "jrsonnet-parser/exp-null-coaelse", + "jrsonnet-ir/exp-null-coaelse", "jrsonnet-cli/exp-null-coaelse", ] # --exp-apply @@ -45,7 +45,7 @@ [dependencies] jrsonnet-evaluator.workspace = true -jrsonnet-parser.workspace = true +jrsonnet-ir.workspace = true jrsonnet-cli.workspace = true jrsonnet-gcmodule.workspace = true --- a/cmds/jrsonnet/src/main.rs +++ b/cmds/jrsonnet/src/main.rs @@ -11,7 +11,7 @@ error::{Error as JrError, ErrorKind}, ResultExt, State, Val, }; -use jrsonnet_parser::{SourceDefaultIgnoreJpath, SourcePath}; +use jrsonnet_ir::{SourceDefaultIgnoreJpath, SourcePath}; #[cfg(feature = "mimalloc")] #[global_allocator] --- a/crates/jrsonnet-cli/Cargo.toml +++ b/crates/jrsonnet-cli/Cargo.toml @@ -29,7 +29,7 @@ [dependencies] jrsonnet-evaluator = { workspace = true, features = ["explaining-traces"] } -jrsonnet-parser.workspace = true +jrsonnet-ir.workspace = true jrsonnet-stdlib.workspace = true jrsonnet-gcmodule.workspace = true --- a/crates/jrsonnet-evaluator/Cargo.toml +++ b/crates/jrsonnet-evaluator/Cargo.toml @@ -22,17 +22,18 @@ # Allows to preserve field order in objects exp-preserve-order = [] # Implements field destructuring -exp-destruct = ["jrsonnet-parser/exp-destruct"] +exp-destruct = ["jrsonnet-peg-parser/exp-destruct"] # Iteration over objects yields [key, value] elements exp-object-iteration = [] # Bigint type exp-bigint = ["num-bigint", "jrsonnet-types/exp-bigint"] # obj?.field, obj?.['field'] -exp-null-coaelse = ["jrsonnet-parser/exp-null-coaelse"] +exp-null-coaelse = ["jrsonnet-peg-parser/exp-null-coaelse"] [dependencies] jrsonnet-interner.workspace = true -jrsonnet-parser.workspace = true +jrsonnet-ir.workspace = true +jrsonnet-peg-parser.workspace = true jrsonnet-types.workspace = true jrsonnet-macros.workspace = true jrsonnet-gcmodule.workspace = true --- a/crates/jrsonnet-evaluator/src/arr/mod.rs +++ b/crates/jrsonnet-evaluator/src/arr/mod.rs @@ -7,7 +7,7 @@ use jrsonnet_gcmodule::{cc_dyn, Cc}; use jrsonnet_interner::IBytes; -use jrsonnet_parser::{Expr, Spanned}; +use jrsonnet_ir::{Expr, Spanned}; use crate::{function::NativeFn, Context, Result, Thunk, Val}; @@ -85,7 +85,7 @@ pub fn extended(a: Self, b: Self) -> Self { // TODO: benchmark for an optimal value, currently just a arbitrary choice - const ARR_EXTEND_THRESHOLD: usize = 100; + const ARR_EXTEND_THRESHOLD: usize = 1000; if a.is_empty() { b --- a/crates/jrsonnet-evaluator/src/arr/spec.rs +++ b/crates/jrsonnet-evaluator/src/arr/spec.rs @@ -3,7 +3,7 @@ use jrsonnet_gcmodule::{Cc, Trace}; use jrsonnet_interner::{IBytes, IStr}; -use jrsonnet_parser::{Expr, Spanned}; +use jrsonnet_ir::{Expr, Spanned}; use super::ArrValue; use crate::function::NativeFn; --- a/crates/jrsonnet-evaluator/src/async_import.rs +++ b/crates/jrsonnet-evaluator/src/async_import.rs @@ -2,11 +2,12 @@ use std::{any::Any, cell::RefCell, future::Future}; use jrsonnet_gcmodule::Acyclic; -use jrsonnet_parser::{ +use jrsonnet_ir::{ ArgsDesc, AssertExpr, AssertStmt, BindSpec, CompSpec, Destruct, Expr, ExprParam, ExprParams, - FieldMember, FieldName, ForSpecData, IfElse, IfSpecData, ImportKind, ObjBody, ParserSettings, - Slice, SliceDesc, Source, SourcePath, Spanned, + FieldMember, FieldName, ForSpecData, IfElse, IfSpecData, ImportKind, ObjBody, Slice, SliceDesc, + Source, SourcePath, Spanned, }; +use jrsonnet_peg_parser::ParserSettings; use rustc_hash::FxHashMap; use crate::{AsPathLike, FileData, ImportResolver, ResolvePathOwned, State}; @@ -322,7 +323,7 @@ }; let source = Source::new(path.clone(), code.clone()); // If failed - then skip import - file.parsed = jrsonnet_parser::parse(&code, &ParserSettings { source }) + file.parsed = jrsonnet_peg_parser::parse(&code, &ParserSettings { source }) .map(Rc::new) .ok(); if let Some(parsed) = &file.parsed { --- a/crates/jrsonnet-evaluator/src/error.rs +++ b/crates/jrsonnet-evaluator/src/error.rs @@ -2,7 +2,7 @@ use jrsonnet_gcmodule::{Acyclic, Trace}; use jrsonnet_interner::IStr; -use jrsonnet_parser::{BinaryOpType, Source, SourcePath, Span, Spanned, UnaryOpType}; +use jrsonnet_ir::{BinaryOpType, Source, SourcePath, Span, Spanned, UnaryOpType}; use jrsonnet_types::ValType; use thiserror::Error; @@ -169,7 +169,7 @@ ImportSyntaxError { path: Source, #[trace(skip)] - error: Box, + error: Box, }, #[error("runtime error: {}", format_empty_str(.0))] --- a/crates/jrsonnet-evaluator/src/evaluate/destructure.rs +++ b/crates/jrsonnet-evaluator/src/evaluate/destructure.rs @@ -1,7 +1,7 @@ use std::{collections::HashMap, hash::BuildHasher}; use jrsonnet_interner::IStr; -use jrsonnet_parser::{BindSpec, Destruct}; +use jrsonnet_ir::{BindSpec, Destruct}; use crate::{ bail, @@ -31,7 +31,7 @@ Destruct::Skip => {} #[cfg(feature = "exp-destruct")] Destruct::Array { start, rest, end } => { - use jrsonnet_parser::DestructRest; + use jrsonnet_ir::DestructRest; let min_len = start.len() + end.len(); let has_rest = rest.is_some(); --- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs +++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs @@ -2,7 +2,7 @@ use jrsonnet_gcmodule::{Cc, Trace}; use jrsonnet_interner::IStr; -use jrsonnet_parser::{ +use jrsonnet_ir::{ function::ParamName, ArgsDesc, AssertStmt, BinaryOpType, BindSpec, CompSpec, Expr, ExprParams, FieldMember, FieldName, ForSpecData, IfSpecData, ImportKind, LiteralType, ObjBody, ObjMembers, Spanned, @@ -47,17 +47,17 @@ stacker::maybe_grow(RED_ZONE, STACK_PER_RECURSION, f) } -pub fn evaluate_trivial(expr: &Spanned) -> Option { - fn is_trivial(expr: &Spanned) -> bool { - match &**expr { +pub fn evaluate_trivial(expr: &Expr) -> Option { + fn is_trivial(expr: &Expr) -> bool { + match &*expr { Expr::Str(_) | Expr::Num(_) | Expr::Literal(LiteralType::False | LiteralType::True | LiteralType::Null) => true, - Expr::Arr(a) => a.iter().all(is_trivial), + Expr::Arr(a) => a.iter().all(|e| is_trivial(&**e)), _ => false, } } - Some(match &**expr { + Some(match &*expr { Expr::Str(s) => Val::string(s.clone()), Expr::Num(n) => { Val::Num(NumValue::new(*n).expect("parser will not allow non-finite values")) @@ -71,7 +71,7 @@ } Val::Arr(ArrValue::eager( n.iter() - .map(evaluate_trivial) + .map(|e| evaluate_trivial(&**e)) .map(|e| e.expect("checked trivial")) .collect(), )) @@ -395,14 +395,13 @@ } #[allow(clippy::too_many_lines)] -pub fn evaluate(ctx: Context, expr: &Spanned) -> Result { +pub fn evaluate(ctx: Context, expr: &Expr) -> Result { use Expr::*; if let Some(trivial) = evaluate_trivial(expr) { return Ok(trivial); } - let loc = expr.span(); - Ok(match &**expr { + Ok(match expr { Literal(LiteralType::This) => Val::Obj(ctx.try_this()?), Literal(LiteralType::Super) => Val::Obj(ctx.try_sup_this()?.standalone_super()?), Literal(LiteralType::Dollar) => Val::Obj(ctx.try_dollar()?), @@ -433,9 +432,9 @@ BinaryOp(bin) => evaluate_binary_op_special(ctx, &bin.lhs, bin.op, &bin.rhs)?, UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(ctx, v)?)?, Var(name) => in_frame( - CallLocation::new(&loc), + CallLocation::new(&name.span()), || format!("local <{name}> access"), - || ctx.binding(name.clone())?.evaluate(), + || ctx.binding((**name).clone())?.evaluate(), )?, Index { indexable, parts } => ensure_sufficient_stack(|| { let mut parts = parts.iter(); @@ -591,7 +590,13 @@ &Val::Obj(evaluate_object(ctx, b)?), )?, Apply(value, args, tailstrict) => ensure_sufficient_stack(|| { - evaluate_apply(ctx, value, args, CallLocation::new(&loc), *tailstrict) + evaluate_apply( + ctx, + value, + args, + CallLocation::new(&args.span()), + *tailstrict, + ) })?, Function(params, body) => { evaluate_method(ctx, "anonymous".into(), params.clone(), body.clone()) @@ -601,13 +606,13 @@ evaluate(ctx, &assert.rest)? } ErrorStmt(e) => in_frame( - CallLocation::new(&loc), + CallLocation::new(&e.span()), || "error statement".to_owned(), || bail!(RuntimeError(evaluate(ctx, e)?.to_string()?,)), )?, IfElse(if_else) => { if in_frame( - CallLocation::new(&loc), + CallLocation::new(&if_else.cond.0.span()), || "if condition".to_owned(), || bool::from_untyped(evaluate(ctx.clone(), &if_else.cond.0)?), )? { --- a/crates/jrsonnet-evaluator/src/evaluate/operator.rs +++ b/crates/jrsonnet-evaluator/src/evaluate/operator.rs @@ -1,6 +1,6 @@ use std::cmp::Ordering; -use jrsonnet_parser::{BinaryOpType, Expr, Spanned, UnaryOpType}; +use jrsonnet_ir::{BinaryOpType, Expr, Spanned, UnaryOpType}; use crate::{ arr::ArrValue, --- a/crates/jrsonnet-evaluator/src/function/builtin.rs +++ b/crates/jrsonnet-evaluator/src/function/builtin.rs @@ -1,7 +1,7 @@ use std::any::Any; use jrsonnet_gcmodule::{cc_dyn, Trace, TraceBox}; -use jrsonnet_parser::function::{FunctionSignature, ParamDefault, ParamName, ParamParse}; +use jrsonnet_ir::function::{FunctionSignature, ParamDefault, ParamName, ParamParse}; use super::CallLocation; use crate::{Result, Thunk, Val}; --- a/crates/jrsonnet-evaluator/src/function/mod.rs +++ b/crates/jrsonnet-evaluator/src/function/mod.rs @@ -4,7 +4,7 @@ use jrsonnet_gcmodule::{Cc, Trace}; use jrsonnet_interner::IStr; pub use jrsonnet_macros::builtin; -use jrsonnet_parser::{ArgsDesc, Destruct, Expr, ExprParams, Span, Spanned}; +use jrsonnet_ir::{ArgsDesc, Destruct, Expr, ExprParams, Span, Spanned}; use self::{ builtin::{Builtin, StaticBuiltin}, @@ -24,7 +24,7 @@ pub use native::NativeFn; pub use prepared::PreparedFuncVal; -pub use jrsonnet_parser::function::*; +pub use jrsonnet_ir::function::*; /// Function callsite location. /// Either from other jsonnet code, specified by expression location, or from native (without location). --- a/crates/jrsonnet-evaluator/src/function/parse.rs +++ b/crates/jrsonnet-evaluator/src/function/parse.rs @@ -1,6 +1,6 @@ use std::rc::Rc; -use jrsonnet_parser::{ +use jrsonnet_ir::{ function::{FunctionSignature, ParamName}, ArgsDesc, Expr, ExprParams, Spanned, }; --- a/crates/jrsonnet-evaluator/src/function/prepared.rs +++ b/crates/jrsonnet-evaluator/src/function/prepared.rs @@ -1,8 +1,8 @@ use std::rc::Rc; use jrsonnet_gcmodule::{Acyclic, Trace}; -use jrsonnet_parser::function::FunctionSignature; -use jrsonnet_parser::{ExprParams, IStr}; +use jrsonnet_ir::function::FunctionSignature; +use jrsonnet_ir::{ExprParams, IStr}; use rustc_hash::{FxHashMap, FxHashSet}; use crate::destructure::destruct; --- a/crates/jrsonnet-evaluator/src/import.rs +++ b/crates/jrsonnet-evaluator/src/import.rs @@ -10,7 +10,7 @@ use fs::File; use jrsonnet_gcmodule::Acyclic; use jrsonnet_interner::IBytes; -use jrsonnet_parser::{ +use jrsonnet_ir::{ IStr, SourceDefaultIgnoreJpath, SourceDirectory, SourceFifo, SourceFile, SourcePath, }; --- a/crates/jrsonnet-evaluator/src/lib.rs +++ b/crates/jrsonnet-evaluator/src/lib.rs @@ -44,8 +44,9 @@ pub use jrsonnet_interner::{IBytes, IStr}; #[doc(hidden)] pub use jrsonnet_macros; -pub use jrsonnet_parser as parser; -use jrsonnet_parser::{Expr, ParserSettings, Source, SourcePath, Spanned}; +pub use jrsonnet_ir as parser; +use jrsonnet_ir::{Expr, Source, SourcePath, Spanned}; +use jrsonnet_peg_parser::ParserSettings; pub use obj::*; pub use rustc_hash; use rustc_hash::FxHashMap; @@ -344,7 +345,7 @@ let file_name = Source::new(path.clone(), code.clone()); if file.parsed.is_none() { file.parsed = Some( - jrsonnet_parser::parse( + jrsonnet_peg_parser::parse( &code, &ParserSettings { source: file_name.clone(), @@ -460,7 +461,7 @@ pub fn evaluate_snippet(&self, name: impl Into, code: impl Into) -> Result { let code = code.into(); let source = Source::new_virtual(name.into(), code.clone()); - let parsed = jrsonnet_parser::parse( + let parsed = jrsonnet_peg_parser::parse( &code, &ParserSettings { source: source.clone(), @@ -481,7 +482,7 @@ ) -> Result { let code = code.into(); let source = Source::new_virtual(name.into(), code.clone()); - let parsed = jrsonnet_parser::parse( + let parsed = jrsonnet_peg_parser::parse( &code, &ParserSettings { source: source.clone(), --- a/crates/jrsonnet-evaluator/src/obj/mod.rs +++ b/crates/jrsonnet-evaluator/src/obj/mod.rs @@ -5,12 +5,12 @@ use educe::Educe; use jrsonnet_gcmodule::{cc_dyn, Acyclic, Cc, Trace, Weak}; use jrsonnet_interner::IStr; -use jrsonnet_parser::Span; +use jrsonnet_ir::Span; use rustc_hash::{FxHashMap, FxHashSet}; mod oop; -pub use jrsonnet_parser::Visibility; +pub use jrsonnet_ir::Visibility; pub use oop::ObjValueBuilder; use crate::{ --- a/crates/jrsonnet-evaluator/src/obj/oop.rs +++ b/crates/jrsonnet-evaluator/src/obj/oop.rs @@ -8,7 +8,7 @@ bail, error::ErrorKind::*, in_frame, CcUnbound, MaybeUnbound, Result, Thunk, Unbound, Val, }; use jrsonnet_gcmodule::{Cc, Trace}; -use jrsonnet_parser::IStr; +use jrsonnet_ir::IStr; use rustc_hash::{FxHashMap, FxHashSet}; use super::ordering::{FieldIndex, SuperDepth}; --- a/crates/jrsonnet-evaluator/src/tla.rs +++ b/crates/jrsonnet-evaluator/src/tla.rs @@ -2,7 +2,7 @@ use jrsonnet_gcmodule::Trace; use jrsonnet_interner::IStr; -use jrsonnet_parser::{SourceFifo, SourcePath}; +use jrsonnet_ir::{SourceFifo, SourcePath}; use crate::{ function::{CallLocation, PreparedFuncVal}, --- a/crates/jrsonnet-evaluator/src/trace/mod.rs +++ b/crates/jrsonnet-evaluator/src/trace/mod.rs @@ -6,9 +6,9 @@ }; use jrsonnet_gcmodule::Trace; -use jrsonnet_parser::CodeLocation; +use jrsonnet_ir::CodeLocation; #[cfg(feature = "explaining-traces")] -use jrsonnet_parser::Span; +use jrsonnet_ir::Span; use crate::{error::ErrorKind, Error}; --- a/crates/jrsonnet-formatter/src/lib.rs +++ b/crates/jrsonnet-formatter/src/lib.rs @@ -855,7 +855,7 @@ } pub struct FormatOptions { - // 0 for hard tabs + // 0 for hard tabs, otherwise number of spaces pub indent: u8, } --- /dev/null +++ b/crates/jrsonnet-ir/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "jrsonnet-ir" +description = "jsonnet language parser and AST" +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +version.workspace = true + +[features] +default = [] +exp-destruct = [] +exp-null-coaelse = [] + +[dependencies] +jrsonnet-interner.workspace = true +jrsonnet-gcmodule.workspace = true + +static_assertions.workspace = true + +peg.workspace = true + +[dev-dependencies] +insta.workspace = true --- /dev/null +++ b/crates/jrsonnet-ir/README.adoc @@ -0,0 +1,3 @@ += jrsonnet-parser + +Parser for jsonnet language --- /dev/null +++ b/crates/jrsonnet-ir/src/expr.rs @@ -0,0 +1,493 @@ +use std::{ + fmt::{self, Debug, Display}, + ops::Deref, + rc::Rc, +}; + +use jrsonnet_gcmodule::Acyclic; +use jrsonnet_interner::IStr; + +use crate::{ + function::{FunctionSignature, ParamDefault, ParamName, ParamParse}, + source::Source, +}; + +#[derive(Debug, PartialEq, Acyclic)] +pub enum FieldName { + /// {fixed: 2} + Fixed(IStr), + /// {["dyn"+"amic"]: 3} + Dyn(Spanned), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Acyclic)] +#[repr(u8)] +pub enum Visibility { + /// : + Normal, + /// :: + Hidden, + /// ::: + Unhide, +} + +impl Visibility { + pub fn is_visible(&self) -> bool { + matches!(self, Self::Normal | Self::Unhide) + } +} + +#[derive(Debug, PartialEq, Acyclic)] +pub struct AssertStmt(pub Spanned, pub Option>); + +#[derive(Debug, PartialEq, Acyclic)] +pub struct FieldMember { + pub name: FieldName, + pub plus: bool, + pub params: Option, + pub visibility: Visibility, + pub value: Rc>, +} + +#[derive(Debug, PartialEq, Acyclic)] +pub enum Member { + Field(FieldMember), + BindStmt(BindSpec), + AssertStmt(AssertStmt), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Acyclic)] +pub enum UnaryOpType { + Plus, + Minus, + BitNot, + Not, +} + +impl Display for UnaryOpType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use UnaryOpType::*; + write!( + f, + "{}", + match self { + Plus => "+", + Minus => "-", + BitNot => "~", + Not => "!", + } + ) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Acyclic)] +pub enum BinaryOpType { + Mul, + Div, + + /// Implemented as intrinsic, put here for completeness + Mod, + + Add, + Sub, + + Lhs, + Rhs, + + Lt, + Gt, + Lte, + Gte, + + BitAnd, + BitOr, + BitXor, + + Eq, + Neq, + + And, + Or, + #[cfg(feature = "exp-null-coaelse")] + NullCoaelse, + + // Equialent to std.objectHasEx(a, b, true) + In, +} + +impl Display for BinaryOpType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use BinaryOpType::*; + write!( + f, + "{}", + match self { + Mul => "*", + Div => "/", + Mod => "%", + Add => "+", + Sub => "-", + Lhs => "<<", + Rhs => ">>", + Lt => "<", + Gt => ">", + Lte => "<=", + Gte => ">=", + BitAnd => "&", + BitOr => "|", + BitXor => "^", + Eq => "==", + Neq => "!=", + And => "&&", + Or => "||", + In => "in", + #[cfg(feature = "exp-null-coaelse")] + NullCoaelse => "??", + } + ) + } +} + +/// name, default value +#[derive(Debug, PartialEq, Acyclic)] +pub struct ExprParam { + pub destruct: Destruct, + pub default: Option>>, +} + +/// Defined function parameters +#[derive(Debug, Clone, PartialEq, Acyclic)] +pub struct ExprParams { + pub exprs: Rc>, + pub signature: FunctionSignature, + binds_len: usize, +} +impl ExprParams { + pub fn len(&self) -> usize { + self.exprs.len() + } + pub fn is_empty(&self) -> bool { + self.exprs.is_empty() + } + + pub fn binds_len(&self) -> usize { + self.binds_len + } + pub fn new(exprs: Vec) -> Self { + Self { + signature: FunctionSignature::new( + exprs + .iter() + .map(|p| { + ParamParse::new( + p.destruct.name(), + ParamDefault::exists(p.default.is_some()), + ) + }) + .collect(), + ), + binds_len: exprs.iter().map(|v| v.destruct.binds_len()).sum(), + exprs: Rc::new(exprs), + } + } +} + +#[derive(Debug, PartialEq, Acyclic)] +pub struct ArgsDesc { + pub unnamed: Vec>>, + pub named: Vec<(IStr, Rc>)>, +} +impl ArgsDesc { + pub fn new(unnamed: Vec>>, named: Vec<(IStr, Rc>)>) -> Self { + Self { unnamed, named } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Acyclic)] +pub enum DestructRest { + /// ...rest + Keep(IStr), + /// ... + Drop, +} + +#[derive(Debug, Clone, PartialEq, Acyclic)] +pub enum Destruct { + Full(IStr), + #[cfg(feature = "exp-destruct")] + Skip, + #[cfg(feature = "exp-destruct")] + Array { + start: Vec, + rest: Option, + end: Vec, + }, + #[cfg(feature = "exp-destruct")] + Object { + #[allow(clippy::type_complexity)] + fields: Vec<(IStr, Option, Option>>)>, + rest: Option, + }, +} +impl Destruct { + /// Name of destructure, used for function parameter names + pub fn name(&self) -> ParamName { + match self { + Self::Full(name) => ParamName::Named(name.clone()), + #[cfg(feature = "exp-destruct")] + _ => ParamName::Unnamed, + } + } + pub fn binds_len(&self) -> usize { + #[cfg(feature = "exp-destruct")] + fn cap_rest(rest: &Option) -> usize { + match rest { + Some(DestructRest::Keep(_)) => 1, + Some(DestructRest::Drop) => 0, + None => 0, + } + } + match self { + Self::Full(_) => 1, + #[cfg(feature = "exp-destruct")] + Self::Skip => 0, + #[cfg(feature = "exp-destruct")] + Self::Array { start, rest, end } => { + start.iter().map(Destruct::binds_len).sum::() + + end.iter().map(Destruct::binds_len).sum::() + + cap_rest(rest) + } + #[cfg(feature = "exp-destruct")] + Self::Object { fields, rest } => { + let mut out = 0; + for (_, into, _) in fields { + match into { + Some(v) => out += v.binds_len(), + // Field is destructured to default name + None => out += 1, + } + } + out + cap_rest(rest) + } + } + } +} + +#[derive(Debug, PartialEq, Acyclic)] +pub enum BindSpec { + Field { + into: Destruct, + value: Rc>, + }, + Function { + name: IStr, + params: ExprParams, + value: Rc>, + }, +} +impl BindSpec { + pub fn binds_len(&self) -> usize { + match self { + BindSpec::Field { into, .. } => into.binds_len(), + BindSpec::Function { .. } => 1, + } + } +} + +#[derive(Debug, PartialEq, Acyclic)] +pub struct IfSpecData(pub Spanned); + +#[derive(Debug, PartialEq, Acyclic)] +pub struct ForSpecData(pub Destruct, pub Spanned); + +#[derive(Debug, PartialEq, Acyclic)] +pub enum CompSpec { + IfSpec(IfSpecData), + ForSpec(ForSpecData), +} + +#[derive(Debug, PartialEq, Acyclic)] +pub struct ObjComp { + pub locals: Rc>, + pub field: Rc, + pub compspecs: Vec, +} + +#[derive(Debug, PartialEq, Acyclic)] +pub struct ObjMembers { + pub locals: Rc>, + pub asserts: Rc>, + pub fields: Vec, +} + +#[derive(Debug, PartialEq, Acyclic)] +pub enum ObjBody { + MemberList(ObjMembers), + ObjComp(ObjComp), +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Acyclic)] +pub enum LiteralType { + This, + Super, + Dollar, + Null, + True, + False, +} + +#[derive(Debug, PartialEq, Acyclic)] +pub struct SliceDesc { + pub start: Option>, + pub end: Option>, + pub step: Option>, +} + +#[derive(Debug, PartialEq, Acyclic)] +pub struct AssertExpr { + pub assert: AssertStmt, + pub rest: Spanned, +} + +#[derive(Debug, PartialEq, Acyclic)] +pub struct BinaryOp { + pub lhs: Spanned, + pub op: BinaryOpType, + pub rhs: Spanned, +} + +#[derive(Debug, PartialEq, Acyclic)] +pub enum ImportKind { + Normal, + Str, + Bin, +} + +#[derive(Debug, PartialEq, Acyclic)] +pub struct IfElse { + pub cond: IfSpecData, + pub cond_then: Spanned, + pub cond_else: Option>, +} + +#[derive(Debug, PartialEq, Acyclic)] +pub struct Slice { + pub value: Spanned, + pub slice: SliceDesc, +} + +/// Syntax base +#[derive(Debug, PartialEq, Acyclic)] +pub enum Expr { + Literal(LiteralType), + + /// String value: "hello" + Str(IStr), + /// Number: 1, 2.0, 2e+20 + Num(f64), + /// Variable name: test + Var(Spanned), + + /// Array of expressions: [1, 2, "Hello"] + Arr(Rc>>), + /// Array comprehension: + /// ```jsonnet + /// ingredients: [ + /// { kind: kind, qty: 4 / 3 } + /// for kind in [ + /// 'Honey Syrup', + /// 'Lemon Juice', + /// 'Farmers Gin', + /// ] + /// ], + /// ``` + ArrComp(Rc>, Vec), + + /// Object: {a: 2} + Obj(ObjBody), + /// Object extension: var1 {b: 2} + ObjExtend(Rc>, ObjBody), + + /// -2 + UnaryOp(UnaryOpType, Box>), + /// 2 - 2 + BinaryOp(Box), + /// assert 2 == 2 : "Math is broken" + AssertExpr(Rc), + /// local a = 2; { b: a } + LocalExpr(Vec, Box>), + + /// import* "hello" + Import(ImportKind, Box>), + /// error "I'm broken" + ErrorStmt(Box>), + /// a(b, c) + Apply(Box>, Spanned, bool), + /// a[b], a.b, a?.b + Index { + indexable: Box>, + parts: Vec, + }, + /// function(x) x + Function(ExprParams, Rc>), + /// if true == false then 1 else 2 + IfElse(Box), + Slice(Box), +} + +#[derive(Debug, PartialEq, Acyclic)] +pub struct IndexPart { + pub value: Spanned, + #[cfg(feature = "exp-null-coaelse")] + pub null_coaelse: bool, +} + +/// file, begin offset, end offset +#[derive(Clone, PartialEq, Eq, Acyclic)] +#[repr(C)] +pub struct Span(pub Source, pub u32, pub u32); +impl Span { + pub fn belongs_to(&self, other: &Span) -> bool { + other.0 == self.0 && other.1 <= self.1 && other.2 >= self.2 + } +} + +static_assertions::assert_eq_size!(Span, (usize, usize)); + +impl Debug for Span { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}:{:?}-{:?}", self.0, self.1, self.2) + } +} + +#[derive(Clone, PartialEq, Acyclic)] +pub struct Spanned(T, Span); +impl Deref for Spanned { + type Target = T; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl Spanned { + #[inline] + pub fn new(v: T, s: Span) -> Self { + Self(v, s) + } + #[inline] + pub fn span(&self) -> Span { + self.1.clone() + } +} + +impl Debug for Spanned { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let expr = &**self; + if f.alternate() { + write!(f, "{:#?}", expr)?; + } else { + write!(f, "{:?}", expr)?; + } + write!(f, " from {:?}", self.span())?; + Ok(()) + } +} --- /dev/null +++ b/crates/jrsonnet-ir/src/function.rs @@ -0,0 +1,132 @@ +use std::fmt; +use std::ops::Deref; +use std::rc::Rc; + +use jrsonnet_gcmodule::Acyclic; +use jrsonnet_interner::IStr; + +#[derive(Clone, Acyclic, Debug, PartialEq, Eq)] +pub enum ParamName { + Unnamed, + Named(IStr), +} +impl ParamName { + pub fn as_str(&self) -> Option<&str> { + match self { + ParamName::Unnamed => None, + ParamName::Named(istr) => Some(istr), + } + } + pub fn is_anonymous(&self) -> bool { + matches!(self, Self::Unnamed) + } + pub fn is_named(&self) -> bool { + matches!(self, Self::Named(_)) + } +} +impl PartialEq for ParamName { + fn eq(&self, other: &IStr) -> bool { + match self { + ParamName::Unnamed => false, + ParamName::Named(istr) => istr == other, + } + } +} + +impl fmt::Display for ParamName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self { + Self::Named(v) => write!(f, "{v}"), + Self::Unnamed => write!(f, ""), + } + } +} + +#[derive(Clone, Copy, Debug, Acyclic, PartialEq, Eq)] +pub enum ParamDefault { + None, + Exists, + Literal(&'static str), +} +impl ParamDefault { + pub const fn exists(is_exists: bool) -> Self { + if is_exists { + Self::Exists + } else { + Self::None + } + } +} +impl fmt::Display for ParamDefault { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ParamDefault::None => Ok(()), + ParamDefault::Exists => write!(f, " = "), + ParamDefault::Literal(lit) => write!(f, " = {lit}"), + } + } +} + +#[derive(Clone, Acyclic, Debug, PartialEq, Eq)] +pub struct ParamParse { + name: ParamName, + default: ParamDefault, +} +impl ParamParse { + pub fn new(name: ParamName, default: ParamDefault) -> Self { + Self { name, default } + } + /// Parameter name for named call parsing + pub fn name(&self) -> &ParamName { + &self.name + } + pub fn default(&self) -> ParamDefault { + self.default + } + pub fn has_default(&self) -> bool { + !matches!(self.default, ParamDefault::None) + } +} +impl fmt::Display for ParamParse { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}{}", self.name, self.default) + } +} + +#[derive(Debug, Clone, Acyclic, PartialEq, Eq)] +pub struct FunctionSignature(Rc<[ParamParse]>); +impl Deref for FunctionSignature { + type Target = [ParamParse]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +thread_local! { + static EMPTY_SIGNATURE: FunctionSignature = FunctionSignature::new([].into()); +} + +impl FunctionSignature { + pub fn new(v: Rc<[ParamParse]>) -> Self { + Self(v) + } + pub fn empty() -> Self { + EMPTY_SIGNATURE.with(|p| p.clone()) + } +} +impl fmt::Display for FunctionSignature { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.0.is_empty() { + return write!(f, "(/*no arguments*/)"); + } + write!(f, "(")?; + for (i, par) in self.0.iter().enumerate() { + if i != 0 { + write!(f, ", ")?; + } + write!(f, "{par}")?; + } + write!(f, ")") + } +} --- /dev/null +++ b/crates/jrsonnet-ir/src/lib.rs @@ -0,0 +1,15 @@ +#![allow(clippy::redundant_closure_call, clippy::derive_partial_eq_without_eq)] + +mod expr; +pub use expr::*; +pub use jrsonnet_interner::IStr; +pub mod function; +mod location; +mod source; +pub mod unescape; + +pub use location::CodeLocation; +pub use source::{ + Source, SourceDefaultIgnoreJpath, SourceDirectory, SourceFifo, SourceFile, SourcePath, + SourcePathT, SourceVirtual, +}; --- /dev/null +++ b/crates/jrsonnet-ir/src/location.rs @@ -0,0 +1,115 @@ +#[allow(clippy::module_name_repetitions)] +#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] +pub struct CodeLocation { + pub offset: usize, + + pub line: usize, + pub column: usize, + + pub line_start_offset: usize, + pub line_end_offset: usize, +} + +#[allow(clippy::module_name_repetitions)] +pub fn location_to_offset(mut file: &str, mut line: usize, column: usize) -> Option { + let mut offset = 0; + while line > 1 { + let pos = file.find('\n')?; + offset += pos + 1; + file = &file[pos + 1..]; + line -= 1; + } + offset += column - 1; + Some(offset) +} + +#[allow(clippy::module_name_repetitions)] +pub fn offset_to_location(file: &str, offsets: &[u32; S]) -> [CodeLocation; S] { + if offsets.is_empty() { + return [CodeLocation::default(); S]; + } + let mut line = 1; + let mut column = 1; + let max_offset = *offsets.iter().max().expect("offsets is not empty"); + + let mut offset_map = offsets + .iter() + .enumerate() + .map(|(pos, offset)| (*offset, pos)) + .collect::>(); + offset_map.sort_by_key(|v| v.0); + offset_map.reverse(); + + let mut out = [CodeLocation::default(); S]; + let mut with_no_known_line_ending = vec![]; + let mut this_line_offset = 0; + for (pos, ch) in file + .chars() + .enumerate() + .chain(std::iter::once((file.len(), ' '))) + { + column += 1; + match offset_map.last() { + Some(x) if x.0 == pos as u32 => { + let out_idx = x.1; + with_no_known_line_ending.push(out_idx); + out[out_idx].offset = pos; + out[out_idx].line = line; + out[out_idx].column = column; + out[out_idx].line_start_offset = this_line_offset; + offset_map.pop(); + } + _ => {} + } + if ch == '\n' { + line += 1; + column = 1; + + for idx in with_no_known_line_ending.drain(..) { + out[idx].line_end_offset = pos; + } + this_line_offset = pos + 1; + + if pos == max_offset as usize + 1 { + break; + } + } + } + let file_end = file.chars().count(); + for idx in with_no_known_line_ending { + out[idx].line_end_offset = file_end; + } + + out +} + +#[cfg(test)] +pub mod tests { + use super::{offset_to_location, CodeLocation}; + + #[test] + fn test() { + assert_eq!( + offset_to_location( + "hello world\n_______________________________________________________", + &[0, 14] + ), + [ + CodeLocation { + offset: 0, + line: 1, + column: 2, + line_start_offset: 0, + line_end_offset: 11, + }, + CodeLocation { + offset: 14, + line: 2, + column: 4, + line_start_offset: 12, + line_end_offset: 67 + } + ] + ) + } +} --- /dev/null +++ b/crates/jrsonnet-ir/src/source.rs @@ -0,0 +1,307 @@ +use std::{ + any::Any, + fmt::{self, Debug, Display}, + hash::{Hash, Hasher}, + path::{Path, PathBuf}, + rc::Rc, +}; + +use jrsonnet_gcmodule::Acyclic; +use jrsonnet_interner::{IBytes, IStr}; + +use crate::location::{location_to_offset, offset_to_location, CodeLocation}; + +macro_rules! any_ext_methods { + ($T:ident) => { + fn as_any(&self) -> &dyn Any; + fn dyn_hash(&self, hasher: &mut dyn Hasher); + fn dyn_eq(&self, other: &dyn $T) -> bool; + fn dyn_debug(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result; + }; +} +macro_rules! any_ext_impl { + ($T:ident) => { + fn as_any(&self) -> &dyn Any { + self + } + fn dyn_hash(&self, mut hasher: &mut dyn Hasher) { + self.hash(&mut hasher) + } + fn dyn_eq(&self, other: &dyn $T) -> bool { + let Some(other) = other.as_any().downcast_ref::() else { + return false; + }; + let this = ::as_any(self) + .downcast_ref::() + .expect("restricted by impl"); + this == other + } + fn dyn_debug(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + ::fmt(self, fmt) + } + }; +} +macro_rules! any_ext { + ($T:ident) => { + impl Hash for dyn $T { + fn hash(&self, state: &mut H) { + self.dyn_hash(state) + } + } + impl PartialEq for dyn $T { + fn eq(&self, other: &Self) -> bool { + self.dyn_eq(other) + } + } + impl Eq for dyn $T {} + }; +} +pub trait SourcePathT: Acyclic + Debug + Display { + /// This method should be checked by resolver before panicking with bad SourcePath input + /// if `true` - then resolver may threat this path as default, and default is usally a CWD + fn is_default(&self) -> bool; + fn path(&self) -> Option<&Path>; + any_ext_methods!(SourcePathT); +} +any_ext!(SourcePathT); + +/// Represents location of a file +/// +/// Standard CLI only operates using +/// - [`SourceFile`] - for any file +/// - [`SourceDirectory`] - for resolution from CWD +/// - [`SourceVirtual`] - for stdlib/ext-str +/// - [`SourceFifo`] - for /dev/fd/X (This path may appear with `jrsonnet <(command_that_produces_jsonnet)`) +/// +/// From all of those, only [`SourceVirtual`] may be constructed manually, any other path kind should be only obtained +/// from assigned `ImportResolver` +/// However, you should always check `is_default` method return, as it will return true for any paths, where default +/// search location is applicable +/// +/// Resolver may also return custom implementations of this trait, for example it may return http url in case of remotely loaded files +#[derive(Eq, Clone, Acyclic)] +pub struct SourcePath(Rc); +impl SourcePath { + pub fn new(inner: impl SourcePathT) -> Self { + Self(Rc::new(inner)) + } + pub fn downcast_ref(&self) -> Option<&T> { + self.0.as_any().downcast_ref() + } + pub fn is_default(&self) -> bool { + self.0.is_default() + } + pub fn path(&self) -> Option<&Path> { + self.0.path() + } +} +impl Hash for SourcePath { + fn hash(&self, state: &mut H) { + self.0.hash(state); + } +} +impl PartialEq for SourcePath { + #[allow(clippy::op_ref)] + fn eq(&self, other: &Self) -> bool { + &*self.0 == &*other.0 + } +} +impl Display for SourcePath { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} +impl Debug for SourcePath { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self.0) + } +} +impl Default for SourcePath { + fn default() -> Self { + Self(Rc::new(SourceDefault)) + } +} + +#[derive(Acyclic, Hash, PartialEq, Eq, Debug)] +struct SourceDefault; +impl Display for SourceDefault { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "") + } +} +impl SourcePathT for SourceDefault { + fn is_default(&self) -> bool { + true + } + fn path(&self) -> Option<&Path> { + None + } + any_ext_impl!(SourcePathT); +} + +#[derive(Acyclic, Hash, PartialEq, Eq, Debug)] +pub struct SourceDefaultIgnoreJpath; +impl Display for SourceDefaultIgnoreJpath { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "") + } +} +impl SourcePathT for SourceDefaultIgnoreJpath { + fn is_default(&self) -> bool { + true + } + fn path(&self) -> Option<&Path> { + None + } + any_ext_impl!(SourcePathT); +} + +/// Represents path to the file on the disk +/// Directories shouldn't be put here, as resolution for files differs from resolution for directories: +/// +/// When `file` is being resolved from `SourceFile(a/b/c)`, it should be resolved to `SourceFile(a/b/file)`, +/// however if it is being resolved from `SourceDirectory(a/b/c)`, then it should be resolved to `SourceDirectory(a/b/c/file)` +#[derive(Acyclic, Hash, PartialEq, Eq, Debug)] +pub struct SourceFile(PathBuf); +impl SourceFile { + pub fn new(path: PathBuf) -> Self { + Self(path) + } + pub fn path(&self) -> &Path { + &self.0 + } +} +impl Display for SourceFile { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0.display()) + } +} +impl SourcePathT for SourceFile { + fn is_default(&self) -> bool { + false + } + fn path(&self) -> Option<&Path> { + Some(&self.0) + } + any_ext_impl!(SourcePathT); +} + +/// Represents path to the directory on the disk +/// +/// See also [`SourceFile`] +#[derive(Acyclic, Hash, PartialEq, Eq, Debug)] +pub struct SourceDirectory(PathBuf); +impl SourceDirectory { + pub fn new(path: PathBuf) -> Self { + Self(path) + } + pub fn path(&self) -> &Path { + &self.0 + } +} +impl Display for SourceDirectory { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0.display()) + } +} +impl SourcePathT for SourceDirectory { + fn is_default(&self) -> bool { + false + } + fn path(&self) -> Option<&Path> { + Some(&self.0) + } + any_ext_impl!(SourcePathT); +} + +/// Represents virtual file, whose are located in memory, and shouldn't be cached +/// +/// It is used for --ext-code=.../--tla-code=.../standard library source code by default, +/// and user can construct arbitrary values by hand, without asking import resolver +#[derive(Acyclic, Hash, PartialEq, Eq, Clone)] +pub struct SourceVirtual(pub IStr); +impl Display for SourceVirtual { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "virtual:{}", self.0) + } +} +impl fmt::Debug for SourceVirtual { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "virtual:{}", self.0) + } +} +impl SourcePathT for SourceVirtual { + fn is_default(&self) -> bool { + true + } + fn path(&self) -> Option<&Path> { + None + } + any_ext_impl!(SourcePathT); +} + +/// Represents resolved FIFO file, those files may only be read once, and this type is only used for +/// unix, where user might want to do `jrsonnet <(command_that_produces_jsonnet_source)` +/// In most cases, user most probably want to use `jrsonnet -` instead of `jrsonnet /dev/stdin` +/// for better cross-platform support. +// PartialEq is limited to ptr equality +#[allow(clippy::derived_hash_with_manual_eq)] +#[derive(Acyclic, Debug, Hash)] +pub struct SourceFifo(pub String, pub IBytes); +impl PartialEq for SourceFifo { + fn eq(&self, other: &Self) -> bool { + std::ptr::eq(self, other) + } +} +impl fmt::Display for SourceFifo { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "fifo({:?})", self.0) + } +} +impl SourcePathT for SourceFifo { + fn is_default(&self) -> bool { + // In case of FD input, user won't expect relative paths to be resolved from /dev/fd/ + true + } + + fn path(&self) -> Option<&Path> { + None + } + + any_ext_impl!(SourcePathT); +} + +/// Either real file, or virtual +/// Hash of FileName always have same value as raw Path, to make it possible to use with raw_entry_mut +#[derive(Clone, PartialEq, Eq, Acyclic)] +pub struct Source(pub Rc<(SourcePath, IStr)>); + +impl Source { + pub fn new(path: SourcePath, code: IStr) -> Self { + Self(Rc::new((path, code))) + } + + pub fn new_virtual(name: IStr, code: IStr) -> Self { + Self::new(SourcePath::new(SourceVirtual(name)), code) + } + + pub fn code(&self) -> &str { + &self.0 .1 + } + + pub fn source_path(&self) -> &SourcePath { + &self.0 .0 + } + + pub fn map_source_locations(&self, locs: &[u32; S]) -> [CodeLocation; S] { + offset_to_location(&self.0 .1, locs) + } + pub fn map_from_source_location(&self, line: usize, column: usize) -> Option { + location_to_offset(&self.0 .1, line, column) + } +} +impl fmt::Debug for Source { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self.0 .0) + } +} --- /dev/null +++ b/crates/jrsonnet-ir/src/unescape.rs @@ -0,0 +1,55 @@ +use std::str::Chars; + +fn decode_unicode(chars: &mut Chars) -> Option { + IntoIterator::into_iter([chars.next()?, chars.next()?, chars.next()?, chars.next()?]) + .map(|c| c.to_digit(16).map(|f| f as u16)) + .try_fold(0u16, |acc, v| Some((acc << 4) | (v?))) +} + +pub fn unescape(s: &str) -> Option { + let mut chars = s.chars(); + let mut out = String::with_capacity(s.len()); + + while let Some(c) = chars.next() { + if c != '\\' { + out.push(c); + continue; + } + match chars.next()? { + c @ ('\\' | '"' | '\'') => out.push(c), + 'b' => out.push('\u{0008}'), + 'f' => out.push('\u{000c}'), + 'n' => out.push('\n'), + 'r' => out.push('\r'), + 't' => out.push('\t'), + 'u' => match decode_unicode(&mut chars)? { + // May only be second byte + 0xDC00..=0xDFFF => return None, + // Surrogate pair + n1 @ 0xD800..=0xDBFF => { + if chars.next() != Some('\\') { + return None; + } + if chars.next() != Some('u') { + return None; + } + let n2 = decode_unicode(&mut chars)?; + if !matches!(n2, 0xDC00..=0xDFFF) { + return None; + } + let n = (((n1 - 0xD800) as u32) << 10 | (n2 - 0xDC00) as u32) + 0x1_0000; + out.push(char::from_u32(n)?); + } + n => out.push(char::from_u32(n as u32)?), + }, + 'x' => { + let c = IntoIterator::into_iter([chars.next()?, chars.next()?]) + .map(|c| c.to_digit(16)) + .try_fold(0u32, |acc, v| Some((acc << 8) | (v?)))?; + out.push(char::from_u32(c)?) + } + _ => return None, + } + } + Some(out) +} --- a/crates/jrsonnet-parser/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "jrsonnet-parser" -description = "jsonnet language parser and AST" -authors.workspace = true -edition.workspace = true -license.workspace = true -repository.workspace = true -version.workspace = true - -[features] -default = [] -exp-destruct = [] -exp-null-coaelse = [] - -[dependencies] -jrsonnet-interner.workspace = true -jrsonnet-gcmodule.workspace = true - -static_assertions.workspace = true - -peg.workspace = true - -[dev-dependencies] -insta.workspace = true --- a/crates/jrsonnet-parser/README.adoc +++ /dev/null @@ -1,3 +0,0 @@ -= jrsonnet-parser - -Parser for jsonnet language --- a/crates/jrsonnet-parser/src/expr.rs +++ /dev/null @@ -1,493 +0,0 @@ -use std::{ - fmt::{self, Debug, Display}, - ops::Deref, - rc::Rc, -}; - -use jrsonnet_gcmodule::Acyclic; -use jrsonnet_interner::IStr; - -use crate::{ - function::{FunctionSignature, ParamDefault, ParamName, ParamParse}, - source::Source, -}; - -#[derive(Debug, PartialEq, Acyclic)] -pub enum FieldName { - /// {fixed: 2} - Fixed(IStr), - /// {["dyn"+"amic"]: 3} - Dyn(Spanned), -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Acyclic)] -#[repr(u8)] -pub enum Visibility { - /// : - Normal, - /// :: - Hidden, - /// ::: - Unhide, -} - -impl Visibility { - pub fn is_visible(&self) -> bool { - matches!(self, Self::Normal | Self::Unhide) - } -} - -#[derive(Debug, PartialEq, Acyclic)] -pub struct AssertStmt(pub Spanned, pub Option>); - -#[derive(Debug, PartialEq, Acyclic)] -pub struct FieldMember { - pub name: FieldName, - pub plus: bool, - pub params: Option, - pub visibility: Visibility, - pub value: Rc>, -} - -#[derive(Debug, PartialEq, Acyclic)] -pub(crate) enum Member { - Field(FieldMember), - BindStmt(BindSpec), - AssertStmt(AssertStmt), -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Acyclic)] -pub enum UnaryOpType { - Plus, - Minus, - BitNot, - Not, -} - -impl Display for UnaryOpType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use UnaryOpType::*; - write!( - f, - "{}", - match self { - Plus => "+", - Minus => "-", - BitNot => "~", - Not => "!", - } - ) - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Acyclic)] -pub enum BinaryOpType { - Mul, - Div, - - /// Implemented as intrinsic, put here for completeness - Mod, - - Add, - Sub, - - Lhs, - Rhs, - - Lt, - Gt, - Lte, - Gte, - - BitAnd, - BitOr, - BitXor, - - Eq, - Neq, - - And, - Or, - #[cfg(feature = "exp-null-coaelse")] - NullCoaelse, - - // Equialent to std.objectHasEx(a, b, true) - In, -} - -impl Display for BinaryOpType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use BinaryOpType::*; - write!( - f, - "{}", - match self { - Mul => "*", - Div => "/", - Mod => "%", - Add => "+", - Sub => "-", - Lhs => "<<", - Rhs => ">>", - Lt => "<", - Gt => ">", - Lte => "<=", - Gte => ">=", - BitAnd => "&", - BitOr => "|", - BitXor => "^", - Eq => "==", - Neq => "!=", - And => "&&", - Or => "||", - In => "in", - #[cfg(feature = "exp-null-coaelse")] - NullCoaelse => "??", - } - ) - } -} - -/// name, default value -#[derive(Debug, PartialEq, Acyclic)] -pub struct ExprParam { - pub destruct: Destruct, - pub default: Option>>, -} - -/// Defined function parameters -#[derive(Debug, Clone, PartialEq, Acyclic)] -pub struct ExprParams { - pub exprs: Rc>, - pub signature: FunctionSignature, - binds_len: usize, -} -impl ExprParams { - pub fn len(&self) -> usize { - self.exprs.len() - } - pub fn is_empty(&self) -> bool { - self.exprs.is_empty() - } - - pub fn binds_len(&self) -> usize { - self.binds_len - } - pub fn new(exprs: Vec) -> Self { - Self { - signature: FunctionSignature::new( - exprs - .iter() - .map(|p| { - ParamParse::new( - p.destruct.name(), - ParamDefault::exists(p.default.is_some()), - ) - }) - .collect(), - ), - binds_len: exprs.iter().map(|v| v.destruct.binds_len()).sum(), - exprs: Rc::new(exprs), - } - } -} - -#[derive(Debug, PartialEq, Acyclic)] -pub struct ArgsDesc { - pub unnamed: Vec>>, - pub named: Vec<(IStr, Rc>)>, -} -impl ArgsDesc { - pub fn new(unnamed: Vec>>, named: Vec<(IStr, Rc>)>) -> Self { - Self { unnamed, named } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Acyclic)] -pub enum DestructRest { - /// ...rest - Keep(IStr), - /// ... - Drop, -} - -#[derive(Debug, Clone, PartialEq, Acyclic)] -pub enum Destruct { - Full(IStr), - #[cfg(feature = "exp-destruct")] - Skip, - #[cfg(feature = "exp-destruct")] - Array { - start: Vec, - rest: Option, - end: Vec, - }, - #[cfg(feature = "exp-destruct")] - Object { - #[allow(clippy::type_complexity)] - fields: Vec<(IStr, Option, Option>>)>, - rest: Option, - }, -} -impl Destruct { - /// Name of destructure, used for function parameter names - pub fn name(&self) -> ParamName { - match self { - Self::Full(name) => ParamName::Named(name.clone()), - #[cfg(feature = "exp-destruct")] - _ => ParamName::Unnamed, - } - } - pub fn binds_len(&self) -> usize { - #[cfg(feature = "exp-destruct")] - fn cap_rest(rest: &Option) -> usize { - match rest { - Some(DestructRest::Keep(_)) => 1, - Some(DestructRest::Drop) => 0, - None => 0, - } - } - match self { - Self::Full(_) => 1, - #[cfg(feature = "exp-destruct")] - Self::Skip => 0, - #[cfg(feature = "exp-destruct")] - Self::Array { start, rest, end } => { - start.iter().map(Destruct::binds_len).sum::() - + end.iter().map(Destruct::binds_len).sum::() - + cap_rest(rest) - } - #[cfg(feature = "exp-destruct")] - Self::Object { fields, rest } => { - let mut out = 0; - for (_, into, _) in fields { - match into { - Some(v) => out += v.binds_len(), - // Field is destructured to default name - None => out += 1, - } - } - out + cap_rest(rest) - } - } - } -} - -#[derive(Debug, PartialEq, Acyclic)] -pub enum BindSpec { - Field { - into: Destruct, - value: Rc>, - }, - Function { - name: IStr, - params: ExprParams, - value: Rc>, - }, -} -impl BindSpec { - pub fn binds_len(&self) -> usize { - match self { - BindSpec::Field { into, .. } => into.binds_len(), - BindSpec::Function { .. } => 1, - } - } -} - -#[derive(Debug, PartialEq, Acyclic)] -pub struct IfSpecData(pub Spanned); - -#[derive(Debug, PartialEq, Acyclic)] -pub struct ForSpecData(pub Destruct, pub Spanned); - -#[derive(Debug, PartialEq, Acyclic)] -pub enum CompSpec { - IfSpec(IfSpecData), - ForSpec(ForSpecData), -} - -#[derive(Debug, PartialEq, Acyclic)] -pub struct ObjComp { - pub locals: Rc>, - pub field: Rc, - pub compspecs: Vec, -} - -#[derive(Debug, PartialEq, Acyclic)] -pub struct ObjMembers { - pub locals: Rc>, - pub asserts: Rc>, - pub fields: Vec, -} - -#[derive(Debug, PartialEq, Acyclic)] -pub enum ObjBody { - MemberList(ObjMembers), - ObjComp(ObjComp), -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy, Acyclic)] -pub enum LiteralType { - This, - Super, - Dollar, - Null, - True, - False, -} - -#[derive(Debug, PartialEq, Acyclic)] -pub struct SliceDesc { - pub start: Option>, - pub end: Option>, - pub step: Option>, -} - -#[derive(Debug, PartialEq, Acyclic)] -pub struct AssertExpr { - pub assert: AssertStmt, - pub rest: Spanned, -} - -#[derive(Debug, PartialEq, Acyclic)] -pub struct BinaryOp { - pub lhs: Spanned, - pub op: BinaryOpType, - pub rhs: Spanned, -} - -#[derive(Debug, PartialEq, Acyclic)] -pub enum ImportKind { - Normal, - Str, - Bin, -} - -#[derive(Debug, PartialEq, Acyclic)] -pub struct IfElse { - pub cond: IfSpecData, - pub cond_then: Spanned, - pub cond_else: Option>, -} - -#[derive(Debug, PartialEq, Acyclic)] -pub struct Slice { - pub value: Spanned, - pub slice: SliceDesc, -} - -/// Syntax base -#[derive(Debug, PartialEq, Acyclic)] -pub enum Expr { - Literal(LiteralType), - - /// String value: "hello" - Str(IStr), - /// Number: 1, 2.0, 2e+20 - Num(f64), - /// Variable name: test - Var(IStr), - - /// Array of expressions: [1, 2, "Hello"] - Arr(Rc>>), - /// Array comprehension: - /// ```jsonnet - /// ingredients: [ - /// { kind: kind, qty: 4 / 3 } - /// for kind in [ - /// 'Honey Syrup', - /// 'Lemon Juice', - /// 'Farmers Gin', - /// ] - /// ], - /// ``` - ArrComp(Rc>, Vec), - - /// Object: {a: 2} - Obj(ObjBody), - /// Object extension: var1 {b: 2} - ObjExtend(Rc>, ObjBody), - - /// -2 - UnaryOp(UnaryOpType, Box>), - /// 2 - 2 - BinaryOp(Box), - /// assert 2 == 2 : "Math is broken" - AssertExpr(Rc), - /// local a = 2; { b: a } - LocalExpr(Vec, Box>), - - /// import* "hello" - Import(ImportKind, Box>), - /// error "I'm broken" - ErrorStmt(Box>), - /// a(b, c) - Apply(Box>, ArgsDesc, bool), - /// a[b], a.b, a?.b - Index { - indexable: Box>, - parts: Vec, - }, - /// function(x) x - Function(ExprParams, Rc>), - /// if true == false then 1 else 2 - IfElse(Box), - Slice(Box), -} - -#[derive(Debug, PartialEq, Acyclic)] -pub struct IndexPart { - pub value: Spanned, - #[cfg(feature = "exp-null-coaelse")] - pub null_coaelse: bool, -} - -/// file, begin offset, end offset -#[derive(Clone, PartialEq, Eq, Acyclic)] -#[repr(C)] -pub struct Span(pub Source, pub u32, pub u32); -impl Span { - pub fn belongs_to(&self, other: &Span) -> bool { - other.0 == self.0 && other.1 <= self.1 && other.2 >= self.2 - } -} - -static_assertions::assert_eq_size!(Span, (usize, usize)); - -impl Debug for Span { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}:{:?}-{:?}", self.0, self.1, self.2) - } -} - -#[derive(Clone, PartialEq, Acyclic)] -pub struct Spanned(T, Span); -impl Deref for Spanned { - type Target = T; - fn deref(&self) -> &Self::Target { - &self.0 - } -} -impl Spanned { - #[inline] - pub fn new(v: T, s: Span) -> Self { - Self(v, s) - } - #[inline] - pub fn span(&self) -> Span { - self.1.clone() - } -} - -impl Debug for Spanned { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let expr = &**self; - if f.alternate() { - write!(f, "{:#?}", expr)?; - } else { - write!(f, "{:?}", expr)?; - } - write!(f, " from {:?}", self.span())?; - Ok(()) - } -} --- a/crates/jrsonnet-parser/src/function.rs +++ /dev/null @@ -1,132 +0,0 @@ -use std::fmt; -use std::ops::Deref; -use std::rc::Rc; - -use jrsonnet_gcmodule::Acyclic; -use jrsonnet_interner::IStr; - -#[derive(Clone, Acyclic, Debug, PartialEq, Eq)] -pub enum ParamName { - Unnamed, - Named(IStr), -} -impl ParamName { - pub fn as_str(&self) -> Option<&str> { - match self { - ParamName::Unnamed => None, - ParamName::Named(istr) => Some(istr), - } - } - pub fn is_anonymous(&self) -> bool { - matches!(self, Self::Unnamed) - } - pub fn is_named(&self) -> bool { - matches!(self, Self::Named(_)) - } -} -impl PartialEq for ParamName { - fn eq(&self, other: &IStr) -> bool { - match self { - ParamName::Unnamed => false, - ParamName::Named(istr) => istr == other, - } - } -} - -impl fmt::Display for ParamName { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match &self { - Self::Named(v) => write!(f, "{v}"), - Self::Unnamed => write!(f, ""), - } - } -} - -#[derive(Clone, Copy, Debug, Acyclic, PartialEq, Eq)] -pub enum ParamDefault { - None, - Exists, - Literal(&'static str), -} -impl ParamDefault { - pub const fn exists(is_exists: bool) -> Self { - if is_exists { - Self::Exists - } else { - Self::None - } - } -} -impl fmt::Display for ParamDefault { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - ParamDefault::None => Ok(()), - ParamDefault::Exists => write!(f, " = "), - ParamDefault::Literal(lit) => write!(f, " = {lit}"), - } - } -} - -#[derive(Clone, Acyclic, Debug, PartialEq, Eq)] -pub struct ParamParse { - name: ParamName, - default: ParamDefault, -} -impl ParamParse { - pub fn new(name: ParamName, default: ParamDefault) -> Self { - Self { name, default } - } - /// Parameter name for named call parsing - pub fn name(&self) -> &ParamName { - &self.name - } - pub fn default(&self) -> ParamDefault { - self.default - } - pub fn has_default(&self) -> bool { - !matches!(self.default, ParamDefault::None) - } -} -impl fmt::Display for ParamParse { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}{}", self.name, self.default) - } -} - -#[derive(Debug, Clone, Acyclic, PartialEq, Eq)] -pub struct FunctionSignature(Rc<[ParamParse]>); -impl Deref for FunctionSignature { - type Target = [ParamParse]; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -thread_local! { - static EMPTY_SIGNATURE: FunctionSignature = FunctionSignature::new([].into()); -} - -impl FunctionSignature { - pub fn new(v: Rc<[ParamParse]>) -> Self { - Self(v) - } - pub fn empty() -> Self { - EMPTY_SIGNATURE.with(|p| p.clone()) - } -} -impl fmt::Display for FunctionSignature { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if self.0.is_empty() { - return write!(f, "(/*no arguments*/)"); - } - write!(f, "(")?; - for (i, par) in self.0.iter().enumerate() { - if i != 0 { - write!(f, ", ")?; - } - write!(f, "{par}")?; - } - write!(f, ")") - } -} --- a/crates/jrsonnet-parser/src/lib.rs +++ /dev/null @@ -1,553 +0,0 @@ -#![allow(clippy::redundant_closure_call, clippy::derive_partial_eq_without_eq)] - -use std::rc::Rc; - -use peg::parser; -mod expr; -pub use expr::*; -pub use jrsonnet_interner::IStr; -pub use peg; -pub mod function; -mod location; -mod source; -mod unescape; - -pub use location::CodeLocation; -pub use source::{ - Source, SourceDefaultIgnoreJpath, SourceDirectory, SourceFifo, SourceFile, SourcePath, - SourcePathT, SourceVirtual, -}; - -pub struct ParserSettings { - pub source: Source, -} - -macro_rules! expr_bin { - ($a:ident $op:ident $b:ident) => { - Expr::BinaryOp(Box::new(BinaryOp { - lhs: $a, - op: $op, - rhs: $b, - })) - }; -} -macro_rules! expr_un { - ($op:ident $a:ident) => { - Expr::UnaryOp($op, Box::new($a)) - }; -} - -parser! { - grammar jsonnet_parser() for str { - use peg::ParseLiteral; - - rule eof() = quiet!{![_]} / expected!("") - rule eol() = "\n" / eof() - - /// Standard C-like comments - rule comment() - = "//" (!eol()[_])* eol() - / "/*" (!("*/")[_])* "*/" - / "#" (!eol()[_])* eol() - - rule single_whitespace() = quiet!{([' ' | '\r' | '\n' | '\t'] / comment())} / expected!("") - rule _() = quiet!{([' ' | '\r' | '\n' | '\t']+) / comment()}* / expected!("") - - /// For comma-delimited elements - rule comma() = quiet!{_ "," _} / expected!("") - rule alpha() -> char = c:$(['_' | 'a'..='z' | 'A'..='Z']) {c.chars().next().unwrap()} - rule digit() -> char = d:$(['0'..='9']) {d.chars().next().unwrap()} - rule end_of_ident() = !['0'..='9' | '_' | 'a'..='z' | 'A'..='Z'] - /// Sequence of digits - rule uint_str() -> &'input str = a:$(digit()+ ("_" digit()+)*) { a } - /// Number in scientific notation format - rule number() -> f64 = quiet!{a:$(uint_str() ("." uint_str())? (['e'|'E'] (s:['+'|'-'])? uint_str())?) {? a.replace("_","").parse().map_err(|_| "") }} / expected!("") - - /// Reserved word followed by any non-alphanumberic - rule reserved() = ("assert" / "else" / "error" / "false" / "for" / "function" / "if" / "import" / "importstr" / "importbin" / "in" / "local" / "null" / "tailstrict" / "then" / "self" / "super" / "true") end_of_ident() - rule id() -> IStr = v:$(quiet!{ !reserved() alpha() (alpha() / digit())*} / expected!("")) { v.into() } - - rule keyword(id: &'static str) -> () - = ##parse_string_literal(id) end_of_ident() - - pub rule param(s: &ParserSettings) -> expr::ExprParam = destruct:destruct(s) expr:(_ "=" _ expr:expr(s){expr})? { expr::ExprParam { destruct, default: expr.map(Rc::new) } } - pub rule params(s: &ParserSettings) -> expr::ExprParams - = params:param(s) ** comma() comma()? { expr::ExprParams::new(params) } - / { expr::ExprParams::new(Vec::new()) } - - pub rule arg(s: &ParserSettings) -> (Option, Rc>) - = name:(quiet! { (s:id() _ "=" !['='] _ {s})? } / expected!("")) expr:expr(s) {(name, Rc::new(expr))} - - pub rule args(s: &ParserSettings) -> expr::ArgsDesc - = args:arg(s)**comma() comma()? {? - let unnamed_count = args.iter().take_while(|(n, _)| n.is_none()).count(); - let mut unnamed = Vec::with_capacity(unnamed_count); - let mut named = Vec::with_capacity(args.len() - unnamed_count); - let mut named_started = false; - for (name, value) in args { - if let Some(name) = name { - named_started = true; - named.push((name, value)); - } else { - if named_started { - return Err("") - } - unnamed.push(value); - } - } - Ok(expr::ArgsDesc::new(unnamed, named)) - } - - pub rule destruct_rest() -> expr::DestructRest - = "..." into:(_ into:id() {into})? {if let Some(into) = into { - expr::DestructRest::Keep(into) - } else {expr::DestructRest::Drop}} - pub rule destruct_array(s: &ParserSettings) -> expr::Destruct - = "[" _ start:destruct(s)**comma() rest:( - comma() _ rest:destruct_rest()? end:( - comma() end:destruct(s)**comma() (_ comma())? {end} - / comma()? {Vec::new()} - ) {(rest, end)} - / comma()? {(None, Vec::new())} - ) _ "]" {? - #[cfg(feature = "exp-destruct")] return Ok(expr::Destruct::Array { - start, - rest: rest.0, - end: rest.1, - }); - #[cfg(not(feature = "exp-destruct"))] Err("!!!experimental destructuring was not enabled") - } - pub rule destruct_object(s: &ParserSettings) -> expr::Destruct - = "{" _ - fields:(name:id() into:(_ ":" _ into:destruct(s) {into})? default:(_ "=" _ v:expr(s) {v})? {(name, into, default.map(Rc::new))})**comma() - rest:( - comma() rest:destruct_rest()? {rest} - / comma()? {None} - ) - _ "}" {? - #[cfg(feature = "exp-destruct")] return Ok(expr::Destruct::Object { - fields, - rest, - }); - #[cfg(not(feature = "exp-destruct"))] Err("!!!experimental destructuring was not enabled") - } - pub rule destruct(s: &ParserSettings) -> expr::Destruct - = v:id() {expr::Destruct::Full(v)} - / "?" {? - #[cfg(feature = "exp-destruct")] return Ok(expr::Destruct::Skip); - #[cfg(not(feature = "exp-destruct"))] Err("!!!experimental destructuring was not enabled") - } - / arr:destruct_array(s) {arr} - / obj:destruct_object(s) {obj} - - pub rule bind(s: &ParserSettings) -> expr::BindSpec - = into:destruct(s) _ "=" _ value:expr(s) {expr::BindSpec::Field{into, value: Rc::new(value)}} - / name:id() _ "(" _ params:params(s) _ ")" _ "=" _ value:expr(s) {expr::BindSpec::Function{name, params, value: Rc::new(value)}} - - pub rule assertion(s: &ParserSettings) -> expr::AssertStmt - = keyword("assert") _ cond:expr(s) msg:(_ ":" _ e:expr(s) {e})? { expr::AssertStmt(cond, msg) } - - pub rule whole_line() -> &'input str - = str:$((!['\n'][_])* "\n") {str} - pub rule string_block() -> String - = "|||" chomped:"-"? (!['\n']single_whitespace())* "\n" - empty_lines:$(['\n']*) - prefix:[' ' | '\t']+ first_line:whole_line() - lines:("\n" {"\n"} / [' ' | '\t']*<{prefix.len()}> s:whole_line() {s})* - [' ' | '\t']*<, {prefix.len() - 1}> "|||" - { - let mut l = empty_lines.to_owned(); - l.push_str(first_line); - l.extend(lines); - if chomped.is_some() { - debug_assert!(l.ends_with('\n')); - l.truncate(l.len() - 1); - } - l - } - - rule hex_char() - = quiet! { ['0'..='9' | 'a'..='f' | 'A'..='F'] } / expected!("") - - rule string_char(c: rule<()>) - = (!['\\']!c()[_])+ - / "\\\\" - / "\\u" hex_char() hex_char() hex_char() hex_char() - / "\\x" hex_char() hex_char() - / ['\\'] (quiet! { ['b' | 'f' | 'n' | 'r' | 't' | '"' | '\''] } / expected!("")) - pub rule string() -> String - = ['"'] str:$(string_char(<"\"">)*) ['"'] {? unescape::unescape(str).ok_or("")} - / ['\''] str:$(string_char(<"\'">)*) ['\''] {? unescape::unescape(str).ok_or("")} - / quiet!{ "@'" str:$(("''" / (!['\''][_]))*) "'" {str.replace("''", "'")} - / "@\"" str:$(("\"\"" / (!['"'][_]))*) "\"" {str.replace("\"\"", "\"")} - / string_block() } / expected!("") - - pub rule field_name(s: &ParserSettings) -> expr::FieldName - = name:id() {expr::FieldName::Fixed(name)} - / name:string() {expr::FieldName::Fixed(name.into())} - / "[" _ expr:expr(s) _ "]" {expr::FieldName::Dyn(expr)} - pub rule visibility() -> expr::Visibility - = ":::" {expr::Visibility::Unhide} - / "::" {expr::Visibility::Hidden} - / ":" {expr::Visibility::Normal} - pub rule field(s: &ParserSettings) -> expr::FieldMember - = name:field_name(s) _ plus:"+"? _ visibility:visibility() _ value:expr(s) {expr::FieldMember{ - name, - plus: plus.is_some(), - params: None, - visibility, - value: Rc::new(value), - }} - / name:field_name(s) _ "(" _ params:params(s) _ ")" _ visibility:visibility() _ value:expr(s) {expr::FieldMember{ - name, - plus: false, - params: Some(params), - visibility, - value: Rc::new(value), - }} - pub rule obj_local(s: &ParserSettings) -> BindSpec - = keyword("local") _ bind:bind(s) {bind} - pub rule member(s: &ParserSettings) -> expr::Member - = bind:obj_local(s) {expr::Member::BindStmt(bind)} - / assertion:assertion(s) {expr::Member::AssertStmt(assertion)} - / field:field(s) {expr::Member::Field(field)} - pub rule objinside(s: &ParserSettings) -> expr::ObjBody - = pre_locals:(b: obj_local(s) comma() {b})* &"[" field:field(s) post_locals:(comma() b:obj_local(s) {b})* _ ("," _)? forspec:forspec(s) others:(_ rest:compspec(s) {rest})? { - let mut compspecs = vec![CompSpec::ForSpec(forspec)]; - compspecs.extend(others.unwrap_or_default()); - let mut locals = pre_locals; - locals.extend(post_locals); - expr::ObjBody::ObjComp(expr::ObjComp{ - locals: Rc::new(locals), - field: Rc::new(field), - compspecs, - }) - } - / members:(member(s) ** comma()) comma()? { - let mut locals = Vec::new(); - let mut asserts = Vec::new(); - let mut fields = Vec::new(); - for member in members { - match member { - Member::Field(field_member) => fields.push(field_member), - Member::BindStmt(bind_spec) => locals.push(bind_spec), - Member::AssertStmt(assert_stmt) => asserts.push(assert_stmt), - } - } - expr::ObjBody::MemberList(ObjMembers { - locals: Rc::new(locals), asserts: Rc::new(asserts), fields - }) - } - pub rule ifspec(s: &ParserSettings) -> IfSpecData - = keyword("if") _ expr:expr(s) {IfSpecData(expr)} - pub rule forspec(s: &ParserSettings) -> ForSpecData - = keyword("for") _ id:destruct(s) _ keyword("in") _ cond:expr(s) {ForSpecData(id, cond)} - pub rule compspec(s: &ParserSettings) -> Vec - = s:(i:ifspec(s) { expr::CompSpec::IfSpec(i) } / f:forspec(s) {expr::CompSpec::ForSpec(f)} ) ** _ {s} - pub rule local_expr(s: &ParserSettings) -> Expr - = keyword("local") _ binds:bind(s) ** comma() (_ ",")? _ ";" _ expr:expr(s) { Expr::LocalExpr(binds, Box::new(expr)) } - pub rule string_expr(s: &ParserSettings) -> Expr - = s:string() {Expr::Str(s.into())} - pub rule obj_expr(s: &ParserSettings) -> Expr - = "{" _ body:objinside(s) _ "}" {Expr::Obj(body)} - pub rule array_expr(s: &ParserSettings) -> Expr - = "[" _ elems:(expr(s) ** comma()) _ comma()? "]" {Expr::Arr(Rc::new(elems))} - pub rule array_comp_expr(s: &ParserSettings) -> Expr - = "[" _ expr:expr(s) _ comma()? _ forspec:forspec(s) _ others:(others: compspec(s) _ {others})? "]" { - let mut specs = vec![CompSpec::ForSpec(forspec)]; - specs.extend(others.unwrap_or_default()); - Expr::ArrComp(Rc::new(expr), specs) - } - pub rule number_expr(s: &ParserSettings) -> Expr - = n:number() {? if n.is_finite() { - Ok(expr::Expr::Num(n)) - } else { - Err("!!!numbers are finite") - }} - pub rule var_expr(s: &ParserSettings) -> Expr - = n:id() { expr::Expr::Var(n) } - pub rule id_loc(s: &ParserSettings) -> Spanned - = a:position!() n:id() b:position!() { Spanned::new(expr::Expr::Str(n), Span(s.source.clone(), a as u32,b as u32)) } - pub rule if_then_else_expr(s: &ParserSettings) -> Expr - = cond:ifspec(s) _ keyword("then") _ cond_then:expr(s) cond_else:(_ keyword("else") _ e:expr(s) {e})? {Expr::IfElse(Box::new(IfElse{ - cond, - cond_then, - cond_else, - }))} - - pub rule literal(s: &ParserSettings) -> 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)} - - rule import_kind() -> ImportKind - = keyword("importstr") { ImportKind::Str } - / keyword("importbin") { ImportKind::Bin } - / keyword("import") { ImportKind::Normal } - - pub rule expr_basic(s: &ParserSettings) -> Expr - = literal(s) - - / string_expr(s) / number_expr(s) - / array_expr(s) - / obj_expr(s) - / array_expr(s) - / array_comp_expr(s) - - / kind:import_kind() _ path:expr(s) {Expr::Import(kind, Box::new(path))} - - / var_expr(s) - / local_expr(s) - / if_then_else_expr(s) - - / keyword("function") _ "(" _ params:params(s) _ ")" _ expr:expr(s) {Expr::Function(params, Rc::new(expr))} - / assert:assertion(s) _ ";" _ rest:expr(s) { Expr::AssertExpr(Rc::new(AssertExpr{ - assert, rest - })) } - - / keyword("error") _ expr:expr(s) { Expr::ErrorStmt(Box::new(expr)) } - - rule slice_part(s: &ParserSettings) -> Option> - = _ e:(e:expr(s) _{e})? {e} - pub rule slice_desc(s: &ParserSettings) -> SliceDesc - = start:slice_part(s) ":" pair:(end:slice_part(s) step:(":" e:slice_part(s){e})? {(end, step.flatten())})? { - let (end, step) = if let Some((end, step)) = pair { - (end, step) - }else{ - (None, None) - }; - - SliceDesc { start, end, step } - } - - rule binop(x: rule<()>) -> () - = quiet!{ x() } / expected!("") - rule unaryop(x: rule<()>) -> () - = quiet!{ x() } / expected!("") - - rule ensure_null_coaelse() - = "" {? - #[cfg(not(feature = "exp-null-coaelse"))] return Err("!!!experimental null coaelscing was not enabled"); - #[cfg(feature = "exp-null-coaelse")] Ok(()) - } - use BinaryOpType::*; - use UnaryOpType::*; - rule expr(s: &ParserSettings) -> Spanned - = precedence! { - "(" _ e:expr(s) _ ")" {e} - start:position!() v:@ end:position!() { Spanned::new(v, Span(s.source.clone(), start as u32, end as u32)) } - -- - a:(@) _ binop(<"||">) _ b:@ {expr_bin!(a Or b)} - a:(@) _ binop(<"??">) _ ensure_null_coaelse() b:@ { - #[cfg(feature = "exp-null-coaelse")] return expr_bin!(a NullCoaelse b); - unreachable!("ensure_null_coaelse will fail if feature is not enabled") - } - -- - a:(@) _ binop(<"&&">) _ b:@ {expr_bin!(a And b)} - -- - a:(@) _ binop(<"|">) _ b:@ {expr_bin!(a BitOr b)} - -- - a:@ _ binop(<"^">) _ b:(@) {expr_bin!(a BitXor b)} - -- - a:(@) _ binop(<"&">) _ b:@ {expr_bin!(a BitAnd b)} - -- - a:(@) _ binop(<"==">) _ b:@ {expr_bin!(a Eq b)} - a:(@) _ binop(<"!=">) _ b:@ {expr_bin!(a Neq b)} - -- - a:(@) _ binop(<"<">) _ b:@ {expr_bin!(a Lt b)} - a:(@) _ binop(<">">) _ b:@ {expr_bin!(a Gt b)} - a:(@) _ binop(<"<=">) _ b:@ {expr_bin!(a Lte b)} - a:(@) _ binop(<">=">) _ b:@ {expr_bin!(a Gte b)} - a:(@) _ binop() _ b:@ {expr_bin!(a In b)} - -- - a:(@) _ binop(<"<<">) _ b:@ {expr_bin!(a Lhs b)} - a:(@) _ binop(<">>">) _ b:@ {expr_bin!(a Rhs b)} - -- - a:(@) _ binop(<"+">) _ b:@ {expr_bin!(a Add b)} - a:(@) _ binop(<"-">) _ b:@ {expr_bin!(a Sub b)} - -- - a:(@) _ binop(<"*">) _ b:@ {expr_bin!(a Mul b)} - a:(@) _ binop(<"/">) _ b:@ {expr_bin!(a Div b)} - a:(@) _ binop(<"%">) _ b:@ {expr_bin!(a Mod b)} - -- - unaryop(<"+">) _ b:@ {expr_un!(Plus b)} - unaryop(<"-">) _ b:@ {expr_un!(Minus b)} - unaryop(<"!">) _ b:@ {expr_un!(Not b)} - unaryop(<"~">) _ b:@ {expr_un!(BitNot b)} - -- - value:(@) _ "[" _ slice:slice_desc(s) _ "]" {Expr::Slice(Box::new(Slice{value, slice}))} - indexable:(@) _ parts:index_part(s)+ {Expr::Index{indexable: Box::new(indexable), parts}} - a:(@) _ "(" _ args:args(s) _ ")" ts:(_ keyword("tailstrict"))? {Expr::Apply(Box::new(a), args, ts.is_some())} - a:(@) _ "{" _ body:objinside(s) _ "}" {Expr::ObjExtend(Rc::new(a), body)} - -- - e:expr_basic(s) {e} - } - pub rule index_part(s: &ParserSettings) -> IndexPart - = n:("?" _ ensure_null_coaelse())? "." _ value:id_loc(s) {IndexPart { - value, - #[cfg(feature = "exp-null-coaelse")] - null_coaelse: n.is_some(), - }} - / n:("?" _ "." _ ensure_null_coaelse())? "[" _ value:expr(s) _ "]" {IndexPart { - value, - #[cfg(feature = "exp-null-coaelse")] - null_coaelse: n.is_some(), - }} - - pub rule jsonnet(s: &ParserSettings) -> Spanned = _ e:expr(s) _ {e} - } -} - -pub type ParseError = peg::error::ParseError; -pub fn parse(str: &str, settings: &ParserSettings) -> Result, ParseError> { - jsonnet_parser::jsonnet(str, settings) -} -/// Used for importstr values -pub fn string_to_expr(str: IStr, settings: &ParserSettings) -> Spanned { - let len = str.len(); - Spanned::new(Expr::Str(str), Span(settings.source.clone(), 0, len as u32)) -} - -#[cfg(test)] -pub mod tests { - use insta::assert_snapshot; - use jrsonnet_interner::IStr; - - use super::parse; - use crate::{source::Source, ParserSettings}; - - fn parsep(s: &str) -> String { - let v = parse( - s, - &ParserSettings { - source: Source::new_virtual("".into(), IStr::empty()), - }, - ) - .unwrap(); - format!("{v:#?}") - } - - macro_rules! parse { - ($s:expr) => { - assert_snapshot!(parsep($s)); - }; - } - - #[test] - fn multiline_string() { - parse!("|||\n Hello world!\n a\n|||"); - parse!("|||\n Hello world!\n a\n|||"); - parse!("|||\n\t\tHello world!\n\t\t\ta\n|||"); - parse!("|||\n Hello world!\n a\n |||"); - } - - #[test] - fn slice() { - parse!("a[1:]"); - parse!("a[1::]"); - parse!("a[:1:]"); - parse!("a[::1]"); - parse!("str[:len - 1]"); - } - - #[test] - fn string_escaping() { - parse!(r#""Hello, \"world\"!""#); - parse!(r#"'Hello \'world\'!'"#); - parse!(r#"'\\\\'"#); - } - - #[test] - fn string_unescaping() { - parse!(r#""Hello\nWorld""#); - } - - #[test] - fn string_verbantim() { - parse!(r#"@"Hello\n""World""""#); - } - - #[test] - fn imports() { - parse!("import \"hello\""); - parse!("importstr \"garnish.txt\""); - parse!("importbin \"garnish.bin\""); - } - - #[test] - fn empty_object() { - parse!("{}"); - } - - #[test] - fn basic_math() { - parse!("2+2*2"); - parse!("2 + 2 * 2 "); - parse!("2+(2+2*2)"); - parse!("2//comment\n+//comment\n3/*test*/*/*test*/4"); - } - - #[test] - fn suffix() { - parse!("std.test"); - parse!("std(2)"); - parse!("std.test(2)"); - parse!("a[b]"); - } - - #[test] - fn array_comp() { - parse!("[std.deepJoin(x) for x in arr]"); - } - - #[test] - fn reserved() { - parse!("null"); - parse!("nulla"); - } - - #[test] - fn multiple_args_buf() { - parse!("a(b, null_fields)"); - } - - #[test] - fn infix_precedence() { - parse!("!a && !b"); - parse!("!a / !b"); - } - - #[test] - fn double_negation() { - parse!("!!a"); - } - - #[test] - fn array_test_error() { - parse!("[a for a in b if c for e in f]"); - } - - #[test] - fn missing_newline_between_comment_and_eof() { - parse!( - "{a:1} - - //+213" - ); - } - - #[test] - fn default_param_before_nondefault() { - parse!("local x(foo = 'foo', bar) = null; null"); - } - - #[test] - fn add_location_info_to_all_sub_expressions() { - parse!("{} { local x = 1, x: x } + {}"); - } -} --- a/crates/jrsonnet-parser/src/location.rs +++ /dev/null @@ -1,115 +0,0 @@ -#[allow(clippy::module_name_repetitions)] -#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] -pub struct CodeLocation { - pub offset: usize, - - pub line: usize, - pub column: usize, - - pub line_start_offset: usize, - pub line_end_offset: usize, -} - -#[allow(clippy::module_name_repetitions)] -pub fn location_to_offset(mut file: &str, mut line: usize, column: usize) -> Option { - let mut offset = 0; - while line > 1 { - let pos = file.find('\n')?; - offset += pos + 1; - file = &file[pos + 1..]; - line -= 1; - } - offset += column - 1; - Some(offset) -} - -#[allow(clippy::module_name_repetitions)] -pub fn offset_to_location(file: &str, offsets: &[u32; S]) -> [CodeLocation; S] { - if offsets.is_empty() { - return [CodeLocation::default(); S]; - } - let mut line = 1; - let mut column = 1; - let max_offset = *offsets.iter().max().expect("offsets is not empty"); - - let mut offset_map = offsets - .iter() - .enumerate() - .map(|(pos, offset)| (*offset, pos)) - .collect::>(); - offset_map.sort_by_key(|v| v.0); - offset_map.reverse(); - - let mut out = [CodeLocation::default(); S]; - let mut with_no_known_line_ending = vec![]; - let mut this_line_offset = 0; - for (pos, ch) in file - .chars() - .enumerate() - .chain(std::iter::once((file.len(), ' '))) - { - column += 1; - match offset_map.last() { - Some(x) if x.0 == pos as u32 => { - let out_idx = x.1; - with_no_known_line_ending.push(out_idx); - out[out_idx].offset = pos; - out[out_idx].line = line; - out[out_idx].column = column; - out[out_idx].line_start_offset = this_line_offset; - offset_map.pop(); - } - _ => {} - } - if ch == '\n' { - line += 1; - column = 1; - - for idx in with_no_known_line_ending.drain(..) { - out[idx].line_end_offset = pos; - } - this_line_offset = pos + 1; - - if pos == max_offset as usize + 1 { - break; - } - } - } - let file_end = file.chars().count(); - for idx in with_no_known_line_ending { - out[idx].line_end_offset = file_end; - } - - out -} - -#[cfg(test)] -pub mod tests { - use super::{offset_to_location, CodeLocation}; - - #[test] - fn test() { - assert_eq!( - offset_to_location( - "hello world\n_______________________________________________________", - &[0, 14] - ), - [ - CodeLocation { - offset: 0, - line: 1, - column: 2, - line_start_offset: 0, - line_end_offset: 11, - }, - CodeLocation { - offset: 14, - line: 2, - column: 4, - line_start_offset: 12, - line_end_offset: 67 - } - ] - ) - } -} --- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__add_location_info_to_all_sub_expressions.snap +++ /dev/null @@ -1,57 +0,0 @@ ---- -source: crates/jrsonnet-parser/src/lib.rs -expression: "parsep(\"{} { local x = 1, x: x } + {}\")" ---- -BinaryOp( - BinaryOp { - lhs: ObjExtend( - Obj( - MemberList( - ObjMembers { - locals: [], - asserts: [], - fields: [], - }, - ), - ) from virtual::0-2, - MemberList( - ObjMembers { - locals: [ - Field { - into: Full( - "x", - ), - value: Num( - 1.0, - ) from virtual::15-16, - }, - ], - asserts: [], - fields: [ - FieldMember { - name: Fixed( - "x", - ), - plus: false, - params: None, - visibility: Normal, - value: Var( - "x", - ) from virtual::21-22, - }, - ], - }, - ), - ) from virtual::0-24, - op: Add, - rhs: Obj( - MemberList( - ObjMembers { - locals: [], - asserts: [], - fields: [], - }, - ), - ) from virtual::27-29, - }, -) from virtual::0-29 --- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__array_comp.snap +++ /dev/null @@ -1,41 +0,0 @@ ---- -source: crates/jrsonnet-parser/src/lib.rs -expression: "parsep(\"[std.deepJoin(x) for x in arr]\")" ---- -ArrComp( - Apply( - Index { - indexable: Var( - "std", - ) from virtual::1-4, - parts: [ - IndexPart { - value: Str( - "deepJoin", - ) from virtual::5-13, - }, - ], - } from virtual::1-13, - ArgsDesc { - unnamed: [ - Var( - "x", - ) from virtual::14-15, - ], - named: [], - }, - false, - ) from virtual::1-16, - [ - ForSpec( - ForSpecData( - Full( - "x", - ), - Var( - "arr", - ) from virtual::26-29, - ), - ), - ], -) from virtual::0-30 --- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__array_test_error.snap +++ /dev/null @@ -1,38 +0,0 @@ ---- -source: crates/jrsonnet-parser/src/lib.rs -expression: "parsep(\"[a for a in b if c for e in f]\")" ---- -ArrComp( - Var( - "a", - ) from virtual::1-2, - [ - ForSpec( - ForSpecData( - Full( - "a", - ), - Var( - "b", - ) from virtual::12-13, - ), - ), - IfSpec( - IfSpecData( - Var( - "c", - ) from virtual::17-18, - ), - ), - ForSpec( - ForSpecData( - Full( - "e", - ), - Var( - "f", - ) from virtual::28-29, - ), - ), - ], -) from virtual::0-30 --- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__basic_math-2.snap +++ /dev/null @@ -1,23 +0,0 @@ ---- -source: crates/jrsonnet-parser/src/lib.rs -expression: "parsep(\"2\t+ \t 2\t *\t2 \t\")" ---- -BinaryOp( - BinaryOp { - lhs: Num( - 2.0, - ) from virtual::0-1, - op: Add, - rhs: BinaryOp( - BinaryOp { - lhs: Num( - 2.0, - ) from virtual::7-8, - op: Mul, - rhs: Num( - 2.0, - ) from virtual::13-14, - }, - ) from virtual::7-14, - }, -) from virtual::0-14 --- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__basic_math-3.snap +++ /dev/null @@ -1,31 +0,0 @@ ---- -source: crates/jrsonnet-parser/src/lib.rs -expression: "parsep(\"2+(2+2*2)\")" ---- -BinaryOp( - BinaryOp { - lhs: Num( - 2.0, - ) from virtual::0-1, - op: Add, - rhs: BinaryOp( - BinaryOp { - lhs: Num( - 2.0, - ) from virtual::3-4, - op: Add, - rhs: BinaryOp( - BinaryOp { - lhs: Num( - 2.0, - ) from virtual::5-6, - op: Mul, - rhs: Num( - 2.0, - ) from virtual::7-8, - }, - ) from virtual::5-8, - }, - ) from virtual::3-8, - }, -) from virtual::0-9 --- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__basic_math-4.snap +++ /dev/null @@ -1,23 +0,0 @@ ---- -source: crates/jrsonnet-parser/src/lib.rs -expression: "parsep(\"2//comment\\n+//comment\\n3/*test*/*/*test*/4\")" ---- -BinaryOp( - BinaryOp { - lhs: Num( - 2.0, - ) from virtual::0-1, - op: Add, - rhs: BinaryOp( - BinaryOp { - lhs: Num( - 3.0, - ) from virtual::22-23, - op: Mul, - rhs: Num( - 4.0, - ) from virtual::40-41, - }, - ) from virtual::22-41, - }, -) from virtual::0-41 --- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__basic_math.snap +++ /dev/null @@ -1,23 +0,0 @@ ---- -source: crates/jrsonnet-parser/src/lib.rs -expression: "parsep(\"2+2*2\")" ---- -BinaryOp( - BinaryOp { - lhs: Num( - 2.0, - ) from virtual::0-1, - op: Add, - rhs: BinaryOp( - BinaryOp { - lhs: Num( - 2.0, - ) from virtual::2-3, - op: Mul, - rhs: Num( - 2.0, - ) from virtual::4-5, - }, - ) from virtual::2-5, - }, -) from virtual::0-5 --- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__default_param_before_nondefault.snap +++ /dev/null @@ -1,54 +0,0 @@ ---- -source: crates/jrsonnet-parser/src/lib.rs -expression: "parsep(\"local x(foo = 'foo', bar) = null; null\")" ---- -LocalExpr( - [ - Function { - name: "x", - params: ExprParams { - exprs: [ - ExprParam { - destruct: Full( - "foo", - ), - default: Some( - Str( - "foo", - ) from virtual::14-19, - ), - }, - ExprParam { - destruct: Full( - "bar", - ), - default: None, - }, - ], - signature: FunctionSignature( - [ - ParamParse { - name: Named( - "foo", - ), - default: Exists, - }, - ParamParse { - name: Named( - "bar", - ), - default: None, - }, - ], - ), - binds_len: 2, - }, - value: Literal( - Null, - ) from virtual::28-32, - }, - ], - Literal( - Null, - ) from virtual::34-38, -) from virtual::0-38 --- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__double_negation.snap +++ /dev/null @@ -1,13 +0,0 @@ ---- -source: crates/jrsonnet-parser/src/lib.rs -expression: "parsep(\"!!a\")" ---- -UnaryOp( - Not, - UnaryOp( - Not, - Var( - "a", - ) from virtual::2-3, - ) from virtual::1-3, -) from virtual::0-3 --- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__empty_object.snap +++ /dev/null @@ -1,13 +0,0 @@ ---- -source: crates/jrsonnet-parser/src/lib.rs -expression: "parsep(\"{}\")" ---- -Obj( - MemberList( - ObjMembers { - locals: [], - asserts: [], - fields: [], - }, - ), -) from virtual::0-2 --- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__imports-2.snap +++ /dev/null @@ -1,10 +0,0 @@ ---- -source: crates/jrsonnet-parser/src/lib.rs -expression: "parsep(\"importstr \\\"garnish.txt\\\"\")" ---- -Import( - Str, - Str( - "garnish.txt", - ) from virtual::10-23, -) from virtual::0-23 --- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__imports-3.snap +++ /dev/null @@ -1,10 +0,0 @@ ---- -source: crates/jrsonnet-parser/src/lib.rs -expression: "parsep(\"importbin \\\"garnish.bin\\\"\")" ---- -Import( - Bin, - Str( - "garnish.bin", - ) from virtual::10-23, -) from virtual::0-23 --- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__imports.snap +++ /dev/null @@ -1,10 +0,0 @@ ---- -source: crates/jrsonnet-parser/src/lib.rs -expression: "parsep(\"import \\\"hello\\\"\")" ---- -Import( - Normal, - Str( - "hello", - ) from virtual::7-14, -) from virtual::0-14 --- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__infix_precedence-2.snap +++ /dev/null @@ -1,21 +0,0 @@ ---- -source: crates/jrsonnet-parser/src/lib.rs -expression: "parsep(\"!a / !b\")" ---- -BinaryOp( - BinaryOp { - lhs: UnaryOp( - Not, - Var( - "a", - ) from virtual::1-2, - ) from virtual::0-2, - op: Div, - rhs: UnaryOp( - Not, - Var( - "b", - ) from virtual::6-7, - ) from virtual::5-7, - }, -) from virtual::0-7 --- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__infix_precedence.snap +++ /dev/null @@ -1,21 +0,0 @@ ---- -source: crates/jrsonnet-parser/src/lib.rs -expression: "parsep(\"!a && !b\")" ---- -BinaryOp( - BinaryOp { - lhs: UnaryOp( - Not, - Var( - "a", - ) from virtual::1-2, - ) from virtual::0-2, - op: And, - rhs: UnaryOp( - Not, - Var( - "b", - ) from virtual::7-8, - ) from virtual::6-8, - }, -) from virtual::0-8 --- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__missing_newline_between_comment_and_eof.snap +++ /dev/null @@ -1,25 +0,0 @@ ---- -source: crates/jrsonnet-parser/src/lib.rs -expression: "parsep(\"{a:1}\n\n\t\t\t//+213\")" ---- -Obj( - MemberList( - ObjMembers { - locals: [], - asserts: [], - fields: [ - FieldMember { - name: Fixed( - "a", - ), - plus: false, - params: None, - visibility: Normal, - value: Num( - 1.0, - ) from virtual::3-4, - }, - ], - }, - ), -) from virtual::0-5 --- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__multiline_string-2.snap +++ /dev/null @@ -1,7 +0,0 @@ ---- -source: crates/jrsonnet-parser/src/lib.rs -expression: "parsep(\"|||\\n Hello world!\\n a\\n|||\")" ---- -Str( - "Hello world!\n a\n", -) from virtual::0-27 --- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__multiline_string-3.snap +++ /dev/null @@ -1,7 +0,0 @@ ---- -source: crates/jrsonnet-parser/src/lib.rs -expression: "parsep(\"|||\\n\\t\\tHello world!\\n\\t\\t\\ta\\n|||\")" ---- -Str( - "Hello world!\n\ta\n", -) from virtual::0-27 --- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__multiline_string-4.snap +++ /dev/null @@ -1,7 +0,0 @@ ---- -source: crates/jrsonnet-parser/src/lib.rs -expression: "parsep(\"|||\\n Hello world!\\n a\\n |||\")" ---- -Str( - "Hello world!\n a\n", -) from virtual::0-30 --- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__multiline_string.snap +++ /dev/null @@ -1,7 +0,0 @@ ---- -source: crates/jrsonnet-parser/src/lib.rs -expression: "parsep(\"|||\\n Hello world!\\n a\\n|||\")" ---- -Str( - "Hello world!\n a\n", -) from virtual::0-31 --- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__multiple_args_buf.snap +++ /dev/null @@ -1,21 +0,0 @@ ---- -source: crates/jrsonnet-parser/src/lib.rs -expression: "parsep(\"a(b, null_fields)\")" ---- -Apply( - Var( - "a", - ) from virtual::0-1, - ArgsDesc { - unnamed: [ - Var( - "b", - ) from virtual::2-3, - Var( - "null_fields", - ) from virtual::5-16, - ], - named: [], - }, - false, -) from virtual::0-17 --- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__reserved-2.snap +++ /dev/null @@ -1,7 +0,0 @@ ---- -source: crates/jrsonnet-parser/src/lib.rs -expression: "parsep(\"nulla\")" ---- -Var( - "nulla", -) from virtual::0-5 --- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__reserved.snap +++ /dev/null @@ -1,7 +0,0 @@ ---- -source: crates/jrsonnet-parser/src/lib.rs -expression: "parsep(\"null\")" ---- -Literal( - Null, -) from virtual::0-4 --- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__slice-2.snap +++ /dev/null @@ -1,20 +0,0 @@ ---- -source: crates/jrsonnet-parser/src/lib.rs -expression: "parsep(\"a[1::]\")" ---- -Slice( - Slice { - value: Var( - "a", - ) from virtual::0-1, - slice: SliceDesc { - start: Some( - Num( - 1.0, - ) from virtual::2-3, - ), - end: None, - step: None, - }, - }, -) from virtual::0-6 --- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__slice-3.snap +++ /dev/null @@ -1,20 +0,0 @@ ---- -source: crates/jrsonnet-parser/src/lib.rs -expression: "parsep(\"a[:1:]\")" ---- -Slice( - Slice { - value: Var( - "a", - ) from virtual::0-1, - slice: SliceDesc { - start: None, - end: Some( - Num( - 1.0, - ) from virtual::3-4, - ), - step: None, - }, - }, -) from virtual::0-6 --- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__slice-4.snap +++ /dev/null @@ -1,20 +0,0 @@ ---- -source: crates/jrsonnet-parser/src/lib.rs -expression: "parsep(\"a[::1]\")" ---- -Slice( - Slice { - value: Var( - "a", - ) from virtual::0-1, - slice: SliceDesc { - start: None, - end: None, - step: Some( - Num( - 1.0, - ) from virtual::4-5, - ), - }, - }, -) from virtual::0-6 --- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__slice-5.snap +++ /dev/null @@ -1,28 +0,0 @@ ---- -source: crates/jrsonnet-parser/src/lib.rs -expression: "parsep(\"str[:len - 1]\")" ---- -Slice( - Slice { - value: Var( - "str", - ) from virtual::0-3, - slice: SliceDesc { - start: None, - end: Some( - BinaryOp( - BinaryOp { - lhs: Var( - "len", - ) from virtual::5-8, - op: Sub, - rhs: Num( - 1.0, - ) from virtual::11-12, - }, - ) from virtual::5-12, - ), - step: None, - }, - }, -) from virtual::0-13 --- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__slice.snap +++ /dev/null @@ -1,20 +0,0 @@ ---- -source: crates/jrsonnet-parser/src/lib.rs -expression: "parsep(\"a[1:]\")" ---- -Slice( - Slice { - value: Var( - "a", - ) from virtual::0-1, - slice: SliceDesc { - start: Some( - Num( - 1.0, - ) from virtual::2-3, - ), - end: None, - step: None, - }, - }, -) from virtual::0-5 --- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__string_escaping-2.snap +++ /dev/null @@ -1,7 +0,0 @@ ---- -source: crates/jrsonnet-parser/src/lib.rs -expression: "parsep(r#\"'Hello \\'world\\'!'\"#)" ---- -Str( - "Hello 'world'!", -) from virtual::0-18 --- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__string_escaping-3.snap +++ /dev/null @@ -1,7 +0,0 @@ ---- -source: crates/jrsonnet-parser/src/lib.rs -expression: "parsep(r#\"'\\\\\\\\'\"#)" ---- -Str( - "\\\\", -) from virtual::0-6 --- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__string_escaping.snap +++ /dev/null @@ -1,7 +0,0 @@ ---- -source: crates/jrsonnet-parser/src/lib.rs -expression: "parsep(r#\"\"Hello, \\\"world\\\"!\"\"#)" ---- -Str( - "Hello, \"world\"!", -) from virtual::0-19 --- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__string_unescaping.snap +++ /dev/null @@ -1,7 +0,0 @@ ---- -source: crates/jrsonnet-parser/src/lib.rs -expression: "parsep(r#\"\"Hello\\nWorld\"\"#)" ---- -Str( - "Hello\nWorld", -) from virtual::0-14 --- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__string_verbantim.snap +++ /dev/null @@ -1,7 +0,0 @@ ---- -source: crates/jrsonnet-parser/src/lib.rs -expression: "parsep(r#\"@\"Hello\\n\"\"World\"\"\"\"#)" ---- -Str( - "Hello\\n\"World\"", -) from virtual::0-19 --- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__suffix-2.snap +++ /dev/null @@ -1,18 +0,0 @@ ---- -source: crates/jrsonnet-parser/src/lib.rs -expression: "parsep(\"std(2)\")" ---- -Apply( - Var( - "std", - ) from virtual::0-3, - ArgsDesc { - unnamed: [ - Num( - 2.0, - ) from virtual::4-5, - ], - named: [], - }, - false, -) from virtual::0-6 --- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__suffix-3.snap +++ /dev/null @@ -1,27 +0,0 @@ ---- -source: crates/jrsonnet-parser/src/lib.rs -expression: "parsep(\"std.test(2)\")" ---- -Apply( - Index { - indexable: Var( - "std", - ) from virtual::0-3, - parts: [ - IndexPart { - value: Str( - "test", - ) from virtual::4-8, - }, - ], - } from virtual::0-8, - ArgsDesc { - unnamed: [ - Num( - 2.0, - ) from virtual::9-10, - ], - named: [], - }, - false, -) from virtual::0-11 --- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__suffix-4.snap +++ /dev/null @@ -1,16 +0,0 @@ ---- -source: crates/jrsonnet-parser/src/lib.rs -expression: "parsep(\"a[b]\")" ---- -Index { - indexable: Var( - "a", - ) from virtual::0-1, - parts: [ - IndexPart { - value: Var( - "b", - ) from virtual::2-3, - }, - ], -} from virtual::0-4 --- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__suffix.snap +++ /dev/null @@ -1,16 +0,0 @@ ---- -source: crates/jrsonnet-parser/src/lib.rs -expression: "parsep(\"std.test\")" ---- -Index { - indexable: Var( - "std", - ) from virtual::0-3, - parts: [ - IndexPart { - value: Str( - "test", - ) from virtual::4-8, - }, - ], -} from virtual::0-8 --- a/crates/jrsonnet-parser/src/source.rs +++ /dev/null @@ -1,307 +0,0 @@ -use std::{ - any::Any, - fmt::{self, Debug, Display}, - hash::{Hash, Hasher}, - path::{Path, PathBuf}, - rc::Rc, -}; - -use jrsonnet_gcmodule::Acyclic; -use jrsonnet_interner::{IBytes, IStr}; - -use crate::location::{location_to_offset, offset_to_location, CodeLocation}; - -macro_rules! any_ext_methods { - ($T:ident) => { - fn as_any(&self) -> &dyn Any; - fn dyn_hash(&self, hasher: &mut dyn Hasher); - fn dyn_eq(&self, other: &dyn $T) -> bool; - fn dyn_debug(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result; - }; -} -macro_rules! any_ext_impl { - ($T:ident) => { - fn as_any(&self) -> &dyn Any { - self - } - fn dyn_hash(&self, mut hasher: &mut dyn Hasher) { - self.hash(&mut hasher) - } - fn dyn_eq(&self, other: &dyn $T) -> bool { - let Some(other) = other.as_any().downcast_ref::() else { - return false; - }; - let this = ::as_any(self) - .downcast_ref::() - .expect("restricted by impl"); - this == other - } - fn dyn_debug(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - ::fmt(self, fmt) - } - }; -} -macro_rules! any_ext { - ($T:ident) => { - impl Hash for dyn $T { - fn hash(&self, state: &mut H) { - self.dyn_hash(state) - } - } - impl PartialEq for dyn $T { - fn eq(&self, other: &Self) -> bool { - self.dyn_eq(other) - } - } - impl Eq for dyn $T {} - }; -} -pub trait SourcePathT: Acyclic + Debug + Display { - /// This method should be checked by resolver before panicking with bad SourcePath input - /// if `true` - then resolver may threat this path as default, and default is usally a CWD - fn is_default(&self) -> bool; - fn path(&self) -> Option<&Path>; - any_ext_methods!(SourcePathT); -} -any_ext!(SourcePathT); - -/// Represents location of a file -/// -/// Standard CLI only operates using -/// - [`SourceFile`] - for any file -/// - [`SourceDirectory`] - for resolution from CWD -/// - [`SourceVirtual`] - for stdlib/ext-str -/// - [`SourceFifo`] - for /dev/fd/X (This path may appear with `jrsonnet <(command_that_produces_jsonnet)`) -/// -/// From all of those, only [`SourceVirtual`] may be constructed manually, any other path kind should be only obtained -/// from assigned `ImportResolver` -/// However, you should always check `is_default` method return, as it will return true for any paths, where default -/// search location is applicable -/// -/// Resolver may also return custom implementations of this trait, for example it may return http url in case of remotely loaded files -#[derive(Eq, Clone, Acyclic)] -pub struct SourcePath(Rc); -impl SourcePath { - pub fn new(inner: impl SourcePathT) -> Self { - Self(Rc::new(inner)) - } - pub fn downcast_ref(&self) -> Option<&T> { - self.0.as_any().downcast_ref() - } - pub fn is_default(&self) -> bool { - self.0.is_default() - } - pub fn path(&self) -> Option<&Path> { - self.0.path() - } -} -impl Hash for SourcePath { - fn hash(&self, state: &mut H) { - self.0.hash(state); - } -} -impl PartialEq for SourcePath { - #[allow(clippy::op_ref)] - fn eq(&self, other: &Self) -> bool { - &*self.0 == &*other.0 - } -} -impl Display for SourcePath { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} -impl Debug for SourcePath { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self.0) - } -} -impl Default for SourcePath { - fn default() -> Self { - Self(Rc::new(SourceDefault)) - } -} - -#[derive(Acyclic, Hash, PartialEq, Eq, Debug)] -struct SourceDefault; -impl Display for SourceDefault { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "") - } -} -impl SourcePathT for SourceDefault { - fn is_default(&self) -> bool { - true - } - fn path(&self) -> Option<&Path> { - None - } - any_ext_impl!(SourcePathT); -} - -#[derive(Acyclic, Hash, PartialEq, Eq, Debug)] -pub struct SourceDefaultIgnoreJpath; -impl Display for SourceDefaultIgnoreJpath { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "") - } -} -impl SourcePathT for SourceDefaultIgnoreJpath { - fn is_default(&self) -> bool { - true - } - fn path(&self) -> Option<&Path> { - None - } - any_ext_impl!(SourcePathT); -} - -/// Represents path to the file on the disk -/// Directories shouldn't be put here, as resolution for files differs from resolution for directories: -/// -/// When `file` is being resolved from `SourceFile(a/b/c)`, it should be resolved to `SourceFile(a/b/file)`, -/// however if it is being resolved from `SourceDirectory(a/b/c)`, then it should be resolved to `SourceDirectory(a/b/c/file)` -#[derive(Acyclic, Hash, PartialEq, Eq, Debug)] -pub struct SourceFile(PathBuf); -impl SourceFile { - pub fn new(path: PathBuf) -> Self { - Self(path) - } - pub fn path(&self) -> &Path { - &self.0 - } -} -impl Display for SourceFile { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0.display()) - } -} -impl SourcePathT for SourceFile { - fn is_default(&self) -> bool { - false - } - fn path(&self) -> Option<&Path> { - Some(&self.0) - } - any_ext_impl!(SourcePathT); -} - -/// Represents path to the directory on the disk -/// -/// See also [`SourceFile`] -#[derive(Acyclic, Hash, PartialEq, Eq, Debug)] -pub struct SourceDirectory(PathBuf); -impl SourceDirectory { - pub fn new(path: PathBuf) -> Self { - Self(path) - } - pub fn path(&self) -> &Path { - &self.0 - } -} -impl Display for SourceDirectory { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0.display()) - } -} -impl SourcePathT for SourceDirectory { - fn is_default(&self) -> bool { - false - } - fn path(&self) -> Option<&Path> { - Some(&self.0) - } - any_ext_impl!(SourcePathT); -} - -/// Represents virtual file, whose are located in memory, and shouldn't be cached -/// -/// It is used for --ext-code=.../--tla-code=.../standard library source code by default, -/// and user can construct arbitrary values by hand, without asking import resolver -#[derive(Acyclic, Hash, PartialEq, Eq, Clone)] -pub struct SourceVirtual(pub IStr); -impl Display for SourceVirtual { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "virtual:{}", self.0) - } -} -impl fmt::Debug for SourceVirtual { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "virtual:{}", self.0) - } -} -impl SourcePathT for SourceVirtual { - fn is_default(&self) -> bool { - true - } - fn path(&self) -> Option<&Path> { - None - } - any_ext_impl!(SourcePathT); -} - -/// Represents resolved FIFO file, those files may only be read once, and this type is only used for -/// unix, where user might want to do `jrsonnet <(command_that_produces_jsonnet_source)` -/// In most cases, user most probably want to use `jrsonnet -` instead of `jrsonnet /dev/stdin` -/// for better cross-platform support. -// PartialEq is limited to ptr equality -#[allow(clippy::derived_hash_with_manual_eq)] -#[derive(Acyclic, Debug, Hash)] -pub struct SourceFifo(pub String, pub IBytes); -impl PartialEq for SourceFifo { - fn eq(&self, other: &Self) -> bool { - std::ptr::eq(self, other) - } -} -impl fmt::Display for SourceFifo { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "fifo({:?})", self.0) - } -} -impl SourcePathT for SourceFifo { - fn is_default(&self) -> bool { - // In case of FD input, user won't expect relative paths to be resolved from /dev/fd/ - true - } - - fn path(&self) -> Option<&Path> { - None - } - - any_ext_impl!(SourcePathT); -} - -/// Either real file, or virtual -/// Hash of FileName always have same value as raw Path, to make it possible to use with raw_entry_mut -#[derive(Clone, PartialEq, Eq, Acyclic)] -pub struct Source(pub Rc<(SourcePath, IStr)>); - -impl Source { - pub fn new(path: SourcePath, code: IStr) -> Self { - Self(Rc::new((path, code))) - } - - pub fn new_virtual(name: IStr, code: IStr) -> Self { - Self::new(SourcePath::new(SourceVirtual(name)), code) - } - - pub fn code(&self) -> &str { - &self.0 .1 - } - - pub fn source_path(&self) -> &SourcePath { - &self.0 .0 - } - - pub fn map_source_locations(&self, locs: &[u32; S]) -> [CodeLocation; S] { - offset_to_location(&self.0 .1, locs) - } - pub fn map_from_source_location(&self, line: usize, column: usize) -> Option { - location_to_offset(&self.0 .1, line, column) - } -} -impl fmt::Debug for Source { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self.0 .0) - } -} --- a/crates/jrsonnet-parser/src/unescape.rs +++ /dev/null @@ -1,55 +0,0 @@ -use std::str::Chars; - -fn decode_unicode(chars: &mut Chars) -> Option { - IntoIterator::into_iter([chars.next()?, chars.next()?, chars.next()?, chars.next()?]) - .map(|c| c.to_digit(16).map(|f| f as u16)) - .try_fold(0u16, |acc, v| Some((acc << 4) | (v?))) -} - -pub fn unescape(s: &str) -> Option { - let mut chars = s.chars(); - let mut out = String::with_capacity(s.len()); - - while let Some(c) = chars.next() { - if c != '\\' { - out.push(c); - continue; - } - match chars.next()? { - c @ ('\\' | '"' | '\'') => out.push(c), - 'b' => out.push('\u{0008}'), - 'f' => out.push('\u{000c}'), - 'n' => out.push('\n'), - 'r' => out.push('\r'), - 't' => out.push('\t'), - 'u' => match decode_unicode(&mut chars)? { - // May only be second byte - 0xDC00..=0xDFFF => return None, - // Surrogate pair - n1 @ 0xD800..=0xDBFF => { - if chars.next() != Some('\\') { - return None; - } - if chars.next() != Some('u') { - return None; - } - let n2 = decode_unicode(&mut chars)?; - if !matches!(n2, 0xDC00..=0xDFFF) { - return None; - } - let n = (((n1 - 0xD800) as u32) << 10 | (n2 - 0xDC00) as u32) + 0x1_0000; - out.push(char::from_u32(n)?); - } - n => out.push(char::from_u32(n as u32)?), - }, - 'x' => { - let c = IntoIterator::into_iter([chars.next()?, chars.next()?]) - .map(|c| c.to_digit(16)) - .try_fold(0u32, |acc, v| Some((acc << 8) | (v?)))?; - out.push(char::from_u32(c)?) - } - _ => return None, - } - } - Some(out) -} --- /dev/null +++ b/crates/jrsonnet-peg-parser/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "jrsonnet-peg-parser" +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +version.workspace = true + +[dependencies] +jrsonnet-ir.workspace = true +peg.workspace = true + +[lints] +workspace = true + +[dev-dependencies] +insta.workspace = true + +[features] +default = [] +exp-destruct = ["jrsonnet-ir/exp-destruct"] +exp-null-coaelse = ["jrsonnet-ir/exp-null-coaelse"] --- /dev/null +++ b/crates/jrsonnet-peg-parser/src/lib.rs @@ -0,0 +1,556 @@ +use jrsonnet_ir::{ + BinaryOp, Expr, ExprParams, IStr, IndexPart, Member, Slice, SliceDesc, Source, Span, Spanned, + ExprParam, ArgsDesc, AssertExpr, ImportKind, LiteralType, IfElse, CompSpec, ForSpecData, IfSpecData, ObjMembers, ObjBody, + ObjComp, FieldMember, Visibility, FieldName, unescape, AssertStmt, BindSpec, Destruct, DestructRest, +}; +use peg::parser; +use std::rc::Rc; + +pub struct ParserSettings { + pub source: Source, +} + +macro_rules! expr_bin { + ($a:ident $op:ident $b:ident) => { + Expr::BinaryOp(Box::new(BinaryOp { + lhs: $a, + op: $op, + rhs: $b, + })) + }; +} +macro_rules! expr_un { + ($op:ident $a:ident) => { + Expr::UnaryOp($op, Box::new($a)) + }; +} + +parser! { + grammar jsonnet_parser() for str { + use peg::ParseLiteral; + + rule eof() = quiet!{![_]} / expected!("") + rule eol() = "\n" / eof() + + /// Standard C-like comments + rule comment() + = "//" (!eol()[_])* eol() + / "/*" (!("*/")[_])* "*/" + / "#" (!eol()[_])* eol() + + rule single_whitespace() = quiet!{([' ' | '\r' | '\n' | '\t'] / comment())} / expected!("") + rule _() = quiet!{([' ' | '\r' | '\n' | '\t']+) / comment()}* / expected!("") + + /// For comma-delimited elements + rule comma() = quiet!{_ "," _} / expected!("") + rule alpha() -> char = c:$(['_' | 'a'..='z' | 'A'..='Z']) {c.chars().next().unwrap()} + rule digit() -> char = d:$(['0'..='9']) {d.chars().next().unwrap()} + rule end_of_ident() = !['0'..='9' | '_' | 'a'..='z' | 'A'..='Z'] + /// Sequence of digits + rule uint_str() -> &'input str = a:$(digit()+ ("_" digit()+)*) { a } + /// Number in scientific notation format + rule number() -> f64 = quiet!{a:$(uint_str() ("." uint_str())? (['e'|'E'] (s:['+'|'-'])? uint_str())?) {? a.replace("_","").parse().map_err(|_| "") }} / expected!("") + + /// Reserved word followed by any non-alphanumberic + rule reserved() = ("assert" / "else" / "error" / "false" / "for" / "function" / "if" / "import" / "importstr" / "importbin" / "in" / "local" / "null" / "tailstrict" / "then" / "self" / "super" / "true") end_of_ident() + rule id() -> IStr = v:$(quiet!{ !reserved() alpha() (alpha() / digit())*} / expected!("")) { v.into() } + + rule keyword(id: &'static str) -> () + = ##parse_string_literal(id) end_of_ident() + + pub rule param(s: &ParserSettings) -> ExprParam = destruct:destruct(s) expr:(_ "=" _ expr:expr(s){expr})? { ExprParam { destruct, default: expr.map(Rc::new) } } + pub rule params(s: &ParserSettings) -> ExprParams + = params:param(s) ** comma() comma()? { ExprParams::new(params) } + / { ExprParams::new(Vec::new()) } + + pub rule arg(s: &ParserSettings) -> (Option, Rc>) + = name:(quiet! { (s:id() _ "=" !['='] _ {s})? } / expected!("")) expr:expr(s) {(name, Rc::new(expr))} + + pub rule args(s: &ParserSettings) -> ArgsDesc + = args:arg(s)**comma() comma()? {? + let unnamed_count = args.iter().take_while(|(n, _)| n.is_none()).count(); + let mut unnamed = Vec::with_capacity(unnamed_count); + let mut named = Vec::with_capacity(args.len() - unnamed_count); + let mut named_started = false; + for (name, value) in args { + if let Some(name) = name { + named_started = true; + named.push((name, value)); + } else { + if named_started { + return Err("") + } + unnamed.push(value); + } + } + Ok(ArgsDesc::new(unnamed, named)) + } + + pub rule destruct_rest() -> DestructRest + = "..." into:(_ into:id() {into})? {if let Some(into) = into { + DestructRest::Keep(into) + } else {DestructRest::Drop}} + pub rule destruct_array(s: &ParserSettings) -> Destruct + = "[" _ start:destruct(s)**comma() rest:( + comma() _ rest:destruct_rest()? end:( + comma() end:destruct(s)**comma() (_ comma())? {end} + / comma()? {Vec::new()} + ) {(rest, end)} + / comma()? {(None, Vec::new())} + ) _ "]" {? + #[cfg(feature = "exp-destruct")] return Ok(expr::Destruct::Array { + start, + rest: rest.0, + end: rest.1, + }); + #[cfg(not(feature = "exp-destruct"))] Err("!!!experimental destructuring was not enabled") + } + pub rule destruct_object(s: &ParserSettings) -> Destruct + = "{" _ + fields:(name:id() into:(_ ":" _ into:destruct(s) {into})? default:(_ "=" _ v:expr(s) {v})? {(name, into, default.map(Rc::new))})**comma() + rest:( + comma() rest:destruct_rest()? {rest} + / comma()? {None} + ) + _ "}" {? + #[cfg(feature = "exp-destruct")] return Ok(expr::Destruct::Object { + fields, + rest, + }); + #[cfg(not(feature = "exp-destruct"))] Err("!!!experimental destructuring was not enabled") + } + pub rule destruct(s: &ParserSettings) -> Destruct + = v:id() {Destruct::Full(v)} + / "?" {? + #[cfg(feature = "exp-destruct")] return Ok(expr::Destruct::Skip); + #[cfg(not(feature = "exp-destruct"))] Err("!!!experimental destructuring was not enabled") + } + / arr:destruct_array(s) {arr} + / obj:destruct_object(s) {obj} + + pub rule bind(s: &ParserSettings) -> BindSpec + = into:destruct(s) _ "=" _ value:expr(s) {BindSpec::Field{into, value: Rc::new(value)}} + / name:id() _ "(" _ params:params(s) _ ")" _ "=" _ value:expr(s) {BindSpec::Function{name, params, value: Rc::new(value)}} + + pub rule assertion(s: &ParserSettings) -> AssertStmt + = keyword("assert") _ cond:expr(s) msg:(_ ":" _ e:expr(s) {e})? { AssertStmt(cond, msg) } + + pub rule whole_line() -> &'input str + = str:$((!['\n'][_])* "\n") {str} + pub rule string_block() -> String + = "|||" chomped:"-"? (!['\n']single_whitespace())* "\n" + empty_lines:$(['\n']*) + prefix:[' ' | '\t']+ first_line:whole_line() + lines:("\n" {"\n"} / [' ' | '\t']*<{prefix.len()}> s:whole_line() {s})* + [' ' | '\t']*<, {prefix.len() - 1}> "|||" + { + let mut l = empty_lines.to_owned(); + l.push_str(first_line); + l.extend(lines); + if chomped.is_some() { + debug_assert!(l.ends_with('\n')); + l.truncate(l.len() - 1); + } + l + } + + rule hex_char() + = quiet! { ['0'..='9' | 'a'..='f' | 'A'..='F'] } / expected!("") + + rule string_char(c: rule<()>) + = (!['\\']!c()[_])+ + / "\\\\" + / "\\u" hex_char() hex_char() hex_char() hex_char() + / "\\x" hex_char() hex_char() + / ['\\'] (quiet! { ['b' | 'f' | 'n' | 'r' | 't' | '"' | '\''] } / expected!("")) + pub rule string() -> String + = ['"'] str:$(string_char(<"\"">)*) ['"'] {? unescape::unescape(str).ok_or("")} + / ['\''] str:$(string_char(<"\'">)*) ['\''] {? unescape::unescape(str).ok_or("")} + / quiet!{ "@'" str:$(("''" / (!['\''][_]))*) "'" {str.replace("''", "'")} + / "@\"" str:$(("\"\"" / (!['"'][_]))*) "\"" {str.replace("\"\"", "\"")} + / string_block() } / expected!("") + + pub rule field_name(s: &ParserSettings) -> FieldName + = name:id() {FieldName::Fixed(name)} + / name:string() {FieldName::Fixed(name.into())} + / "[" _ expr:expr(s) _ "]" {FieldName::Dyn(expr)} + pub rule visibility() -> Visibility + = ":::" {Visibility::Unhide} + / "::" {Visibility::Hidden} + / ":" {Visibility::Normal} + pub rule field(s: &ParserSettings) -> FieldMember + = name:field_name(s) _ plus:"+"? _ visibility:visibility() _ value:expr(s) {FieldMember{ + name, + plus: plus.is_some(), + params: None, + visibility, + value: Rc::new(value), + }} + / name:field_name(s) _ "(" _ params:params(s) _ ")" _ visibility:visibility() _ value:expr(s) {FieldMember{ + name, + plus: false, + params: Some(params), + visibility, + value: Rc::new(value), + }} + pub rule obj_local(s: &ParserSettings) -> BindSpec + = keyword("local") _ bind:bind(s) {bind} + pub rule member(s: &ParserSettings) -> Member + = bind:obj_local(s) {Member::BindStmt(bind)} + / assertion:assertion(s) {Member::AssertStmt(assertion)} + / field:field(s) {Member::Field(field)} + pub rule objinside(s: &ParserSettings) -> ObjBody + = members:(member(s) ** comma()) comma()? _ compspecs:compspecs(s)? {? + Ok(if let Some(compspecs) = compspecs { + let mut locals = Vec::new(); + let mut field = None; + for member in members { + match member { + Member::Field(field_member) => if field.replace(field_member).is_some() { + return Err("") + }, + Member::BindStmt(bind_spec) => locals.push(bind_spec), + Member::AssertStmt(assert_stmt) => return Err(""), + } + } + ObjBody::ObjComp(ObjComp { + locals: Rc::new(locals), + field: field.map(Rc::new).ok_or("")?, + compspecs + }) + } else { + let mut locals = Vec::new(); + let mut asserts = Vec::new(); + let mut fields = Vec::new(); + for member in members { + match member { + Member::Field(field_member) => fields.push(field_member), + Member::BindStmt(bind_spec) => locals.push(bind_spec), + Member::AssertStmt(assert_stmt) => asserts.push(assert_stmt), + } + } + ObjBody::MemberList(ObjMembers { + locals: Rc::new(locals), + asserts: Rc::new(asserts), + fields + }) + }) + } + pub rule ifspec(s: &ParserSettings) -> IfSpecData + = keyword("if") _ expr:expr(s) {IfSpecData(expr)} + pub rule forspec(s: &ParserSettings) -> ForSpecData + = keyword("for") _ id:destruct(s) _ keyword("in") _ cond:expr(s) {ForSpecData(id, cond)} + rule compspec(s: &ParserSettings) -> CompSpec + = i:ifspec(s) { CompSpec::IfSpec(i) } / f:forspec(s) {CompSpec::ForSpec(f)} + pub rule compspecs(s: &ParserSettings) -> Vec + = specs:compspec(s) ++ _ {? + if !matches!(specs[0], CompSpec::ForSpec(_)) { + return Err("") + } + Ok(specs) + } + pub rule local_expr(s: &ParserSettings) -> Expr + = keyword("local") _ binds:bind(s) ** comma() (_ ",")? _ ";" _ expr:expr(s) { Expr::LocalExpr(binds, Box::new(expr)) } + pub rule string_expr(s: &ParserSettings) -> Expr + = s:string() {Expr::Str(s.into())} + pub rule obj_expr(s: &ParserSettings) -> Expr + = "{" _ body:objinside(s) _ "}" {Expr::Obj(body)} + pub rule array_expr(s: &ParserSettings) -> Expr + = "[" _ elems:(expr(s) ** comma()) _ comma()? "]" {Expr::Arr(Rc::new(elems))} + pub rule array_comp_expr(s: &ParserSettings) -> Expr + = "[" _ expr:expr(s) _ comma()? _ specs:(r: compspecs(s) _ {r}) "]" { + Expr::ArrComp(Rc::new(expr), specs) + } + pub rule number_expr(s: &ParserSettings) -> Expr + = n:number() {? if n.is_finite() { + Ok(Expr::Num(n)) + } else { + Err("!!!numbers are finite") + }} + pub rule var_expr(s: &ParserSettings) -> Expr + = n:id() { Expr::Var(n) } + pub rule id_loc(s: &ParserSettings) -> Spanned + = a:position!() n:id() b:position!() { Spanned::new(Expr::Str(n), Span(s.source.clone(), a as u32,b as u32)) } + pub rule if_then_else_expr(s: &ParserSettings) -> Expr + = cond:ifspec(s) _ keyword("then") _ cond_then:expr(s) cond_else:(_ keyword("else") _ e:expr(s) {e})? {Expr::IfElse(Box::new(IfElse{ + cond, + cond_then, + cond_else, + }))} + + pub rule literal(s: &ParserSettings) -> 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)} + + rule import_kind() -> ImportKind + = keyword("importstr") { ImportKind::Str } + / keyword("importbin") { ImportKind::Bin } + / keyword("import") { ImportKind::Normal } + + pub rule expr_basic(s: &ParserSettings) -> Expr + = literal(s) + + / string_expr(s) / number_expr(s) + / array_expr(s) + / obj_expr(s) + / array_expr(s) + / array_comp_expr(s) + + / kind:import_kind() _ path:expr(s) {Expr::Import(kind, Box::new(path))} + + / var_expr(s) + / local_expr(s) + / if_then_else_expr(s) + + / keyword("function") _ "(" _ params:params(s) _ ")" _ expr:expr(s) {Expr::Function(params, Rc::new(expr))} + / assert:assertion(s) _ ";" _ rest:expr(s) { Expr::AssertExpr(Rc::new(AssertExpr{ + assert, rest + })) } + + / keyword("error") _ expr:expr(s) { Expr::ErrorStmt(Box::new(expr)) } + + rule slice_part(s: &ParserSettings) -> Option> + = _ e:(e:expr(s) _{e})? {e} + pub rule slice_desc(s: &ParserSettings) -> SliceDesc + = start:slice_part(s) ":" pair:(end:slice_part(s) step:(":" e:slice_part(s){e})? {(end, step.flatten())})? { + let (end, step) = if let Some((end, step)) = pair { + (end, step) + }else{ + (None, None) + }; + + SliceDesc { start, end, step } + } + + rule binop(x: rule<()>) -> () + = quiet!{ x() } / expected!("") + rule unaryop(x: rule<()>) -> () + = quiet!{ x() } / expected!("") + + rule ensure_null_coaelse() + = "" {? + #[cfg(not(feature = "exp-null-coaelse"))] return Err("!!!experimental null coaelscing was not enabled"); + #[cfg(feature = "exp-null-coaelse")] Ok(()) + } + use jrsonnet_ir::BinaryOpType::*; + use jrsonnet_ir::UnaryOpType::*; + rule expr(s: &ParserSettings) -> Spanned + = precedence! { + "(" _ e:expr(s) _ ")" {e} + start:position!() v:@ end:position!() { Spanned::new(v, Span(s.source.clone(), start as u32, end as u32)) } + -- + a:(@) _ binop(<"||">) _ b:@ {expr_bin!(a Or b)} + a:(@) _ binop(<"??">) _ ensure_null_coaelse() b:@ { + #[cfg(feature = "exp-null-coaelse")] return expr_bin!(a NullCoaelse b); + unreachable!("ensure_null_coaelse will fail if feature is not enabled") + } + -- + a:(@) _ binop(<"&&">) _ b:@ {expr_bin!(a And b)} + -- + a:(@) _ binop(<"|">) _ b:@ {expr_bin!(a BitOr b)} + -- + a:@ _ binop(<"^">) _ b:(@) {expr_bin!(a BitXor b)} + -- + a:(@) _ binop(<"&">) _ b:@ {expr_bin!(a BitAnd b)} + -- + a:(@) _ binop(<"==">) _ b:@ {expr_bin!(a Eq b)} + a:(@) _ binop(<"!=">) _ b:@ {expr_bin!(a Neq b)} + -- + a:(@) _ binop(<"<">) _ b:@ {expr_bin!(a Lt b)} + a:(@) _ binop(<">">) _ b:@ {expr_bin!(a Gt b)} + a:(@) _ binop(<"<=">) _ b:@ {expr_bin!(a Lte b)} + a:(@) _ binop(<">=">) _ b:@ {expr_bin!(a Gte b)} + a:(@) _ binop() _ b:@ {expr_bin!(a In b)} + -- + a:(@) _ binop(<"<<">) _ b:@ {expr_bin!(a Lhs b)} + a:(@) _ binop(<">>">) _ b:@ {expr_bin!(a Rhs b)} + -- + a:(@) _ binop(<"+">) _ b:@ {expr_bin!(a Add b)} + a:(@) _ binop(<"-">) _ b:@ {expr_bin!(a Sub b)} + -- + a:(@) _ binop(<"*">) _ b:@ {expr_bin!(a Mul b)} + a:(@) _ binop(<"/">) _ b:@ {expr_bin!(a Div b)} + a:(@) _ binop(<"%">) _ b:@ {expr_bin!(a Mod b)} + -- + unaryop(<"+">) _ b:@ {expr_un!(Plus b)} + unaryop(<"-">) _ b:@ {expr_un!(Minus b)} + unaryop(<"!">) _ b:@ {expr_un!(Not b)} + unaryop(<"~">) _ b:@ {expr_un!(BitNot b)} + -- + value:(@) _ "[" _ slice:slice_desc(s) _ "]" {Expr::Slice(Box::new(Slice{value, slice}))} + indexable:(@) _ parts:index_part(s)+ {Expr::Index{indexable: Box::new(indexable), parts}} + a:(@) _ "(" _ args:args(s) _ ")" ts:(_ keyword("tailstrict"))? {Expr::Apply(Box::new(a), args, ts.is_some())} + a:(@) _ "{" _ body:objinside(s) _ "}" {Expr::ObjExtend(Rc::new(a), body)} + -- + e:expr_basic(s) {e} + } + pub rule index_part(s: &ParserSettings) -> IndexPart + = n:("?" _ ensure_null_coaelse())? "." _ value:id_loc(s) {IndexPart { + value, + #[cfg(feature = "exp-null-coaelse")] + null_coaelse: n.is_some(), + }} + / n:("?" _ "." _ ensure_null_coaelse())? "[" _ value:expr(s) _ "]" {IndexPart { + value, + #[cfg(feature = "exp-null-coaelse")] + null_coaelse: n.is_some(), + }} + + pub rule jsonnet(s: &ParserSettings) -> Spanned = _ e:expr(s) _ {e} + } +} + +pub type ParseError = peg::error::ParseError; +pub fn parse(str: &str, settings: &ParserSettings) -> Result, ParseError> { + jsonnet_parser::jsonnet(str, settings) +} +/// Used for importstr values +pub fn string_to_expr(str: IStr, settings: &ParserSettings) -> Spanned { + let len = str.len(); + Spanned::new(Expr::Str(str), Span(settings.source.clone(), 0, len as u32)) +} + +#[cfg(test)] +pub mod tests { + use insta::assert_snapshot; + use jrsonnet_ir::{IStr, Source}; + + use super::parse; + use crate::ParserSettings; + + fn parsep(s: &str) -> String { + let v = parse( + s, + &ParserSettings { + source: Source::new_virtual("".into(), IStr::empty()), + }, + ) + .unwrap(); + format!("{v:#?}") + } + + macro_rules! parse { + ($s:expr) => { + assert_snapshot!(parsep($s)); + }; + } + + #[test] + fn multiline_string() { + parse!("|||\n Hello world!\n a\n|||"); + parse!("|||\n Hello world!\n a\n|||"); + parse!("|||\n\t\tHello world!\n\t\t\ta\n|||"); + parse!("|||\n Hello world!\n a\n |||"); + } + + #[test] + fn slice() { + parse!("a[1:]"); + parse!("a[1::]"); + parse!("a[:1:]"); + parse!("a[::1]"); + parse!("str[:len - 1]"); + } + + #[test] + fn string_escaping() { + parse!(r#""Hello, \"world\"!""#); + parse!(r#"'Hello \'world\'!'"#); + parse!(r#"'\\\\'"#); + } + + #[test] + fn string_unescaping() { + parse!(r#""Hello\nWorld""#); + } + + #[test] + fn string_verbantim() { + parse!(r#"@"Hello\n""World""""#); + } + + #[test] + fn imports() { + parse!("import \"hello\""); + parse!("importstr \"garnish.txt\""); + parse!("importbin \"garnish.bin\""); + } + + #[test] + fn empty_object() { + parse!("{}"); + } + + #[test] + fn basic_math() { + parse!("2+2*2"); + parse!("2 + 2 * 2 "); + parse!("2+(2+2*2)"); + parse!("2//comment\n+//comment\n3/*test*/*/*test*/4"); + } + + #[test] + fn suffix() { + parse!("std.test"); + parse!("std(2)"); + parse!("std.test(2)"); + parse!("a[b]"); + } + + #[test] + fn array_comp() { + parse!("[std.deepJoin(x) for x in arr]"); + } + + #[test] + fn reserved() { + parse!("null"); + parse!("nulla"); + } + + #[test] + fn multiple_args_buf() { + parse!("a(b, null_fields)"); + } + + #[test] + fn infix_precedence() { + parse!("!a && !b"); + parse!("!a / !b"); + } + + #[test] + fn double_negation() { + parse!("!!a"); + } + + #[test] + fn array_test_error() { + parse!("[a for a in b if c for e in f]"); + } + + #[test] + fn missing_newline_between_comment_and_eof() { + parse!( + "{a:1} + + //+213" + ); + } + + #[test] + fn default_param_before_nondefault() { + parse!("local x(foo = 'foo', bar) = null; null"); + } + + #[test] + fn add_location_info_to_all_sub_expressions() { + parse!("{} { local x = 1, x: x } + {}"); + } +} --- /dev/null +++ b/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__add_location_info_to_all_sub_expressions.snap @@ -0,0 +1,57 @@ +--- +source: crates/jrsonnet-peg-parser/src/lib.rs +expression: "parsep(\"{} { local x = 1, x: x } + {}\")" +--- +BinaryOp( + BinaryOp { + lhs: ObjExtend( + Obj( + MemberList( + ObjMembers { + locals: [], + asserts: [], + fields: [], + }, + ), + ) from virtual::0-2, + MemberList( + ObjMembers { + locals: [ + Field { + into: Full( + "x", + ), + value: Num( + 1.0, + ) from virtual::15-16, + }, + ], + asserts: [], + fields: [ + FieldMember { + name: Fixed( + "x", + ), + plus: false, + params: None, + visibility: Normal, + value: Var( + "x", + ) from virtual::21-22, + }, + ], + }, + ), + ) from virtual::0-24, + op: Add, + rhs: Obj( + MemberList( + ObjMembers { + locals: [], + asserts: [], + fields: [], + }, + ), + ) from virtual::27-29, + }, +) from virtual::0-29 --- /dev/null +++ b/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__array_comp.snap @@ -0,0 +1,41 @@ +--- +source: crates/jrsonnet-peg-parser/src/lib.rs +expression: "parsep(\"[std.deepJoin(x) for x in arr]\")" +--- +ArrComp( + Apply( + Index { + indexable: Var( + "std", + ) from virtual::1-4, + parts: [ + IndexPart { + value: Str( + "deepJoin", + ) from virtual::5-13, + }, + ], + } from virtual::1-13, + ArgsDesc { + unnamed: [ + Var( + "x", + ) from virtual::14-15, + ], + named: [], + }, + false, + ) from virtual::1-16, + [ + ForSpec( + ForSpecData( + Full( + "x", + ), + Var( + "arr", + ) from virtual::26-29, + ), + ), + ], +) from virtual::0-30 --- /dev/null +++ b/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__array_test_error.snap @@ -0,0 +1,38 @@ +--- +source: crates/jrsonnet-peg-parser/src/lib.rs +expression: "parsep(\"[a for a in b if c for e in f]\")" +--- +ArrComp( + Var( + "a", + ) from virtual::1-2, + [ + ForSpec( + ForSpecData( + Full( + "a", + ), + Var( + "b", + ) from virtual::12-13, + ), + ), + IfSpec( + IfSpecData( + Var( + "c", + ) from virtual::17-18, + ), + ), + ForSpec( + ForSpecData( + Full( + "e", + ), + Var( + "f", + ) from virtual::28-29, + ), + ), + ], +) from virtual::0-30 --- /dev/null +++ b/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__basic_math.snap @@ -0,0 +1,23 @@ +--- +source: crates/jrsonnet-peg-parser/src/lib.rs +expression: "parsep(\"2+2*2\")" +--- +BinaryOp( + BinaryOp { + lhs: Num( + 2.0, + ) from virtual::0-1, + op: Add, + rhs: BinaryOp( + BinaryOp { + lhs: Num( + 2.0, + ) from virtual::2-3, + op: Mul, + rhs: Num( + 2.0, + ) from virtual::4-5, + }, + ) from virtual::2-5, + }, +) from virtual::0-5 --- /dev/null +++ b/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__default_param_before_nondefault.snap @@ -0,0 +1,54 @@ +--- +source: crates/jrsonnet-peg-parser/src/lib.rs +expression: "parsep(\"local x(foo = 'foo', bar) = null; null\")" +--- +LocalExpr( + [ + Function { + name: "x", + params: ExprParams { + exprs: [ + ExprParam { + destruct: Full( + "foo", + ), + default: Some( + Str( + "foo", + ) from virtual::14-19, + ), + }, + ExprParam { + destruct: Full( + "bar", + ), + default: None, + }, + ], + signature: FunctionSignature( + [ + ParamParse { + name: Named( + "foo", + ), + default: Exists, + }, + ParamParse { + name: Named( + "bar", + ), + default: None, + }, + ], + ), + binds_len: 2, + }, + value: Literal( + Null, + ) from virtual::28-32, + }, + ], + Literal( + Null, + ) from virtual::34-38, +) from virtual::0-38 --- /dev/null +++ b/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__double_negation.snap @@ -0,0 +1,13 @@ +--- +source: crates/jrsonnet-peg-parser/src/lib.rs +expression: "parsep(\"!!a\")" +--- +UnaryOp( + Not, + UnaryOp( + Not, + Var( + "a", + ) from virtual::2-3, + ) from virtual::1-3, +) from virtual::0-3 --- /dev/null +++ b/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__empty_object.snap @@ -0,0 +1,13 @@ +--- +source: crates/jrsonnet-peg-parser/src/lib.rs +expression: "parsep(\"{}\")" +--- +Obj( + MemberList( + ObjMembers { + locals: [], + asserts: [], + fields: [], + }, + ), +) from virtual::0-2 --- /dev/null +++ b/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__imports.snap @@ -0,0 +1,10 @@ +--- +source: crates/jrsonnet-peg-parser/src/lib.rs +expression: "parsep(\"import \\\"hello\\\"\")" +--- +Import( + Normal, + Str( + "hello", + ) from virtual::7-14, +) from virtual::0-14 --- /dev/null +++ b/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__infix_precedence.snap @@ -0,0 +1,21 @@ +--- +source: crates/jrsonnet-peg-parser/src/lib.rs +expression: "parsep(\"!a && !b\")" +--- +BinaryOp( + BinaryOp { + lhs: UnaryOp( + Not, + Var( + "a", + ) from virtual::1-2, + ) from virtual::0-2, + op: And, + rhs: UnaryOp( + Not, + Var( + "b", + ) from virtual::7-8, + ) from virtual::6-8, + }, +) from virtual::0-8 --- /dev/null +++ b/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__missing_newline_between_comment_and_eof.snap @@ -0,0 +1,25 @@ +--- +source: crates/jrsonnet-peg-parser/src/lib.rs +expression: "parsep(\"{a:1}\n\n\t\t\t//+213\")" +--- +Obj( + MemberList( + ObjMembers { + locals: [], + asserts: [], + fields: [ + FieldMember { + name: Fixed( + "a", + ), + plus: false, + params: None, + visibility: Normal, + value: Num( + 1.0, + ) from virtual::3-4, + }, + ], + }, + ), +) from virtual::0-5 --- /dev/null +++ b/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__multiline_string.snap @@ -0,0 +1,7 @@ +--- +source: crates/jrsonnet-peg-parser/src/lib.rs +expression: "parsep(\"|||\\n Hello world!\\n a\\n|||\")" +--- +Str( + "Hello world!\n a\n", +) from virtual::0-31 --- /dev/null +++ b/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__multiple_args_buf.snap @@ -0,0 +1,21 @@ +--- +source: crates/jrsonnet-peg-parser/src/lib.rs +expression: "parsep(\"a(b, null_fields)\")" +--- +Apply( + Var( + "a", + ) from virtual::0-1, + ArgsDesc { + unnamed: [ + Var( + "b", + ) from virtual::2-3, + Var( + "null_fields", + ) from virtual::5-16, + ], + named: [], + }, + false, +) from virtual::0-17 --- /dev/null +++ b/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__reserved.snap @@ -0,0 +1,7 @@ +--- +source: crates/jrsonnet-peg-parser/src/lib.rs +expression: "parsep(\"null\")" +--- +Literal( + Null, +) from virtual::0-4 --- /dev/null +++ b/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__slice.snap @@ -0,0 +1,20 @@ +--- +source: crates/jrsonnet-peg-parser/src/lib.rs +expression: "parsep(\"a[1:]\")" +--- +Slice( + Slice { + value: Var( + "a", + ) from virtual::0-1, + slice: SliceDesc { + start: Some( + Num( + 1.0, + ) from virtual::2-3, + ), + end: None, + step: None, + }, + }, +) from virtual::0-5 --- /dev/null +++ b/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__string_escaping.snap @@ -0,0 +1,7 @@ +--- +source: crates/jrsonnet-peg-parser/src/lib.rs +expression: "parsep(r#\"\"Hello, \\\"world\\\"!\"\"#)" +--- +Str( + "Hello, \"world\"!", +) from virtual::0-19 --- /dev/null +++ b/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__string_unescaping.snap @@ -0,0 +1,7 @@ +--- +source: crates/jrsonnet-peg-parser/src/lib.rs +expression: "parsep(r#\"\"Hello\\nWorld\"\"#)" +--- +Str( + "Hello\nWorld", +) from virtual::0-14 --- /dev/null +++ b/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__string_verbantim.snap @@ -0,0 +1,7 @@ +--- +source: crates/jrsonnet-peg-parser/src/lib.rs +expression: "parsep(r#\"@\"Hello\\n\"\"World\"\"\"\"#)" +--- +Str( + "Hello\\n\"World\"", +) from virtual::0-19 --- /dev/null +++ b/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__suffix.snap @@ -0,0 +1,16 @@ +--- +source: crates/jrsonnet-peg-parser/src/lib.rs +expression: "parsep(\"std.test\")" +--- +Index { + indexable: Var( + "std", + ) from virtual::0-3, + parts: [ + IndexPart { + value: Str( + "test", + ) from virtual::4-8, + }, + ], +} from virtual::0-8 --- a/crates/jrsonnet-stdlib/Cargo.toml +++ b/crates/jrsonnet-stdlib/Cargo.toml @@ -16,14 +16,17 @@ # Bigint type exp-bigint = ["dep:num-bigint", "jrsonnet-evaluator/exp-bigint"] -exp-null-coaelse = ["jrsonnet-parser/exp-null-coaelse", "jrsonnet-evaluator/exp-null-coaelse"] +exp-null-coaelse = [ + "jrsonnet-ir/exp-null-coaelse", + "jrsonnet-evaluator/exp-null-coaelse", +] # std.regexMatch and other helpers exp-regex = ["dep:regex", "dep:lru", "dep:rustc-hash"] [dependencies] jrsonnet-evaluator.workspace = true jrsonnet-macros.workspace = true -jrsonnet-parser.workspace = true +jrsonnet-ir.workspace = true jrsonnet-gcmodule.workspace = true # Used for std.parseJson/std.parseYaml @@ -50,6 +53,3 @@ regex = { workspace = true, optional = true } lru = { workspace = true, optional = true } rustc-hash = { workspace = true, optional = true } - -[build-dependencies] -jrsonnet-parser.workspace = true --- /dev/null +++ b/crates/jrsonnet-stdlib/result @@ -0,0 +1 @@ +/nix/store/2fxmjvz4f6c8g6gi37y35h356vj0n8k6-benchmarks \ No newline at end of file --- a/crates/jrsonnet-stdlib/src/compat.rs +++ b/crates/jrsonnet-stdlib/src/compat.rs @@ -8,7 +8,7 @@ #[allow(non_snake_case)] pub fn builtin___compare(v1: Val, v2: Val) -> Result { Ok( - match evaluate_compare_op(&v1, &v2, jrsonnet_parser::BinaryOpType::Lt)? { + match evaluate_compare_op(&v1, &v2, jrsonnet_ir::BinaryOpType::Lt)? { Ordering::Less => -1, Ordering::Equal => 0, Ordering::Greater => 1, @@ -30,7 +30,7 @@ let ordering = evaluate_compare_op( &Val::Arr(arr1), &Val::Arr(arr2), - jrsonnet_parser::BinaryOpType::Lt, + jrsonnet_ir::BinaryOpType::Lt, )?; Ok($operator.contains(&ordering)) } --- a/crates/jrsonnet-stdlib/src/lib.rs +++ b/crates/jrsonnet-stdlib/src/lib.rs @@ -20,7 +20,7 @@ ContextBuilder, IStr, ObjValue, ObjValueBuilder, Thunk, Val, }; use jrsonnet_gcmodule::{Acyclic, Cc, Trace}; -use jrsonnet_parser::Source; +use jrsonnet_ir::Source; pub use manifest::*; pub use math::*; pub use misc::*; --- a/crates/jrsonnet-stdlib/src/manifest/ini.rs +++ b/crates/jrsonnet-stdlib/src/manifest/ini.rs @@ -4,8 +4,8 @@ manifest::{ManifestFormat, ToStringFormat}, typed::{FromUntyped, Typed}, ObjValue, Result, ResultExt, Val, + IStr, }; -use jrsonnet_parser::IStr; pub struct IniFormat { #[cfg(feature = "exp-preserve-order")] --- a/crates/jrsonnet-stdlib/src/sets.rs +++ b/crates/jrsonnet-stdlib/src/sets.rs @@ -3,7 +3,7 @@ use jrsonnet_evaluator::{ function::builtin, operator::evaluate_compare_op, val::ArrValue, Result, Thunk, Val, }; -use jrsonnet_parser::BinaryOpType; +use jrsonnet_ir::BinaryOpType; use crate::keyf::KeyF; --- a/crates/jrsonnet-stdlib/src/sort.rs +++ b/crates/jrsonnet-stdlib/src/sort.rs @@ -9,7 +9,7 @@ val::{equals, ArrValue}, Result, Thunk, Val, }; -use jrsonnet_parser::BinaryOpType; +use jrsonnet_ir::BinaryOpType; use crate::{eval_on_empty, keyf::KeyF}; --- a/flake.nix +++ b/flake.nix @@ -160,6 +160,7 @@ ] ++ lib.optionals (!stdenv.isDarwin) [ valgrind + kdePackages.kcachegrind ]; }; }; --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -52,10 +52,9 @@ } => { let out = sh.create_temp_dir()?; - // build-std cmd!( sh, - "cargo build -Zbuild-std --target={target} --profile releasedebug" + "cargo build --target={target} --profile releasedebug" ) .run()?; let built = format!("./target/{target}/releasedebug/jrsonnet");