difftreelog
slop: implement ir parser
in: master
86 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -638,6 +638,7 @@
"jrsonnet-gcmodule",
"jrsonnet-interner",
"jrsonnet-ir",
+ "jrsonnet-ir-parser",
"jrsonnet-macros",
"jrsonnet-peg-parser",
"jrsonnet-types",
crates/jrsonnet-evaluator/Cargo.tomldiffbeforeafterboth--- a/crates/jrsonnet-evaluator/Cargo.toml
+++ b/crates/jrsonnet-evaluator/Cargo.toml
@@ -13,11 +13,13 @@
workspace = true
[features]
-default = ["explaining-traces"]
+default = ["explaining-traces", "ir-parser"]
# Rustc-like trace visualization
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
+ir-parser = ["dep:jrsonnet-ir-parser"]
# Allows to preserve field order in objects
exp-preserve-order = []
@@ -28,12 +30,13 @@
# Bigint type
exp-bigint = ["num-bigint", "jrsonnet-types/exp-bigint"]
# obj?.field, obj?.['field']
-exp-null-coaelse = ["jrsonnet-peg-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-ir-parser = { workspace = true, optional = true }
jrsonnet-types.workspace = true
jrsonnet-macros.workspace = true
jrsonnet-gcmodule.workspace = true
crates/jrsonnet-evaluator/src/async_import.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/async_import.rs
+++ b/crates/jrsonnet-evaluator/src/async_import.rs
@@ -7,6 +7,9 @@
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;
@@ -323,7 +326,7 @@
};
let source = Source::new(path.clone(), code.clone());
// If failed - then skip import
- file.parsed = jrsonnet_peg_parser::parse(&code, &ParserSettings { source })
+ file.parsed = crate::parse_jsonnet(&code, &ParserSettings { source })
.map(Rc::new)
.ok();
if let Some(parsed) = &file.parsed {
crates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/error.rs
+++ b/crates/jrsonnet-evaluator/src/error.rs
@@ -154,6 +154,7 @@
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
@@ -172,6 +173,14 @@
error: Box<jrsonnet_peg_parser::ParseError>,
},
+ #[cfg(feature = "ir-parser")]
+ #[error("syntax error: {error}")]
+ ImportSyntaxError {
+ path: Source,
+ #[trace(skip)]
+ error: Box<jrsonnet_ir_parser::ParseError>,
+ },
+
#[error("runtime error: {}", format_empty_str(.0))]
RuntimeError(IStr),
#[error("stack overflow, try to reduce recursion, or set --max-stack to bigger value")]
crates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/lib.rs
+++ b/crates/jrsonnet-evaluator/src/lib.rs
@@ -46,6 +46,9 @@
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;
pub use obj::*;
pub use rustc_hash;
@@ -56,6 +59,22 @@
use crate::gc::WithCapacityExt as _;
+#[cfg(feature = "ir-parser")]
+pub(crate) fn parse_jsonnet(
+ code: &str,
+ settings: &ParserSettings,
+) -> Result<Expr, jrsonnet_ir_parser::ParseError> {
+ jrsonnet_ir_parser::parse(code, settings)
+}
+
+#[cfg(not(feature = "ir-parser"))]
+pub(crate) fn parse_jsonnet(
+ code: &str,
+ settings: &ParserSettings,
+) -> Result<Expr, jrsonnet_peg_parser::ParseError> {
+ jrsonnet_peg_parser::parse(code, settings)
+}
+
cc_dyn!(
#[derive(Clone)]
CcUnbound<V>,
@@ -345,7 +364,7 @@
let file_name = Source::new(path.clone(), code.clone());
if file.parsed.is_none() {
file.parsed = Some(
- jrsonnet_peg_parser::parse(
+ parse_jsonnet(
&code,
&ParserSettings {
source: file_name.clone(),
@@ -461,7 +480,7 @@
pub fn evaluate_snippet(&self, name: impl Into<IStr>, code: impl Into<IStr>) -> Result<Val> {
let code = code.into();
let source = Source::new_virtual(name.into(), code.clone());
- let parsed = jrsonnet_peg_parser::parse(
+ let parsed = parse_jsonnet(
&code,
&ParserSettings {
source: source.clone(),
@@ -482,7 +501,7 @@
) -> Result<Val> {
let code = code.into();
let source = Source::new_virtual(name.into(), code.clone());
- let parsed = jrsonnet_peg_parser::parse(
+ let parsed = parse_jsonnet(
&code,
&ParserSettings {
source: source.clone(),
crates/jrsonnet-ir-parser/Cargo.tomldiffbeforeafterboth--- a/crates/jrsonnet-ir-parser/Cargo.toml
+++ b/crates/jrsonnet-ir-parser/Cargo.toml
@@ -6,6 +6,9 @@
repository.workspace = true
version.workspace = true
+[features]
+exp-null-coaelse = ["jrsonnet-ir/exp-null-coaelse"]
+
[dependencies]
insta.workspace = true
jrsonnet-gcmodule.workspace = true
crates/jrsonnet-ir-parser/src/lib.rsdiffbeforeafterboth1use std::rc::Rc;1use std::rc::Rc;223use 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};101112pub struct ParserSettings {13 pub source: Source,14}1516#[derive(Debug, Clone)]17pub struct ParseErrorLocation {18 pub offset: usize,19}2021#[derive(Debug, Clone)]22pub struct ParseError {23 pub message: String,24 pub location: ParseErrorLocation,25}2627impl std::fmt::Display for ParseError {28 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {29 write!(f, "{}", self.message)30 }31}3233type R<T> = Result<T, ParseError>;3411struct 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}164017impl<'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::WHITESPACE49 | SyntaxKind::SINGLE_LINE_SLASH_COMMENT50 | SyntaxKind::SINGLE_LINE_HASH_COMMENT51 | SyntaxKind::MULTI_LINE_COMMENT52 )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 }5927 fn peek(&self) -> SyntaxKind {60 fn peek(&self) -> SyntaxKind {28 self.lexemes[self.offset].kind61 if self.at_eof() {62 SyntaxKind::EOF63 } else {64 self.lexemes[self.offset].kind65 }29 }66 }6730 fn text(&self) -> &str {68 fn text(&self) -> &'a str {31 self.lexemes[self.offset].text69 self.lexemes[self.offset].text32 }70 }7133 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() == kind35 }74 }7536 fn eat_any(&mut self) {76 fn eat_any(&mut self) {37 self.offset += 177 self.offset += 1;38 }78 }397940 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 }438344 fn try_eat(&mut self, t: SyntaxKind) -> bool {84 fn try_eat(&mut self, t: SyntaxKind) -> bool {48 }88 }49 false89 false50 }90 }9151 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 }105106 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 }5511756 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.058 }126 }12759 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.161 }130 }131132 fn error(&self, message: String) -> ParseError {133 ParseError {134 location: ParseErrorLocation {135 offset: self.span_start() as usize,136 },137 message,138 }139 }140141 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 }155156 fn at_ident(&self) -> bool {157 self.at(SyntaxKind::IDENT) && !is_reserved(self.lexemes[self.offset].text)158 }62}159}63160161fn 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}176177fn 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}183184fn 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 result221 }222 _ => return Err(p.error(format!("expected string, got {}", p.current_desc()))),223 };224 p.eat_any();225 Ok(s.into())226}227228fn is_string_token(kind: SyntaxKind) -> bool {229 matches!(230 kind,231 SyntaxKind::STRING_DOUBLE232 | SyntaxKind::STRING_SINGLE233 | SyntaxKind::STRING_DOUBLE_VERBATIM234 | SyntaxKind::STRING_SINGLE_VERBATIM235 | SyntaxKind::STRING_BLOCK236 )237}238239fn parse_number(p: &mut Parser<'_>) -> R<f64> {240 let text = p.text();241 let n: f64 = text242 .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}25164fn 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}7726578fn 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();8283 Spanned::new(v, Span(p.source.clone(), start, end))84}8586fn 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 None94 };273 };95 dbg!(AssertStmt(cond, msg))274 Ok(AssertStmt(cond, msg))96}275}9727698fn 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}103282104fn 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 None291 };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}115298116fn slice_desc(p: &mut Parser<'_>, start: Option<Spanned<Expr>>) -> SliceDesc {299fn slice_desc(p: &mut Parser<'_>, start: Option<Spanned<Expr>>) -> R<SliceDesc> {117 // start118 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 None123 };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 None311 }126 } else {312 } else {127 None313 None128 };314 };129 SliceDesc { start, end, step }315 Ok(SliceDesc { start, end, step })130}316}131317132fn 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}321322fn 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 None333 };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}347348fn 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}384385fn 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}406407fn 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}419420fn 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}434435fn field(p: &mut Parser<'_>) -> R<FieldMember> {436 let name = spanned(p, field_name)?;437438 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}464465fn 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}475476fn 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}483484fn 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}499500fn 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 }508509 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 }519520 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}569570fn expr_basic(p: &mut Parser<'_>) -> R<Expr> {571 if let Some(lit) = literal(p) {572 return Ok(Expr::Literal(lit));573 }574575 match p.peek() {576 SyntaxKind::STRING_DOUBLE577 | SyntaxKind::STRING_SINGLE578 | SyntaxKind::STRING_DOUBLE_VERBATIM579 | SyntaxKind::STRING_SINGLE_VERBATIM580 | SyntaxKind::STRING_BLOCK => Ok(Expr::Str(parse_string_content(p)?)),581582 SyntaxKind::FLOAT => Ok(Expr::Num(parse_number(p)?)),583584 T!['('] => {585 p.eat(T!['('])?;586 let e = expr(p)?;587 p.eat(T![')'])?;588 Ok(e)589 }590591 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 }622623 T!['{'] => {624 p.eat(T!['{'])?;625 let body = objinside(p)?;626 p.eat(T!['}'])?;627 Ok(Expr::Obj(body))628 }629630 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 }643644 T![if] => Ok(Expr::IfElse(Box::new(if_else(p)?))),645646 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 }654655 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 }661662 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 }667668 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 }676677 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 }685686 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 }694695 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 }707708 _ => Err(p.error(format!("unexpected {}", p.current_desc()))),709 }710}711712/// 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}145723146 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();147730148 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 // ?.field748 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 }156763157 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!['['])?;778158 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 slice781 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 first789 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 parts795 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 }168824169 dbg!(e)825 flush_index_parts(&mut e, &mut parts);826 Ok(e)170}827}171828172fn 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}175834835fn 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::Lt846 | BinaryOpType::Gt847 | BinaryOpType::Lte848 | BinaryOpType::Gte849 | 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}181855182 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}865866fn 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}892893fn 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 };902903 loop {904 if p.at_eof() {905 break;906 }907908 let Some(op) = binary_op(p) else {909 break;910 };911912 let (lbp, rbp) = infix_binding_power(op);913 if lbp < min_bp {914 break;915 }916917 p.eat_any();918 let rhs = expr_bp(p, rbp)?;919 lhs = Expr::BinaryOp(Box::new(BinaryOp { lhs, op, rhs }));920 }921922 Ok(lhs)923}924925fn expr(p: &mut Parser<'_>) -> R<Expr> {926 expr_bp(p, 0)927}928929pub 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}950951pub 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}958959#[cfg(test)]960mod tests {961 use std::fs;962963 use insta::{assert_snapshot, glob};964 use jrsonnet_ir::{IStr, Source};965966 use super::*;967968 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 }973974 #[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 }980981 #[test]982 fn literals() {983 let v = parse_str("[null, true, false, self, super, $]");984 assert_snapshot!(format!("{v:#?}"));985 }986987 #[test]988 fn basic_math() {989 let v = parse_str("2+2*2");990 assert_snapshot!(format!("{v:#?}"));991 }992993 #[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 }998999 #[test]1000 fn strings() {1001 let v = parse_str(r#"["hello", 'world', @"raw""str", @'raw''str']"#);1002 assert_snapshot!(format!("{v:#?}"));1003 }10041005 #[test]1006 fn object() {1007 let v = parse_str("{a: 1, b:: 2, c::: 3}");1008 assert_snapshot!(format!("{v:#?}"));1009 }10101011 #[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 }10161017 #[test]1018 fn if_then_else() {1019 let v = parse_str("if true then 1 else 2");1020 assert_snapshot!(format!("{v:#?}"));1021 }10221023 #[test]1024 fn imports() {1025 let v = parse_str(r#"[import "a", importstr "b", importbin "c"]"#);1026 assert_snapshot!(format!("{v:#?}"));1027 }10281029 #[test]1030 fn array_comp() {1031 let v = parse_str("[x for x in arr]");1032 assert_snapshot!(format!("{v:#?}"));1033 }10341035 #[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 }10411042 #[test]1043 fn obj_extend() {1044 let v = parse_str("{} { x: 1 }");1045 assert_snapshot!(format!("{v:#?}"));1046 }10471048 #[test]1049 fn unary_ops() {1050 let v = parse_str("!a && !b");1051 assert_snapshot!(format!("{v:#?}"));1052 }10531054 #[test]1055 fn error_expr() {1056 let v = parse_str("error \"bad\"");1057 assert_snapshot!(format!("{v:#?}"));1058 }10591060 #[test]1061 fn slice() {1062 let v = parse_str("[a[1:], a[1::], a[:1:], a[::1]]");1063 assert_snapshot!(format!("{v:#?}"));1064 }10651066 #[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}1841079crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__array_comp.snapdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__array_comp.snap
@@ -0,0 +1,21 @@
+---
+source: crates/jrsonnet-ir-parser/src/lib.rs
+expression: "format!(\"{v:#?}\")"
+---
+ArrComp(
+ Var(
+ "x" from virtual:<test>:1-2,
+ ),
+ [
+ ForSpec(
+ ForSpecData {
+ destruct: Full(
+ "x",
+ ),
+ over: Var(
+ "arr" from virtual:<test>:12-15,
+ ),
+ },
+ ),
+ ],
+)
crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__basic_math.snapdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__basic_math.snap
@@ -0,0 +1,23 @@
+---
+source: crates/jrsonnet-ir-parser/src/lib.rs
+expression: "format!(\"{v:#?}\")"
+---
+BinaryOp(
+ BinaryOp {
+ lhs: Num(
+ 2.0,
+ ),
+ op: Add,
+ rhs: BinaryOp(
+ BinaryOp {
+ lhs: Num(
+ 2.0,
+ ),
+ op: Mul,
+ rhs: Num(
+ 2.0,
+ ),
+ },
+ ),
+ },
+)
crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__basic_test.snapdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__basic_test.snap
@@ -0,0 +1,31 @@
+---
+source: crates/jrsonnet-ir-parser/src/lib.rs
+expression: "format!(\"{v:#?}\")"
+---
+AssertExpr(
+ AssertExpr {
+ assert: AssertStmt(
+ Index {
+ indexable: Literal(
+ True,
+ ),
+ parts: [
+ IndexPart {
+ span: virtual:<test>:12-17,
+ value: Literal(
+ False,
+ ),
+ },
+ ],
+ } from virtual:<test>:7-18,
+ Some(
+ Literal(
+ False,
+ ) from virtual:<test>:21-26,
+ ),
+ ),
+ rest: Literal(
+ True,
+ ),
+ },
+)
crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__error_expr.snapdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__error_expr.snap
@@ -0,0 +1,10 @@
+---
+source: crates/jrsonnet-ir-parser/src/lib.rs
+expression: "format!(\"{v:#?}\")"
+---
+ErrorStmt(
+ virtual:<test>:0-5,
+ Str(
+ "bad",
+ ),
+)
crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__function_and_call.snapdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__function_and_call.snap
@@ -0,0 +1,80 @@
+---
+source: crates/jrsonnet-ir-parser/src/lib.rs
+expression: "format!(\"{v:#?}\")"
+---
+LocalExpr(
+ [
+ Function {
+ name: "f",
+ params: ExprParams {
+ exprs: [
+ ExprParam {
+ destruct: Full(
+ "x",
+ ),
+ default: None,
+ },
+ ExprParam {
+ destruct: Full(
+ "y",
+ ),
+ default: Some(
+ Num(
+ 1.0,
+ ),
+ ),
+ },
+ ],
+ signature: FunctionSignature(
+ [
+ ParamParse {
+ name: Named(
+ "x",
+ ),
+ default: None,
+ },
+ ParamParse {
+ name: Named(
+ "y",
+ ),
+ default: Exists,
+ },
+ ],
+ ),
+ binds_len: 2,
+ },
+ value: BinaryOp(
+ BinaryOp {
+ lhs: Var(
+ "x" from virtual:<test>:18-19,
+ ),
+ op: Add,
+ rhs: Var(
+ "y" from virtual:<test>:22-23,
+ ),
+ },
+ ),
+ },
+ ],
+ Apply(
+ Var(
+ "f" from virtual:<test>:25-26,
+ ),
+ ArgsDesc {
+ unnamed: [
+ Num(
+ 2.0,
+ ),
+ ],
+ named: [
+ (
+ "y",
+ Num(
+ 3.0,
+ ),
+ ),
+ ],
+ } from virtual:<test>:26-34,
+ false,
+ ),
+)
crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__if_then_else.snapdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__if_then_else.snap
@@ -0,0 +1,22 @@
+---
+source: crates/jrsonnet-ir-parser/src/lib.rs
+expression: "format!(\"{v:#?}\")"
+---
+IfElse(
+ IfElse {
+ cond: IfSpecData {
+ span: virtual:<test>:0-2,
+ cond: Literal(
+ True,
+ ),
+ },
+ cond_then: Num(
+ 1.0,
+ ),
+ cond_else: Some(
+ Num(
+ 2.0,
+ ),
+ ),
+ },
+)
crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__imports.snapdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__imports.snap
@@ -0,0 +1,26 @@
+---
+source: crates/jrsonnet-ir-parser/src/lib.rs
+expression: "format!(\"{v:#?}\")"
+---
+Arr(
+ [
+ Import(
+ Normal from virtual:<test>:1-7,
+ Str(
+ "a",
+ ),
+ ),
+ Import(
+ Str from virtual:<test>:13-22,
+ Str(
+ "b",
+ ),
+ ),
+ Import(
+ Bin from virtual:<test>:28-37,
+ Str(
+ "c",
+ ),
+ ),
+ ],
+)
crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__index_and_suffix.snapdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__index_and_suffix.snap
@@ -0,0 +1,44 @@
+---
+source: crates/jrsonnet-ir-parser/src/lib.rs
+expression: "format!(\"{v:#?}\")"
+---
+Index {
+ indexable: Apply(
+ Index {
+ indexable: Var(
+ "std" from virtual:<test>:0-3,
+ ),
+ parts: [
+ IndexPart {
+ span: virtual:<test>:4-8,
+ value: Str(
+ "test",
+ ),
+ },
+ ],
+ },
+ ArgsDesc {
+ unnamed: [
+ Num(
+ 2.0,
+ ),
+ ],
+ named: [],
+ } from virtual:<test>:8-11,
+ false,
+ ),
+ parts: [
+ IndexPart {
+ span: virtual:<test>:12-17,
+ value: Str(
+ "field",
+ ),
+ },
+ IndexPart {
+ span: virtual:<test>:18-19,
+ value: Num(
+ 0.0,
+ ),
+ },
+ ],
+}
crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__literals.snapdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__literals.snap
@@ -0,0 +1,26 @@
+---
+source: crates/jrsonnet-ir-parser/src/lib.rs
+expression: "format!(\"{v:#?}\")"
+---
+Arr(
+ [
+ Literal(
+ Null,
+ ),
+ Literal(
+ True,
+ ),
+ Literal(
+ False,
+ ),
+ Literal(
+ This,
+ ),
+ Literal(
+ Super,
+ ),
+ Literal(
+ Dollar,
+ ),
+ ],
+)
crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__obj_extend.snapdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__obj_extend.snap
@@ -0,0 +1,34 @@
+---
+source: crates/jrsonnet-ir-parser/src/lib.rs
+expression: "format!(\"{v:#?}\")"
+---
+ObjExtend(
+ Obj(
+ MemberList(
+ ObjMembers {
+ locals: [],
+ asserts: [],
+ fields: [],
+ },
+ ),
+ ),
+ MemberList(
+ ObjMembers {
+ locals: [],
+ asserts: [],
+ fields: [
+ FieldMember {
+ name: Fixed(
+ "x",
+ ) from virtual:<test>:5-6,
+ plus: false,
+ params: None,
+ visibility: Normal,
+ value: Num(
+ 1.0,
+ ),
+ },
+ ],
+ },
+ ),
+)
crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__object.snapdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__object.snap
@@ -0,0 +1,47 @@
+---
+source: crates/jrsonnet-ir-parser/src/lib.rs
+expression: "format!(\"{v:#?}\")"
+---
+Obj(
+ MemberList(
+ ObjMembers {
+ locals: [],
+ asserts: [],
+ fields: [
+ FieldMember {
+ name: Fixed(
+ "a",
+ ) from virtual:<test>:1-2,
+ plus: false,
+ params: None,
+ visibility: Normal,
+ value: Num(
+ 1.0,
+ ),
+ },
+ FieldMember {
+ name: Fixed(
+ "b",
+ ) from virtual:<test>:7-8,
+ plus: false,
+ params: None,
+ visibility: Hidden,
+ value: Num(
+ 2.0,
+ ),
+ },
+ FieldMember {
+ name: Fixed(
+ "c",
+ ) from virtual:<test>:14-15,
+ plus: false,
+ params: None,
+ visibility: Unhide,
+ value: Num(
+ 3.0,
+ ),
+ },
+ ],
+ },
+ ),
+)
crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@array_comp.jsonnet.snapdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@array_comp.jsonnet.snap
@@ -0,0 +1,82 @@
+---
+source: crates/jrsonnet-ir-parser/src/lib.rs
+expression: v
+input_file: crates/jrsonnet-peg-parser/src/tests/array_comp.jsonnet
+---
+Arr(
+ [
+ ArrComp(
+ Apply(
+ Index {
+ indexable: Var(
+ "std" from virtual:<test>:3-6,
+ ),
+ parts: [
+ IndexPart {
+ span: virtual:<test>:7-15,
+ value: Str(
+ "deepJoin",
+ ),
+ },
+ ],
+ },
+ ArgsDesc {
+ unnamed: [
+ Var(
+ "x" from virtual:<test>:16-17,
+ ),
+ ],
+ named: [],
+ } from virtual:<test>:15-18,
+ false,
+ ),
+ [
+ ForSpec(
+ ForSpecData {
+ destruct: Full(
+ "x",
+ ),
+ over: Var(
+ "arr" from virtual:<test>:28-31,
+ ),
+ },
+ ),
+ ],
+ ),
+ ArrComp(
+ Var(
+ "a" from virtual:<test>:35-36,
+ ),
+ [
+ ForSpec(
+ ForSpecData {
+ destruct: Full(
+ "a",
+ ),
+ over: Var(
+ "b" from virtual:<test>:46-47,
+ ),
+ },
+ ),
+ IfSpec(
+ IfSpecData {
+ span: virtual:<test>:48-50,
+ cond: Var(
+ "c" from virtual:<test>:51-52,
+ ),
+ },
+ ),
+ ForSpec(
+ ForSpecData {
+ destruct: Full(
+ "e",
+ ),
+ over: Var(
+ "f" from virtual:<test>:62-63,
+ ),
+ },
+ ),
+ ],
+ ),
+ ],
+)
crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@basic_math.jsonnet.snapdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@basic_math.jsonnet.snap
@@ -0,0 +1,120 @@
+---
+source: crates/jrsonnet-ir-parser/src/lib.rs
+expression: v
+input_file: crates/jrsonnet-peg-parser/src/tests/basic_math.jsonnet
+---
+Arr(
+ [
+ BinaryOp(
+ BinaryOp {
+ lhs: Num(
+ 2.0,
+ ),
+ op: Add,
+ rhs: BinaryOp(
+ BinaryOp {
+ lhs: Num(
+ 2.0,
+ ),
+ op: Mul,
+ rhs: Num(
+ 2.0,
+ ),
+ },
+ ),
+ },
+ ),
+ BinaryOp(
+ BinaryOp {
+ lhs: Num(
+ 2.0,
+ ),
+ op: Add,
+ rhs: BinaryOp(
+ BinaryOp {
+ lhs: Num(
+ 2.0,
+ ),
+ op: Mul,
+ rhs: Num(
+ 2.0,
+ ),
+ },
+ ),
+ },
+ ),
+ BinaryOp(
+ BinaryOp {
+ lhs: BinaryOp(
+ BinaryOp {
+ lhs: Num(
+ 2.0,
+ ),
+ op: Add,
+ rhs: Num(
+ 2.0,
+ ),
+ },
+ ),
+ op: Add,
+ rhs: BinaryOp(
+ BinaryOp {
+ lhs: Num(
+ 2.0,
+ ),
+ op: Mul,
+ rhs: Num(
+ 2.0,
+ ),
+ },
+ ),
+ },
+ ),
+ BinaryOp(
+ BinaryOp {
+ lhs: Num(
+ 2.0,
+ ),
+ op: Add,
+ rhs: BinaryOp(
+ BinaryOp {
+ lhs: Num(
+ 2.0,
+ ),
+ op: Add,
+ rhs: BinaryOp(
+ BinaryOp {
+ lhs: Num(
+ 2.0,
+ ),
+ op: Mul,
+ rhs: Num(
+ 2.0,
+ ),
+ },
+ ),
+ },
+ ),
+ },
+ ),
+ BinaryOp(
+ BinaryOp {
+ lhs: Num(
+ 2.0,
+ ),
+ op: Add,
+ rhs: BinaryOp(
+ BinaryOp {
+ lhs: Num(
+ 3.0,
+ ),
+ op: Mul,
+ rhs: Num(
+ 4.0,
+ ),
+ },
+ ),
+ },
+ ),
+ ],
+)
crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@comment_eof.jsonnet.snapdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@comment_eof.jsonnet.snap
@@ -0,0 +1,26 @@
+---
+source: crates/jrsonnet-ir-parser/src/lib.rs
+expression: v
+input_file: crates/jrsonnet-peg-parser/src/tests/comment_eof.jsonnet
+---
+Obj(
+ MemberList(
+ ObjMembers {
+ locals: [],
+ asserts: [],
+ fields: [
+ FieldMember {
+ name: Fixed(
+ "a",
+ ) from virtual:<test>:1-2,
+ plus: false,
+ params: None,
+ visibility: Normal,
+ value: Num(
+ 1.0,
+ ),
+ },
+ ],
+ },
+ ),
+)
crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@default_nondefault.jsonnet.snapdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@default_nondefault.jsonnet.snap
@@ -0,0 +1,55 @@
+---
+source: crates/jrsonnet-ir-parser/src/lib.rs
+expression: v
+input_file: crates/jrsonnet-peg-parser/src/tests/default_nondefault.jsonnet
+---
+LocalExpr(
+ [
+ Function {
+ name: "x",
+ params: ExprParams {
+ exprs: [
+ ExprParam {
+ destruct: Full(
+ "foo",
+ ),
+ default: Some(
+ Str(
+ "foo",
+ ),
+ ),
+ },
+ 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,
+ ),
+ },
+ ],
+ Literal(
+ Null,
+ ),
+)
crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@empty_object.jsonnet.snapdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@empty_object.jsonnet.snap
@@ -0,0 +1,14 @@
+---
+source: crates/jrsonnet-ir-parser/src/lib.rs
+expression: v
+input_file: crates/jrsonnet-peg-parser/src/tests/empty_object.jsonnet
+---
+Obj(
+ MemberList(
+ ObjMembers {
+ locals: [],
+ asserts: [],
+ fields: [],
+ },
+ ),
+)
crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@imports.jsonnet.snapdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@imports.jsonnet.snap
@@ -0,0 +1,27 @@
+---
+source: crates/jrsonnet-ir-parser/src/lib.rs
+expression: v
+input_file: crates/jrsonnet-peg-parser/src/tests/imports.jsonnet
+---
+Arr(
+ [
+ Import(
+ Normal from virtual:<test>:2-8,
+ Str(
+ "hello",
+ ),
+ ),
+ Import(
+ Str from virtual:<test>:18-27,
+ Str(
+ "garnish.txt",
+ ),
+ ),
+ Import(
+ Bin from virtual:<test>:43-52,
+ Str(
+ "garnish.bin",
+ ),
+ ),
+ ],
+)
crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@infix.jsonnet.snapdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@infix.jsonnet.snap
@@ -0,0 +1,52 @@
+---
+source: crates/jrsonnet-ir-parser/src/lib.rs
+expression: v
+input_file: crates/jrsonnet-peg-parser/src/tests/infix.jsonnet
+---
+Arr(
+ [
+ BinaryOp(
+ BinaryOp {
+ lhs: UnaryOp(
+ Not,
+ Var(
+ "a" from virtual:<test>:3-4,
+ ),
+ ),
+ op: And,
+ rhs: UnaryOp(
+ Not,
+ Var(
+ "b" from virtual:<test>:9-10,
+ ),
+ ),
+ },
+ ),
+ UnaryOp(
+ Not,
+ BinaryOp(
+ BinaryOp {
+ lhs: Var(
+ "a" from virtual:<test>:13-14,
+ ),
+ op: Div,
+ rhs: UnaryOp(
+ Not,
+ Var(
+ "b" from virtual:<test>:18-19,
+ ),
+ ),
+ },
+ ),
+ ),
+ UnaryOp(
+ Not,
+ UnaryOp(
+ Not,
+ Var(
+ "a" from virtual:<test>:23-24,
+ ),
+ ),
+ ),
+ ],
+)
crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@multiline.jsonnet.snapdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@multiline.jsonnet.snap
@@ -0,0 +1,21 @@
+---
+source: crates/jrsonnet-ir-parser/src/lib.rs
+expression: v
+input_file: crates/jrsonnet-peg-parser/src/tests/multiline.jsonnet
+---
+Arr(
+ [
+ Str(
+ "Hello world!\na\n",
+ ),
+ Str(
+ "Hello world!\na\n",
+ ),
+ Str(
+ "Hello world!\n\ta\n",
+ ),
+ Str(
+ "Hello world!\n a\n",
+ ),
+ ],
+)
crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@reserved.jsonnet.snapdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@reserved.jsonnet.snap
@@ -0,0 +1,32 @@
+---
+source: crates/jrsonnet-ir-parser/src/lib.rs
+expression: v
+input_file: crates/jrsonnet-peg-parser/src/tests/reserved.jsonnet
+---
+Arr(
+ [
+ Literal(
+ Null,
+ ),
+ Var(
+ "nulla" from virtual:<test>:8-13,
+ ),
+ Apply(
+ Var(
+ "a" from virtual:<test>:15-16,
+ ),
+ ArgsDesc {
+ unnamed: [
+ Var(
+ "b" from virtual:<test>:17-18,
+ ),
+ Var(
+ "null_fields" from virtual:<test>:20-31,
+ ),
+ ],
+ named: [],
+ } from virtual:<test>:16-32,
+ false,
+ ),
+ ],
+)
crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@slice.jsonnet.snapdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@slice.jsonnet.snap
@@ -0,0 +1,97 @@
+---
+source: crates/jrsonnet-ir-parser/src/lib.rs
+expression: v
+input_file: crates/jrsonnet-peg-parser/src/tests/slice.jsonnet
+---
+Arr(
+ [
+ Slice(
+ Slice {
+ value: Var(
+ "a" from virtual:<test>:2-3,
+ ),
+ slice: SliceDesc {
+ start: Some(
+ Num(
+ 1.0,
+ ) from virtual:<test>:4-5,
+ ),
+ end: None,
+ step: None,
+ },
+ },
+ ),
+ Slice(
+ Slice {
+ value: Var(
+ "a" from virtual:<test>:9-10,
+ ),
+ slice: SliceDesc {
+ start: Some(
+ Num(
+ 1.0,
+ ) from virtual:<test>:11-12,
+ ),
+ end: None,
+ step: None,
+ },
+ },
+ ),
+ Slice(
+ Slice {
+ value: Var(
+ "a" from virtual:<test>:17-18,
+ ),
+ slice: SliceDesc {
+ start: None,
+ end: Some(
+ Num(
+ 1.0,
+ ) from virtual:<test>:20-21,
+ ),
+ step: None,
+ },
+ },
+ ),
+ Slice(
+ Slice {
+ value: Var(
+ "a" from virtual:<test>:25-26,
+ ),
+ slice: SliceDesc {
+ start: None,
+ end: None,
+ step: Some(
+ Num(
+ 1.0,
+ ) from virtual:<test>:29-30,
+ ),
+ },
+ },
+ ),
+ Slice(
+ Slice {
+ value: Var(
+ "str" from virtual:<test>:33-36,
+ ),
+ slice: SliceDesc {
+ start: None,
+ end: Some(
+ BinaryOp(
+ BinaryOp {
+ lhs: Var(
+ "len" from virtual:<test>:38-41,
+ ),
+ op: Sub,
+ rhs: Num(
+ 1.0,
+ ),
+ },
+ ) from virtual:<test>:38-45,
+ ),
+ step: None,
+ },
+ },
+ ),
+ ],
+)
crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@string_escaping.jsonnet.snapdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@string_escaping.jsonnet.snap
@@ -0,0 +1,24 @@
+---
+source: crates/jrsonnet-ir-parser/src/lib.rs
+expression: v
+input_file: crates/jrsonnet-peg-parser/src/tests/string_escaping.jsonnet
+---
+Arr(
+ [
+ Str(
+ "Hello, \"world\"!",
+ ),
+ Str(
+ "Hello 'world'!",
+ ),
+ Str(
+ "\\\\",
+ ),
+ Str(
+ "Hello\nWorld",
+ ),
+ Str(
+ "Hello\\n\"World\"",
+ ),
+ ],
+)
crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@subexp.jsonnet.snapdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@subexp.jsonnet.snap
@@ -0,0 +1,58 @@
+---
+source: crates/jrsonnet-ir-parser/src/lib.rs
+expression: v
+input_file: crates/jrsonnet-peg-parser/src/tests/subexp.jsonnet
+---
+BinaryOp(
+ BinaryOp {
+ lhs: ObjExtend(
+ Obj(
+ MemberList(
+ ObjMembers {
+ locals: [],
+ asserts: [],
+ fields: [],
+ },
+ ),
+ ),
+ MemberList(
+ ObjMembers {
+ locals: [
+ Field {
+ into: Full(
+ "x",
+ ),
+ value: Num(
+ 1.0,
+ ),
+ },
+ ],
+ asserts: [],
+ fields: [
+ FieldMember {
+ name: Fixed(
+ "x",
+ ) from virtual:<test>:18-19,
+ plus: false,
+ params: None,
+ visibility: Normal,
+ value: Var(
+ "x" from virtual:<test>:21-22,
+ ),
+ },
+ ],
+ },
+ ),
+ ),
+ op: Add,
+ rhs: Obj(
+ MemberList(
+ ObjMembers {
+ locals: [],
+ asserts: [],
+ fields: [],
+ },
+ ),
+ ),
+ },
+)
crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@suffix.jsonnet.snapdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@suffix.jsonnet.snap
@@ -0,0 +1,73 @@
+---
+source: crates/jrsonnet-ir-parser/src/lib.rs
+expression: v
+input_file: crates/jrsonnet-peg-parser/src/tests/suffix.jsonnet
+---
+Arr(
+ [
+ Index {
+ indexable: Var(
+ "std" from virtual:<test>:2-5,
+ ),
+ parts: [
+ IndexPart {
+ span: virtual:<test>:6-10,
+ value: Str(
+ "test",
+ ),
+ },
+ ],
+ },
+ Apply(
+ Var(
+ "std" from virtual:<test>:12-15,
+ ),
+ ArgsDesc {
+ unnamed: [
+ Num(
+ 2.0,
+ ),
+ ],
+ named: [],
+ } from virtual:<test>:15-18,
+ false,
+ ),
+ Apply(
+ Index {
+ indexable: Var(
+ "std" from virtual:<test>:20-23,
+ ),
+ parts: [
+ IndexPart {
+ span: virtual:<test>:24-28,
+ value: Str(
+ "test",
+ ),
+ },
+ ],
+ },
+ ArgsDesc {
+ unnamed: [
+ Num(
+ 2.0,
+ ),
+ ],
+ named: [],
+ } from virtual:<test>:28-31,
+ false,
+ ),
+ Index {
+ indexable: Var(
+ "a" from virtual:<test>:33-34,
+ ),
+ parts: [
+ IndexPart {
+ span: virtual:<test>:35-36,
+ value: Var(
+ "b" from virtual:<test>:35-36,
+ ),
+ },
+ ],
+ },
+ ],
+)
crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__slice.snapdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__slice.snap
@@ -0,0 +1,72 @@
+---
+source: crates/jrsonnet-ir-parser/src/lib.rs
+expression: "format!(\"{v:#?}\")"
+---
+Arr(
+ [
+ Slice(
+ Slice {
+ value: Var(
+ "a" from virtual:<test>:1-2,
+ ),
+ slice: SliceDesc {
+ start: Some(
+ Num(
+ 1.0,
+ ) from virtual:<test>:3-4,
+ ),
+ end: None,
+ step: None,
+ },
+ },
+ ),
+ Slice(
+ Slice {
+ value: Var(
+ "a" from virtual:<test>:8-9,
+ ),
+ slice: SliceDesc {
+ start: Some(
+ Num(
+ 1.0,
+ ) from virtual:<test>:10-11,
+ ),
+ end: None,
+ step: None,
+ },
+ },
+ ),
+ Slice(
+ Slice {
+ value: Var(
+ "a" from virtual:<test>:16-17,
+ ),
+ slice: SliceDesc {
+ start: None,
+ end: Some(
+ Num(
+ 1.0,
+ ) from virtual:<test>:19-20,
+ ),
+ step: None,
+ },
+ },
+ ),
+ Slice(
+ Slice {
+ value: Var(
+ "a" from virtual:<test>:24-25,
+ ),
+ slice: SliceDesc {
+ start: None,
+ end: None,
+ step: Some(
+ Num(
+ 1.0,
+ ) from virtual:<test>:28-29,
+ ),
+ },
+ },
+ ),
+ ],
+)
crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__strings.snapdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__strings.snap
@@ -0,0 +1,20 @@
+---
+source: crates/jrsonnet-ir-parser/src/lib.rs
+expression: "format!(\"{v:#?}\")"
+---
+Arr(
+ [
+ Str(
+ "hello",
+ ),
+ Str(
+ "world",
+ ),
+ Str(
+ "raw\"str",
+ ),
+ Str(
+ "raw'str",
+ ),
+ ],
+)
crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__unary_ops.snapdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__unary_ops.snap
@@ -0,0 +1,21 @@
+---
+source: crates/jrsonnet-ir-parser/src/lib.rs
+expression: "format!(\"{v:#?}\")"
+---
+BinaryOp(
+ BinaryOp {
+ lhs: UnaryOp(
+ Not,
+ Var(
+ "a" from virtual:<test>:1-2,
+ ),
+ ),
+ op: And,
+ rhs: UnaryOp(
+ Not,
+ Var(
+ "b" from virtual:<test>:7-8,
+ ),
+ ),
+ },
+)
crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__underscore_numbers.snapdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__underscore_numbers.snap
@@ -0,0 +1,17 @@
+---
+source: crates/jrsonnet-ir-parser/src/lib.rs
+expression: "format!(\"{v:#?}\")"
+---
+Arr(
+ [
+ Num(
+ 1000.0,
+ ),
+ Num(
+ 1000.0001,
+ ),
+ Num(
+ 100000000000.0,
+ ),
+ ],
+)
tests/Cargo.tomldiffbeforeafterboth--- a/tests/Cargo.toml
+++ b/tests/Cargo.toml
@@ -4,6 +4,11 @@
edition = "2024"
publish = false
+[features]
+default = ["ir-parser"]
+ir-parser = ["jrsonnet-evaluator/ir-parser"]
+exp-null-coaelse = ["jrsonnet-evaluator/exp-null-coaelse"]
+
[lints]
workspace = true
tests/cpp_test_suite_golden_override/error.parse.array_comma.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.parse.array_comma.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.parse.array_comma.jsonnet.golden
@@ -1,2 +1,2 @@
-syntax error: expected one of "(", ".", "?", "[", "]", "{", <binary op>, <comma>, got "3"
+syntax error: expected R_BRACK, got "3"
error.parse.array_comma.jsonnet:17:7
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.parse.function_arg_positional_after_named.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.parse.function_arg_positional_after_named.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.parse.function_arg_positional_after_named.jsonnet.golden
@@ -1,2 +1,2 @@
-syntax error: expected one of "(", ".", "?", "[", "{", <binary op>, <comma>, <named argument>, got ")"
- error.parse.function_arg_positional_after_named.jsonnet:19:11
\ No newline at end of file
+syntax error: positional argument after named argument
+ error.parse.function_arg_positional_after_named.jsonnet:19:10
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.parse.index_unterminated.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.parse.index_unterminated.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.parse.index_unterminated.jsonnet.golden
@@ -1,2 +1,2 @@
-syntax error: expected one of "(", ":", "[", "{", <identifier>, <number>, <string>, <unary op>, ['"'], ['\''], got "EOF"
- error.parse.index_unterminated.jsonnet:17:4
\ No newline at end of file
+syntax error: unexpected token in expression: EOF
+ error.parse.index_unterminated.jsonnet:17:3
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.parse.method_plus.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.parse.method_plus.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.parse.method_plus.jsonnet.golden
@@ -1,2 +1,2 @@
-syntax error: expected one of ":", "::", ":::", got "+"
+syntax error: expected COLON, got "+"
error.parse.method_plus.jsonnet:17:18
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.parse.object_comma.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.parse.object_comma.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.parse.object_comma.jsonnet.golden
@@ -1,2 +1,2 @@
-syntax error: expected one of "(", ".", "?", "[", "{", "}", <binary op>, got "z"
+syntax error: expected R_BRACE, got "z"
error.parse.object_comma.jsonnet:17:11
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.parse.object_comprehension_local_clash.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.parse.object_comprehension_local_clash.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.parse.object_comprehension_local_clash.jsonnet.golden
@@ -1,2 +1,2 @@
-syntax error: expected one of "(", ".", "?", "[", "{", "}", <binary op>, <comma>, got ":"
+syntax error: expected R_BRACE, got ":"
error.parse.object_comprehension_local_clash.jsonnet:17:29
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.parse.self_in_computed_field.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.parse.self_in_computed_field.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.parse.self_in_computed_field.jsonnet.golden
@@ -1,2 +1,2 @@
-syntax error: expected one of "[", "}", <identifier>, <string>, ['"'], ['\''], got "s"
+syntax error: expected field name, got SELF_KW
error.parse.self_in_computed_field.jsonnet:17:15
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.parse.static_error_bad_number.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.parse.static_error_bad_number.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.parse.static_error_bad_number.jsonnet.golden
@@ -1,2 +1,2 @@
-syntax error: expected one of "(", "[", "{", <identifier>, <number>, <string>, <unary op>, ['"'], ['\''], got "."
+syntax error: unexpected token in expression: DOT
error.parse.static_error_bad_number.jsonnet:17:1
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.parse.string.invalid_escape.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.parse.string.invalid_escape.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.parse.string.invalid_escape.jsonnet.golden
@@ -1,2 +1,2 @@
-syntax error: expected <escape character>, got "o"
- error.parse.string.invalid_escape.jsonnet:17:3
\ No newline at end of file
+syntax error: invalid string escape
+ error.parse.string.invalid_escape.jsonnet:17:1
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.parse.string.invalid_escape_unicode_non_hex.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.parse.string.invalid_escape_unicode_non_hex.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.parse.string.invalid_escape_unicode_non_hex.jsonnet.golden
@@ -1,2 +1,2 @@
-syntax error: expected <hex char>, got "t"
- error.parse.string.invalid_escape_unicode_non_hex.jsonnet:17:7
\ No newline at end of file
+syntax error: invalid string escape
+ error.parse.string.invalid_escape_unicode_non_hex.jsonnet:17:1
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.parse.string.invalid_escape_unicode_short.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.parse.string.invalid_escape_unicode_short.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.parse.string.invalid_escape_unicode_short.jsonnet.golden
@@ -1,2 +1,2 @@
-syntax error: expected <hex char>, got "\n"
- error.parse.string.invalid_escape_unicode_short.jsonnet:17:7
\ No newline at end of file
+syntax error: unexpected token: ERROR_STRING_DOUBLE_UNTERMINATED
+ error.parse.string.invalid_escape_unicode_short.jsonnet:17:1
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.parse.string.invalid_escape_unicode_short2.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.parse.string.invalid_escape_unicode_short2.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.parse.string.invalid_escape_unicode_short2.jsonnet.golden
@@ -1,2 +1,2 @@
-syntax error: expected <hex char>, got "\""
- error.parse.string.invalid_escape_unicode_short2.jsonnet:17:7
\ No newline at end of file
+syntax error: invalid string escape
+ error.parse.string.invalid_escape_unicode_short2.jsonnet:17:1
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.parse.string.invalid_escape_unicode_short3.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.parse.string.invalid_escape_unicode_short3.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.parse.string.invalid_escape_unicode_short3.jsonnet.golden
@@ -1,2 +1,2 @@
-syntax error: expected <hex char>, got "\n"
- error.parse.string.invalid_escape_unicode_short3.jsonnet:17:7
\ No newline at end of file
+syntax error: unexpected token: ERROR_STRING_DOUBLE_UNTERMINATED
+ error.parse.string.invalid_escape_unicode_short3.jsonnet:17:1
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.parse.string.unfinished.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.parse.string.unfinished.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.parse.string.unfinished.jsonnet.golden
@@ -1,2 +1,2 @@
-syntax error: expected one of "\\\\", "\\u", "\\x", ['"'], ['\\'], [_], got "EOF"
- error.parse.string.unfinished.jsonnet:17:3
\ No newline at end of file
+syntax error: unexpected token: ERROR_STRING_DOUBLE_UNTERMINATED
+ error.parse.string.unfinished.jsonnet:17:1
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.parse.string.unfinished2.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.parse.string.unfinished2.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.parse.string.unfinished2.jsonnet.golden
@@ -1,2 +1,2 @@
-syntax error: expected one of "\\\\", "\\u", "\\x", ['\''], ['\\'], [_], got "EOF"
- error.parse.string.unfinished2.jsonnet:17:3
\ No newline at end of file
+syntax error: unexpected token: ERROR_STRING_SINGLE_UNTERMINATED
+ error.parse.string.unfinished2.jsonnet:17:1
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.parse.string_multi_no_newline.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.parse.string_multi_no_newline.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.parse.string_multi_no_newline.jsonnet.golden
@@ -1,2 +1,2 @@
-syntax error: expected one of "(", "[", "{", <identifier>, <number>, <string>, <unary op>, ['"'], ['\''], got "|"
+syntax error: unexpected token: ERROR_STRING_BLOCK_MISSING_NEW_LINE
error.parse.string_multi_no_newline.jsonnet:17:1
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.parse.text_block_bad_whitespace.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.parse.text_block_bad_whitespace.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.parse.text_block_bad_whitespace.jsonnet.golden
@@ -1,2 +1,2 @@
-syntax error: expected one of "(", "[", "{", <identifier>, <number>, <string>, <unary op>, ['"'], ['\''], got "|"
+syntax error: unexpected token: ERROR_STRING_BLOCK_MISSING_TERMINATION
error.parse.text_block_bad_whitespace.jsonnet:17:1
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.parse.text_block_eof.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.parse.text_block_eof.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.parse.text_block_eof.jsonnet.golden
@@ -1,2 +1,2 @@
-syntax error: expected one of "(", "[", "{", <identifier>, <number>, <string>, <unary op>, ['"'], ['\''], got "|"
+syntax error: unexpected token: ERROR_STRING_BLOCK_UNEXPECTED_END
error.parse.text_block_eof.jsonnet:17:1
\ No newline at end of file
tests/cpp_test_suite_golden_override/error.parse.text_block_not_terminated.jsonnet.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.parse.text_block_not_terminated.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.parse.text_block_not_terminated.jsonnet.golden
@@ -1,2 +1,2 @@
-syntax error: expected one of "(", "[", "{", <identifier>, <number>, <string>, <unary op>, ['"'], ['\''], got "|"
+syntax error: unexpected token: ERROR_STRING_BLOCK_UNEXPECTED_END
error.parse.text_block_not_terminated.jsonnet:17:1
\ No newline at end of file
tests/cpp_test_suite_golden_override_ir_parser/error.import_syntax-error.jsonnet.goldendiffbeforeafterboth--- /dev/null
+++ b/tests/cpp_test_suite_golden_override_ir_parser/error.import_syntax-error.jsonnet.golden
@@ -0,0 +1,3 @@
+syntax error: unterminated double-quoted string
+ syntax_error.jsonnet:1:1
+ error.import_syntax-error.jsonnet:1:1-8: import "lib/syntax_error.jsonnet"
\ No newline at end of file
tests/cpp_test_suite_golden_override_ir_parser/error.overflow.jsonnet.goldendiffbeforeafterboth--- /dev/null
+++ b/tests/cpp_test_suite_golden_override_ir_parser/error.overflow.jsonnet.golden
@@ -0,0 +1,2 @@
+syntax error: numbers are finite
+ error.overflow.jsonnet:17:1
\ No newline at end of file
tests/cpp_test_suite_golden_override_ir_parser/error.overflow3.jsonnet.goldendiffbeforeafterboth--- /dev/null
+++ b/tests/cpp_test_suite_golden_override_ir_parser/error.overflow3.jsonnet.golden
@@ -0,0 +1,2 @@
+syntax error: numbers are finite
+ error.overflow3.jsonnet:17:1
\ No newline at end of file
tests/cpp_test_suite_golden_override_ir_parser/error.parse.array_comma.jsonnet.goldendiffbeforeafterboth--- /dev/null
+++ b/tests/cpp_test_suite_golden_override_ir_parser/error.parse.array_comma.jsonnet.golden
@@ -0,0 +1,2 @@
+syntax error: expected ']', got number "3"
+ error.parse.array_comma.jsonnet:17:7
\ No newline at end of file
tests/cpp_test_suite_golden_override_ir_parser/error.parse.index_unterminated.jsonnet.goldendiffbeforeafterboth--- /dev/null
+++ b/tests/cpp_test_suite_golden_override_ir_parser/error.parse.index_unterminated.jsonnet.golden
@@ -0,0 +1,2 @@
+syntax error: unexpected end of file
+ error.parse.index_unterminated.jsonnet:17:3
\ No newline at end of file
tests/cpp_test_suite_golden_override_ir_parser/error.parse.method_plus.jsonnet.goldendiffbeforeafterboth--- /dev/null
+++ b/tests/cpp_test_suite_golden_override_ir_parser/error.parse.method_plus.jsonnet.golden
@@ -0,0 +1,2 @@
+syntax error: expected ':', got '+'
+ error.parse.method_plus.jsonnet:17:18
\ No newline at end of file
tests/cpp_test_suite_golden_override_ir_parser/error.parse.object_comma.jsonnet.goldendiffbeforeafterboth--- /dev/null
+++ b/tests/cpp_test_suite_golden_override_ir_parser/error.parse.object_comma.jsonnet.golden
@@ -0,0 +1,2 @@
+syntax error: expected '}', got identifier "z"
+ error.parse.object_comma.jsonnet:17:11
\ No newline at end of file
tests/cpp_test_suite_golden_override_ir_parser/error.parse.object_comprehension_local_clash.jsonnet.goldendiffbeforeafterboth--- /dev/null
+++ b/tests/cpp_test_suite_golden_override_ir_parser/error.parse.object_comprehension_local_clash.jsonnet.golden
@@ -0,0 +1,2 @@
+syntax error: expected '}', got ':'
+ error.parse.object_comprehension_local_clash.jsonnet:17:29
\ No newline at end of file
tests/cpp_test_suite_golden_override_ir_parser/error.parse.self_in_computed_field.jsonnet.goldendiffbeforeafterboth--- /dev/null
+++ b/tests/cpp_test_suite_golden_override_ir_parser/error.parse.self_in_computed_field.jsonnet.golden
@@ -0,0 +1,2 @@
+syntax error: expected field name, got 'self'
+ error.parse.self_in_computed_field.jsonnet:17:15
\ No newline at end of file
tests/cpp_test_suite_golden_override_ir_parser/error.parse.static_error_bad_number.jsonnet.goldendiffbeforeafterboth--- /dev/null
+++ b/tests/cpp_test_suite_golden_override_ir_parser/error.parse.static_error_bad_number.jsonnet.golden
@@ -0,0 +1,2 @@
+syntax error: unexpected '.'
+ error.parse.static_error_bad_number.jsonnet:17:1
\ No newline at end of file
tests/cpp_test_suite_golden_override_ir_parser/error.parse.string.invalid_escape_unicode_short.jsonnet.goldendiffbeforeafterboth--- /dev/null
+++ b/tests/cpp_test_suite_golden_override_ir_parser/error.parse.string.invalid_escape_unicode_short.jsonnet.golden
@@ -0,0 +1,2 @@
+syntax error: unterminated double-quoted string
+ error.parse.string.invalid_escape_unicode_short.jsonnet:17:1
\ No newline at end of file
tests/cpp_test_suite_golden_override_ir_parser/error.parse.string.invalid_escape_unicode_short3.jsonnet.goldendiffbeforeafterboth--- /dev/null
+++ b/tests/cpp_test_suite_golden_override_ir_parser/error.parse.string.invalid_escape_unicode_short3.jsonnet.golden
@@ -0,0 +1,2 @@
+syntax error: unterminated double-quoted string
+ error.parse.string.invalid_escape_unicode_short3.jsonnet:17:1
\ No newline at end of file
tests/cpp_test_suite_golden_override_ir_parser/error.parse.string.unfinished.jsonnet.goldendiffbeforeafterboth--- /dev/null
+++ b/tests/cpp_test_suite_golden_override_ir_parser/error.parse.string.unfinished.jsonnet.golden
@@ -0,0 +1,2 @@
+syntax error: unterminated double-quoted string
+ error.parse.string.unfinished.jsonnet:17:1
\ No newline at end of file
tests/cpp_test_suite_golden_override_ir_parser/error.parse.string.unfinished2.jsonnet.goldendiffbeforeafterboth--- /dev/null
+++ b/tests/cpp_test_suite_golden_override_ir_parser/error.parse.string.unfinished2.jsonnet.golden
@@ -0,0 +1,2 @@
+syntax error: unterminated single-quoted string
+ error.parse.string.unfinished2.jsonnet:17:1
\ No newline at end of file
tests/cpp_test_suite_golden_override_ir_parser/error.parse.string_multi_no_newline.jsonnet.goldendiffbeforeafterboth--- /dev/null
+++ b/tests/cpp_test_suite_golden_override_ir_parser/error.parse.string_multi_no_newline.jsonnet.golden
@@ -0,0 +1,2 @@
+syntax error: text block requires new line after |||
+ error.parse.string_multi_no_newline.jsonnet:17:1
\ No newline at end of file
tests/cpp_test_suite_golden_override_ir_parser/error.parse.text_block_bad_whitespace.jsonnet.goldendiffbeforeafterboth--- /dev/null
+++ b/tests/cpp_test_suite_golden_override_ir_parser/error.parse.text_block_bad_whitespace.jsonnet.golden
@@ -0,0 +1,2 @@
+syntax error: unterminated text block
+ error.parse.text_block_bad_whitespace.jsonnet:17:1
\ No newline at end of file
tests/cpp_test_suite_golden_override_ir_parser/error.parse.text_block_eof.jsonnet.goldendiffbeforeafterboth--- /dev/null
+++ b/tests/cpp_test_suite_golden_override_ir_parser/error.parse.text_block_eof.jsonnet.golden
@@ -0,0 +1,2 @@
+syntax error: unexpected end of text block
+ error.parse.text_block_eof.jsonnet:17:1
\ No newline at end of file
tests/cpp_test_suite_golden_override_ir_parser/error.parse.text_block_not_terminated.jsonnet.goldendiffbeforeafterboth--- /dev/null
+++ b/tests/cpp_test_suite_golden_override_ir_parser/error.parse.text_block_not_terminated.jsonnet.golden
@@ -0,0 +1,2 @@
+syntax error: unexpected end of text block
+ error.parse.text_block_not_terminated.jsonnet:17:1
\ No newline at end of file
tests/go_testdata_golden_override_ir_parser/error_hexnumber.jsonnet.goldendiffbeforeafterboth--- /dev/null
+++ b/tests/go_testdata_golden_override_ir_parser/error_hexnumber.jsonnet.golden
@@ -0,0 +1,2 @@
+syntax error: expected end of file, got identifier "x42"
+ error_hexnumber.jsonnet:1:2
\ No newline at end of file
tests/go_testdata_golden_override_ir_parser/import_syntax_error.jsonnet.goldendiffbeforeafterboth--- /dev/null
+++ b/tests/go_testdata_golden_override_ir_parser/import_syntax_error.jsonnet.golden
@@ -0,0 +1,3 @@
+syntax error: unexpected end of file
+ syntax_error.jsonnet:1:4
+ import_syntax_error.jsonnet:1:1-8: import "syntax_error.jsonnet"
\ No newline at end of file
tests/go_testdata_golden_override_ir_parser/object_comp_assert.jsonnet.goldendiffbeforeafterboth--- /dev/null
+++ b/tests/go_testdata_golden_override_ir_parser/object_comp_assert.jsonnet.golden
@@ -0,0 +1,2 @@
+syntax error: asserts are unsupported in object comprehension
+ object_comp_assert.jsonnet:1:46
\ No newline at end of file
tests/go_testdata_golden_override_ir_parser/object_comp_illegal.jsonnet.goldendiffbeforeafterboth--- /dev/null
+++ b/tests/go_testdata_golden_override_ir_parser/object_comp_illegal.jsonnet.golden
@@ -0,0 +1,2 @@
+syntax error: missing object comprehension field
+ object_comp_illegal.jsonnet:1:34
\ No newline at end of file
tests/go_testdata_golden_override_ir_parser/static_error_eof.jsonnet.goldendiffbeforeafterboth--- /dev/null
+++ b/tests/go_testdata_golden_override_ir_parser/static_error_eof.jsonnet.golden
@@ -0,0 +1,2 @@
+syntax error: expected ';', got end of file
+ static_error_eof.jsonnet:1:12
\ No newline at end of file
tests/go_testdata_golden_override_ir_parser/syntax_error.jsonnet.goldendiffbeforeafterboth--- /dev/null
+++ b/tests/go_testdata_golden_override_ir_parser/syntax_error.jsonnet.golden
@@ -0,0 +1,2 @@
+syntax error: unexpected end of file
+ syntax_error.jsonnet:1:4
\ No newline at end of file
tests/go_testdata_golden_override_ir_parser/unfinished_args.jsonnet.goldendiffbeforeafterboth--- /dev/null
+++ b/tests/go_testdata_golden_override_ir_parser/unfinished_args.jsonnet.golden
@@ -0,0 +1,2 @@
+syntax error: expected ')', got end of file
+ unfinished_args.jsonnet:1:17
\ No newline at end of file
tests/golden/null_coalesce_chain.jsonnetdiffbeforeafterboth--- /dev/null
+++ b/tests/golden/null_coalesce_chain.jsonnet
@@ -0,0 +1,10 @@
+// Regression test: chained index a.b.c.d should produce a single
+// Index { a, [b, c, d] } not nested Index nodes.
+// This matters for exp-null-coaelse where a?.b.c should skip .c if .b is null.
+
+local obj = { a: { b: { c: 42 } } };
+
+[
+ obj.a.b.c,
+ {a: {b: 1}}.a.b,
+]
tests/golden_null_coalesce/null_coalesce_access.jsonnetdiffbeforeafterboth--- /dev/null
+++ b/tests/golden_null_coalesce/null_coalesce_access.jsonnet
@@ -0,0 +1,18 @@
+// Test null-coalesce chained access: a?.b.c should return null when b is missing,
+// not fail with "field c not found on null".
+
+local obj = { a: { b: { c: 42 } } };
+
+[
+ // null-coalesce on missing field should return null, not error
+ obj?.missing.b.c,
+
+ // null-coalesce on present field continues
+ obj?.a.b.c,
+
+ // null-coalesce with bracket index
+ obj?.["missing"].b.c,
+
+ // chained null-coalesce
+ obj?.a?.missing.c,
+]
tests/tests/cpp_test_suite.rsdiffbeforeafterboth--- a/tests/tests/cpp_test_suite.rs
+++ b/tests/tests/cpp_test_suite.rs
@@ -241,9 +241,26 @@
golden = Some(golden_path);
}
+ // ir-parser has its own override layer
+ #[cfg(feature = "ir-parser")]
+ let ir_parser_override_path = {
+ let p = root_tests
+ .join(format!("{root_dir}_golden_override_ir_parser"))
+ .join(golden_path.file_name().expect("file has basename"));
+ if let Some(golden_path) = read_file(&p)? {
+ golden = Some(golden_path);
+ }
+ p
+ };
+
// Otherwise assume test should just not fail and return true.
let golden = golden.unwrap_or_else(|| "true".to_owned());
+ #[cfg(feature = "ir-parser")]
+ let update_golden_path = &ir_parser_override_path;
+ #[cfg(not(feature = "ir-parser"))]
+ let update_golden_path = &golden_override;
+
match (serde_json::from_str(&result), serde_json::from_str(&golden)) {
(Err(_), Ok(_)) => panic!(
"unexpected error for golden {}:\n<got>\n{result}\n</got>\n<golden>\n{golden}\n</golden>",
@@ -258,7 +275,7 @@
let diff = JsonDiff::diff_string(&golden, &result_v, false);
if let Some(diff) = diff {
if env::var_os("UPDATE_GOLDEN").is_some() {
- fs::write(golden_override, result)?;
+ fs::write(update_golden_path, result)?;
} else {
panic!(
"Result \n{result_v:#}\n\
@@ -273,7 +290,7 @@
(Err(_), Err(_)) => {
if result != golden.trim_end() {
if env::var_os("UPDATE_GOLDEN").is_some() {
- fs::write(golden_override, result)?;
+ fs::write(update_golden_path, result)?;
} else {
panic!(
"golden didn't match for {}:\n<got>\n{result}\n</got>\n<golden>\n{golden}\n</golden>",
tests/tests/golden.rsdiffbeforeafterboth--- a/tests/tests/golden.rs
+++ b/tests/tests/golden.rs
@@ -45,3 +45,13 @@
assert_snapshot!(result);
});
}
+
+#[test]
+#[cfg(feature = "exp-null-coaelse")]
+fn golden_null_coalesce() {
+ glob!("../", "golden_null_coalesce/*.jsonnet", |path| {
+ let result = run(path);
+
+ assert_snapshot!(result);
+ });
+}
tests/tests/snapshots/golden__golden@null_coalesce_chain.jsonnet.snapdiffbeforeafterboth--- /dev/null
+++ b/tests/tests/snapshots/golden__golden@null_coalesce_chain.jsonnet.snap
@@ -0,0 +1,9 @@
+---
+source: tests/tests/golden.rs
+expression: result
+input_file: tests/golden/null_coalesce_chain.jsonnet
+---
+[
+ 42,
+ 1
+]
tests/tests/snapshots/golden__golden_null_coalesce.snapdiffbeforeafterboth--- /dev/null
+++ b/tests/tests/snapshots/golden__golden_null_coalesce.snap
@@ -0,0 +1,11 @@
+---
+source: tests/tests/golden.rs
+expression: result
+input_file: tests/golden_null_coalesce/null_coalesce_access.jsonnet
+---
+[
+ null,
+ 42,
+ null,
+ null
+]