git.delta.rocks / jrsonnet / refs/commits / 325f0a12bbb7

difftreelog

source

crates/jrsonnet-ir-parser/src/lib.rs25.7 KiBsourcehistory
1use std::rc::Rc;23use jrsonnet_gcmodule::Acyclic;4use jrsonnet_ir::{5	unescape, ArgsDesc, AssertExpr, AssertStmt, BinaryOp, BinaryOpType, BindSpec, CompSpec,6	Destruct, DestructRest, Expr, ExprParam, ExprParams, FieldMember, FieldName, ForSpecData, IStr,7	IfElse, IfSpecData, ImportKind, IndexPart, LiteralType, Member, ObjBody, ObjComp, ObjMembers,8	Slice, SliceDesc, Source, Span, Spanned, UnaryOpType, Visibility,9};10use jrsonnet_lexer::{collect_lexed_str_block, Lexeme, Lexer, SyntaxKind, T};1112pub 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>;3435struct Parser<'a> {36	lexemes: Vec<Lexeme<'a>>,37	offset: usize,38	source: Source,39}4041impl<'a> Parser<'a> {42	fn new(code: &'a str, source: Source) -> Self {43		Self {44			lexemes: Lexer::new(code)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				})54				.collect(),55			offset: 0,56			source,57		}58	}5960	fn peek(&self) -> SyntaxKind {61		if self.at_eof() {62			SyntaxKind::EOF63		} else {64			self.lexemes[self.offset].kind65		}66	}6768	fn text(&self) -> &'a str {69		self.lexemes[self.offset].text70	}7172	fn at(&self, kind: SyntaxKind) -> bool {73		!self.at_eof() && self.peek() == kind74	}7576	fn eat_any(&mut self) {77		self.offset += 1;78	}7980	fn at_eof(&self) -> bool {81		self.offset >= self.lexemes.len()82	}8384	fn try_eat(&mut self, t: SyntaxKind) -> bool {85		if self.at(t) {86			self.eat_any();87			return true;88		}89		false90	}9192	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<()> {107		if !self.at(t) {108			return Err(self.error(format!(109				"expected {}, got {}",110				t.display_name(),111				self.current_desc(),112			)));113		}114		self.eat_any();115		Ok(())116	}117118	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		}125		self.lexemes[self.offset].range.0126	}127128	fn span_end(&self) -> u32 {129		self.lexemes[self.offset - 1].range.1130	}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	}159}160161fn 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}251252fn literal(p: &mut Parser<'_>) -> Option<LiteralType> {253	let t = match p.peek() {254		T![self] => LiteralType::This,255		T![super] => LiteralType::Super,256		T!['$'] => LiteralType::Dollar,257		T![null] => LiteralType::Null,258		T![true] => LiteralType::True,259		T![false] => LiteralType::False,260		_ => return None,261	};262	p.eat_any();263	Some(t)264}265266fn assert_stmt(p: &mut Parser<'_>) -> R<AssertStmt> {267	p.eat(T![assert])?;268	let cond = spanned(p, expr)?;269	let msg = if p.try_eat(T![:]) {270		Some(spanned(p, expr)?)271	} else {272		None273	};274	Ok(AssertStmt(cond, msg))275}276277fn if_spec_data(p: &mut Parser<'_>) -> R<IfSpecData> {278	let v = spanned(p, |p| p.eat(T![if]))?;279	let cond = expr(p)?;280	Ok(IfSpecData { span: v.span, cond })281}282283fn if_else(p: &mut Parser<'_>) -> R<IfElse> {284	let cond = if_spec_data(p)?;285	p.eat(T![then])?;286	let cond_then = expr(p)?;287	let cond_else = if p.try_eat(T![else]) {288		Some(expr(p)?)289	} else {290		None291	};292	Ok(IfElse {293		cond,294		cond_then,295		cond_else,296	})297}298299fn slice_desc(p: &mut Parser<'_>, start: Option<Spanned<Expr>>) -> R<SliceDesc> {300	p.eat(T![:])?;301	let end = if !p.at(T![:]) && !p.at(T![']']) {302		Some(spanned(p, expr)?)303	} else {304		None305	};306	let step = if p.try_eat(T![:]) {307		if !p.at(T![']']) {308			Some(spanned(p, expr)?)309		} else {310			None311		}312	} else {313		None314	};315	Ok(SliceDesc { start, end, step })316}317318fn destruct(p: &mut Parser<'_>) -> R<Destruct> {319	if p.at_ident() {320		return Ok(Destruct::Full(p.expect_ident()?));321	}322	#[cfg(not(feature = "exp-destruct"))]323	return Err(p.error(format!(324		"expected identifier, got {}",325		p.current_desc()326	)));327	#[cfg(feature = "exp-destruct")]328	{329		if p.try_eat(T![?]) {330			return Ok(Destruct::Skip);331		}332		if p.at(T!['[']) {333			return destruct_array(p);334		}335		if p.at(T!['{']) {336			return destruct_object(p);337		}338		Err(p.error(format!(339			"expected destructure pattern, got {}",340			p.current_desc()341		)))342	}343}344345#[cfg(feature = "exp-destruct")]346fn destruct_rest(p: &mut Parser<'_>) -> R<DestructRest> {347	p.eat(T![...])?;348	if p.at_ident() {349		Ok(DestructRest::Keep(p.expect_ident()?))350	} else {351		Ok(DestructRest::Drop)352	}353}354355#[cfg(feature = "exp-destruct")]356fn destruct_array(p: &mut Parser<'_>) -> R<Destruct> {357	p.eat(T!['['])?;358	let mut start = Vec::new();359	let mut rest = None;360	let mut end = Vec::new();361	if !p.at(T![']']) {362		loop {363			if p.at(T![...]) {364				rest = Some(destruct_rest(p)?);365				if p.try_eat(T![,]) {366					if !p.at(T![']']) {367						loop {368							end.push(destruct(p)?);369							if !p.try_eat(T![,]) {370								break;371							}372							if p.at(T![']']) {373								break;374							}375						}376					}377				}378				break;379			}380			start.push(destruct(p)?);381			if !p.try_eat(T![,]) {382				break;383			}384			if p.at(T![']']) {385				break;386			}387		}388	}389	p.eat(T![']'])?;390	Ok(Destruct::Array { start, rest, end })391}392393#[cfg(feature = "exp-destruct")]394fn destruct_object(p: &mut Parser<'_>) -> R<Destruct> {395	p.eat(T!['{'])?;396	let mut fields = Vec::new();397	let mut rest = None;398	if !p.at(T!['}']) {399		loop {400			if p.at(T![...]) {401				rest = Some(destruct_rest(p)?);402				p.try_eat(T![,]);403				break;404			}405			let name = p.expect_ident()?;406			let into = if p.try_eat(T![:]) {407				Some(destruct(p)?)408			} else {409				None410			};411			let default = if p.try_eat(T![=]) {412				Some(Rc::new(spanned(p, expr)?))413			} else {414				None415			};416			fields.push((name, into, default));417			if !p.try_eat(T![,]) {418				break;419			}420			if p.at(T!['}']) {421				break;422			}423		}424	}425	p.eat(T!['}'])?;426	Ok(Destruct::Object { fields, rest })427}428429fn params(p: &mut Parser<'_>) -> R<ExprParams> {430	if p.at(T![')']) {431		return Ok(ExprParams::new(Vec::new()));432	}433	let mut result = Vec::new();434	loop {435		let d = destruct(p)?;436		let default = if p.try_eat(T![=]) {437			Some(Rc::new(expr(p)?))438		} else {439			None440		};441		result.push(ExprParam {442			destruct: d,443			default,444		});445		if !p.try_eat(T![,]) {446			break;447		}448		if p.at(T![')']) {449			break;450		}451	}452	Ok(ExprParams::new(result))453}454455fn args(p: &mut Parser<'_>) -> R<ArgsDesc> {456	if p.at(T![')']) {457		return Ok(ArgsDesc::new(Vec::new(), Vec::new()));458	}459	let mut unnamed = Vec::new();460	let mut named = Vec::new();461	let mut named_started = false;462	loop {463		let is_named = p.at_ident() && {464			let next_offset = p.offset + 1;465			next_offset < p.lexemes.len() && p.lexemes[next_offset].kind == T![=] && {466				let after_eq = next_offset + 1;467				after_eq >= p.lexemes.len() || p.lexemes[after_eq].kind != T![=]468			}469		};470		if is_named {471			let name: IStr = p.expect_ident()?;472			p.eat(T![=])?;473			let value = Rc::new(expr(p)?);474			named.push((name, value));475			named_started = true;476		} else {477			if named_started {478				return Err(p.error("positional argument after named argument".into()));479			}480			unnamed.push(Rc::new(expr(p)?));481		}482		if !p.try_eat(T![,]) {483			break;484		}485		if p.at(T![')']) {486			break;487		}488	}489	Ok(ArgsDesc::new(unnamed, named))490}491492fn bind(p: &mut Parser<'_>) -> R<BindSpec> {493	#[cfg(feature = "exp-destruct")]494	{495		if !p.at_ident() {496			let d = destruct(p)?;497			p.eat(T![=])?;498			let value = Rc::new(expr(p)?);499			return Ok(BindSpec::Field { into: d, value });500		}501	}502	let name = p.expect_ident()?;503	if p.try_eat(T!['(']) {504		let ps = params(p)?;505		p.eat(T![')'])?;506		p.eat(T![=])?;507		let value = Rc::new(expr(p)?);508		Ok(BindSpec::Function {509			name,510			params: ps,511			value,512		})513	} else {514		p.eat(T![=])?;515		let value = Rc::new(expr(p)?);516		Ok(BindSpec::Field {517			into: Destruct::Full(name),518			value,519		})520	}521}522523fn visibility(p: &mut Parser<'_>) -> R<Visibility> {524	p.eat(T![:])?;525	if p.try_eat(T![:]) {526		if p.try_eat(T![:]) {527			Ok(Visibility::Unhide)528		} else {529			Ok(Visibility::Hidden)530		}531	} else {532		Ok(Visibility::Normal)533	}534}535536fn field_name(p: &mut Parser<'_>) -> R<FieldName> {537	if p.at_ident() {538		Ok(FieldName::Fixed(p.expect_ident()?))539	} else if is_string_token(p.peek()) {540		Ok(FieldName::Fixed(parse_string_content(p)?))541	} else if p.at(T!['[']) {542		p.eat(T!['['])?;543		let e = expr(p)?;544		p.eat(T![']'])?;545		Ok(FieldName::Dyn(e))546	} else {547		Err(p.error(format!("expected field name, got {}", p.current_desc())))548	}549}550551fn field(p: &mut Parser<'_>) -> R<FieldMember> {552	let name = spanned(p, field_name)?;553554	if p.at(T!['(']) {555		p.eat(T!['('])?;556		let ps = params(p)?;557		p.eat(T![')'])?;558		let vis = visibility(p)?;559		let value = Rc::new(expr(p)?);560		Ok(FieldMember {561			name,562			plus: false,563			params: Some(ps),564			visibility: vis,565			value,566		})567	} else {568		let plus = p.try_eat(T![+]);569		let vis = visibility(p)?;570		let value = Rc::new(expr(p)?);571		Ok(FieldMember {572			name,573			plus,574			params: None,575			visibility: vis,576			value,577		})578	}579}580581fn member(p: &mut Parser<'_>) -> R<Member> {582	if p.at(T![local]) {583		p.eat(T![local])?;584		Ok(Member::BindStmt(bind(p)?))585	} else if p.at(T![assert]) {586		Ok(Member::AssertStmt(assert_stmt(p)?))587	} else {588		Ok(Member::Field(field(p)?))589	}590}591592fn for_spec(p: &mut Parser<'_>) -> R<ForSpecData> {593	p.eat(T![for])?;594	let d = destruct(p)?;595	p.eat(T![in])?;596	let over = expr(p)?;597	Ok(ForSpecData { destruct: d, over })598}599600fn compspecs(p: &mut Parser<'_>) -> R<Vec<CompSpec>> {601	let mut specs = Vec::new();602	specs.push(CompSpec::ForSpec(for_spec(p)?));603	loop {604		if p.at(T![for]) {605			specs.push(CompSpec::ForSpec(for_spec(p)?));606		} else if p.at(T![if]) {607			let isd = if_spec_data(p)?;608			specs.push(CompSpec::IfSpec(isd));609		} else {610			break;611		}612	}613	Ok(specs)614}615616fn objinside(p: &mut Parser<'_>) -> R<ObjBody> {617	if p.at(T!['}']) {618		return Ok(ObjBody::MemberList(ObjMembers {619			locals: Rc::new(Vec::new()),620			asserts: Rc::new(Vec::new()),621			fields: Vec::new(),622		}));623	}624625	let mut members = Vec::new();626	loop {627		members.push(member(p)?);628		if !p.try_eat(T![,]) {629			break;630		}631		if p.at(T!['}']) || p.at(T![for]) {632			break;633		}634	}635636	if p.at(T![for]) {637		let specs = compspecs(p)?;638		let mut locals = Vec::new();639		let mut field_member = None;640		for m in members {641			match m {642				Member::Field(f) => {643					if field_member.is_some() {644						return Err(p.error(645							"object comprehension can only contain one field".into(),646						));647					}648					field_member = Some(f);649				}650				Member::BindStmt(b) => locals.push(b),651				Member::AssertStmt(_) => {652					return Err(p.error(653						"asserts are unsupported in object comprehension".into(),654					));655				}656			}657		}658		Ok(ObjBody::ObjComp(ObjComp {659			locals: Rc::new(locals),660			field: Rc::new(661				field_member.ok_or_else(|| {662					p.error("missing object comprehension field".into())663				})?,664			),665			compspecs: specs,666		}))667	} else {668		let mut locals = Vec::new();669		let mut asserts = Vec::new();670		let mut fields = Vec::new();671		for m in members {672			match m {673				Member::Field(f) => fields.push(f),674				Member::BindStmt(b) => locals.push(b),675				Member::AssertStmt(a) => asserts.push(a),676			}677		}678		Ok(ObjBody::MemberList(ObjMembers {679			locals: Rc::new(locals),680			asserts: Rc::new(asserts),681			fields,682		}))683	}684}685686fn expr_basic(p: &mut Parser<'_>) -> R<Expr> {687	if let Some(lit) = literal(p) {688		return Ok(Expr::Literal(lit));689	}690691	match p.peek() {692		SyntaxKind::STRING_DOUBLE693		| SyntaxKind::STRING_SINGLE694		| SyntaxKind::STRING_DOUBLE_VERBATIM695		| SyntaxKind::STRING_SINGLE_VERBATIM696		| SyntaxKind::STRING_BLOCK => Ok(Expr::Str(parse_string_content(p)?)),697698		SyntaxKind::FLOAT => Ok(Expr::Num(parse_number(p)?)),699700		T!['('] => {701			p.eat(T!['('])?;702			let e = expr(p)?;703			p.eat(T![')'])?;704			Ok(e)705		}706707		T!['['] => {708			p.eat(T!['['])?;709			if p.at(T![']']) {710				p.eat(T![']'])?;711				return Ok(Expr::Arr(Rc::new(Vec::new())));712			}713			let first = expr(p)?;714			if p.at(T![for]) {715				let specs = compspecs(p)?;716				p.eat(T![']'])?;717				Ok(Expr::ArrComp(Rc::new(first), specs))718			} else if p.at(T![,]) && {719				let next = p.offset + 1;720				next < p.lexemes.len() && p.lexemes[next].kind == T![for]721			} {722				p.eat(T![,])?;723				let specs = compspecs(p)?;724				p.eat(T![']'])?;725				Ok(Expr::ArrComp(Rc::new(first), specs))726			} else {727				let mut elems = vec![first];728				while p.try_eat(T![,]) {729					if p.at(T![']']) {730						break;731					}732					elems.push(expr(p)?);733				}734				p.eat(T![']'])?;735				Ok(Expr::Arr(Rc::new(elems)))736			}737		}738739		T!['{'] => {740			p.eat(T!['{'])?;741			let body = objinside(p)?;742			p.eat(T!['}'])?;743			Ok(Expr::Obj(body))744		}745746		T![local] => {747			p.eat(T![local])?;748			let mut binds = Vec::new();749			loop {750				binds.push(bind(p)?);751				if !p.try_eat(T![,]) {752					break;753				}754			}755			p.eat(T![;])?;756			let body = expr(p)?;757			Ok(Expr::LocalExpr(binds, Box::new(body)))758		}759760		T![if] => Ok(Expr::IfElse(Box::new(if_else(p)?))),761762		T![function] => {763			p.eat(T![function])?;764			p.eat(T!['('])?;765			let ps = params(p)?;766			p.eat(T![')'])?;767			let body = expr(p)?;768			Ok(Expr::Function(ps, Rc::new(body)))769		}770771		T![assert] => {772			let a = assert_stmt(p)?;773			p.eat(T![;])?;774			let rest = expr(p)?;775			Ok(Expr::AssertExpr(Rc::new(AssertExpr { assert: a, rest })))776		}777778		T![error] => {779			let span = spanned(p, |p| p.eat(T![error]))?;780			let e = expr(p)?;781			Ok(Expr::ErrorStmt(span.span, Box::new(e)))782		}783784		T![importstr] => {785			let kind = spanned(p, |p| {786				p.eat(T![importstr])?;787				Ok(ImportKind::Str)788			})?;789			let path = expr(p)?;790			Ok(Expr::Import(kind, Box::new(path)))791		}792793		T![importbin] => {794			let kind = spanned(p, |p| {795				p.eat(T![importbin])?;796				Ok(ImportKind::Bin)797			})?;798			let path = expr(p)?;799			Ok(Expr::Import(kind, Box::new(path)))800		}801802		T![import] => {803			let kind = spanned(p, |p| {804				p.eat(T![import])?;805				Ok(ImportKind::Normal)806			})?;807			let path = expr(p)?;808			Ok(Expr::Import(kind, Box::new(path)))809		}810811		SyntaxKind::IDENT => {812			let text = p.text();813			if is_reserved(text) {814				return Err(p.error(format!("unexpected reserved word '{text}'")));815			}816			let n = spanned(p, |p| {817				let s: IStr = p.text().into();818				p.eat_any();819				Ok(s)820			})?;821			Ok(Expr::Var(n))822		}823824		_ => Err(p.error(format!("unexpected {}", p.current_desc()))),825	}826}827828/// Flush accumulated index parts into an Expr::Index wrapping `e`.829fn flush_index_parts(e: &mut Expr, parts: &mut Vec<IndexPart>) {830	if parts.is_empty() {831		return;832	}833	let old = std::mem::replace(e, Expr::Literal(LiteralType::Null));834	*e = Expr::Index {835		indexable: Box::new(old),836		parts: std::mem::take(parts),837	};838}839840fn expr_suffix(p: &mut Parser<'_>) -> R<Expr> {841	let mut e = expr_basic(p)?;842	// Accumulate consecutive index parts (.field, [expr], ?.field, ?.[expr])843	// into a single Expr::Index. This is critical for null-coalesce semantics:844	// a?.b.c needs all parts in one Index so the evaluator can skip .c when .b is null.845	let mut parts: Vec<IndexPart> = Vec::new();846847	loop {848		#[cfg(feature = "exp-null-coaelse")]849		if p.at(T![?]) {850			p.eat_any();851			if p.try_eat(T![.]) {852				if p.at(T!['[']) {853					// ?.[expr]854					p.eat(T!['['])?;855					let idx = spanned(p, expr)?;856					p.eat(T![']'])?;857					parts.push(IndexPart {858						span: idx.span,859						value: idx.value,860						null_coaelse: true,861					});862				} else {863					// ?.field864					let id_spanned = spanned(p, |p| {865						let name = p.expect_ident()?;866						Ok(Expr::Str(name))867					})?;868					parts.push(IndexPart {869						span: id_spanned.span,870						value: id_spanned.value,871						null_coaelse: true,872					});873				}874			} else {875				return Err(p.error("expected '.' after '?'".into()));876			}877			continue;878		}879880		if p.at(T![.]) {881			p.eat(T![.])?;882			let id_spanned = spanned(p, |p| {883				let name = p.expect_ident()?;884				Ok(Expr::Str(name))885			})?;886			parts.push(IndexPart {887				span: id_spanned.span,888				value: id_spanned.value,889				#[cfg(feature = "exp-null-coaelse")]890				null_coaelse: false,891			});892		} else if p.at(T!['[']) {893			p.eat(T!['['])?;894895			if p.at(T![:]) {896				// Slice: flush index parts first, then handle slice897				flush_index_parts(&mut e, &mut parts);898				let slice = slice_desc(p, None)?;899				p.eat(T![']'])?;900				e = Expr::Slice(Box::new(Slice { value: e, slice }));901			} else {902				let idx = spanned(p, expr)?;903				if p.at(T![:]) {904					// Slice with start: flush index parts first905					flush_index_parts(&mut e, &mut parts);906					let slice = slice_desc(p, Some(idx))?;907					p.eat(T![']'])?;908					e = Expr::Slice(Box::new(Slice { value: e, slice }));909				} else {910					// Bracket index: add to parts911					p.eat(T![']'])?;912					parts.push(IndexPart {913						span: idx.span,914						value: idx.value,915						#[cfg(feature = "exp-null-coaelse")]916						null_coaelse: false,917					});918				}919			}920		} else if p.at(T!['(']) {921			flush_index_parts(&mut e, &mut parts);922			let args_spanned = spanned(p, |p| {923				p.eat(T!['('])?;924				let a = args(p)?;925				p.eat(T![')'])?;926				Ok(a)927			})?;928			let tailstrict = p.try_eat(T![tailstrict]);929			e = Expr::Apply(Box::new(e), args_spanned, tailstrict);930		} else if p.at(T!['{']) {931			flush_index_parts(&mut e, &mut parts);932			p.eat(T!['{'])?;933			let body = objinside(p)?;934			p.eat(T!['}'])?;935			e = Expr::ObjExtend(Rc::new(e), body);936		} else {937			break;938		}939	}940941	flush_index_parts(&mut e, &mut parts);942	Ok(e)943}944945fn prefix_binding_power(op: UnaryOpType) -> u8 {946	match op {947		UnaryOpType::Plus | UnaryOpType::Minus | UnaryOpType::Not | UnaryOpType::BitNot => 20,948	}949}950951fn infix_binding_power(op: BinaryOpType) -> (u8, u8) {952	match op {953		BinaryOpType::Or => (2, 3),954		#[cfg(feature = "exp-null-coaelse")]955		BinaryOpType::NullCoaelse => (2, 3),956		BinaryOpType::And => (4, 5),957		BinaryOpType::BitOr => (6, 7),958		BinaryOpType::BitXor => (8, 9),959		BinaryOpType::BitAnd => (10, 11),960		BinaryOpType::Eq | BinaryOpType::Neq => (12, 13),961		BinaryOpType::Lt962		| BinaryOpType::Gt963		| BinaryOpType::Lte964		| BinaryOpType::Gte965		| BinaryOpType::In => (14, 15),966		BinaryOpType::Lhs | BinaryOpType::Rhs => (16, 17),967		BinaryOpType::Add | BinaryOpType::Sub => (18, 19),968		BinaryOpType::Mul | BinaryOpType::Div | BinaryOpType::Mod => (20, 21),969	}970}971972fn unary_op(kind: SyntaxKind) -> Option<UnaryOpType> {973	match kind {974		T![+] => Some(UnaryOpType::Plus),975		T![-] => Some(UnaryOpType::Minus),976		T![!] => Some(UnaryOpType::Not),977		T![~] => Some(UnaryOpType::BitNot),978		_ => None,979	}980}981982fn binary_op(p: &Parser<'_>) -> Option<BinaryOpType> {983	match p.peek() {984		T![||] => Some(BinaryOpType::Or),985		T![&&] => Some(BinaryOpType::And),986		T![|] => Some(BinaryOpType::BitOr),987		T![^] => Some(BinaryOpType::BitXor),988		T![&] => Some(BinaryOpType::BitAnd),989		T![==] => Some(BinaryOpType::Eq),990		T![!=] => Some(BinaryOpType::Neq),991		T![<] => Some(BinaryOpType::Lt),992		T![>] => Some(BinaryOpType::Gt),993		T![<=] => Some(BinaryOpType::Lte),994		T![>=] => Some(BinaryOpType::Gte),995		T![<<] => Some(BinaryOpType::Lhs),996		T![>>] => Some(BinaryOpType::Rhs),997		T![+] => Some(BinaryOpType::Add),998		T![-] => Some(BinaryOpType::Sub),999		T![*] => Some(BinaryOpType::Mul),1000		T![/] => Some(BinaryOpType::Div),1001		T![%] => Some(BinaryOpType::Mod),1002		T![in] => Some(BinaryOpType::In),1003		#[cfg(feature = "exp-null-coaelse")]1004		T![??] => Some(BinaryOpType::NullCoaelse),1005		_ => None,1006	}1007}10081009fn expr_bp(p: &mut Parser<'_>, min_bp: u8) -> R<Expr> {1010	let mut lhs = if let Some(op) = unary_op(p.peek()) {1011		p.eat_any();1012		let rbp = prefix_binding_power(op);1013		let rhs = expr_bp(p, rbp)?;1014		Expr::UnaryOp(op, Box::new(rhs))1015	} else {1016		expr_suffix(p)?1017	};10181019	loop {1020		if p.at_eof() {1021			break;1022		}10231024		let Some(op) = binary_op(p) else {1025			break;1026		};10271028		let (lbp, rbp) = infix_binding_power(op);1029		if lbp < min_bp {1030			break;1031		}10321033		p.eat_any();1034		let rhs = expr_bp(p, rbp)?;1035		lhs = Expr::BinaryOp(Box::new(BinaryOp { lhs, op, rhs }));1036	}10371038	Ok(lhs)1039}10401041fn expr(p: &mut Parser<'_>) -> R<Expr> {1042	expr_bp(p, 0)1043}10441045pub fn parse(str: &str, settings: &ParserSettings) -> Result<Expr, ParseError> {1046	let mut p = Parser::new(str, settings.source.clone());1047	for lexeme in &p.lexemes {1048		if let Some(desc) = lexeme.kind.error_description() {1049			return Err(ParseError {1050				message: desc.to_owned(),1051				location: ParseErrorLocation {1052					offset: lexeme.range.0 as usize,1053				},1054			});1055		}1056	}1057	let e = expr(&mut p)?;1058	if !p.at_eof() {1059		return Err(p.error(format!(1060			"expected end of file, got {}",1061			p.current_desc(),1062		)));1063	}1064	Ok(e)1065}10661067pub fn string_to_expr(s: IStr, settings: &ParserSettings) -> Spanned<Expr> {1068	let len = s.len();1069	Spanned::new(1070		Expr::Str(s),1071		Span(settings.source.clone(), 0, len as u32),1072	)1073}10741075#[cfg(test)]1076mod tests {1077	use std::fs;10781079	use insta::{assert_snapshot, glob};1080	use jrsonnet_ir::{IStr, Source};10811082	use super::*;10831084	fn parse_str(input: &str) -> Expr {1085		let source = Source::new_virtual("<test>".into(), input.into());1086		let settings = ParserSettings { source };1087		parse(input, &settings).unwrap()1088	}10891090	#[test]1091	#[cfg(not(feature = "exp-null-coaelse"))]1092	fn basic_test() {1093		let v = parse_str("assert true[false] : false ; true");1094		assert_snapshot!(format!("{v:#?}"));1095	}10961097	#[test]1098	fn literals() {1099		let v = parse_str("[null, true, false, self, super, $]");1100		assert_snapshot!(format!("{v:#?}"));1101	}11021103	#[test]1104	fn basic_math() {1105		let v = parse_str("2+2*2");1106		assert_snapshot!(format!("{v:#?}"));1107	}11081109	#[test]1110	fn underscore_numbers() {1111		let v = parse_str("[1_000, 1_000.000_1, 1_0e1_0]");1112		assert_snapshot!(format!("{v:#?}"));1113	}11141115	#[test]1116	fn strings() {1117		let v = parse_str(r#"["hello", 'world', @"raw""str", @'raw''str']"#);1118		assert_snapshot!(format!("{v:#?}"));1119	}11201121	#[test]1122	fn object() {1123		let v = parse_str("{a: 1, b:: 2, c::: 3}");1124		assert_snapshot!(format!("{v:#?}"));1125	}11261127	#[test]1128	fn function_and_call() {1129		let v = parse_str("local f(x, y=1) = x + y; f(2, y=3)");1130		assert_snapshot!(format!("{v:#?}"));1131	}11321133	#[test]1134	fn if_then_else() {1135		let v = parse_str("if true then 1 else 2");1136		assert_snapshot!(format!("{v:#?}"));1137	}11381139	#[test]1140	fn imports() {1141		let v = parse_str(r#"[import "a", importstr "b", importbin "c"]"#);1142		assert_snapshot!(format!("{v:#?}"));1143	}11441145	#[test]1146	fn array_comp() {1147		let v = parse_str("[x for x in arr]");1148		assert_snapshot!(format!("{v:#?}"));1149	}11501151	#[test]1152	#[cfg(not(feature = "exp-null-coaelse"))]1153	fn index_and_suffix() {1154		let v = parse_str("std.test(2).field[0]");1155		assert_snapshot!(format!("{v:#?}"));1156	}11571158	#[test]1159	fn obj_extend() {1160		let v = parse_str("{} { x: 1 }");1161		assert_snapshot!(format!("{v:#?}"));1162	}11631164	#[test]1165	fn unary_ops() {1166		let v = parse_str("!a && !b");1167		assert_snapshot!(format!("{v:#?}"));1168	}11691170	#[test]1171	fn error_expr() {1172		let v = parse_str("error \"bad\"");1173		assert_snapshot!(format!("{v:#?}"));1174	}11751176	#[test]1177	fn slice() {1178		let v = parse_str("[a[1:], a[1::], a[:1:], a[::1]]");1179		assert_snapshot!(format!("{v:#?}"));1180	}11811182	#[test]1183	#[cfg(not(feature = "exp-null-coaelse"))]1184	fn peg_snapshots() {1185		glob!("../../jrsonnet-peg-parser/src", "tests/*.jsonnet", |path| {1186			let input = fs::read_to_string(path).expect("read test file");1187			let source = Source::new_virtual("<test>".into(), IStr::empty());1188			let settings = ParserSettings { source };1189			let v = parse(&input, &settings).unwrap();1190			let v = format!("{v:#?}");1191			assert_snapshot!(v);1192		});1193	}1194}