git.delta.rocks / jrsonnet / refs/heads / master

difftreelog

source

crates/jrsonnet-ir-parser/src/lib.rs26.2 KiBsourcehistory
1use jrsonnet_gcmodule::Acyclic;2use jrsonnet_ir::{3	ArgsDesc, AssertExpr, AssertStmt, BinaryOp, BinaryOpType, BindSpec, CompSpec, Destruct, Expr,4	ExprParam, ExprParams, FieldMember, FieldName, ForSpecData, IStr, IdentityKind, IfElse,5	IfSpecData, ImportKind, IndexPart, Member, NumValue, ObjBody, ObjComp, ObjMembers, Slice,6	SliceDesc, Source, Span, Spanned, TrivialVal, UnaryOpType, Visibility, unescape,7};8use jrsonnet_lexer::{Lexeme, Lexer, Span as LexSpan, SyntaxKind, T, collect_lexed_str_block};910pub struct ParserSettings {11	pub source: Source,12}1314#[derive(Debug, Clone)]15pub struct ParseError {16	pub message: String,17	pub location: Span,18}1920impl std::fmt::Display for ParseError {21	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {22		write!(f, "{}", self.message)23	}24}2526type Result<T> = std::result::Result<T, ParseError>;2728struct Parser<'a> {29	lexemes: Vec<Lexeme<'a>>,30	offset: usize,31	source: Source,32}3334impl<'a> Parser<'a> {35	fn new(code: &'a str, source: Source) -> Self {36		Self {37			lexemes: Lexer::new(code)38				.filter(|l| {39					!matches!(40						l.kind,41						SyntaxKind::WHITESPACE42							| SyntaxKind::SINGLE_LINE_SLASH_COMMENT43							| SyntaxKind::SINGLE_LINE_HASH_COMMENT44							| SyntaxKind::MULTI_LINE_COMMENT45					)46				})47				.collect(),48			offset: 0,49			source,50		}51	}5253	fn peek(&self) -> SyntaxKind {54		if self.at_eof() {55			SyntaxKind::EOF56		} else {57			self.lexemes[self.offset].kind58		}59	}6061	fn text(&self) -> &'a str {62		self.lexemes[self.offset].text63	}6465	fn at(&self, kind: SyntaxKind) -> bool {66		!self.at_eof() && self.peek() == kind67	}6869	#[allow(dead_code)]70	fn nth(&self, n: usize) -> SyntaxKind {71		self.lexemes72			.get(self.offset + n)73			.map_or(SyntaxKind::EOF, |l| l.kind)74	}7576	fn eat_any(&mut self) {77		self.offset += 1;78	}7980	fn eat_any_spanned(&mut self) -> Span {81		let start = self.span_start();82		self.eat_any();83		let end = self.span_end();84		Span(self.source.clone(), start, end)85	}8687	fn at_eof(&self) -> bool {88		self.offset >= self.lexemes.len()89	}9091	fn try_eat(&mut self, t: SyntaxKind) -> bool {92		if self.at(t) {93			self.eat_any();94			return true;95		}96		false97	}9899	fn current_desc(&self) -> String {100		if self.at_eof() {101			return "end of file".to_owned();102		}103		let kind = self.peek();104		let text = self.text();105		let name = kind.display_name();106		if matches!(kind, SyntaxKind::IDENT | SyntaxKind::FLOAT) {107			format!("{name} \"{text}\"")108		} else {109			name.to_owned()110		}111	}112113	fn eat(&mut self, t: SyntaxKind) -> Result<()> {114		if !self.at(t) {115			return Err(self.error(format!(116				"expected {}, got {}",117				t.display_name(),118				self.current_desc(),119			)));120		}121		self.eat_any();122		Ok(())123	}124	fn eat_spanned(&mut self, t: SyntaxKind) -> Result<Span> {125		let start = self.span_start();126		self.eat(t)?;127		let end = self.span_end();128		Ok(Span(self.source.clone(), start, end))129	}130131	fn span_start(&self) -> u32 {132		if self.at_eof() {133			if let Some(last) = self.lexemes.last() {134				return last.range.1;135			}136			return 0;137		}138		self.lexemes[self.offset].range.0139	}140141	fn span_end(&self) -> u32 {142		self.lexemes[self.offset - 1].range.1143	}144145	fn error(&self, message: String) -> ParseError {146		if self.offset == self.lexemes.len() {147			let pos = self.lexemes.last().map_or(0, |v| v.range.1);148			return ParseError {149				location: Span(self.source.clone(), pos, pos),150				message,151			};152		}153		let LexSpan(start, end) = self.lexemes[self.offset].range;154		ParseError {155			location: Span(self.source.clone(), start, end),156			message,157		}158	}159}160161fn spanned<T: Acyclic>(162	p: &mut Parser<'_>,163	cb: impl FnOnce(&mut Parser<'_>) -> Result<T>,164) -> Result<Spanned<T>> {165	let start = p.span_start();166	let v = cb(p)?;167	let end = p.span_end();168	Ok(Spanned::new(v, Span(p.source.clone(), start, end)))169}170171fn parse_string_content(p: &mut Parser<'_>) -> Result<IStr> {172	let kind = p.peek();173	let text = p.text();174	let s = match kind {175		SyntaxKind::STRING_DOUBLE => {176			let inner = &text[1..text.len() - 1];177			unescape::unescape(inner).ok_or_else(|| p.error("invalid string escape".into()))?178		}179		SyntaxKind::STRING_SINGLE => {180			let inner = &text[1..text.len() - 1];181			unescape::unescape(inner).ok_or_else(|| p.error("invalid string escape".into()))?182		}183		SyntaxKind::STRING_DOUBLE_VERBATIM => {184			let inner = &text[2..text.len() - 1];185			inner.replace("\"\"", "\"")186		}187		SyntaxKind::STRING_SINGLE_VERBATIM => {188			let inner = &text[2..text.len() - 1];189			inner.replace("''", "'")190		}191		SyntaxKind::STRING_BLOCK => {192			let inner = &text[3..];193			let collected = collect_lexed_str_block(inner)194				.map_err(|_| p.error("invalid string block".into()))?;195			let mut result = String::new();196			for (i, line) in collected.lines.iter().enumerate() {197				if i > 0 {198					result.push('\n');199				}200				result.push_str(line);201			}202			if !collected.truncate {203				result.push('\n');204			}205			result206		}207		_ => return Err(p.error(format!("expected string, got {}", p.current_desc()))),208	};209	p.eat_any();210	Ok(s.into())211}212213fn is_string_token(kind: SyntaxKind) -> bool {214	matches!(215		kind,216		SyntaxKind::STRING_DOUBLE217			| SyntaxKind::STRING_SINGLE218			| SyntaxKind::STRING_DOUBLE_VERBATIM219			| SyntaxKind::STRING_SINGLE_VERBATIM220			| SyntaxKind::STRING_BLOCK221	)222}223224fn parse_number(p: &mut Parser<'_>) -> Result<NumValue> {225	let text = p.text();226	let n: f64 = text227		.replace('_', "")228		.parse()229		.map_err(|_| p.error(format!("invalid number literal: {text}")))?;230231	let v = match NumValue::try_from(n) {232		Ok(v) => v,233		Err(e) => return Err(p.error(format!("invalid number value: {e}"))),234	};235236	p.eat_any();237238	Ok(v)239}240241fn ident(p: &mut Parser<'_>) -> Result<IStr> {242	if !p.at(SyntaxKind::IDENT) {243		return Err(p.error(format!("expected identifier, got {}", p.current_desc())));244	}245	let text = p.text();246	p.eat_any();247	Ok(IStr::from(text))248}249250fn assert_stmt(p: &mut Parser<'_>) -> Result<AssertStmt> {251	p.eat(T![assert])?;252	let assertion = spanned(p, expr)?;253	let message = if p.try_eat(T![:]) {254		Some(expr(p)?)255	} else {256		None257	};258	Ok(AssertStmt { assertion, message })259}260261fn if_spec_data(p: &mut Parser<'_>) -> Result<IfSpecData> {262	let v = spanned(p, |p| p.eat(T![if]))?;263	let cond = expr(p)?;264	Ok(IfSpecData { span: v.span, cond })265}266267fn if_else(p: &mut Parser<'_>) -> Result<IfElse> {268	let cond = if_spec_data(p)?;269	p.eat(T![then])?;270	let cond_then = expr(p)?;271	let cond_else = if p.try_eat(T![else]) {272		Some(expr(p)?)273	} else {274		None275	};276	Ok(IfElse {277		cond,278		cond_then,279		cond_else,280	})281}282283fn slice_desc(p: &mut Parser<'_>, start: Option<Spanned<Expr>>) -> Result<SliceDesc> {284	p.eat(T![:])?;285	let end = if !p.at(T![:]) && !p.at(T![']']) {286		Some(spanned(p, expr)?)287	} else {288		None289	};290	let step = if p.try_eat(T![:]) {291		if p.at(T![']']) {292			None293		} else {294			Some(spanned(p, expr)?)295		}296	} else {297		None298	};299	Ok(SliceDesc { start, end, step })300}301302fn destruct(p: &mut Parser<'_>) -> Result<Destruct> {303	if p.at(SyntaxKind::IDENT) {304		return Ok(Destruct::Full(spanned(p, ident)?));305	}306	#[cfg(not(feature = "exp-destruct"))]307	return Err(p.error(format!("expected identifier, got {}", p.current_desc())));308	#[cfg(feature = "exp-destruct")]309	{310		if p.try_eat(T![?]) {311			return Ok(Destruct::Skip);312		}313		if p.at(T!['[']) {314			return destruct_array(p);315		}316		if p.at(T!['{']) {317			return destruct_object(p);318		}319		Err(p.error(format!(320			"expected destructure pattern, got {}",321			p.current_desc()322		)))323	}324}325326#[cfg(feature = "exp-destruct")]327fn destruct_rest(p: &mut Parser<'_>) -> Result<jrsonnet_ir::DestructRest> {328	p.eat(T![...])?;329	if p.at(SyntaxKind::IDENT) {330		Ok(jrsonnet_ir::DestructRest::Keep(ident(p)?))331	} else {332		Ok(jrsonnet_ir::DestructRest::Drop)333	}334}335336#[cfg(feature = "exp-destruct")]337fn destruct_array(p: &mut Parser<'_>) -> Result<Destruct> {338	p.eat(T!['['])?;339	let mut start = Vec::new();340	let mut rest = None;341	let mut end = Vec::new();342	if !p.at(T![']']) {343		loop {344			if p.at(T![...]) {345				rest = Some(destruct_rest(p)?);346				if p.try_eat(T![,]) {347					if !p.at(T![']']) {348						loop {349							end.push(destruct(p)?);350							if !p.try_eat(T![,]) {351								break;352							}353							if p.at(T![']']) {354								break;355							}356						}357					}358				}359				break;360			}361			start.push(destruct(p)?);362			if !p.try_eat(T![,]) {363				break;364			}365			if p.at(T![']']) {366				break;367			}368		}369	}370	p.eat(T![']'])?;371	Ok(Destruct::Array { start, rest, end })372}373374#[cfg(feature = "exp-destruct")]375fn destruct_object(p: &mut Parser<'_>) -> Result<Destruct> {376	p.eat(T!['{'])?;377	let mut fields = Vec::new();378	let mut rest = None;379	if !p.at(T!['}']) {380		loop {381			if p.at(T![...]) {382				rest = Some(destruct_rest(p)?);383				p.try_eat(T![,]);384				break;385			}386			let name = ident(p)?;387			let into = if p.try_eat(T![:]) {388				Some(destruct(p)?)389			} else {390				None391			};392			let default = if p.try_eat(T![=]) {393				Some(spanned(p, expr)?)394			} else {395				None396			};397			fields.push((name, into, default));398			if !p.try_eat(T![,]) {399				break;400			}401			if p.at(T!['}']) {402				break;403			}404		}405	}406	p.eat(T!['}'])?;407	Ok(Destruct::Object { fields, rest })408}409410fn params(p: &mut Parser<'_>) -> Result<ExprParams> {411	if p.at(T![')']) {412		return Ok(ExprParams::new(Vec::new()));413	}414	let mut result = Vec::new();415	loop {416		let d = destruct(p)?;417		let default = if p.try_eat(T![=]) {418			Some(expr(p)?)419		} else {420			None421		};422		result.push(ExprParam {423			destruct: d,424			default,425		});426		if !p.try_eat(T![,]) {427			break;428		}429		if p.at(T![')']) {430			break;431		}432	}433	Ok(ExprParams::new(result))434}435436fn args(p: &mut Parser<'_>) -> Result<ArgsDesc> {437	if p.at(T![')']) {438		return Ok(ArgsDesc::new(Vec::new(), Vec::new(), Vec::new()));439	}440	let mut unnamed = Vec::new();441	let mut names = Vec::new();442	let mut values = Vec::new();443	let mut named_started = false;444	loop {445		let is_named = p.at(SyntaxKind::IDENT) && {446			let next_offset = p.offset + 1;447			next_offset < p.lexemes.len() && p.lexemes[next_offset].kind == T![=]448		};449		if is_named {450			let name: IStr = ident(p)?;451			p.eat(T![=])?;452453			names.push(name);454			values.push(expr(p)?);455			named_started = true;456		} else {457			if named_started {458				return Err(p.error("positional argument after named argument".into()));459			}460			unnamed.push(expr(p)?);461		}462		if !p.try_eat(T![,]) {463			break;464		}465		if p.at(T![')']) {466			break;467		}468	}469	Ok(ArgsDesc::new(unnamed, names, values))470}471472fn bind(p: &mut Parser<'_>) -> Result<BindSpec> {473	#[cfg(feature = "exp-destruct")]474	{475		if !p.at(SyntaxKind::IDENT) {476			let d = destruct(p)?;477			p.eat(T![=])?;478			return Ok(BindSpec::Field {479				into: d,480				value: expr(p)?,481			});482		}483	}484	let name = spanned(p, ident)?;485	if p.try_eat(T!['(']) {486		let ps = params(p)?;487		p.eat(T![')'])?;488		p.eat(T![=])?;489		Ok(BindSpec::Function {490			name,491			params: ps,492			value: expr(p)?,493		})494	} else {495		p.eat(T![=])?;496		Ok(BindSpec::Field {497			into: Destruct::Full(name),498			value: expr(p)?,499		})500	}501}502503fn visibility(p: &mut Parser<'_>) -> Result<Visibility> {504	p.eat(T![:])?;505	if p.try_eat(T![:]) {506		if p.try_eat(T![:]) {507			Ok(Visibility::Unhide)508		} else {509			Ok(Visibility::Hidden)510		}511	} else {512		Ok(Visibility::Normal)513	}514}515516fn field_name(p: &mut Parser<'_>) -> Result<FieldName> {517	if p.at(SyntaxKind::IDENT) {518		Ok(FieldName::Fixed(ident(p)?))519	} else if is_string_token(p.peek()) {520		Ok(FieldName::Fixed(parse_string_content(p)?))521	} else if p.at(T!['[']) {522		p.eat(T!['['])?;523		let e = expr(p)?;524		p.eat(T![']'])?;525		Ok(FieldName::Dyn(e))526	} else {527		Err(p.error(format!("expected field name, got {}", p.current_desc())))528	}529}530531fn field(p: &mut Parser<'_>) -> Result<FieldMember> {532	let name = spanned(p, field_name)?;533534	if p.at(T!['(']) {535		p.eat(T!['('])?;536		let ps = params(p)?;537		p.eat(T![')'])?;538		let vis = visibility(p)?;539		Ok(FieldMember {540			name,541			plus: false,542			params: Some(ps),543			visibility: vis,544			value: expr(p)?,545		})546	} else {547		let plus = p.try_eat(T![+]);548		let vis = visibility(p)?;549		Ok(FieldMember {550			name,551			plus,552			params: None,553			visibility: vis,554			value: expr(p)?,555		})556	}557}558559fn member(p: &mut Parser<'_>) -> Result<Member> {560	if p.at(T![local]) {561		p.eat(T![local])?;562		Ok(Member::BindStmt(bind(p)?))563	} else if p.at(T![assert]) {564		Ok(Member::AssertStmt(assert_stmt(p)?))565	} else {566		Ok(Member::Field(field(p)?))567	}568}569570fn for_spec(p: &mut Parser<'_>) -> Result<CompSpec> {571	p.eat(T![for])?;572	#[cfg(feature = "exp-object-iteration")]573	if p.at(T!['[']) && p.nth(1) == SyntaxKind::IDENT && p.nth(2) == T![']'] && p.nth(3) == T![:] {574		p.eat(T!['['])?;575		let key = ident(p)?;576		p.eat(T![']'])?;577		let visibility = visibility(p)?;578		let value = destruct(p)?;579		p.eat(T![in])?;580		let over = expr(p)?;581		return Ok(CompSpec::ForObjSpec(jrsonnet_ir::ForObjSpecData {582			key,583			visibility,584			value,585			over,586		}));587	}588	let d = destruct(p)?;589	p.eat(T![in])?;590	let over = expr(p)?;591	Ok(CompSpec::ForSpec(ForSpecData { destruct: d, over }))592}593594fn compspecs(p: &mut Parser<'_>) -> Result<Vec<CompSpec>> {595	let mut specs = Vec::new();596	specs.push(for_spec(p)?);597	loop {598		if p.at(T![for]) {599			specs.push(for_spec(p)?);600		} else if p.at(T![if]) {601			let isd = if_spec_data(p)?;602			specs.push(CompSpec::IfSpec(isd));603		} else {604			break;605		}606	}607	Ok(specs)608}609610fn objinside(p: &mut Parser<'_>) -> Result<ObjBody> {611	if p.at(T!['}']) {612		return Ok(ObjBody::MemberList(ObjMembers {613			locals: Vec::new(),614			asserts: Vec::new(),615			fields: Vec::new(),616		}));617	}618619	let mut members = Vec::new();620	loop {621		members.push(member(p)?);622		if !p.try_eat(T![,]) {623			break;624		}625		if p.at(T!['}']) || p.at(T![for]) {626			break;627		}628	}629630	if p.at(T![for]) {631		let specs = compspecs(p)?;632		let mut locals = Vec::new();633		let mut field_member = None;634		for m in members {635			match m {636				Member::Field(f) => {637					if field_member.is_some() {638						return Err(639							p.error("object comprehension can only contain one field".into())640						);641					}642					field_member = Some(f);643				}644				Member::BindStmt(b) => locals.push(b),645				Member::AssertStmt(_) => {646					return Err(p.error("asserts are unsupported in object comprehension".into()));647				}648			}649		}650		Ok(ObjBody::ObjComp(ObjComp {651			locals,652			field: Box::new(653				field_member.ok_or_else(|| p.error("missing object comprehension field".into()))?,654			),655			compspecs: specs,656		}))657	} else {658		let mut locals = Vec::new();659		let mut asserts = Vec::new();660		let mut fields = Vec::new();661		for m in members {662			match m {663				Member::Field(f) => fields.push(f),664				Member::BindStmt(b) => locals.push(b),665				Member::AssertStmt(a) => asserts.push(a),666			}667		}668		Ok(ObjBody::MemberList(ObjMembers {669			locals,670			asserts,671			fields,672		}))673	}674}675676#[allow(clippy::too_many_lines)]677fn expr_basic(p: &mut Parser<'_>) -> Result<Expr> {678	match p.peek() {679		T![self] => Ok(Expr::Identity(p.eat_any_spanned(), IdentityKind::This)),680		T![super] => Ok(Expr::Identity(p.eat_any_spanned(), IdentityKind::Super)),681		T!['$'] => Ok(Expr::Identity(p.eat_any_spanned(), IdentityKind::Dollar)),682		T![null] => {683			p.eat_any();684			Ok(Expr::Trivial(TrivialVal::Null))685		}686		T![true] => {687			p.eat_any();688			Ok(Expr::Trivial(TrivialVal::Bool(true)))689		}690		T![false] => {691			p.eat_any();692			Ok(Expr::Trivial(TrivialVal::Bool(false)))693		}694695		SyntaxKind::STRING_DOUBLE696		| SyntaxKind::STRING_SINGLE697		| SyntaxKind::STRING_DOUBLE_VERBATIM698		| SyntaxKind::STRING_SINGLE_VERBATIM699		| SyntaxKind::STRING_BLOCK => Ok(Expr::Trivial(TrivialVal::Str(parse_string_content(p)?))),700701		SyntaxKind::FLOAT => Ok(Expr::Trivial(TrivialVal::Num(parse_number(p)?))),702703		T!['('] => {704			p.eat(T!['('])?;705			let e = expr(p)?;706			p.eat(T![')'])?;707			Ok(e)708		}709710		T!['['] => {711			p.eat(T!['['])?;712			if p.at(T![']']) {713				p.eat(T![']'])?;714				return Ok(Expr::Arr(Vec::new()));715			}716			let first = expr(p)?;717			if p.at(T![for]) {718				let specs = compspecs(p)?;719				p.eat(T![']'])?;720				Ok(Expr::ArrComp(Box::new(first), specs))721			} else if p.at(T![,]) && {722				let next = p.offset + 1;723				next < p.lexemes.len() && p.lexemes[next].kind == T![for]724			} {725				p.eat(T![,])?;726				let specs = compspecs(p)?;727				p.eat(T![']'])?;728				Ok(Expr::ArrComp(Box::new(first), specs))729			} else {730				let mut elems = vec![first];731				while p.try_eat(T![,]) {732					if p.at(T![']']) {733						break;734					}735					elems.push(expr(p)?);736				}737				p.eat(T![']'])?;738				Ok(Expr::Arr(elems))739			}740		}741742		T!['{'] => {743			p.eat(T!['{'])?;744			let body = objinside(p)?;745			p.eat(T!['}'])?;746			Ok(Expr::Obj(body))747		}748749		T![local] => {750			p.eat(T![local])?;751			let mut binds = Vec::new();752			loop {753				if p.at(T![;]) {754					break;755				}756				binds.push(bind(p)?);757				if !p.try_eat(T![,]) {758					break;759				}760			}761			p.eat(T![;])?;762			let body = expr(p)?;763			Ok(Expr::LocalExpr(binds, Box::new(body)))764		}765766		T![if] => Ok(Expr::IfElse(Box::new(if_else(p)?))),767768		T![function] => {769			let span = p.eat_spanned(T![function])?;770			p.eat(T!['('])?;771			let ps = params(p)?;772			p.eat(T![')'])?;773			let body = expr(p)?;774			Ok(Expr::Function(span, ps, Box::new(body)))775		}776777		T![assert] => {778			let a = assert_stmt(p)?;779			p.eat(T![;])?;780			let rest = expr(p)?;781			Ok(Expr::AssertExpr(Box::new(AssertExpr { assert: a, rest })))782		}783784		T![error] => {785			let span = spanned(p, |p| p.eat(T![error]))?;786			let e = expr(p)?;787			Ok(Expr::ErrorStmt(span.span, Box::new(e)))788		}789790		T![importstr] => {791			let kind = spanned(p, |p| {792				p.eat(T![importstr])?;793				Ok(ImportKind::Str)794			})?;795			let path = expr(p)?;796			Ok(Expr::Import(kind, Box::new(path)))797		}798799		T![importbin] => {800			let kind = spanned(p, |p| {801				p.eat(T![importbin])?;802				Ok(ImportKind::Bin)803			})?;804			let path = expr(p)?;805			Ok(Expr::Import(kind, Box::new(path)))806		}807808		T![import] => {809			let kind = spanned(p, |p| {810				p.eat(T![import])?;811				Ok(ImportKind::Normal)812			})?;813			let path = expr(p)?;814			Ok(Expr::Import(kind, Box::new(path)))815		}816817		SyntaxKind::IDENT => {818			let n = spanned(p, |p| {819				let s: IStr = p.text().into();820				p.eat_any();821				Ok(s)822			})?;823			Ok(Expr::Var(n))824		}825826		_ => Err(p.error(format!("unexpected {}", p.current_desc()))),827	}828}829830fn flush_index_parts(e: &mut Expr, parts: &mut Vec<IndexPart>) {831	if parts.is_empty() {832		return;833	}834	let old = std::mem::replace(e, Expr::Trivial(TrivialVal::Null));835	*e = Expr::Index {836		indexable: Box::new(old),837		parts: std::mem::take(parts),838	};839}840841fn expr_suffix(p: &mut Parser<'_>) -> Result<Expr> {842	let mut e = expr_basic(p)?;843	// Accumulate consecutive index parts (.field, [expr], ?.field, ?.[expr])844	// into a single Expr::Index. This is critical for null-coalesce semantics:845	// a?.b.c needs all parts in one Index so the evaluator can skip .c when .b is null.846	let mut parts: Vec<IndexPart> = Vec::new();847848	loop {849		#[cfg(feature = "exp-null-coaelse")]850		if p.at(T![?]) {851			p.eat_any();852			if p.try_eat(T![.]) {853				if p.at(T!['[']) {854					// ?.[expr]855					p.eat(T!['['])?;856					let idx = spanned(p, expr)?;857					p.eat(T![']'])?;858					parts.push(IndexPart {859						span: idx.span,860						value: idx.value,861						null_coaelse: true,862					});863				} else {864					// ?.field865					let id_spanned = spanned(p, |p| Ok(Expr::Trivial(TrivialVal::Str(ident(p)?))))?;866					parts.push(IndexPart {867						span: id_spanned.span,868						value: id_spanned.value,869						null_coaelse: true,870					});871				}872			} else {873				return Err(p.error("expected '.' after '?'".into()));874			}875			continue;876		}877878		if p.at(T![.]) {879			p.eat(T![.])?;880			let id_spanned = spanned(p, |p| Ok(Expr::Trivial(TrivialVal::Str(ident(p)?))))?;881			parts.push(IndexPart {882				span: id_spanned.span,883				value: id_spanned.value,884				#[cfg(feature = "exp-null-coaelse")]885				null_coaelse: false,886			});887		} else if p.at(T!['[']) {888			p.eat(T!['['])?;889890			if p.at(T![:]) {891				// Slice: flush index parts first, then handle slice892				flush_index_parts(&mut e, &mut parts);893				let slice = slice_desc(p, None)?;894				p.eat(T![']'])?;895				e = Expr::Slice(Box::new(Slice { value: e, slice }));896			} else {897				let idx = spanned(p, expr)?;898				if p.at(T![:]) {899					// Slice with start: flush index parts first900					flush_index_parts(&mut e, &mut parts);901					let slice = slice_desc(p, Some(idx))?;902					p.eat(T![']'])?;903					e = Expr::Slice(Box::new(Slice { value: e, slice }));904				} else {905					// Bracket index: add to parts906					p.eat(T![']'])?;907					parts.push(IndexPart {908						span: idx.span,909						value: idx.value,910						#[cfg(feature = "exp-null-coaelse")]911						null_coaelse: false,912					});913				}914			}915		} else if p.at(T!['(']) {916			flush_index_parts(&mut e, &mut parts);917			let args_spanned = spanned(p, |p| {918				p.eat(T!['('])?;919				let a = args(p)?;920				p.eat(T![')'])?;921				Ok(a)922			})?;923			let tailstrict = p.try_eat(T![tailstrict]);924			e = Expr::Apply(Box::new(e), args_spanned, tailstrict);925		} else if p.at(T!['{']) {926			flush_index_parts(&mut e, &mut parts);927			p.eat(T!['{'])?;928			let body = objinside(p)?;929			p.eat(T!['}'])?;930			e = Expr::ObjExtend(Box::new(e), body);931		} else {932			break;933		}934	}935936	flush_index_parts(&mut e, &mut parts);937	Ok(e)938}939940fn prefix_binding_power(op: UnaryOpType) -> u8 {941	match op {942		UnaryOpType::Plus | UnaryOpType::Minus | UnaryOpType::Not | UnaryOpType::BitNot => 20,943	}944}945946fn infix_binding_power(op: BinaryOpType) -> (u8, u8) {947	match op {948		BinaryOpType::Or => (2, 3),949		#[cfg(feature = "exp-null-coaelse")]950		BinaryOpType::NullCoaelse => (2, 3),951		BinaryOpType::And => (4, 5),952		BinaryOpType::BitOr => (6, 7),953		BinaryOpType::BitXor => (8, 9),954		BinaryOpType::BitAnd => (10, 11),955		BinaryOpType::Eq | BinaryOpType::Neq => (12, 13),956		BinaryOpType::Lt957		| BinaryOpType::Gt958		| BinaryOpType::Lte959		| BinaryOpType::Gte960		| BinaryOpType::In => (14, 15),961		BinaryOpType::Lhs | BinaryOpType::Rhs => (16, 17),962		BinaryOpType::Add | BinaryOpType::Sub => (18, 19),963		BinaryOpType::Mul | BinaryOpType::Div | BinaryOpType::Mod => (20, 21),964	}965}966967fn unary_op(kind: SyntaxKind) -> Option<UnaryOpType> {968	match kind {969		T![+] => Some(UnaryOpType::Plus),970		T![-] => Some(UnaryOpType::Minus),971		T![!] => Some(UnaryOpType::Not),972		T![~] => Some(UnaryOpType::BitNot),973		_ => None,974	}975}976977fn binary_op(p: &Parser<'_>) -> Option<BinaryOpType> {978	match p.peek() {979		T![||] => Some(BinaryOpType::Or),980		T![&&] => Some(BinaryOpType::And),981		T![|] => Some(BinaryOpType::BitOr),982		T![^] => Some(BinaryOpType::BitXor),983		T![&] => Some(BinaryOpType::BitAnd),984		T![==] => Some(BinaryOpType::Eq),985		T![!=] => Some(BinaryOpType::Neq),986		T![<] => Some(BinaryOpType::Lt),987		T![>] => Some(BinaryOpType::Gt),988		T![<=] => Some(BinaryOpType::Lte),989		T![>=] => Some(BinaryOpType::Gte),990		T![<<] => Some(BinaryOpType::Lhs),991		T![>>] => Some(BinaryOpType::Rhs),992		T![+] => Some(BinaryOpType::Add),993		T![-] => Some(BinaryOpType::Sub),994		T![*] => Some(BinaryOpType::Mul),995		T![/] => Some(BinaryOpType::Div),996		T![%] => Some(BinaryOpType::Mod),997		T![in] => Some(BinaryOpType::In),998		#[cfg(feature = "exp-null-coaelse")]999		T![??] => Some(BinaryOpType::NullCoaelse),1000		_ => None,1001	}1002}10031004fn expr_bp(p: &mut Parser<'_>, min_bp: u8) -> Result<Expr> {1005	let mut lhs = if let Some(op) = unary_op(p.peek()) {1006		p.eat_any();1007		let rbp = prefix_binding_power(op);1008		let rhs = expr_bp(p, rbp)?;1009		Expr::UnaryOp(op, Box::new(rhs))1010	} else {1011		expr_suffix(p)?1012	};10131014	loop {1015		if p.at_eof() {1016			break;1017		}10181019		let Some(op) = binary_op(p) else {1020			break;1021		};10221023		let (lbp, rbp) = infix_binding_power(op);1024		if lbp < min_bp {1025			break;1026		}10271028		p.eat_any();1029		let rhs = expr_bp(p, rbp)?;1030		lhs = Expr::BinaryOp(Box::new(BinaryOp { lhs, op, rhs }));1031	}10321033	Ok(lhs)1034}10351036fn expr(p: &mut Parser<'_>) -> Result<Expr> {1037	expr_bp(p, 0)1038}10391040pub fn parse(str: &str, settings: &ParserSettings) -> Result<Expr> {1041	let mut p = Parser::new(str, settings.source.clone());1042	for lexeme in &p.lexemes {1043		if let Some(desc) = lexeme.kind.error_description() {1044			return Err(ParseError {1045				message: desc.to_owned(),1046				location: Span(p.source.clone(), lexeme.range.0, lexeme.range.1),1047			});1048		}1049	}1050	let e = expr(&mut p)?;1051	if !p.at_eof() {1052		return Err(p.error(format!("expected end of file, got {}", p.current_desc())));1053	}1054	Ok(e)1055}10561057pub fn string_to_expr(s: IStr, settings: &ParserSettings) -> Spanned<Expr> {1058	let len = u32::try_from(s.len()).expect("code size is limited by 4gb");10591060	Spanned::new(1061		Expr::Trivial(TrivialVal::Str(s)),1062		Span(settings.source.clone(), 0, len),1063	)1064}10651066#[cfg(test)]1067mod tests {1068	use insta::assert_snapshot;10691070	use super::*;10711072	fn parse_str(input: &str) -> Expr {1073		let source = Source::new_virtual("<test>".into(), input.into());1074		let settings = ParserSettings { source };1075		parse(input, &settings).unwrap()1076	}10771078	#[test]1079	#[cfg(not(feature = "exp-null-coaelse"))]1080	fn basic_test() {1081		let v = parse_str("assert true[false] : false ; true");1082		assert_snapshot!(format!("{v:#?}"));1083	}10841085	#[test]1086	fn literals() {1087		let v = parse_str("[null, true, false, self, super, $]");1088		assert_snapshot!(format!("{v:#?}"));1089	}10901091	#[test]1092	fn basic_math() {1093		let v = parse_str("2+2*2");1094		assert_snapshot!(format!("{v:#?}"));1095	}10961097	#[test]1098	fn underscore_numbers() {1099		let v = parse_str("[1_000, 1_000.000_1, 1_0e1_0]");1100		assert_snapshot!(format!("{v:#?}"));1101	}11021103	#[test]1104	fn strings() {1105		let v = parse_str(r#"["hello", 'world', @"raw""str", @'raw''str']"#);1106		assert_snapshot!(format!("{v:#?}"));1107	}11081109	#[test]1110	fn object() {1111		let v = parse_str("{a: 1, b:: 2, c::: 3}");1112		assert_snapshot!(format!("{v:#?}"));1113	}11141115	#[test]1116	fn function_and_call() {1117		let v = parse_str("local f(x, y=1) = x + y; f(2, y=3)");1118		assert_snapshot!(format!("{v:#?}"));1119	}11201121	#[test]1122	fn if_then_else() {1123		let v = parse_str("if true then 1 else 2");1124		assert_snapshot!(format!("{v:#?}"));1125	}11261127	#[test]1128	fn imports() {1129		let v = parse_str(r#"[import "a", importstr "b", importbin "c"]"#);1130		assert_snapshot!(format!("{v:#?}"));1131	}11321133	#[test]1134	fn array_comp() {1135		let v = parse_str("[x for x in arr]");1136		assert_snapshot!(format!("{v:#?}"));1137	}11381139	#[test]1140	#[cfg(not(feature = "exp-null-coaelse"))]1141	fn index_and_suffix() {1142		let v = parse_str("std.test(2).field[0]");1143		assert_snapshot!(format!("{v:#?}"));1144	}11451146	#[test]1147	fn obj_extend() {1148		let v = parse_str("{} { x: 1 }");1149		assert_snapshot!(format!("{v:#?}"));1150	}11511152	#[test]1153	fn unary_ops() {1154		let v = parse_str("!a && !b");1155		assert_snapshot!(format!("{v:#?}"));1156	}11571158	#[test]1159	fn error_expr() {1160		let v = parse_str("error \"bad\"");1161		assert_snapshot!(format!("{v:#?}"));1162	}11631164	#[test]1165	fn slice() {1166		let v = parse_str("[a[1:], a[1::], a[:1:], a[::1]]");1167		assert_snapshot!(format!("{v:#?}"));1168	}11691170	#[test]1171	#[cfg(not(feature = "exp-null-coaelse"))]1172	fn peg_snapshots() {1173		use std::fs;11741175		use insta::glob;1176		use jrsonnet_ir::{IStr, Source};11771178		glob!("../../jrsonnet-peg-parser/src", "tests/*.jsonnet", |path| {1179			let input = fs::read_to_string(path).expect("read test file");1180			let source = Source::new_virtual("<test>".into(), IStr::empty());1181			let settings = ParserSettings { source };1182			let v = parse(&input, &settings).unwrap();1183			let v = format!("{v:#?}");1184			assert_snapshot!(v);1185		});1186	}1187}