--- a/crates/jrsonnet-evaluator/Cargo.toml +++ b/crates/jrsonnet-evaluator/Cargo.toml @@ -18,24 +18,26 @@ explaining-traces = ["annotate-snippets", "hi-doc"] # Allows library authors to throw custom errors anyhow-error = ["anyhow"] -# Use hand-written recursive descent parser instead of PEG parser +# Use hand-written recursive descent parser ir-parser = ["dep:jrsonnet-ir-parser"] +# Use PEG parser +peg-parser = ["dep:jrsonnet-peg-parser"] # Allows to preserve field order in objects exp-preserve-order = [] # Implements field destructuring -exp-destruct = ["jrsonnet-peg-parser/exp-destruct"] +exp-destruct = ["jrsonnet-peg-parser?/exp-destruct", "jrsonnet-ir-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-peg-parser/exp-null-coaelse", "jrsonnet-ir-parser?/exp-null-coaelse"] +exp-null-coaelse = ["jrsonnet-peg-parser?/exp-null-coaelse", "jrsonnet-ir-parser?/exp-null-coaelse"] [dependencies] jrsonnet-interner.workspace = true jrsonnet-ir.workspace = true -jrsonnet-peg-parser.workspace = true +jrsonnet-peg-parser = { workspace = true, optional = true } jrsonnet-ir-parser = { workspace = true, optional = true } jrsonnet-types.workspace = true jrsonnet-macros.workspace = true --- a/crates/jrsonnet-evaluator/src/async_import.rs +++ b/crates/jrsonnet-evaluator/src/async_import.rs @@ -7,10 +7,6 @@ FieldMember, FieldName, ForSpecData, IfElse, IfSpecData, ImportKind, ObjBody, Slice, SliceDesc, Source, SourcePath, Spanned, }; -#[cfg(feature = "ir-parser")] -use jrsonnet_ir_parser::ParserSettings; -#[cfg(not(feature = "ir-parser"))] -use jrsonnet_peg_parser::ParserSettings; use rustc_hash::FxHashMap; use crate::{AsPathLike, FileData, ImportResolver, ResolvePathOwned, State}; @@ -326,7 +322,7 @@ }; let source = Source::new(path.clone(), code.clone()); // If failed - then skip import - file.parsed = crate::parse_jsonnet(&code, &ParserSettings { source }) + file.parsed = crate::parse_jsonnet(&code, 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 @@ -14,6 +14,22 @@ ObjValue, ResolvePathOwned, }; +#[derive(Debug, Clone)] +pub struct SyntaxErrorLocation { + pub offset: usize, +} + +#[derive(Debug, Clone)] +pub struct SyntaxError { + pub message: String, + pub location: SyntaxErrorLocation, +} +impl fmt::Display for SyntaxError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.message) + } +} + pub(crate) fn format_found(list: &[IStr], what: &str) -> String { if list.is_empty() { return String::new(); @@ -154,31 +170,11 @@ ImportNotSupported(SourcePath, ResolvePathOwned), #[error("can't import from virtual file")] CantImportFromVirtualFile, - #[cfg(not(feature = "ir-parser"))] - #[error( - "syntax error: {}", - // Peg has no fancier way to handle critical parsing errors https://github.com/kevinmehall/rust-peg/issues/225 - {.error.expected.tokens().find(|t| t.starts_with("!!!")).map_or_else(|| { - format!( - "expected {}, got {:?}", - .error.expected, - .path.code().chars().nth(error.location.offset) - .map_or_else(|| "EOF".into(), |c| c.to_string()) - ) - }, |v| v[3..].into())} - )] - ImportSyntaxError { - path: Source, - #[trace(skip)] - error: Box, - }, - - #[cfg(feature = "ir-parser")] #[error("syntax error: {error}")] ImportSyntaxError { path: Source, #[trace(skip)] - error: Box, + error: Box, }, #[error("runtime error: {}", format_empty_str(.0))] --- a/crates/jrsonnet-evaluator/src/lib.rs +++ b/crates/jrsonnet-evaluator/src/lib.rs @@ -46,10 +46,11 @@ use jrsonnet_ir::{Expr, Source, SourcePath}; #[doc(hidden)] pub use jrsonnet_macros; -#[cfg(feature = "ir-parser")] -use jrsonnet_ir_parser::ParserSettings; -#[cfg(not(feature = "ir-parser"))] -use jrsonnet_peg_parser::ParserSettings; + +#[cfg(not(any(feature = "ir-parser", feature = "peg-parser")))] +compile_error!("at least one of `ir-parser` or `peg-parser` features must be enabled"); + +pub use error::{SyntaxError, SyntaxErrorLocation}; pub use obj::*; pub use rustc_hash; use rustc_hash::FxHashMap; @@ -59,20 +60,64 @@ use crate::gc::WithCapacityExt as _; +pub(crate) fn parse_jsonnet(code: &str, source: Source) -> Result { + #[cfg(all(feature = "ir-parser", feature = "peg-parser"))] + { + if std::env::var_os("JRSONNET_LEGACY_PARSER").is_some() { + return parse_peg(code, source); + } + return parse_ir(code, source); + } + #[cfg(all(feature = "ir-parser", not(feature = "peg-parser")))] + { + return parse_ir(code, source); + } + #[cfg(all(feature = "peg-parser", not(feature = "ir-parser")))] + { + return parse_peg(code, source); + } +} + #[cfg(feature = "ir-parser")] -pub(crate) fn parse_jsonnet( - code: &str, - settings: &ParserSettings, -) -> Result { - jrsonnet_ir_parser::parse(code, settings) +fn parse_ir(code: &str, source: Source) -> Result { + jrsonnet_ir_parser::parse(code, &jrsonnet_ir_parser::ParserSettings { source }).map_err( + |e| SyntaxError { + message: e.message, + location: SyntaxErrorLocation { + offset: e.location.offset, + }, + }, + ) } -#[cfg(not(feature = "ir-parser"))] -pub(crate) fn parse_jsonnet( - code: &str, - settings: &ParserSettings, -) -> Result { - jrsonnet_peg_parser::parse(code, settings) +#[cfg(feature = "peg-parser")] +fn parse_peg(code: &str, source: Source) -> Result { + jrsonnet_peg_parser::parse(code, &jrsonnet_peg_parser::ParserSettings { source }).map_err( + |e| { + let message = e + .expected + .tokens() + .find(|t| t.starts_with("!!!")) + .map_or_else( + || { + format!( + "expected {}, got {:?}", + e.expected, + code.chars() + .nth(e.location.offset) + .map_or_else(|| "EOF".into(), |c: char| c.to_string()) + ) + }, + |v| v[3..].into(), + ); + SyntaxError { + message, + location: SyntaxErrorLocation { + offset: e.location.offset, + }, + } + }, + ) } cc_dyn!( @@ -364,12 +409,7 @@ let file_name = Source::new(path.clone(), code.clone()); if file.parsed.is_none() { file.parsed = Some( - parse_jsonnet( - &code, - &ParserSettings { - source: file_name.clone(), - }, - ) + parse_jsonnet(&code, file_name.clone()) .map(Rc::new) .map_err(|e| ImportSyntaxError { path: file_name.clone(), @@ -480,12 +520,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 = parse_jsonnet( - &code, - &ParserSettings { - source: source.clone(), - }, - ) + let parsed = parse_jsonnet(&code, source.clone()) .map_err(|e| ImportSyntaxError { path: source.clone(), error: Box::new(e), @@ -501,12 +536,7 @@ ) -> Result { let code = code.into(); let source = Source::new_virtual(name.into(), code.clone()); - let parsed = parse_jsonnet( - &code, - &ParserSettings { - source: source.clone(), - }, - ) + let parsed = parse_jsonnet(&code, source.clone()) .map_err(|e| ImportSyntaxError { path: source.clone(), error: Box::new(e),