git.delta.rocks / jrsonnet / refs/commits / a34df9df5436

difftreelog

slop: implement ir parser

kwvkpnlqYaroslav Bolyukin2026-03-23parent: #6314afd.patch.diff
in: master

86 files changed

modifiedCargo.lockdiffbeforeafterboth
638 "jrsonnet-gcmodule",638 "jrsonnet-gcmodule",
639 "jrsonnet-interner",639 "jrsonnet-interner",
640 "jrsonnet-ir",640 "jrsonnet-ir",
641 "jrsonnet-ir-parser",
641 "jrsonnet-macros",642 "jrsonnet-macros",
642 "jrsonnet-peg-parser",643 "jrsonnet-peg-parser",
643 "jrsonnet-types",644 "jrsonnet-types",
modifiedcrates/jrsonnet-evaluator/Cargo.tomldiffbeforeafterboth
13workspace = true13workspace = true
1414
15[features]15[features]
16default = ["explaining-traces"]16default = ["explaining-traces", "ir-parser"]
17# Rustc-like trace visualization17# Rustc-like trace visualization
18explaining-traces = ["annotate-snippets", "hi-doc"]18explaining-traces = ["annotate-snippets", "hi-doc"]
19# Allows library authors to throw custom errors19# Allows library authors to throw custom errors
20anyhow-error = ["anyhow"]20anyhow-error = ["anyhow"]
21# Use hand-written recursive descent parser instead of PEG parser
22ir-parser = ["dep:jrsonnet-ir-parser"]
2123
22# Allows to preserve field order in objects24# Allows to preserve field order in objects
23exp-preserve-order = []25exp-preserve-order = []
28# Bigint type30# Bigint type
29exp-bigint = ["num-bigint", "jrsonnet-types/exp-bigint"]31exp-bigint = ["num-bigint", "jrsonnet-types/exp-bigint"]
30# obj?.field, obj?.['field']32# obj?.field, obj?.['field']
31exp-null-coaelse = ["jrsonnet-peg-parser/exp-null-coaelse"]33exp-null-coaelse = ["jrsonnet-peg-parser/exp-null-coaelse", "jrsonnet-ir-parser?/exp-null-coaelse"]
3234
33[dependencies]35[dependencies]
34jrsonnet-interner.workspace = true36jrsonnet-interner.workspace = true
35jrsonnet-ir.workspace = true37jrsonnet-ir.workspace = true
36jrsonnet-peg-parser.workspace = true38jrsonnet-peg-parser.workspace = true
39jrsonnet-ir-parser = { workspace = true, optional = true }
37jrsonnet-types.workspace = true40jrsonnet-types.workspace = true
38jrsonnet-macros.workspace = true41jrsonnet-macros.workspace = true
39jrsonnet-gcmodule.workspace = true42jrsonnet-gcmodule.workspace = true
modifiedcrates/jrsonnet-evaluator/src/async_import.rsdiffbeforeafterboth
7 FieldMember, FieldName, ForSpecData, IfElse, IfSpecData, ImportKind, ObjBody, Slice, SliceDesc,7 FieldMember, FieldName, ForSpecData, IfElse, IfSpecData, ImportKind, ObjBody, Slice, SliceDesc,
8 Source, SourcePath, Spanned,8 Source, SourcePath, Spanned,
9};9};
10#[cfg(feature = "ir-parser")]
11use jrsonnet_ir_parser::ParserSettings;
12#[cfg(not(feature = "ir-parser"))]
10use jrsonnet_peg_parser::ParserSettings;13use jrsonnet_peg_parser::ParserSettings;
11use rustc_hash::FxHashMap;14use rustc_hash::FxHashMap;
1215
323 };326 };
324 let source = Source::new(path.clone(), code.clone());327 let source = Source::new(path.clone(), code.clone());
325 // If failed - then skip import328 // If failed - then skip import
326 file.parsed = jrsonnet_peg_parser::parse(&code, &ParserSettings { source })329 file.parsed = crate::parse_jsonnet(&code, &ParserSettings { source })
327 .map(Rc::new)330 .map(Rc::new)
328 .ok();331 .ok();
329 if let Some(parsed) = &file.parsed {332 if let Some(parsed) = &file.parsed {
modifiedcrates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth
154 ImportNotSupported(SourcePath, ResolvePathOwned),154 ImportNotSupported(SourcePath, ResolvePathOwned),
155 #[error("can't import from virtual file")]155 #[error("can't import from virtual file")]
156 CantImportFromVirtualFile,156 CantImportFromVirtualFile,
157 #[cfg(not(feature = "ir-parser"))]
157 #[error(158 #[error(
158 "syntax error: {}",159 "syntax error: {}",
159 // Peg has no fancier way to handle critical parsing errors https://github.com/kevinmehall/rust-peg/issues/225160 // Peg has no fancier way to handle critical parsing errors https://github.com/kevinmehall/rust-peg/issues/225
172 error: Box<jrsonnet_peg_parser::ParseError>,173 error: Box<jrsonnet_peg_parser::ParseError>,
173 },174 },
175
176 #[cfg(feature = "ir-parser")]
177 #[error("syntax error: {error}")]
178 ImportSyntaxError {
179 path: Source,
180 #[trace(skip)]
181 error: Box<jrsonnet_ir_parser::ParseError>,
182 },
174183
175 #[error("runtime error: {}", format_empty_str(.0))]184 #[error("runtime error: {}", format_empty_str(.0))]
176 RuntimeError(IStr),185 RuntimeError(IStr),
modifiedcrates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth
46use jrsonnet_ir::{Expr, Source, SourcePath};46use jrsonnet_ir::{Expr, Source, SourcePath};
47#[doc(hidden)]47#[doc(hidden)]
48pub use jrsonnet_macros;48pub use jrsonnet_macros;
49#[cfg(feature = "ir-parser")]
50use jrsonnet_ir_parser::ParserSettings;
51#[cfg(not(feature = "ir-parser"))]
49use jrsonnet_peg_parser::ParserSettings;52use jrsonnet_peg_parser::ParserSettings;
50pub use obj::*;53pub use obj::*;
51pub use rustc_hash;54pub use rustc_hash;
5659
57use crate::gc::WithCapacityExt as _;60use crate::gc::WithCapacityExt as _;
61
62#[cfg(feature = "ir-parser")]
63pub(crate) fn parse_jsonnet(
64 code: &str,
65 settings: &ParserSettings,
66) -> Result<Expr, jrsonnet_ir_parser::ParseError> {
67 jrsonnet_ir_parser::parse(code, settings)
68}
69
70#[cfg(not(feature = "ir-parser"))]
71pub(crate) fn parse_jsonnet(
72 code: &str,
73 settings: &ParserSettings,
74) -> Result<Expr, jrsonnet_peg_parser::ParseError> {
75 jrsonnet_peg_parser::parse(code, settings)
76}
5877
59cc_dyn!(78cc_dyn!(
60 #[derive(Clone)]79 #[derive(Clone)]
345 let file_name = Source::new(path.clone(), code.clone());364 let file_name = Source::new(path.clone(), code.clone());
346 if file.parsed.is_none() {365 if file.parsed.is_none() {
347 file.parsed = Some(366 file.parsed = Some(
348 jrsonnet_peg_parser::parse(367 parse_jsonnet(
349 &code,368 &code,
350 &ParserSettings {369 &ParserSettings {
351 source: file_name.clone(),370 source: file_name.clone(),
461 pub fn evaluate_snippet(&self, name: impl Into<IStr>, code: impl Into<IStr>) -> Result<Val> {480 pub fn evaluate_snippet(&self, name: impl Into<IStr>, code: impl Into<IStr>) -> Result<Val> {
462 let code = code.into();481 let code = code.into();
463 let source = Source::new_virtual(name.into(), code.clone());482 let source = Source::new_virtual(name.into(), code.clone());
464 let parsed = jrsonnet_peg_parser::parse(483 let parsed = parse_jsonnet(
465 &code,484 &code,
466 &ParserSettings {485 &ParserSettings {
467 source: source.clone(),486 source: source.clone(),
482 ) -> Result<Val> {501 ) -> Result<Val> {
483 let code = code.into();502 let code = code.into();
484 let source = Source::new_virtual(name.into(), code.clone());503 let source = Source::new_virtual(name.into(), code.clone());
485 let parsed = jrsonnet_peg_parser::parse(504 let parsed = parse_jsonnet(
486 &code,505 &code,
487 &ParserSettings {506 &ParserSettings {
488 source: source.clone(),507 source: source.clone(),
modifiedcrates/jrsonnet-ir-parser/Cargo.tomldiffbeforeafterboth
6repository.workspace = true6repository.workspace = true
7version.workspace = true7version.workspace = true
8
9[features]
10exp-null-coaelse = ["jrsonnet-ir/exp-null-coaelse"]
811
9[dependencies]12[dependencies]
10insta.workspace = true13insta.workspace = true
modifiedcrates/jrsonnet-ir-parser/src/lib.rsdiffbeforeafterboth
1use std::rc::Rc;1use std::rc::Rc;
22
3use insta::assert_snapshot;
4use jrsonnet_gcmodule::Acyclic;3use jrsonnet_gcmodule::Acyclic;
5use jrsonnet_ir::{4use jrsonnet_ir::{
6 AssertExpr, AssertStmt, Expr, IfElse, IfSpecData, LiteralType, Slice, SliceDesc, Source,5 unescape, ArgsDesc, AssertExpr, AssertStmt, BinaryOp, BinaryOpType, BindSpec, CompSpec,
6 Destruct, Expr, ExprParam, ExprParams, FieldMember, FieldName, ForSpecData, IStr, IfElse,
7 IfSpecData, ImportKind, IndexPart, LiteralType, Member, ObjBody, ObjComp, ObjMembers, Slice,
7 SourceVirtual, Span, Spanned,8 SliceDesc, Source, Span, Spanned, UnaryOpType, Visibility,
8};9};
9use jrsonnet_lexer::{Lexeme, Lexer, SyntaxKind, T};10use jrsonnet_lexer::{collect_lexed_str_block, Lexeme, Lexer, SyntaxKind, T};
1011
12pub struct ParserSettings {
13 pub source: Source,
14}
15
16#[derive(Debug, Clone)]
17pub struct ParseErrorLocation {
18 pub offset: usize,
19}
20
21#[derive(Debug, Clone)]
22pub struct ParseError {
23 pub message: String,
24 pub location: ParseErrorLocation,
25}
26
27impl std::fmt::Display for ParseError {
28 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29 write!(f, "{}", self.message)
30 }
31}
32
33type R<T> = Result<T, ParseError>;
34
11struct Parser<'a> {35struct Parser<'a> {
12 lexemes: Vec<Lexeme<'a>>,36 lexemes: Vec<Lexeme<'a>>,
13 offset: usize,37 offset: usize,
14 source: Source,38 source: Source,
15}39}
1640
17impl<'a> Parser<'a> {41impl<'a> Parser<'a> {
18 fn new(s: &'a str) -> Self {42 fn new(code: &'a str, source: Source) -> Self {
19 Self {43 Self {
20 lexemes: Lexer::new(s)44 lexemes: Lexer::new(code)
21 .filter(|l| l.kind != SyntaxKind::WHITESPACE)45 .filter(|l| {
46 !matches!(
47 l.kind,
48 SyntaxKind::WHITESPACE
49 | SyntaxKind::SINGLE_LINE_SLASH_COMMENT
50 | SyntaxKind::SINGLE_LINE_HASH_COMMENT
51 | SyntaxKind::MULTI_LINE_COMMENT
52 )
53 })
22 .collect(),54 .collect(),
23 offset: 0,55 offset: 0,
24 source: Source::new_virtual("<test>".into(), s.into()),56 source,
25 }57 }
26 }58 }
59
27 fn peek(&self) -> SyntaxKind {60 fn peek(&self) -> SyntaxKind {
28 self.lexemes[self.offset].kind61 if self.at_eof() {
62 SyntaxKind::EOF
63 } else {
64 self.lexemes[self.offset].kind
65 }
29 }66 }
67
30 fn text(&self) -> &str {68 fn text(&self) -> &'a str {
31 self.lexemes[self.offset].text69 self.lexemes[self.offset].text
32 }70 }
71
33 fn at(&self, kind: SyntaxKind) -> bool {72 fn at(&self, kind: SyntaxKind) -> bool {
34 !self.at_eof() && self.peek() == kind73 !self.at_eof() && self.peek() == kind
35 }74 }
75
36 fn eat_any(&mut self) {76 fn eat_any(&mut self) {
37 self.offset += 177 self.offset += 1;
38 }78 }
3979
40 fn at_eof(&self) -> bool {80 fn at_eof(&self) -> bool {
41 self.offset == self.lexemes.len()81 self.offset >= self.lexemes.len()
42 }82 }
4383
44 fn try_eat(&mut self, t: SyntaxKind) -> bool {84 fn try_eat(&mut self, t: SyntaxKind) -> bool {
48 }88 }
49 false89 false
50 }90 }
91
51 fn eat(&mut self, t: SyntaxKind) {92 fn current_desc(&self) -> String {
93 if self.at_eof() {
94 return "end of file".to_owned();
95 }
96 let kind = self.peek();
97 let text = self.text();
98 let name = kind.display_name();
99 if matches!(kind, SyntaxKind::IDENT | SyntaxKind::FLOAT) {
100 format!("{name} \"{text}\"")
101 } else {
102 name.to_owned()
103 }
104 }
105
106 fn eat(&mut self, t: SyntaxKind) -> R<()> {
52 assert_eq!(self.peek(), t);107 if !self.at(t) {
108 return Err(self.error(format!(
109 "expected {}, got {}",
110 t.display_name(),
111 self.current_desc(),
112 )));
113 }
53 self.eat_any();114 self.eat_any();
115 Ok(())
54 }116 }
55117
56 fn span_start(&self) -> u32 {118 fn span_start(&self) -> u32 {
119 if self.at_eof() {
120 if let Some(last) = self.lexemes.last() {
121 return last.range.1;
122 }
123 return 0;
124 }
57 self.lexemes[self.offset].range.0125 self.lexemes[self.offset].range.0
58 }126 }
127
59 fn span_end(&self) -> u32 {128 fn span_end(&self) -> u32 {
60 self.lexemes[self.offset - 1].range.1129 self.lexemes[self.offset - 1].range.1
61 }130 }
131
132 fn error(&self, message: String) -> ParseError {
133 ParseError {
134 location: ParseErrorLocation {
135 offset: self.span_start() as usize,
136 },
137 message,
138 }
139 }
140
141 fn expect_ident(&mut self) -> R<IStr> {
142 if !self.at(SyntaxKind::IDENT) {
143 return Err(self.error(format!("expected identifier, got {}", self.current_desc())));
144 }
145 let text = self.text();
146 if is_reserved(text) {
147 return Err(self.error(format!(
148 "expected identifier, got reserved word '{text}'"
149 )));
150 }
151 let s: IStr = text.into();
152 self.eat_any();
153 Ok(s)
154 }
155
156 fn at_ident(&self) -> bool {
157 self.at(SyntaxKind::IDENT) && !is_reserved(self.lexemes[self.offset].text)
158 }
62}159}
63160
161fn is_reserved(s: &str) -> bool {
162 matches!(
163 s,
164 "assert"
165 | "else" | "error"
166 | "false" | "for"
167 | "function" | "if"
168 | "import" | "importstr"
169 | "importbin" | "in"
170 | "local" | "null"
171 | "tailstrict" | "then"
172 | "self" | "super"
173 | "true"
174 )
175}
176
177fn spanned<T: Acyclic>(p: &mut Parser<'_>, cb: impl FnOnce(&mut Parser<'_>) -> R<T>) -> R<Spanned<T>> {
178 let start = p.span_start();
179 let v = cb(p)?;
180 let end = p.span_end();
181 Ok(Spanned::new(v, Span(p.source.clone(), start, end)))
182}
183
184fn parse_string_content(p: &mut Parser<'_>) -> R<IStr> {
185 let kind = p.peek();
186 let text = p.text();
187 let s = match kind {
188 SyntaxKind::STRING_DOUBLE => {
189 let inner = &text[1..text.len() - 1];
190 unescape::unescape(inner)
191 .ok_or_else(|| p.error("invalid string escape".into()))?
192 }
193 SyntaxKind::STRING_SINGLE => {
194 let inner = &text[1..text.len() - 1];
195 unescape::unescape(inner)
196 .ok_or_else(|| p.error("invalid string escape".into()))?
197 }
198 SyntaxKind::STRING_DOUBLE_VERBATIM => {
199 let inner = &text[2..text.len() - 1];
200 inner.replace("\"\"", "\"")
201 }
202 SyntaxKind::STRING_SINGLE_VERBATIM => {
203 let inner = &text[2..text.len() - 1];
204 inner.replace("''", "'")
205 }
206 SyntaxKind::STRING_BLOCK => {
207 let inner = &text[3..];
208 let collected = collect_lexed_str_block(inner)
209 .map_err(|_| p.error("invalid string block".into()))?;
210 let mut result = String::new();
211 for (i, line) in collected.lines.iter().enumerate() {
212 if i > 0 {
213 result.push('\n');
214 }
215 result.push_str(line);
216 }
217 if !collected.truncate {
218 result.push('\n');
219 }
220 result
221 }
222 _ => return Err(p.error(format!("expected string, got {}", p.current_desc()))),
223 };
224 p.eat_any();
225 Ok(s.into())
226}
227
228fn is_string_token(kind: SyntaxKind) -> bool {
229 matches!(
230 kind,
231 SyntaxKind::STRING_DOUBLE
232 | SyntaxKind::STRING_SINGLE
233 | SyntaxKind::STRING_DOUBLE_VERBATIM
234 | SyntaxKind::STRING_SINGLE_VERBATIM
235 | SyntaxKind::STRING_BLOCK
236 )
237}
238
239fn parse_number(p: &mut Parser<'_>) -> R<f64> {
240 let text = p.text();
241 let n: f64 = text
242 .replace('_', "")
243 .parse()
244 .map_err(|_| p.error(format!("invalid number literal: {text}")))?;
245 if !n.is_finite() {
246 return Err(p.error("numbers are finite".into()));
247 }
248 p.eat_any();
249 Ok(n)
250}
251
64fn literal(p: &mut Parser<'_>) -> Option<LiteralType> {252fn literal(p: &mut Parser<'_>) -> Option<LiteralType> {
65 let t = match p.peek() {253 let t = match p.peek() {
75 Some(t)263 Some(t)
76}264}
77265
78fn spanned<T: Acyclic>(p: &mut Parser<'_>, cb: impl FnOnce(&mut Parser<'_>) -> T) -> Spanned<T> {266fn assert_stmt(p: &mut Parser<'_>) -> R<AssertStmt> {
79 let start = p.span_start();267 p.eat(T![assert])?;
80 let v = cb(p);
81 let end = p.span_end();
82
83 Spanned::new(v, Span(p.source.clone(), start, end))
84}
85
86fn assert_stmt(p: &mut Parser<'_>) -> AssertStmt {
87 p.eat(T![assert]);
88 let cond = spanned(p, expr);268 let cond = spanned(p, expr)?;
89 dbg!(p.peek());
90 let msg = if p.try_eat(T![:]) {269 let msg = if p.try_eat(T![:]) {
91 Some(spanned(p, expr))270 Some(spanned(p, expr)?)
92 } else {271 } else {
93 None272 None
94 };273 };
95 dbg!(AssertStmt(cond, msg))274 Ok(AssertStmt(cond, msg))
96}275}
97276
98fn if_spec_data(p: &mut Parser<'_>) -> IfSpecData {277fn if_spec_data(p: &mut Parser<'_>) -> R<IfSpecData> {
99 let v = spanned(p, |p| p.eat(T![if]));278 let v = spanned(p, |p| p.eat(T![if]))?;
100 let cond = expr(p);279 let cond = expr(p)?;
101 IfSpecData { span: v.span, cond }280 Ok(IfSpecData { span: v.span, cond })
102}281}
103282
104fn if_else(p: &mut Parser<'_>) -> IfElse {283fn if_else(p: &mut Parser<'_>) -> R<IfElse> {
105 let cond = if_spec_data(p);284 let cond = if_spec_data(p)?;
106 p.eat(T![then]);285 p.eat(T![then])?;
107 let cond_then = expr(p);286 let cond_then = expr(p)?;
108 let cond_else = if p.at(T![else]) { Some(expr(p)) } else { None };287 let cond_else = if p.try_eat(T![else]) {
288 Some(expr(p)?)
289 } else {
290 None
291 };
109 IfElse {292 Ok(IfElse {
110 cond,293 cond,
111 cond_then,294 cond_then,
112 cond_else,295 cond_else,
113 }296 })
114}297}
115298
116fn slice_desc(p: &mut Parser<'_>, start: Option<Spanned<Expr>>) -> SliceDesc {299fn slice_desc(p: &mut Parser<'_>, start: Option<Spanned<Expr>>) -> R<SliceDesc> {
117 // start
118 p.eat(T![:]);300 p.eat(T![:])?;
119 let end = if !p.at(T![:]) && !p.at(T![']']) {301 let end = if !p.at(T![:]) && !p.at(T![']']) {
120 Some(spanned(p, expr))302 Some(spanned(p, expr)?)
121 } else {303 } else {
122 None304 None
123 };305 };
124 let step = if p.try_eat(T![:]) && !p.at(T![']']) {306 let step = if p.try_eat(T![:]) {
307 if !p.at(T![']']) {
125 Some(spanned(p, expr))308 Some(spanned(p, expr)?)
309 } else {
310 None
311 }
126 } else {312 } else {
127 None313 None
128 };314 };
129 SliceDesc { start, end, step }315 Ok(SliceDesc { start, end, step })
130}316}
131317
132fn expr_simple(p: &mut Parser<'_>) -> Expr {318fn destruct(p: &mut Parser<'_>) -> R<Destruct> {
133 let mut e = if let Some(literal) = literal(p) {319 Ok(Destruct::Full(p.expect_ident()?))
134 Expr::Literal(literal)320}
321
322fn params(p: &mut Parser<'_>) -> R<ExprParams> {
323 if p.at(T![')']) {
324 return Ok(ExprParams::new(Vec::new()));
325 }
326 let mut result = Vec::new();
327 loop {
328 let d = destruct(p)?;
329 let default = if p.try_eat(T![=]) {
330 Some(Rc::new(expr(p)?))
331 } else {
332 None
333 };
334 result.push(ExprParam {
335 destruct: d,
336 default,
337 });
338 if !p.try_eat(T![,]) {
339 break;
340 }
341 if p.at(T![')']) {
342 break;
343 }
344 }
345 Ok(ExprParams::new(result))
346}
347
348fn args(p: &mut Parser<'_>) -> R<ArgsDesc> {
349 if p.at(T![')']) {
350 return Ok(ArgsDesc::new(Vec::new(), Vec::new()));
351 }
352 let mut unnamed = Vec::new();
353 let mut named = Vec::new();
354 let mut named_started = false;
355 loop {
356 let is_named = p.at_ident() && {
357 let next_offset = p.offset + 1;
358 next_offset < p.lexemes.len() && p.lexemes[next_offset].kind == T![=] && {
359 let after_eq = next_offset + 1;
360 after_eq >= p.lexemes.len() || p.lexemes[after_eq].kind != T![=]
361 }
362 };
363 if is_named {
364 let name: IStr = p.expect_ident()?;
365 p.eat(T![=])?;
366 let value = Rc::new(expr(p)?);
367 named.push((name, value));
368 named_started = true;
369 } else {
370 if named_started {
371 return Err(p.error("positional argument after named argument".into()));
372 }
373 unnamed.push(Rc::new(expr(p)?));
374 }
375 if !p.try_eat(T![,]) {
376 break;
377 }
378 if p.at(T![')']) {
379 break;
380 }
381 }
382 Ok(ArgsDesc::new(unnamed, named))
383}
384
385fn bind(p: &mut Parser<'_>) -> R<BindSpec> {
386 let name = p.expect_ident()?;
387 if p.try_eat(T!['(']) {
388 let ps = params(p)?;
389 p.eat(T![')'])?;
390 p.eat(T![=])?;
391 let value = Rc::new(expr(p)?);
392 Ok(BindSpec::Function {
393 name,
394 params: ps,
395 value,
396 })
397 } else {
398 p.eat(T![=])?;
399 let value = Rc::new(expr(p)?);
400 Ok(BindSpec::Field {
401 into: Destruct::Full(name),
402 value,
403 })
404 }
405}
406
407fn visibility(p: &mut Parser<'_>) -> R<Visibility> {
408 p.eat(T![:])?;
409 if p.try_eat(T![:]) {
410 if p.try_eat(T![:]) {
411 Ok(Visibility::Unhide)
412 } else {
413 Ok(Visibility::Hidden)
414 }
415 } else {
416 Ok(Visibility::Normal)
417 }
418}
419
420fn field_name(p: &mut Parser<'_>) -> R<FieldName> {
421 if p.at_ident() {
422 Ok(FieldName::Fixed(p.expect_ident()?))
423 } else if is_string_token(p.peek()) {
424 Ok(FieldName::Fixed(parse_string_content(p)?))
425 } else if p.at(T!['[']) {
426 p.eat(T!['['])?;
427 let e = expr(p)?;
428 p.eat(T![']'])?;
429 Ok(FieldName::Dyn(e))
430 } else {
431 Err(p.error(format!("expected field name, got {}", p.current_desc())))
432 }
433}
434
435fn field(p: &mut Parser<'_>) -> R<FieldMember> {
436 let name = spanned(p, field_name)?;
437
438 if p.at(T!['(']) {
439 p.eat(T!['('])?;
440 let ps = params(p)?;
441 p.eat(T![')'])?;
442 let vis = visibility(p)?;
443 let value = Rc::new(expr(p)?);
444 Ok(FieldMember {
445 name,
446 plus: false,
447 params: Some(ps),
448 visibility: vis,
449 value,
450 })
451 } else {
452 let plus = p.try_eat(T![+]);
453 let vis = visibility(p)?;
454 let value = Rc::new(expr(p)?);
455 Ok(FieldMember {
456 name,
457 plus,
458 params: None,
459 visibility: vis,
460 value,
461 })
462 }
463}
464
465fn member(p: &mut Parser<'_>) -> R<Member> {
466 if p.at(T![local]) {
467 p.eat(T![local])?;
468 Ok(Member::BindStmt(bind(p)?))
135 } else if p.at(T![assert]) {469 } else if p.at(T![assert]) {
136 let assert = assert_stmt(p);470 Ok(Member::AssertStmt(assert_stmt(p)?))
137 p.eat(T![;]);
138 let rest = expr(p);
139 Expr::AssertExpr(Rc::new(AssertExpr { assert, rest }))
140 } else if p.at(T![if]) {
141 Expr::IfElse(Box::new(if_else(p)))
142 } else {471 } else {
143 panic!("unexpected token: {:?}", p.peek());472 Ok(Member::Field(field(p)?))
473 }
474}
475
476fn for_spec(p: &mut Parser<'_>) -> R<ForSpecData> {
477 p.eat(T![for])?;
478 let d = destruct(p)?;
479 p.eat(T![in])?;
480 let over = expr(p)?;
481 Ok(ForSpecData { destruct: d, over })
482}
483
484fn compspecs(p: &mut Parser<'_>) -> R<Vec<CompSpec>> {
485 let mut specs = Vec::new();
486 specs.push(CompSpec::ForSpec(for_spec(p)?));
487 loop {
488 if p.at(T![for]) {
489 specs.push(CompSpec::ForSpec(for_spec(p)?));
490 } else if p.at(T![if]) {
491 let isd = if_spec_data(p)?;
492 specs.push(CompSpec::IfSpec(isd));
493 } else {
494 break;
495 }
496 }
497 Ok(specs)
498}
499
500fn objinside(p: &mut Parser<'_>) -> R<ObjBody> {
501 if p.at(T!['}']) {
502 return Ok(ObjBody::MemberList(ObjMembers {
503 locals: Rc::new(Vec::new()),
504 asserts: Rc::new(Vec::new()),
505 fields: Vec::new(),
506 }));
507 }
508
509 let mut members = Vec::new();
510 loop {
511 members.push(member(p)?);
512 if !p.try_eat(T![,]) {
513 break;
514 }
515 if p.at(T!['}']) || p.at(T![for]) {
516 break;
517 }
518 }
519
520 if p.at(T![for]) {
521 let specs = compspecs(p)?;
522 let mut locals = Vec::new();
523 let mut field_member = None;
524 for m in members {
525 match m {
526 Member::Field(f) => {
527 if field_member.is_some() {
528 return Err(p.error(
529 "object comprehension can only contain one field".into(),
530 ));
531 }
532 field_member = Some(f);
533 }
534 Member::BindStmt(b) => locals.push(b),
535 Member::AssertStmt(_) => {
536 return Err(p.error(
537 "asserts are unsupported in object comprehension".into(),
538 ));
539 }
540 }
541 }
542 Ok(ObjBody::ObjComp(ObjComp {
543 locals: Rc::new(locals),
544 field: Rc::new(
545 field_member.ok_or_else(|| {
546 p.error("missing object comprehension field".into())
547 })?,
548 ),
549 compspecs: specs,
550 }))
551 } else {
552 let mut locals = Vec::new();
553 let mut asserts = Vec::new();
554 let mut fields = Vec::new();
555 for m in members {
556 match m {
557 Member::Field(f) => fields.push(f),
558 Member::BindStmt(b) => locals.push(b),
559 Member::AssertStmt(a) => asserts.push(a),
560 }
561 }
562 Ok(ObjBody::MemberList(ObjMembers {
563 locals: Rc::new(locals),
564 asserts: Rc::new(asserts),
565 fields,
566 }))
567 }
568}
569
570fn expr_basic(p: &mut Parser<'_>) -> R<Expr> {
571 if let Some(lit) = literal(p) {
572 return Ok(Expr::Literal(lit));
573 }
574
575 match p.peek() {
576 SyntaxKind::STRING_DOUBLE
577 | SyntaxKind::STRING_SINGLE
578 | SyntaxKind::STRING_DOUBLE_VERBATIM
579 | SyntaxKind::STRING_SINGLE_VERBATIM
580 | SyntaxKind::STRING_BLOCK => Ok(Expr::Str(parse_string_content(p)?)),
581
582 SyntaxKind::FLOAT => Ok(Expr::Num(parse_number(p)?)),
583
584 T!['('] => {
585 p.eat(T!['('])?;
586 let e = expr(p)?;
587 p.eat(T![')'])?;
588 Ok(e)
589 }
590
591 T!['['] => {
592 p.eat(T!['['])?;
593 if p.at(T![']']) {
594 p.eat(T![']'])?;
595 return Ok(Expr::Arr(Rc::new(Vec::new())));
596 }
597 let first = expr(p)?;
598 if p.at(T![for]) {
599 let specs = compspecs(p)?;
600 p.eat(T![']'])?;
601 Ok(Expr::ArrComp(Rc::new(first), specs))
602 } else if p.at(T![,]) && {
603 let next = p.offset + 1;
604 next < p.lexemes.len() && p.lexemes[next].kind == T![for]
605 } {
606 p.eat(T![,])?;
607 let specs = compspecs(p)?;
608 p.eat(T![']'])?;
609 Ok(Expr::ArrComp(Rc::new(first), specs))
610 } else {
611 let mut elems = vec![first];
612 while p.try_eat(T![,]) {
613 if p.at(T![']']) {
614 break;
615 }
616 elems.push(expr(p)?);
617 }
618 p.eat(T![']'])?;
619 Ok(Expr::Arr(Rc::new(elems)))
620 }
621 }
622
623 T!['{'] => {
624 p.eat(T!['{'])?;
625 let body = objinside(p)?;
626 p.eat(T!['}'])?;
627 Ok(Expr::Obj(body))
628 }
629
630 T![local] => {
631 p.eat(T![local])?;
632 let mut binds = Vec::new();
633 loop {
634 binds.push(bind(p)?);
635 if !p.try_eat(T![,]) {
636 break;
637 }
638 }
639 p.eat(T![;])?;
640 let body = expr(p)?;
641 Ok(Expr::LocalExpr(binds, Box::new(body)))
642 }
643
644 T![if] => Ok(Expr::IfElse(Box::new(if_else(p)?))),
645
646 T![function] => {
647 p.eat(T![function])?;
648 p.eat(T!['('])?;
649 let ps = params(p)?;
650 p.eat(T![')'])?;
651 let body = expr(p)?;
652 Ok(Expr::Function(ps, Rc::new(body)))
653 }
654
655 T![assert] => {
656 let a = assert_stmt(p)?;
657 p.eat(T![;])?;
658 let rest = expr(p)?;
659 Ok(Expr::AssertExpr(Rc::new(AssertExpr { assert: a, rest })))
660 }
661
662 T![error] => {
663 let span = spanned(p, |p| p.eat(T![error]))?;
664 let e = expr(p)?;
665 Ok(Expr::ErrorStmt(span.span, Box::new(e)))
666 }
667
668 T![importstr] => {
669 let kind = spanned(p, |p| {
670 p.eat(T![importstr])?;
671 Ok(ImportKind::Str)
672 })?;
673 let path = expr(p)?;
674 Ok(Expr::Import(kind, Box::new(path)))
675 }
676
677 T![importbin] => {
678 let kind = spanned(p, |p| {
679 p.eat(T![importbin])?;
680 Ok(ImportKind::Bin)
681 })?;
682 let path = expr(p)?;
683 Ok(Expr::Import(kind, Box::new(path)))
684 }
685
686 T![import] => {
687 let kind = spanned(p, |p| {
688 p.eat(T![import])?;
689 Ok(ImportKind::Normal)
690 })?;
691 let path = expr(p)?;
692 Ok(Expr::Import(kind, Box::new(path)))
693 }
694
695 SyntaxKind::IDENT => {
696 let text = p.text();
697 if is_reserved(text) {
698 return Err(p.error(format!("unexpected reserved word '{text}'")));
699 }
700 let n = spanned(p, |p| {
701 let s: IStr = p.text().into();
702 p.eat_any();
703 Ok(s)
704 })?;
705 Ok(Expr::Var(n))
706 }
707
708 _ => Err(p.error(format!("unexpected {}", p.current_desc()))),
709 }
710}
711
712/// Flush accumulated index parts into an Expr::Index wrapping `e`.
713fn flush_index_parts(e: &mut Expr, parts: &mut Vec<IndexPart>) {
714 if parts.is_empty() {
715 return;
716 }
717 let old = std::mem::replace(e, Expr::Literal(LiteralType::Null));
718 *e = Expr::Index {
719 indexable: Box::new(old),
720 parts: std::mem::take(parts),
144 };721 };
722}
145723
146 dbg!(&e);724fn expr_suffix(p: &mut Parser<'_>) -> R<Expr> {
725 let mut e = expr_basic(p)?;
726 // Accumulate consecutive index parts (.field, [expr], ?.field, ?.[expr])
727 // into a single Expr::Index. This is critical for null-coalesce semantics:
728 // a?.b.c needs all parts in one Index so the evaluator can skip .c when .b is null.
729 let mut parts: Vec<IndexPart> = Vec::new();
147730
148 loop {731 loop {
149 if p.try_eat(T!['[']) {732 #[cfg(feature = "exp-null-coaelse")]
733 if p.at(T![?]) {
734 p.eat_any();
735 if p.try_eat(T![.]) {
150 if p.at(T![:]) {736 if p.at(T!['[']) {
151 let slice = slice_desc(p, None);737 // ?.[expr]
738 p.eat(T!['['])?;
739 let idx = spanned(p, expr)?;
152 e = Expr::Slice(Box::new(Slice { value: e, slice }));740 p.eat(T![']'])?;
741 parts.push(IndexPart {
742 span: idx.span,
743 value: idx.value,
744 null_coaelse: true,
745 });
746 } else {
747 // ?.field
748 let id_spanned = spanned(p, |p| {
749 let name = p.expect_ident()?;
750 Ok(Expr::Str(name))
751 })?;
752 parts.push(IndexPart {
753 span: id_spanned.span,
754 value: id_spanned.value,
755 null_coaelse: true,
756 });
153 p.eat(T![']']);757 }
758 } else {
154 continue;759 return Err(p.error("expected '.' after '?'".into()));
155 }760 }
761 continue;
762 }
156763
157 let idx = spanned(p, expr);764 if p.at(T![.]) {
765 p.eat(T![.])?;
766 let id_spanned = spanned(p, |p| {
767 let name = p.expect_ident()?;
768 Ok(Expr::Str(name))
769 })?;
770 parts.push(IndexPart {
771 span: id_spanned.span,
772 value: id_spanned.value,
773 #[cfg(feature = "exp-null-coaelse")]
774 null_coaelse: false,
775 });
776 } else if p.at(T!['[']) {
777 p.eat(T!['['])?;
778
158 if p.at(T![:]) {779 if p.at(T![:]) {
159 let slice = slice_desc(p, Some(idx));780 // Slice: flush index parts first, then handle slice
781 flush_index_parts(&mut e, &mut parts);
782 let slice = slice_desc(p, None)?;
783 p.eat(T![']'])?;
160 e = Expr::Slice(Box::new(Slice { value: e, slice }));784 e = Expr::Slice(Box::new(Slice { value: e, slice }));
161 } else {785 } else {
786 let idx = spanned(p, expr)?;
787 if p.at(T![:]) {
788 // Slice with start: flush index parts first
789 flush_index_parts(&mut e, &mut parts);
790 let slice = slice_desc(p, Some(idx))?;
791 p.eat(T![']'])?;
792 e = Expr::Slice(Box::new(Slice { value: e, slice }));
793 } else {
794 // Bracket index: add to parts
795 p.eat(T![']'])?;
796 parts.push(IndexPart {
797 span: idx.span,
798 value: idx.value,
799 #[cfg(feature = "exp-null-coaelse")]
800 null_coaelse: false,
801 });
802 }
162 }803 }
163 p.eat(T![']']);804 } else if p.at(T!['(']) {
805 flush_index_parts(&mut e, &mut parts);
806 let args_spanned = spanned(p, |p| {
807 p.eat(T!['('])?;
808 let a = args(p)?;
809 p.eat(T![')'])?;
810 Ok(a)
811 })?;
812 let tailstrict = p.try_eat(T![tailstrict]);
813 e = Expr::Apply(Box::new(e), args_spanned, tailstrict);
814 } else if p.at(T!['{']) {
815 flush_index_parts(&mut e, &mut parts);
816 p.eat(T!['{'])?;
817 let body = objinside(p)?;
818 p.eat(T!['}'])?;
819 e = Expr::ObjExtend(Rc::new(e), body);
164 } else {820 } else {
165 break;821 break;
166 }822 }
167 }823 }
168824
169 dbg!(e)825 flush_index_parts(&mut e, &mut parts);
826 Ok(e)
170}827}
171828
172fn expr(p: &mut Parser<'_>) -> Expr {829fn prefix_binding_power(op: UnaryOpType) -> u8 {
173 expr_simple(p)830 match op {
831 UnaryOpType::Plus | UnaryOpType::Minus | UnaryOpType::Not | UnaryOpType::BitNot => 20,
832 }
174}833}
175834
835fn infix_binding_power(op: BinaryOpType) -> (u8, u8) {
836 match op {
837 BinaryOpType::Or => (2, 3),
176#[test]838 #[cfg(feature = "exp-null-coaelse")]
177fn basic_test() {839 BinaryOpType::NullCoaelse => (2, 3),
840 BinaryOpType::And => (4, 5),
178 let mut parser = Parser::new(" assert true[false] : false ; true ");841 BinaryOpType::BitOr => (6, 7),
842 BinaryOpType::BitXor => (8, 9),
843 BinaryOpType::BitAnd => (10, 11),
844 BinaryOpType::Eq | BinaryOpType::Neq => (12, 13),
845 BinaryOpType::Lt
846 | BinaryOpType::Gt
847 | BinaryOpType::Lte
848 | BinaryOpType::Gte
849 | BinaryOpType::In => (14, 15),
179 let e = expr(&mut parser);850 BinaryOpType::Lhs | BinaryOpType::Rhs => (16, 17),
180 let l = &parser.lexemes;851 BinaryOpType::Add | BinaryOpType::Sub => (18, 19),
852 BinaryOpType::Mul | BinaryOpType::Div | BinaryOpType::Mod => (20, 21),
853 }
854}
181855
182 assert_snapshot!(format!("{l:#?}\n\n---\n\n{e:#?}"));856fn unary_op(kind: SyntaxKind) -> Option<UnaryOpType> {
857 match kind {
858 T![+] => Some(UnaryOpType::Plus),
859 T![-] => Some(UnaryOpType::Minus),
860 T![!] => Some(UnaryOpType::Not),
861 T![~] => Some(UnaryOpType::BitNot),
862 _ => None,
863 }
864}
865
866fn binary_op(p: &Parser<'_>) -> Option<BinaryOpType> {
867 match p.peek() {
868 T![||] => Some(BinaryOpType::Or),
869 T![&&] => Some(BinaryOpType::And),
870 T![|] => Some(BinaryOpType::BitOr),
871 T![^] => Some(BinaryOpType::BitXor),
872 T![&] => Some(BinaryOpType::BitAnd),
873 T![==] => Some(BinaryOpType::Eq),
874 T![!=] => Some(BinaryOpType::Neq),
875 T![<] => Some(BinaryOpType::Lt),
876 T![>] => Some(BinaryOpType::Gt),
877 T![<=] => Some(BinaryOpType::Lte),
878 T![>=] => Some(BinaryOpType::Gte),
879 T![<<] => Some(BinaryOpType::Lhs),
880 T![>>] => Some(BinaryOpType::Rhs),
881 T![+] => Some(BinaryOpType::Add),
882 T![-] => Some(BinaryOpType::Sub),
883 T![*] => Some(BinaryOpType::Mul),
884 T![/] => Some(BinaryOpType::Div),
885 T![%] => Some(BinaryOpType::Mod),
886 T![in] => Some(BinaryOpType::In),
887 #[cfg(feature = "exp-null-coaelse")]
888 T![??] => Some(BinaryOpType::NullCoaelse),
889 _ => None,
890 }
891}
892
893fn expr_bp(p: &mut Parser<'_>, min_bp: u8) -> R<Expr> {
894 let mut lhs = if let Some(op) = unary_op(p.peek()) {
895 p.eat_any();
896 let rbp = prefix_binding_power(op);
897 let rhs = expr_bp(p, rbp)?;
898 Expr::UnaryOp(op, Box::new(rhs))
899 } else {
900 expr_suffix(p)?
901 };
902
903 loop {
904 if p.at_eof() {
905 break;
906 }
907
908 let Some(op) = binary_op(p) else {
909 break;
910 };
911
912 let (lbp, rbp) = infix_binding_power(op);
913 if lbp < min_bp {
914 break;
915 }
916
917 p.eat_any();
918 let rhs = expr_bp(p, rbp)?;
919 lhs = Expr::BinaryOp(Box::new(BinaryOp { lhs, op, rhs }));
920 }
921
922 Ok(lhs)
923}
924
925fn expr(p: &mut Parser<'_>) -> R<Expr> {
926 expr_bp(p, 0)
927}
928
929pub fn parse(str: &str, settings: &ParserSettings) -> Result<Expr, ParseError> {
930 let mut p = Parser::new(str, settings.source.clone());
931 for lexeme in &p.lexemes {
932 if let Some(desc) = lexeme.kind.error_description() {
933 return Err(ParseError {
934 message: desc.to_owned(),
935 location: ParseErrorLocation {
936 offset: lexeme.range.0 as usize,
937 },
938 });
939 }
940 }
941 let e = expr(&mut p)?;
942 if !p.at_eof() {
943 return Err(p.error(format!(
944 "expected end of file, got {}",
945 p.current_desc(),
946 )));
947 }
948 Ok(e)
949}
950
951pub fn string_to_expr(s: IStr, settings: &ParserSettings) -> Spanned<Expr> {
952 let len = s.len();
953 Spanned::new(
954 Expr::Str(s),
955 Span(settings.source.clone(), 0, len as u32),
956 )
957}
958
959#[cfg(test)]
960mod tests {
961 use std::fs;
962
963 use insta::{assert_snapshot, glob};
964 use jrsonnet_ir::{IStr, Source};
965
966 use super::*;
967
968 fn parse_str(input: &str) -> Expr {
969 let source = Source::new_virtual("<test>".into(), input.into());
970 let settings = ParserSettings { source };
971 parse(input, &settings).unwrap()
972 }
973
974 #[test]
975 #[cfg(not(feature = "exp-null-coaelse"))]
976 fn basic_test() {
977 let v = parse_str("assert true[false] : false ; true");
978 assert_snapshot!(format!("{v:#?}"));
979 }
980
981 #[test]
982 fn literals() {
983 let v = parse_str("[null, true, false, self, super, $]");
984 assert_snapshot!(format!("{v:#?}"));
985 }
986
987 #[test]
988 fn basic_math() {
989 let v = parse_str("2+2*2");
990 assert_snapshot!(format!("{v:#?}"));
991 }
992
993 #[test]
994 fn underscore_numbers() {
995 let v = parse_str("[1_000, 1_000.000_1, 1_0e1_0]");
996 assert_snapshot!(format!("{v:#?}"));
997 }
998
999 #[test]
1000 fn strings() {
1001 let v = parse_str(r#"["hello", 'world', @"raw""str", @'raw''str']"#);
1002 assert_snapshot!(format!("{v:#?}"));
1003 }
1004
1005 #[test]
1006 fn object() {
1007 let v = parse_str("{a: 1, b:: 2, c::: 3}");
1008 assert_snapshot!(format!("{v:#?}"));
1009 }
1010
1011 #[test]
1012 fn function_and_call() {
1013 let v = parse_str("local f(x, y=1) = x + y; f(2, y=3)");
1014 assert_snapshot!(format!("{v:#?}"));
1015 }
1016
1017 #[test]
1018 fn if_then_else() {
1019 let v = parse_str("if true then 1 else 2");
1020 assert_snapshot!(format!("{v:#?}"));
1021 }
1022
1023 #[test]
1024 fn imports() {
1025 let v = parse_str(r#"[import "a", importstr "b", importbin "c"]"#);
1026 assert_snapshot!(format!("{v:#?}"));
1027 }
1028
1029 #[test]
1030 fn array_comp() {
1031 let v = parse_str("[x for x in arr]");
1032 assert_snapshot!(format!("{v:#?}"));
1033 }
1034
1035 #[test]
1036 #[cfg(not(feature = "exp-null-coaelse"))]
1037 fn index_and_suffix() {
1038 let v = parse_str("std.test(2).field[0]");
1039 assert_snapshot!(format!("{v:#?}"));
1040 }
1041
1042 #[test]
1043 fn obj_extend() {
1044 let v = parse_str("{} { x: 1 }");
1045 assert_snapshot!(format!("{v:#?}"));
1046 }
1047
1048 #[test]
1049 fn unary_ops() {
1050 let v = parse_str("!a && !b");
1051 assert_snapshot!(format!("{v:#?}"));
1052 }
1053
1054 #[test]
1055 fn error_expr() {
1056 let v = parse_str("error \"bad\"");
1057 assert_snapshot!(format!("{v:#?}"));
1058 }
1059
1060 #[test]
1061 fn slice() {
1062 let v = parse_str("[a[1:], a[1::], a[:1:], a[::1]]");
1063 assert_snapshot!(format!("{v:#?}"));
1064 }
1065
1066 #[test]
1067 #[cfg(not(feature = "exp-null-coaelse"))]
1068 fn peg_snapshots() {
1069 glob!("../../jrsonnet-peg-parser/src", "tests/*.jsonnet", |path| {
1070 let input = fs::read_to_string(path).expect("read test file");
1071 let source = Source::new_virtual("<test>".into(), IStr::empty());
1072 let settings = ParserSettings { source };
1073 let v = parse(&input, &settings).unwrap();
1074 let v = format!("{v:#?}");
1075 assert_snapshot!(v);
1076 });
1077 }
183}1078}
1841079
addedcrates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__array_comp.snapdiffbeforeafterboth

no changes

addedcrates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__basic_math.snapdiffbeforeafterboth

no changes

addedcrates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__basic_test.snapdiffbeforeafterboth

no changes

addedcrates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__error_expr.snapdiffbeforeafterboth

no changes

addedcrates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__function_and_call.snapdiffbeforeafterboth

no changes

addedcrates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__if_then_else.snapdiffbeforeafterboth

no changes

addedcrates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__imports.snapdiffbeforeafterboth

no changes

addedcrates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__index_and_suffix.snapdiffbeforeafterboth

no changes

addedcrates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__literals.snapdiffbeforeafterboth

no changes

addedcrates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__obj_extend.snapdiffbeforeafterboth

no changes

addedcrates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__object.snapdiffbeforeafterboth

no changes

addedcrates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@array_comp.jsonnet.snapdiffbeforeafterboth

no changes

addedcrates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@basic_math.jsonnet.snapdiffbeforeafterboth

no changes

addedcrates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@comment_eof.jsonnet.snapdiffbeforeafterboth

no changes

addedcrates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@default_nondefault.jsonnet.snapdiffbeforeafterboth

no changes

addedcrates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@empty_object.jsonnet.snapdiffbeforeafterboth

no changes

addedcrates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@imports.jsonnet.snapdiffbeforeafterboth

no changes

addedcrates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@infix.jsonnet.snapdiffbeforeafterboth

no changes

addedcrates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@multiline.jsonnet.snapdiffbeforeafterboth

no changes

addedcrates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@reserved.jsonnet.snapdiffbeforeafterboth

no changes

addedcrates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@slice.jsonnet.snapdiffbeforeafterboth

no changes

addedcrates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@string_escaping.jsonnet.snapdiffbeforeafterboth

no changes

addedcrates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@subexp.jsonnet.snapdiffbeforeafterboth

no changes

addedcrates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@suffix.jsonnet.snapdiffbeforeafterboth

no changes

addedcrates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__slice.snapdiffbeforeafterboth

no changes

addedcrates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__strings.snapdiffbeforeafterboth

no changes

addedcrates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__unary_ops.snapdiffbeforeafterboth

no changes

addedcrates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__underscore_numbers.snapdiffbeforeafterboth

no changes

modifiedtests/Cargo.tomldiffbeforeafterboth
4edition = "2024"4edition = "2024"
5publish = false5publish = false
6
7[features]
8default = ["ir-parser"]
9ir-parser = ["jrsonnet-evaluator/ir-parser"]
10exp-null-coaelse = ["jrsonnet-evaluator/exp-null-coaelse"]
611
7[lints]12[lints]
8workspace = true13workspace = true
modifiedtests/cpp_test_suite_golden_override/error.parse.array_comma.jsonnet.goldendiffbeforeafterboth
1syntax error: expected one of "(", ".", "?", "[", "]", "{", <binary op>, <comma>, got "3"1syntax error: expected R_BRACK, got "3"
2 error.parse.array_comma.jsonnet:17:72 error.parse.array_comma.jsonnet:17:7
modifiedtests/cpp_test_suite_golden_override/error.parse.function_arg_positional_after_named.jsonnet.goldendiffbeforeafterboth
1syntax error: expected one of "(", ".", "?", "[", "{", <binary op>, <comma>, <named argument>, got ")"1syntax error: positional argument after named argument
2 error.parse.function_arg_positional_after_named.jsonnet:19:112 error.parse.function_arg_positional_after_named.jsonnet:19:10
modifiedtests/cpp_test_suite_golden_override/error.parse.index_unterminated.jsonnet.goldendiffbeforeafterboth
1syntax error: expected one of "(", ":", "[", "{", <identifier>, <number>, <string>, <unary op>, ['"'], ['\''], got "EOF"1syntax error: unexpected token in expression: EOF
2 error.parse.index_unterminated.jsonnet:17:42 error.parse.index_unterminated.jsonnet:17:3
modifiedtests/cpp_test_suite_golden_override/error.parse.method_plus.jsonnet.goldendiffbeforeafterboth
1syntax error: expected one of ":", "::", ":::", got "+"1syntax error: expected COLON, got "+"
2 error.parse.method_plus.jsonnet:17:182 error.parse.method_plus.jsonnet:17:18
modifiedtests/cpp_test_suite_golden_override/error.parse.object_comma.jsonnet.goldendiffbeforeafterboth
1syntax error: expected one of "(", ".", "?", "[", "{", "}", <binary op>, got "z"1syntax error: expected R_BRACE, got "z"
2 error.parse.object_comma.jsonnet:17:112 error.parse.object_comma.jsonnet:17:11
modifiedtests/cpp_test_suite_golden_override/error.parse.object_comprehension_local_clash.jsonnet.goldendiffbeforeafterboth
1syntax error: expected one of "(", ".", "?", "[", "{", "}", <binary op>, <comma>, got ":"1syntax error: expected R_BRACE, got ":"
2 error.parse.object_comprehension_local_clash.jsonnet:17:292 error.parse.object_comprehension_local_clash.jsonnet:17:29
modifiedtests/cpp_test_suite_golden_override/error.parse.self_in_computed_field.jsonnet.goldendiffbeforeafterboth
1syntax error: expected one of "[", "}", <identifier>, <string>, ['"'], ['\''], got "s"1syntax error: expected field name, got SELF_KW
2 error.parse.self_in_computed_field.jsonnet:17:152 error.parse.self_in_computed_field.jsonnet:17:15
modifiedtests/cpp_test_suite_golden_override/error.parse.static_error_bad_number.jsonnet.goldendiffbeforeafterboth
1syntax error: expected one of "(", "[", "{", <identifier>, <number>, <string>, <unary op>, ['"'], ['\''], got "."1syntax error: unexpected token in expression: DOT
2 error.parse.static_error_bad_number.jsonnet:17:12 error.parse.static_error_bad_number.jsonnet:17:1
modifiedtests/cpp_test_suite_golden_override/error.parse.string.invalid_escape.jsonnet.goldendiffbeforeafterboth
1syntax error: expected <escape character>, got "o"1syntax error: invalid string escape
2 error.parse.string.invalid_escape.jsonnet:17:32 error.parse.string.invalid_escape.jsonnet:17:1
modifiedtests/cpp_test_suite_golden_override/error.parse.string.invalid_escape_unicode_non_hex.jsonnet.goldendiffbeforeafterboth
1syntax error: expected <hex char>, got "t"1syntax error: invalid string escape
2 error.parse.string.invalid_escape_unicode_non_hex.jsonnet:17:72 error.parse.string.invalid_escape_unicode_non_hex.jsonnet:17:1
modifiedtests/cpp_test_suite_golden_override/error.parse.string.invalid_escape_unicode_short.jsonnet.goldendiffbeforeafterboth
1syntax error: expected <hex char>, got "\n"1syntax error: unexpected token: ERROR_STRING_DOUBLE_UNTERMINATED
2 error.parse.string.invalid_escape_unicode_short.jsonnet:17:72 error.parse.string.invalid_escape_unicode_short.jsonnet:17:1
modifiedtests/cpp_test_suite_golden_override/error.parse.string.invalid_escape_unicode_short2.jsonnet.goldendiffbeforeafterboth
1syntax error: expected <hex char>, got "\""1syntax error: invalid string escape
2 error.parse.string.invalid_escape_unicode_short2.jsonnet:17:72 error.parse.string.invalid_escape_unicode_short2.jsonnet:17:1
modifiedtests/cpp_test_suite_golden_override/error.parse.string.invalid_escape_unicode_short3.jsonnet.goldendiffbeforeafterboth
1syntax error: expected <hex char>, got "\n"1syntax error: unexpected token: ERROR_STRING_DOUBLE_UNTERMINATED
2 error.parse.string.invalid_escape_unicode_short3.jsonnet:17:72 error.parse.string.invalid_escape_unicode_short3.jsonnet:17:1
modifiedtests/cpp_test_suite_golden_override/error.parse.string.unfinished.jsonnet.goldendiffbeforeafterboth
1syntax error: expected one of "\\\\", "\\u", "\\x", ['"'], ['\\'], [_], got "EOF"1syntax error: unexpected token: ERROR_STRING_DOUBLE_UNTERMINATED
2 error.parse.string.unfinished.jsonnet:17:32 error.parse.string.unfinished.jsonnet:17:1
modifiedtests/cpp_test_suite_golden_override/error.parse.string.unfinished2.jsonnet.goldendiffbeforeafterboth
1syntax error: expected one of "\\\\", "\\u", "\\x", ['\''], ['\\'], [_], got "EOF"1syntax error: unexpected token: ERROR_STRING_SINGLE_UNTERMINATED
2 error.parse.string.unfinished2.jsonnet:17:32 error.parse.string.unfinished2.jsonnet:17:1
modifiedtests/cpp_test_suite_golden_override/error.parse.string_multi_no_newline.jsonnet.goldendiffbeforeafterboth
1syntax error: expected one of "(", "[", "{", <identifier>, <number>, <string>, <unary op>, ['"'], ['\''], got "|"1syntax error: unexpected token: ERROR_STRING_BLOCK_MISSING_NEW_LINE
2 error.parse.string_multi_no_newline.jsonnet:17:12 error.parse.string_multi_no_newline.jsonnet:17:1
modifiedtests/cpp_test_suite_golden_override/error.parse.text_block_bad_whitespace.jsonnet.goldendiffbeforeafterboth
1syntax error: expected one of "(", "[", "{", <identifier>, <number>, <string>, <unary op>, ['"'], ['\''], got "|"1syntax error: unexpected token: ERROR_STRING_BLOCK_MISSING_TERMINATION
2 error.parse.text_block_bad_whitespace.jsonnet:17:12 error.parse.text_block_bad_whitespace.jsonnet:17:1
modifiedtests/cpp_test_suite_golden_override/error.parse.text_block_eof.jsonnet.goldendiffbeforeafterboth
1syntax error: expected one of "(", "[", "{", <identifier>, <number>, <string>, <unary op>, ['"'], ['\''], got "|"1syntax error: unexpected token: ERROR_STRING_BLOCK_UNEXPECTED_END
2 error.parse.text_block_eof.jsonnet:17:12 error.parse.text_block_eof.jsonnet:17:1
modifiedtests/cpp_test_suite_golden_override/error.parse.text_block_not_terminated.jsonnet.goldendiffbeforeafterboth
1syntax error: expected one of "(", "[", "{", <identifier>, <number>, <string>, <unary op>, ['"'], ['\''], got "|"1syntax error: unexpected token: ERROR_STRING_BLOCK_UNEXPECTED_END
2 error.parse.text_block_not_terminated.jsonnet:17:12 error.parse.text_block_not_terminated.jsonnet:17:1
addedtests/cpp_test_suite_golden_override_ir_parser/error.import_syntax-error.jsonnet.goldendiffbeforeafterboth

no changes

addedtests/cpp_test_suite_golden_override_ir_parser/error.overflow.jsonnet.goldendiffbeforeafterboth

no changes

addedtests/cpp_test_suite_golden_override_ir_parser/error.overflow3.jsonnet.goldendiffbeforeafterboth

no changes

addedtests/cpp_test_suite_golden_override_ir_parser/error.parse.array_comma.jsonnet.goldendiffbeforeafterboth

no changes

addedtests/cpp_test_suite_golden_override_ir_parser/error.parse.index_unterminated.jsonnet.goldendiffbeforeafterboth

no changes

addedtests/cpp_test_suite_golden_override_ir_parser/error.parse.method_plus.jsonnet.goldendiffbeforeafterboth

no changes

addedtests/cpp_test_suite_golden_override_ir_parser/error.parse.object_comma.jsonnet.goldendiffbeforeafterboth

no changes

addedtests/cpp_test_suite_golden_override_ir_parser/error.parse.object_comprehension_local_clash.jsonnet.goldendiffbeforeafterboth

no changes

addedtests/cpp_test_suite_golden_override_ir_parser/error.parse.self_in_computed_field.jsonnet.goldendiffbeforeafterboth

no changes

addedtests/cpp_test_suite_golden_override_ir_parser/error.parse.static_error_bad_number.jsonnet.goldendiffbeforeafterboth

no changes

addedtests/cpp_test_suite_golden_override_ir_parser/error.parse.string.invalid_escape_unicode_short.jsonnet.goldendiffbeforeafterboth

no changes

addedtests/cpp_test_suite_golden_override_ir_parser/error.parse.string.invalid_escape_unicode_short3.jsonnet.goldendiffbeforeafterboth

no changes

addedtests/cpp_test_suite_golden_override_ir_parser/error.parse.string.unfinished.jsonnet.goldendiffbeforeafterboth

no changes

addedtests/cpp_test_suite_golden_override_ir_parser/error.parse.string.unfinished2.jsonnet.goldendiffbeforeafterboth

no changes

addedtests/cpp_test_suite_golden_override_ir_parser/error.parse.string_multi_no_newline.jsonnet.goldendiffbeforeafterboth

no changes

addedtests/cpp_test_suite_golden_override_ir_parser/error.parse.text_block_bad_whitespace.jsonnet.goldendiffbeforeafterboth

no changes

addedtests/cpp_test_suite_golden_override_ir_parser/error.parse.text_block_eof.jsonnet.goldendiffbeforeafterboth

no changes

addedtests/cpp_test_suite_golden_override_ir_parser/error.parse.text_block_not_terminated.jsonnet.goldendiffbeforeafterboth

no changes

addedtests/go_testdata_golden_override_ir_parser/error_hexnumber.jsonnet.goldendiffbeforeafterboth

no changes

addedtests/go_testdata_golden_override_ir_parser/import_syntax_error.jsonnet.goldendiffbeforeafterboth

no changes

addedtests/go_testdata_golden_override_ir_parser/object_comp_assert.jsonnet.goldendiffbeforeafterboth

no changes

addedtests/go_testdata_golden_override_ir_parser/object_comp_illegal.jsonnet.goldendiffbeforeafterboth

no changes

addedtests/go_testdata_golden_override_ir_parser/static_error_eof.jsonnet.goldendiffbeforeafterboth

no changes

addedtests/go_testdata_golden_override_ir_parser/syntax_error.jsonnet.goldendiffbeforeafterboth

no changes

addedtests/go_testdata_golden_override_ir_parser/unfinished_args.jsonnet.goldendiffbeforeafterboth

no changes

addedtests/golden/null_coalesce_chain.jsonnetdiffbeforeafterboth

no changes

addedtests/golden_null_coalesce/null_coalesce_access.jsonnetdiffbeforeafterboth

no changes

modifiedtests/tests/cpp_test_suite.rsdiffbeforeafterboth
241 golden = Some(golden_path);241 golden = Some(golden_path);
242 }242 }
243
244 // ir-parser has its own override layer
245 #[cfg(feature = "ir-parser")]
246 let ir_parser_override_path = {
247 let p = root_tests
248 .join(format!("{root_dir}_golden_override_ir_parser"))
249 .join(golden_path.file_name().expect("file has basename"));
250 if let Some(golden_path) = read_file(&p)? {
251 golden = Some(golden_path);
252 }
253 p
254 };
243255
244 // Otherwise assume test should just not fail and return true.256 // Otherwise assume test should just not fail and return true.
245 let golden = golden.unwrap_or_else(|| "true".to_owned());257 let golden = golden.unwrap_or_else(|| "true".to_owned());
258
259 #[cfg(feature = "ir-parser")]
260 let update_golden_path = &ir_parser_override_path;
261 #[cfg(not(feature = "ir-parser"))]
262 let update_golden_path = &golden_override;
246263
247 match (serde_json::from_str(&result), serde_json::from_str(&golden)) {264 match (serde_json::from_str(&result), serde_json::from_str(&golden)) {
248 (Err(_), Ok(_)) => panic!(265 (Err(_), Ok(_)) => panic!(
258 let diff = JsonDiff::diff_string(&golden, &result_v, false);275 let diff = JsonDiff::diff_string(&golden, &result_v, false);
259 if let Some(diff) = diff {276 if let Some(diff) = diff {
260 if env::var_os("UPDATE_GOLDEN").is_some() {277 if env::var_os("UPDATE_GOLDEN").is_some() {
261 fs::write(golden_override, result)?;278 fs::write(update_golden_path, result)?;
262 } else {279 } else {
263 panic!(280 panic!(
264 "Result \n{result_v:#}\n\281 "Result \n{result_v:#}\n\
273 (Err(_), Err(_)) => {290 (Err(_), Err(_)) => {
274 if result != golden.trim_end() {291 if result != golden.trim_end() {
275 if env::var_os("UPDATE_GOLDEN").is_some() {292 if env::var_os("UPDATE_GOLDEN").is_some() {
276 fs::write(golden_override, result)?;293 fs::write(update_golden_path, result)?;
277 } else {294 } else {
278 panic!(295 panic!(
279 "golden didn't match for {}:\n<got>\n{result}\n</got>\n<golden>\n{golden}\n</golden>",296 "golden didn't match for {}:\n<got>\n{result}\n</got>\n<golden>\n{golden}\n</golden>",
modifiedtests/tests/golden.rsdiffbeforeafterboth
46 });46 });
47}47}
48
49#[test]
50#[cfg(feature = "exp-null-coaelse")]
51fn golden_null_coalesce() {
52 glob!("../", "golden_null_coalesce/*.jsonnet", |path| {
53 let result = run(path);
54
55 assert_snapshot!(result);
56 });
57}
4858
addedtests/tests/snapshots/golden__golden@null_coalesce_chain.jsonnet.snapdiffbeforeafterboth

no changes

addedtests/tests/snapshots/golden__golden_null_coalesce.snapdiffbeforeafterboth

no changes