git.delta.rocks / jrsonnet / refs/commits / 0f5424fc1b4b

difftreelog

feat(rowan) alternative object comp syntax

lmvywrutYaroslav Bolyukin2026-05-06parent: #604f09d.patch.diff
in: master

10 files changed

modifiedcrates/jrsonnet-formatter/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-formatter/src/lib.rs
+++ b/crates/jrsonnet-formatter/src/lib.rs
@@ -13,9 +13,9 @@
 	AstNode, AstToken as _, SyntaxToken,
 	nodes::{
 		Arg, ArgsDesc, Assertion, BinaryOperator, Bind, CompSpec, Destruct, DestructArrayPart,
-		DestructRest, Expr, ExprArray, ExprBase, FieldName, ForSpec, IfSpec, ImportKind, Literal,
-		Member, Name, Number, ObjBody, ObjLocal, ParamsDesc, SliceDesc, SourceFile, Stmt, Suffix,
-		Text, TextKind, UnaryOperator, Visibility,
+		DestructRest, Expr, ExprArray, ExprBase, FieldName, ForObjSpec, ForSpec, IfSpec,
+		ImportKind, Literal, Member, Name, Number, ObjBody, ObjLocal, ParamsDesc, SliceDesc,
+		SourceFile, Stmt, Suffix, Text, TextKind, UnaryOperator, Visibility,
 	},
 };
 
@@ -645,6 +645,11 @@
 		p!(out, str("for ") {self.bind()} str(" in ") {self.expr()});
 	}
 }
+impl Printable for ForObjSpec {
+	fn print(&self, out: &mut PrintItems) {
+		p!(out, str("for [") {self.key()} str("]") {self.visibility()} str(" ") {self.value()} str(" in ") {self.expr()});
+	}
+}
 impl Printable for IfSpec {
 	fn print(&self, out: &mut PrintItems) {
 		p!(out, str("if ") {self.expr()});
@@ -654,6 +659,7 @@
 	fn print(&self, out: &mut PrintItems) {
 		match self {
 			Self::ForSpec(f) => f.print(out),
+			Self::ForObjSpec(f) => f.print(out),
 			Self::IfSpec(i) => i.print(out),
 		}
 	}
modifiedcrates/jrsonnet-rowan-parser/jsonnet.ungramdiffbeforeafterboth
--- a/crates/jrsonnet-rowan-parser/jsonnet.ungram
+++ b/crates/jrsonnet-rowan-parser/jsonnet.ungram
@@ -246,11 +246,21 @@
     bind:Destruct
     'in'
     Expr
+ForObjSpec =
+    'for'
+    '['
+    key:Name
+    ']'
+    Visibility
+    value:Destruct
+    'in'
+    Expr
 IfSpec =
     'if'
     Expr
 CompSpec =
     ForSpec
+|   ForObjSpec
 |   IfSpec
 
 BindDestruct =
modifiedcrates/jrsonnet-rowan-parser/src/generated/nodes.rsdiffbeforeafterboth
--- a/crates/jrsonnet-rowan-parser/src/generated/nodes.rs
+++ b/crates/jrsonnet-rowan-parser/src/generated/nodes.rs
@@ -650,6 +650,37 @@
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct ForObjSpec {
+	pub(crate) syntax: SyntaxNode,
+}
+impl ForObjSpec {
+	pub fn for_kw_token(&self) -> Option<SyntaxToken> {
+		support::token(&self.syntax, T![for])
+	}
+	pub fn l_brack_token(&self) -> Option<SyntaxToken> {
+		support::token(&self.syntax, T!['['])
+	}
+	pub fn key(&self) -> Option<Name> {
+		support::children(&self.syntax).next()
+	}
+	pub fn r_brack_token(&self) -> Option<SyntaxToken> {
+		support::token(&self.syntax, T![']'])
+	}
+	pub fn visibility(&self) -> Option<Visibility> {
+		support::children(&self.syntax).next()
+	}
+	pub fn value(&self) -> Option<Destruct> {
+		support::children(&self.syntax).next()
+	}
+	pub fn in_kw_token(&self) -> Option<SyntaxToken> {
+		support::token(&self.syntax, T![in])
+	}
+	pub fn expr(&self) -> Option<Expr> {
+		support::children(&self.syntax).next()
+	}
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct IfSpec {
 	pub(crate) syntax: SyntaxNode,
 }
@@ -845,6 +876,7 @@
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub enum CompSpec {
 	ForSpec(ForSpec),
+	ForObjSpec(ForObjSpec),
 	IfSpec(IfSpec),
 }
 
@@ -1702,6 +1734,21 @@
 		&self.syntax
 	}
 }
+impl AstNode for ForObjSpec {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		kind == FOR_OBJ_SPEC
+	}
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		if Self::can_cast(syntax.kind()) {
+			Some(Self { syntax })
+		} else {
+			None
+		}
+	}
+	fn syntax(&self) -> &SyntaxNode {
+		&self.syntax
+	}
+}
 impl AstNode for IfSpec {
 	fn can_cast(kind: SyntaxKind) -> bool {
 		kind == IF_SPEC
@@ -2014,6 +2061,11 @@
 		CompSpec::ForSpec(node)
 	}
 }
+impl From<ForObjSpec> for CompSpec {
+	fn from(node: ForObjSpec) -> CompSpec {
+		CompSpec::ForObjSpec(node)
+	}
+}
 impl From<IfSpec> for CompSpec {
 	fn from(node: IfSpec) -> CompSpec {
 		CompSpec::IfSpec(node)
@@ -2022,13 +2074,14 @@
 impl AstNode for CompSpec {
 	fn can_cast(kind: SyntaxKind) -> bool {
 		match kind {
-			FOR_SPEC | IF_SPEC => true,
+			FOR_SPEC | FOR_OBJ_SPEC | IF_SPEC => true,
 			_ => false,
 		}
 	}
 	fn cast(syntax: SyntaxNode) -> Option<Self> {
 		let res = match syntax.kind() {
 			FOR_SPEC => CompSpec::ForSpec(ForSpec { syntax }),
+			FOR_OBJ_SPEC => CompSpec::ForObjSpec(ForObjSpec { syntax }),
 			IF_SPEC => CompSpec::IfSpec(IfSpec { syntax }),
 			_ => return None,
 		};
@@ -2037,6 +2090,7 @@
 	fn syntax(&self) -> &SyntaxNode {
 		match self {
 			CompSpec::ForSpec(it) => &it.syntax,
+			CompSpec::ForObjSpec(it) => &it.syntax,
 			CompSpec::IfSpec(it) => &it.syntax,
 		}
 	}
@@ -3016,6 +3070,11 @@
 		std::fmt::Display::fmt(self.syntax(), f)
 	}
 }
+impl std::fmt::Display for ForObjSpec {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
 impl std::fmt::Display for IfSpec {
 	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 		std::fmt::Display::fmt(self.syntax(), f)
modifiedcrates/jrsonnet-rowan-parser/src/generated/syntax_kinds.rsdiffbeforeafterboth
--- a/crates/jrsonnet-rowan-parser/src/generated/syntax_kinds.rs
+++ b/crates/jrsonnet-rowan-parser/src/generated/syntax_kinds.rs
@@ -145,6 +145,7 @@
 	FIELD_NAME_FIXED,
 	FIELD_NAME_DYNAMIC,
 	FOR_SPEC,
+	FOR_OBJ_SPEC,
 	IF_SPEC,
 	BIND_DESTRUCT,
 	BIND_FUNCTION,
modifiedcrates/jrsonnet-rowan-parser/src/parser.rsdiffbeforeafterboth
before · crates/jrsonnet-rowan-parser/src/parser.rs
1use std::{cell::Cell, fmt, rc::Rc};23use rowan::{GreenNode, TextRange};45use crate::{6	AstToken, SyntaxKind,7	SyntaxKind::*,8	SyntaxNode, T, TS,9	event::Event,10	marker::{CompletedMarker, Marker},11	nodes::{BinaryOperatorKind, Literal, Number, Text, UnaryOperatorKind},12	token_set::SyntaxKindSet,13};1415pub struct Parse {16	pub green_node: GreenNode,17	pub errors: Vec<LocatedSyntaxError>,18}1920pub struct Parser {21	// TODO: remove all trivia before feeding to parser?22	kinds: Vec<SyntaxKind>,23	pub offset: usize,24	pub events: Vec<Event>,25	pub entered: u32,26	pub hints: Vec<(u32, TextRange, String)>,27	pub last_error_token: usize,28	expected_syntax_tracking_state: Rc<Cell<ExpectedSyntax>>,29	steps: Cell<u64>,30}3132#[derive(Clone, Debug)]33pub enum SyntaxError {34	Unexpected {35		expected: ExpectedSyntax,36		found: SyntaxKind,37	},38	Missing {39		expected: ExpectedSyntax,40	},41	Custom {42		error: String,43	},44	Hint {45		error: String,46	},47}48impl fmt::Display for SyntaxError {49	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {50		match self {51			SyntaxError::Unexpected { expected, found } => {52				write!(f, "unexpected {found:?}, expecting {expected}")53			}54			SyntaxError::Missing { expected } => write!(f, "missing {expected}"),55			SyntaxError::Custom { error } | SyntaxError::Hint { error } => write!(f, "{error}"),56		}57	}58}5960#[derive(Debug)]61pub struct LocatedSyntaxError {62	pub error: SyntaxError,63	pub range: TextRange,64}6566impl Parser {67	pub fn new(kinds: Vec<SyntaxKind>) -> Self {68		Self {69			kinds,70			offset: 0,71			events: vec![],72			entered: 0,73			last_error_token: 0,74			hints: vec![],75			expected_syntax_tracking_state: Rc::new(Cell::new(ExpectedSyntax::Unnamed(TS![]))),76			steps: Cell::new(0),77		}78	}79	pub fn clear_outdated_hints(&mut self) {80		let amount = self81			.hints82			.iter()83			.rev()84			.take_while(|h| h.0 > self.entered)85			.count();86		self.hints.truncate(self.hints.len() - amount);87	}88	fn clear_expected_syntaxes(&self) {89		self.expected_syntax_tracking_state90			.set(ExpectedSyntax::Unnamed(TS![]));91	}92	pub fn start(&mut self) -> Marker {93		let start_event_idx = self.events.len();94		self.events.push(Event::Pending);95		self.entered += 1;96		Marker::new(start_event_idx)97	}98	// pub fn start_ranger(&mut self) -> Ranger {99	// 	let pos = self.offset;100	// 	Ranger { pos }101	// }102	pub fn parse(mut self) -> Vec<Event> {103		let m = self.start();104		expr(&mut self);105		if !self.at(EOF) {106			let m = self.start();107			while !self.at(EOF) {108				self.bump();109			}110			m.complete_error(&mut self, "unexpected tokens after end");111		}112		m.complete(&mut self, SOURCE_FILE);113114		self.events115	}116117	pub(crate) fn expect(&mut self, kind: SyntaxKind) {118		self.expect_with_recovery_set(kind, TS![]);119	}120121	pub(crate) fn expect_with_recovery_set(122		&mut self,123		kind: SyntaxKind,124		recovery_set: SyntaxKindSet,125	) {126		if self.at(kind) {127			if kind != EOF {128				self.bump();129			}130		} else {131			self.error_with_recovery_set(recovery_set);132		}133	}134135	// pub(crate) fn expect_with_no_skip(&mut self, kind: SyntaxKind) {136	// 	if self.at(kind) {137	// 		self.bump();138	// 	} else {139	// 		self.error_with_no_skip();140	// 	}141	// }142	pub fn error_with_no_skip(&mut self) -> CompletedMarker {143		self.error_with_recovery_set(SyntaxKindSet::ALL)144	}145146	pub fn error_with_recovery_set(&mut self, recovery_set: SyntaxKindSet) -> CompletedMarker {147		let expected = self.expected_syntax_tracking_state.get();148		self.expected_syntax_tracking_state149			.set(ExpectedSyntax::Unnamed(TS![]));150151		if self.at_end() || self.at_ts(recovery_set) {152			let m = self.start();153			return m.complete_missing(self, expected);154		}155156		let current_token = self.current();157158		self.last_error_token = self.offset;159160		let m = self.start();161		self.bump();162		let m = m.complete_unexpected(self, expected, current_token);163		self.clear_expected_syntaxes();164		m165	}166	fn bump_assert(&mut self, kind: SyntaxKind) {167		assert!(self.at(kind), "expected {kind:?}");168		self.bump_remap(self.current());169	}170	fn bump(&mut self) {171		self.bump_remap(self.current());172	}173	fn bump_remap(&mut self, kind: SyntaxKind) {174		assert_ne!(self.offset, self.kinds.len(), "already at end");175		self.events.push(Event::Token { kind });176		self.offset += 1;177		self.clear_expected_syntaxes();178	}179	fn step(&self) {180		use std::fmt::Write;181		let steps = self.steps.get();182		if steps >= 15_000_000 {183			let mut out = "seems like parsing is stuck".to_owned();184			{185				let last = 20;186				write!(out, "\n\nLast {last} events:").unwrap();187				for (i, event) in self188					.events189					.iter()190					.skip(self.events.len().saturating_sub(last))191					.enumerate()192				{193					write!(out, "\n{i}. {event:?}").unwrap();194				}195			}196			{197				let next = 20;198				write!(out, "\n\nNext {next} tokens:").unwrap();199				for (i, tok) in self.kinds.iter().skip(self.offset).take(next).enumerate() {200					write!(out, "\n{i}. {tok:?}").unwrap();201				}202			}203			panic!("{out}")204		}205		self.steps.set(steps + 1);206	}207	fn nth(&self, i: usize) -> SyntaxKind {208		self.step();209		let mut offset = self.offset;210		for _ in 0..i {211			offset += 1;212		}213		self.kinds.get(offset).copied().unwrap_or(EOF)214	}215	fn current(&self) -> SyntaxKind {216		self.nth(0)217	}218	#[must_use]219	pub(crate) fn expected_syntax_name(&self, name: &'static str) -> ExpectedSyntaxGuard {220		self.expected_syntax_tracking_state221			.set(ExpectedSyntax::Named(name));222223		ExpectedSyntaxGuard::new(Rc::clone(&self.expected_syntax_tracking_state))224	}225	pub fn at(&self, kind: SyntaxKind) -> bool {226		self.nth_at(0, kind)227	}228	pub fn nth_at(&self, n: usize, kind: SyntaxKind) -> bool {229		if n == 0 {230			if let ExpectedSyntax::Unnamed(kinds) = self.expected_syntax_tracking_state.get() {231				let kinds = kinds.with(kind);232				self.expected_syntax_tracking_state233					.set(ExpectedSyntax::Unnamed(kinds));234			}235		}236		self.nth(n) == kind237	}238	pub fn at_ts(&self, set: SyntaxKindSet) -> bool {239		if let ExpectedSyntax::Unnamed(kinds) = self.expected_syntax_tracking_state.get() {240			let kinds = kinds.union(set);241			self.expected_syntax_tracking_state242				.set(ExpectedSyntax::Unnamed(kinds));243		}244		set.contains(self.current())245	}246	pub fn at_end(&self) -> bool {247		self.at(EOF)248	}249}250pub struct ExpectedSyntaxGuard {251	expected_syntax_tracking_state: Rc<Cell<ExpectedSyntax>>,252}253254impl ExpectedSyntaxGuard {255	fn new(expected_syntax_tracking_state: Rc<Cell<ExpectedSyntax>>) -> Self {256		Self {257			expected_syntax_tracking_state,258		}259	}260}261262impl Drop for ExpectedSyntaxGuard {263	fn drop(&mut self) {264		self.expected_syntax_tracking_state265			.set(ExpectedSyntax::Unnamed(TS![]));266	}267}268269#[derive(Clone, Debug, Copy)]270pub enum ExpectedSyntax {271	Named(&'static str),272	Unnamed(SyntaxKindSet),273}274impl fmt::Display for ExpectedSyntax {275	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {276		match self {277			Self::Named(name) => write!(f, "{name}"),278			Self::Unnamed(set) => write!(f, "{set}"),279		}280	}281}282283fn expr(p: &mut Parser) -> CompletedMarker {284	let m = p.start();285	while p.at(T![local]) || p.at(T![assert]) {286		let m = p.start();287288		if p.at(T![local]) {289			p.bump();290			loop {291				if p.at(T![;]) {292					p.bump();293					break;294				}295				bind(p);296297				if p.at(T![,]) {298					p.bump();299					continue;300				}301				p.expect(T![;]);302				break;303			}304			m.complete(p, STMT_LOCAL);305		} else {306			assertion(p);307			p.expect(T![;]);308			m.complete(p, STMT_ASSERT);309		}310	}311	match expr_binding_power(p, 0) {312		Ok(m) | Err(m) => m,313	};314	m.complete(p, EXPR)315}316317fn expr_binding_power(318	p: &mut Parser,319	minimum_binding_power: u8,320) -> Result<CompletedMarker, CompletedMarker> {321	let mut lhs = lhs(p)?;322323	while let Some(op) = BinaryOperatorKind::cast(p.current())324		.or_else(|| p.at(T!['{']).then_some(BinaryOperatorKind::MetaObjectApply))325	{326		let (left_binding_power, right_binding_power) = op.binding_power();327		if left_binding_power < minimum_binding_power {328			break;329		}330331		let m = lhs.wrap(p, EXPR, false);332333		// Object apply is not a real operator, we dont have something to bump334		if op != BinaryOperatorKind::MetaObjectApply {335			p.bump();336		}337338		let m = m.precede(p);339		let parsed_rhs = if p.at(T![local]) || p.at(T![assert]) {340			expr(p);341			true342		} else {343			expr_binding_power(p, right_binding_power)344				.map(|v| v.precede(p).complete(p, EXPR))345				.is_ok()346		};347		lhs = m.complete(348			p,349			if op == BinaryOperatorKind::MetaObjectApply {350				EXPR_OBJ_EXTEND351			} else {352				EXPR_BINARY353			},354		);355356		if !parsed_rhs {357			break;358		}359	}360	Ok(lhs)361}362363const COMPSPEC: SyntaxKindSet = TS![for if];364fn compspec(p: &mut Parser) -> CompletedMarker {365	assert!(p.at_ts(COMPSPEC));366	if p.at(T![for]) {367		let m = p.start();368		p.bump();369		destruct(p);370		p.expect(T![in]);371		expr(p);372		m.complete(p, FOR_SPEC)373	} else if p.at(T![if]) {374		let m = p.start();375		p.bump();376		expr(p);377		m.complete(p, IF_SPEC)378	} else {379		unreachable!()380	}381}382383fn comma(p: &mut Parser) -> bool {384	comma_with_alternatives(p, TS![])385}386fn comma_with_alternatives(p: &mut Parser, set: SyntaxKindSet) -> bool {387	if p.at(T![,]) {388		p.bump();389		true390	} else if p.at_ts(set) {391		let _ex = p.expected_syntax_name("comma");392		p.expect_with_recovery_set(T![,], TS![]);393		true394	} else {395		false396	}397}398399fn field_name(p: &mut Parser) {400	let _e = p.expected_syntax_name("field name");401	let m = p.start();402	if p.at(T!['[']) {403		p.bump();404		expr(p);405		p.expect(T![']']);406		m.complete(p, FIELD_NAME_DYNAMIC);407	} else if p.at(IDENT) {408		name(p);409		m.complete(p, FIELD_NAME_FIXED);410	} else if Text::can_cast(p.current()) {411		text(p);412		m.complete(p, FIELD_NAME_FIXED);413	} else {414		m.forget(p);415		// Recover with ::, :::416		p.error_with_recovery_set(TS![; : '(']);417	}418}419fn visibility(p: &mut Parser) {420	let m = p.start();421	if !p.at_ts(TS![:]) {422		p.error_with_recovery_set(TS![=]);423	}424	p.bump();425	'colons: {426		if !p.at_ts(TS![:]) {427			break 'colons;428		}429		p.bump();430		if !p.at_ts(TS![:]) {431			break 'colons;432		}433		p.bump();434	}435	m.complete(p, VISIBILITY);436}437fn assertion(p: &mut Parser) {438	let m = p.start();439	p.bump_assert(T![assert]);440	expr(p);441	if p.at(T![:]) {442		p.bump();443		expr(p);444	}445	m.complete(p, ASSERTION);446}447fn object(p: &mut Parser) -> CompletedMarker {448	let m_t = p.start();449	let m = p.start();450	p.bump_assert(T!['{']);451452	let mut elems = 0;453	let mut compspecs = Vec::new();454	let mut asserts = Vec::new();455	loop {456		if p.at(T!['}']) {457			p.bump();458			break;459		}460		if p.at_ts(TS![for]) {461			if elems == 0 {462				let m = p.start();463				m.complete_missing(p, ExpectedSyntax::Named("field definition"));464			}465			while p.at_ts(COMPSPEC) {466				compspecs.push(compspec(p));467			}468			if comma_with_alternatives(p, TS![;]) {469				continue;470			}471			p.expect(R_BRACE);472			break;473		}474		let m = p.start();475		if p.at(T![local]) {476			obj_local(p);477			m.complete(p, MEMBER_BIND_STMT);478		} else if p.at(T![assert]) {479			assertion(p);480			asserts.push(m.complete(p, MEMBER_ASSERT_STMT));481		} else {482			field_name(p);483			if p.at(T![+]) {484				p.bump();485			}486			let params = if p.at(T!['(']) {487				params_desc(p);488				visibility(p);489				expr(p);490				true491			} else {492				visibility(p);493				if p.at(T![function]) {494					p.bump_assert(T![function]);495					params_desc(p);496					expr(p);497					true498				} else {499					expr(p);500					false501				}502			};503			elems += 1;504505			if params {506				m.complete(p, MEMBER_FIELD_METHOD)507			} else {508				m.complete(p, MEMBER_FIELD_NORMAL)509			};510		}511		while p.at_ts(COMPSPEC) {512			compspecs.push(compspec(p));513		}514		if comma_with_alternatives(p, TS![;]) {515			continue;516		}517		p.expect(R_BRACE);518		break;519	}520521	if elems > 1 && !compspecs.is_empty() {522		for errored in compspecs {523			errored.wrap_error(524				p,525				"compspec may only be used if there is only one object element",526				true,527			);528		}529		m.complete(p, OBJ_BODY_MEMBER_LIST);530	} else if !compspecs.is_empty() {531		for errored in asserts {532			errored.wrap_error(p, "asserts can't be used in object comprehensions", true);533		}534		m.complete(p, OBJ_BODY_COMP);535	} else {536		m.complete(p, OBJ_BODY_MEMBER_LIST);537	}538	m_t.complete(p, EXPR_OBJECT)539}540fn param(p: &mut Parser) {541	let m = p.start();542	destruct(p);543	if p.at(T![=]) {544		p.bump();545		expr(p);546	}547	m.complete(p, PARAM);548}549fn params_desc(p: &mut Parser) -> CompletedMarker {550	let m = p.start();551	p.bump_assert(T!['(']);552553	loop {554		if p.at(T![')']) {555			p.bump();556			break;557		}558		param(p);559		if comma(p) {560			continue;561		}562		p.expect(T![')']);563		break;564	}565566	m.complete(p, PARAMS_DESC)567}568fn args_desc(p: &mut Parser) {569	let m = p.start();570	p.bump_assert(T!['(']);571572	let started_named = Cell::new(false);573	let mut unnamed_after_named = Vec::new();574575	loop {576		if p.at(T![')']) {577			break;578		}579580		let m = p.start();581		if p.at(IDENT) && p.nth_at(1, T![=]) {582			name(p);583			p.bump();584			expr(p);585			m.complete(p, ARG);586			started_named.set(true);587		} else {588			expr(p);589			let arg = m.complete(p, ARG);590			if started_named.get() {591				unnamed_after_named.push(arg);592			}593		}594		if comma(p) {595			continue;596		}597		break;598	}599	p.expect(T![')']);600	if p.at(T![tailstrict]) {601		p.bump();602	}603604	for errored in unnamed_after_named {605		errored.wrap_error(p, "can't use positional arguments after named", true);606	}607608	m.complete(p, ARGS_DESC);609}610611fn array(p: &mut Parser) -> CompletedMarker {612	// Start the list node613	let m = p.start();614	p.bump_assert(T!['[']);615616	let mut compspecs = Vec::new();617	let mut elems = 0;618619	loop {620		if p.at(T![']']) {621			p.bump();622			break;623		}624		if elems != 0 && p.at_ts(TS![for]) {625			while p.at_ts(COMPSPEC) {626				compspecs.push(compspec(p));627			}628			if comma(p) {629				continue;630			}631			p.expect(T![']']);632			break;633		}634		expr(p);635		elems += 1;636		while p.at_ts(COMPSPEC) {637			compspecs.push(compspec(p));638		}639		if comma(p) {640			continue;641		}642		p.expect(T![']']);643		break;644	}645646	if elems > 1 && !compspecs.is_empty() {647		for spec in compspecs {648			spec.wrap_error(649				p,650				"compspec may only be used if there is only one array element",651				true,652			);653		}654655		m.complete(p, EXPR_ARRAY)656	} else if !compspecs.is_empty() {657		m.complete(p, EXPR_ARRAY_COMP)658	} else {659		m.complete(p, EXPR_ARRAY)660	}661}662/// Returns true if it was slice, false if just index663#[must_use]664fn slice_desc_or_index(p: &mut Parser) -> bool {665	let m = p.start();666	p.bump();667	// Start668	if !p.at(T![:]) {669		expr(p);670	}671	if p.at(T![:]) {672		p.bump();673		// End674		if !p.at_ts(TS![']' :]) {675			expr(p).wrap(p, SLICE_DESC_END, true);676		}677		if p.at(T![:]) {678			p.bump();679			// Step680			if !p.at(T![']']) {681				expr(p).wrap(p, SLICE_DESC_STEP, true);682			}683		}684	} else {685		// It was not a slice686		p.expect(T![']']);687		m.forget(p);688		return false;689	}690	p.expect(T![']']);691	m.complete(p, SLICE_DESC);692	true693}694695fn suffix(p: &mut Parser) {696	loop {697		let start = p.start();698		let _marker: CompletedMarker = if p.at(T![?]) {699			p.bump();700			p.expect(T![.]);701			if p.at(IDENT) {702				name(p);703				start.complete(p, SUFFIX_INDEX)704			} else if p.at(T!['[']) {705				p.bump();706				expr(p);707				p.expect(T![']']);708				start.complete(p, SUFFIX_INDEX_EXPR)709			} else {710				start.complete_missing(p, ExpectedSyntax::Named("index"))711			}712		} else if p.at(T![.]) {713			p.bump();714			name(p);715			start.complete(p, SUFFIX_INDEX)716		} else if p.at(T!['[']) {717			if slice_desc_or_index(p) {718				start.complete(p, SUFFIX_SLICE)719			} else {720				start.complete(p, SUFFIX_INDEX_EXPR)721			}722		} else if p.at(T!['(']) {723			args_desc(p);724			start.complete(p, SUFFIX_APPLY)725		} else {726			start.forget(p);727			break;728		};729	}730}731732fn lhs(p: &mut Parser) -> Result<CompletedMarker, CompletedMarker> {733	let lhs = lhs_basic(p)?;734735	suffix(p);736737	Ok(lhs)738}739fn name(p: &mut Parser) {740	let m = p.start();741	p.expect(IDENT);742	m.complete(p, NAME);743}744fn destruct_rest(p: &mut Parser) {745	let m = p.start();746	p.bump_assert(T![...]);747	if p.at(IDENT) {748		p.bump();749	}750	m.complete(p, DESTRUCT_REST);751}752fn destruct_object_field(p: &mut Parser) {753	let m = p.start();754	name(p);755	if p.at(T![:]) {756		p.bump();757		destruct(p);758	}759	if p.at(T![=]) {760		p.bump();761		expr(p);762	}763	m.complete(p, DESTRUCT_OBJECT_FIELD);764}765fn obj_local(p: &mut Parser) {766	let m = p.start();767	p.bump_assert(T![local]);768	bind(p);769	m.complete(p, OBJ_LOCAL);770}771fn destruct(p: &mut Parser) -> CompletedMarker {772	let m = p.start();773	let _ex = p.expected_syntax_name("destruction specifier");774	if p.at(T![?]) {775		p.bump();776		m.complete(p, DESTRUCT_SKIP)777	} else if p.at(T!['[']) {778		p.bump();779		// let mut had_rest = false;780		loop {781			if p.at(T![']']) {782				p.bump();783				break;784			} else if p.at(T![...]) {785				// let m_err = p.start_ranger();786				destruct_rest(p);787			// if had_rest {788			// 	p.custom_error(m_err.finish(p), "only one rest can be present in array");789			// }790			// had_rest = true;791			} else {792				destruct(p);793			}794			if p.at(T![,]) {795				p.bump();796				continue;797			}798			p.expect(T![']']);799			break;800		}801		m.complete(p, DESTRUCT_ARRAY)802	} else if p.at(T!['{']) {803		p.bump();804		let mut had_rest = false;805		loop {806			if p.at(T!['}']) {807				p.bump();808				break;809			} else if p.at(T![...]) {810				// let m_err = p.start_ranger();811				destruct_rest(p);812				// if had_rest {813				// 	p.custom_error(m_err.finish(p), "only one rest can be present in object");814				// }815				had_rest = true;816			} else {817				if had_rest {818					p.error_with_recovery_set(TS![]);819				}820				destruct_object_field(p);821			}822			if p.at(T![,]) {823				p.bump();824				continue;825			}826			p.expect(T!['}']);827			break;828		}829		m.complete(p, DESTRUCT_OBJECT)830	} else if p.at(IDENT) {831		name(p);832		m.complete(p, DESTRUCT_FULL)833	} else {834		m.forget(p);835		p.error_with_recovery_set(TS![; , '}', '(', :])836	}837}838fn bind(p: &mut Parser) {839	let m = p.start();840	if p.at(IDENT) && p.nth_at(1, T!['(']) {841		name(p);842		params_desc(p);843		p.expect(T![=]);844		expr(p);845		m.complete(p, BIND_FUNCTION)846	} else if p.at(IDENT) && p.nth_at(1, T![=]) && p.nth_at(2, T![function]) {847		name(p);848		p.expect(T![=]);849		p.expect(T![function]);850		params_desc(p);851		expr(p);852		m.complete(p, BIND_FUNCTION)853	} else {854		destruct(p);855		p.expect(T![=]);856		expr(p);857		m.complete(p, BIND_DESTRUCT)858	};859}860fn text(p: &mut Parser) {861	assert!(Text::can_cast(p.current()));862	p.bump();863}864fn number(p: &mut Parser) {865	assert!(Number::can_cast(p.current()));866	p.bump();867}868fn literal(p: &mut Parser) {869	assert!(Literal::can_cast(p.current()));870	p.bump();871}872fn lhs_basic(p: &mut Parser) -> Result<CompletedMarker, CompletedMarker> {873	let _e = p.expected_syntax_name("expression");874	Ok(if Literal::can_cast(p.current()) {875		let m = p.start();876		literal(p);877		m.complete(p, EXPR_LITERAL)878	} else if Text::can_cast(p.current()) {879		let m = p.start();880		text(p);881		m.complete(p, EXPR_STRING)882	} else if Number::can_cast(p.current()) {883		let m = p.start();884		number(p);885		m.complete(p, EXPR_NUMBER)886	} else if p.at(IDENT) {887		let m = p.start();888		name(p);889		m.complete(p, EXPR_VAR)890	} else if p.at(T![if]) {891		let m = p.start();892		p.bump();893		expr(p);894		p.expect(T![then]);895		expr(p).wrap(p, TRUE_EXPR, true);896		if p.at(T![else]) {897			p.bump();898			expr(p).wrap(p, FALSE_EXPR, true);899		}900		m.complete(p, EXPR_IF_THEN_ELSE)901	} else if p.at(T!['[']) {902		array(p)903	} else if p.at(T!['{']) {904		object(p)905	} else if p.at(T![function]) {906		let m = p.start();907		p.bump();908		params_desc(p);909		expr(p);910		m.complete(p, EXPR_FUNCTION)911	} else if p.at(T![error]) {912		let m = p.start();913		p.bump();914		expr(p);915		m.complete(p, EXPR_ERROR)916	} else if p.at(T![import]) || p.at(T![importstr]) || p.at(T![importbin]) {917		let m = p.start();918		p.bump();919		text(p);920		m.complete(p, EXPR_IMPORT)921	} else if let Some(op) = UnaryOperatorKind::cast(p.current()) {922		let ((), right_binding_power) = op.binding_power();923924		let m = p.start();925		p.bump();926		let _ = expr_binding_power(p, right_binding_power).map(|v| v.precede(p).complete(p, EXPR));927		m.complete(p, EXPR_UNARY)928	} else if p.at(T!['(']) {929		let m = p.start();930		p.bump();931		expr(p);932		p.expect(T![')']);933		m.complete(p, EXPR_PARENED)934	} else {935		return Err(p.error_with_no_skip());936	})937}938939impl Parse {940	pub fn syntax(&self) -> SyntaxNode {941		SyntaxNode::new_root(self.green_node.clone())942	}943}
after · crates/jrsonnet-rowan-parser/src/parser.rs
1use std::{cell::Cell, fmt, rc::Rc};23use rowan::{GreenNode, TextRange};45use crate::{6	AstToken, SyntaxKind,7	SyntaxKind::*,8	SyntaxNode, T, TS,9	event::Event,10	marker::{CompletedMarker, Marker},11	nodes::{BinaryOperatorKind, Literal, Number, Text, UnaryOperatorKind},12	token_set::SyntaxKindSet,13};1415pub struct Parse {16	pub green_node: GreenNode,17	pub errors: Vec<LocatedSyntaxError>,18}1920pub struct Parser {21	// TODO: remove all trivia before feeding to parser?22	kinds: Vec<SyntaxKind>,23	pub offset: usize,24	pub events: Vec<Event>,25	pub entered: u32,26	pub hints: Vec<(u32, TextRange, String)>,27	pub last_error_token: usize,28	expected_syntax_tracking_state: Rc<Cell<ExpectedSyntax>>,29	steps: Cell<u64>,30}3132#[derive(Clone, Debug)]33pub enum SyntaxError {34	Unexpected {35		expected: ExpectedSyntax,36		found: SyntaxKind,37	},38	Missing {39		expected: ExpectedSyntax,40	},41	Custom {42		error: String,43	},44	Hint {45		error: String,46	},47}48impl fmt::Display for SyntaxError {49	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {50		match self {51			SyntaxError::Unexpected { expected, found } => {52				write!(f, "unexpected {found:?}, expecting {expected}")53			}54			SyntaxError::Missing { expected } => write!(f, "missing {expected}"),55			SyntaxError::Custom { error } | SyntaxError::Hint { error } => write!(f, "{error}"),56		}57	}58}5960#[derive(Debug)]61pub struct LocatedSyntaxError {62	pub error: SyntaxError,63	pub range: TextRange,64}6566impl Parser {67	pub fn new(kinds: Vec<SyntaxKind>) -> Self {68		Self {69			kinds,70			offset: 0,71			events: vec![],72			entered: 0,73			last_error_token: 0,74			hints: vec![],75			expected_syntax_tracking_state: Rc::new(Cell::new(ExpectedSyntax::Unnamed(TS![]))),76			steps: Cell::new(0),77		}78	}79	pub fn clear_outdated_hints(&mut self) {80		let amount = self81			.hints82			.iter()83			.rev()84			.take_while(|h| h.0 > self.entered)85			.count();86		self.hints.truncate(self.hints.len() - amount);87	}88	fn clear_expected_syntaxes(&self) {89		self.expected_syntax_tracking_state90			.set(ExpectedSyntax::Unnamed(TS![]));91	}92	pub fn start(&mut self) -> Marker {93		let start_event_idx = self.events.len();94		self.events.push(Event::Pending);95		self.entered += 1;96		Marker::new(start_event_idx)97	}98	// pub fn start_ranger(&mut self) -> Ranger {99	// 	let pos = self.offset;100	// 	Ranger { pos }101	// }102	pub fn parse(mut self) -> Vec<Event> {103		let m = self.start();104		expr(&mut self);105		if !self.at(EOF) {106			let m = self.start();107			while !self.at(EOF) {108				self.bump();109			}110			m.complete_error(&mut self, "unexpected tokens after end");111		}112		m.complete(&mut self, SOURCE_FILE);113114		self.events115	}116117	pub(crate) fn expect(&mut self, kind: SyntaxKind) {118		self.expect_with_recovery_set(kind, TS![]);119	}120121	pub(crate) fn expect_with_recovery_set(122		&mut self,123		kind: SyntaxKind,124		recovery_set: SyntaxKindSet,125	) {126		if self.at(kind) {127			if kind != EOF {128				self.bump();129			}130		} else {131			self.error_with_recovery_set(recovery_set);132		}133	}134135	// pub(crate) fn expect_with_no_skip(&mut self, kind: SyntaxKind) {136	// 	if self.at(kind) {137	// 		self.bump();138	// 	} else {139	// 		self.error_with_no_skip();140	// 	}141	// }142	pub fn error_with_no_skip(&mut self) -> CompletedMarker {143		self.error_with_recovery_set(SyntaxKindSet::ALL)144	}145146	pub fn error_with_recovery_set(&mut self, recovery_set: SyntaxKindSet) -> CompletedMarker {147		let expected = self.expected_syntax_tracking_state.get();148		self.expected_syntax_tracking_state149			.set(ExpectedSyntax::Unnamed(TS![]));150151		if self.at_end() || self.at_ts(recovery_set) {152			let m = self.start();153			return m.complete_missing(self, expected);154		}155156		let current_token = self.current();157158		self.last_error_token = self.offset;159160		let m = self.start();161		self.bump();162		let m = m.complete_unexpected(self, expected, current_token);163		self.clear_expected_syntaxes();164		m165	}166	fn bump_assert(&mut self, kind: SyntaxKind) {167		assert!(self.at(kind), "expected {kind:?}");168		self.bump_remap(self.current());169	}170	fn bump(&mut self) {171		self.bump_remap(self.current());172	}173	fn bump_remap(&mut self, kind: SyntaxKind) {174		assert_ne!(self.offset, self.kinds.len(), "already at end");175		self.events.push(Event::Token { kind });176		self.offset += 1;177		self.clear_expected_syntaxes();178	}179	fn step(&self) {180		use std::fmt::Write;181		let steps = self.steps.get();182		if steps >= 15_000_000 {183			let mut out = "seems like parsing is stuck".to_owned();184			{185				let last = 20;186				write!(out, "\n\nLast {last} events:").unwrap();187				for (i, event) in self188					.events189					.iter()190					.skip(self.events.len().saturating_sub(last))191					.enumerate()192				{193					write!(out, "\n{i}. {event:?}").unwrap();194				}195			}196			{197				let next = 20;198				write!(out, "\n\nNext {next} tokens:").unwrap();199				for (i, tok) in self.kinds.iter().skip(self.offset).take(next).enumerate() {200					write!(out, "\n{i}. {tok:?}").unwrap();201				}202			}203			panic!("{out}")204		}205		self.steps.set(steps + 1);206	}207	fn nth(&self, i: usize) -> SyntaxKind {208		self.step();209		let mut offset = self.offset;210		for _ in 0..i {211			offset += 1;212		}213		self.kinds.get(offset).copied().unwrap_or(EOF)214	}215	fn current(&self) -> SyntaxKind {216		self.nth(0)217	}218	#[must_use]219	pub(crate) fn expected_syntax_name(&self, name: &'static str) -> ExpectedSyntaxGuard {220		self.expected_syntax_tracking_state221			.set(ExpectedSyntax::Named(name));222223		ExpectedSyntaxGuard::new(Rc::clone(&self.expected_syntax_tracking_state))224	}225	pub fn at(&self, kind: SyntaxKind) -> bool {226		self.nth_at(0, kind)227	}228	pub fn nth_at(&self, n: usize, kind: SyntaxKind) -> bool {229		if n == 0 {230			if let ExpectedSyntax::Unnamed(kinds) = self.expected_syntax_tracking_state.get() {231				let kinds = kinds.with(kind);232				self.expected_syntax_tracking_state233					.set(ExpectedSyntax::Unnamed(kinds));234			}235		}236		self.nth(n) == kind237	}238	pub fn at_ts(&self, set: SyntaxKindSet) -> bool {239		if let ExpectedSyntax::Unnamed(kinds) = self.expected_syntax_tracking_state.get() {240			let kinds = kinds.union(set);241			self.expected_syntax_tracking_state242				.set(ExpectedSyntax::Unnamed(kinds));243		}244		set.contains(self.current())245	}246	pub fn at_end(&self) -> bool {247		self.at(EOF)248	}249}250pub struct ExpectedSyntaxGuard {251	expected_syntax_tracking_state: Rc<Cell<ExpectedSyntax>>,252}253254impl ExpectedSyntaxGuard {255	fn new(expected_syntax_tracking_state: Rc<Cell<ExpectedSyntax>>) -> Self {256		Self {257			expected_syntax_tracking_state,258		}259	}260}261262impl Drop for ExpectedSyntaxGuard {263	fn drop(&mut self) {264		self.expected_syntax_tracking_state265			.set(ExpectedSyntax::Unnamed(TS![]));266	}267}268269#[derive(Clone, Debug, Copy)]270pub enum ExpectedSyntax {271	Named(&'static str),272	Unnamed(SyntaxKindSet),273}274impl fmt::Display for ExpectedSyntax {275	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {276		match self {277			Self::Named(name) => write!(f, "{name}"),278			Self::Unnamed(set) => write!(f, "{set}"),279		}280	}281}282283fn expr(p: &mut Parser) -> CompletedMarker {284	let m = p.start();285	while p.at(T![local]) || p.at(T![assert]) {286		let m = p.start();287288		if p.at(T![local]) {289			p.bump();290			loop {291				if p.at(T![;]) {292					p.bump();293					break;294				}295				bind(p);296297				if p.at(T![,]) {298					p.bump();299					continue;300				}301				p.expect(T![;]);302				break;303			}304			m.complete(p, STMT_LOCAL);305		} else {306			assertion(p);307			p.expect(T![;]);308			m.complete(p, STMT_ASSERT);309		}310	}311	match expr_binding_power(p, 0) {312		Ok(m) | Err(m) => m,313	};314	m.complete(p, EXPR)315}316317fn expr_binding_power(318	p: &mut Parser,319	minimum_binding_power: u8,320) -> Result<CompletedMarker, CompletedMarker> {321	let mut lhs = lhs(p)?;322323	while let Some(op) = BinaryOperatorKind::cast(p.current())324		.or_else(|| p.at(T!['{']).then_some(BinaryOperatorKind::MetaObjectApply))325	{326		let (left_binding_power, right_binding_power) = op.binding_power();327		if left_binding_power < minimum_binding_power {328			break;329		}330331		let m = lhs.wrap(p, EXPR, false);332333		// Object apply is not a real operator, we dont have something to bump334		if op != BinaryOperatorKind::MetaObjectApply {335			p.bump();336		}337338		let m = m.precede(p);339		let parsed_rhs = if p.at(T![local]) || p.at(T![assert]) {340			expr(p);341			true342		} else {343			expr_binding_power(p, right_binding_power)344				.map(|v| v.precede(p).complete(p, EXPR))345				.is_ok()346		};347		lhs = m.complete(348			p,349			if op == BinaryOperatorKind::MetaObjectApply {350				EXPR_OBJ_EXTEND351			} else {352				EXPR_BINARY353			},354		);355356		if !parsed_rhs {357			break;358		}359	}360	Ok(lhs)361}362363const COMPSPEC: SyntaxKindSet = TS![for if];364fn compspec(p: &mut Parser) -> CompletedMarker {365	assert!(p.at_ts(COMPSPEC));366	if p.at(T![for]) {367		if p.nth_at(1, T!['[']) && p.nth_at(2, IDENT) && p.nth_at(3, T![']']) && p.nth_at(4, T![:])368		{369			let m = p.start();370			p.bump_assert(T![for]);371			p.bump_assert(T!['[']);372			name(p);373			p.expect(T![']']);374			visibility(p);375			destruct(p);376			p.expect(T![in]);377			expr(p);378			return m.complete(p, FOR_OBJ_SPEC);379		}380		let m = p.start();381		p.bump();382		destruct(p);383		p.expect(T![in]);384		expr(p);385		m.complete(p, FOR_SPEC)386	} else if p.at(T![if]) {387		let m = p.start();388		p.bump();389		expr(p);390		m.complete(p, IF_SPEC)391	} else {392		unreachable!()393	}394}395396fn comma(p: &mut Parser) -> bool {397	comma_with_alternatives(p, TS![])398}399fn comma_with_alternatives(p: &mut Parser, set: SyntaxKindSet) -> bool {400	if p.at(T![,]) {401		p.bump();402		true403	} else if p.at_ts(set) {404		let _ex = p.expected_syntax_name("comma");405		p.expect_with_recovery_set(T![,], TS![]);406		true407	} else {408		false409	}410}411412fn field_name(p: &mut Parser) {413	let _e = p.expected_syntax_name("field name");414	let m = p.start();415	if p.at(T!['[']) {416		p.bump();417		expr(p);418		p.expect(T![']']);419		m.complete(p, FIELD_NAME_DYNAMIC);420	} else if p.at(IDENT) {421		name(p);422		m.complete(p, FIELD_NAME_FIXED);423	} else if Text::can_cast(p.current()) {424		text(p);425		m.complete(p, FIELD_NAME_FIXED);426	} else {427		m.forget(p);428		// Recover with ::, :::429		p.error_with_recovery_set(TS![; : '(']);430	}431}432fn visibility(p: &mut Parser) {433	let m = p.start();434	if !p.at_ts(TS![:]) {435		p.error_with_recovery_set(TS![=]);436	}437	p.bump();438	'colons: {439		if !p.at_ts(TS![:]) {440			break 'colons;441		}442		p.bump();443		if !p.at_ts(TS![:]) {444			break 'colons;445		}446		p.bump();447	}448	m.complete(p, VISIBILITY);449}450fn assertion(p: &mut Parser) {451	let m = p.start();452	p.bump_assert(T![assert]);453	expr(p);454	if p.at(T![:]) {455		p.bump();456		expr(p);457	}458	m.complete(p, ASSERTION);459}460fn object(p: &mut Parser) -> CompletedMarker {461	let m_t = p.start();462	let m = p.start();463	p.bump_assert(T!['{']);464465	let mut elems = 0;466	let mut compspecs = Vec::new();467	let mut asserts = Vec::new();468	loop {469		if p.at(T!['}']) {470			p.bump();471			break;472		}473		if p.at_ts(TS![for]) {474			if elems == 0 {475				let m = p.start();476				m.complete_missing(p, ExpectedSyntax::Named("field definition"));477			}478			while p.at_ts(COMPSPEC) {479				compspecs.push(compspec(p));480			}481			if comma_with_alternatives(p, TS![;]) {482				continue;483			}484			p.expect(R_BRACE);485			break;486		}487		let m = p.start();488		if p.at(T![local]) {489			obj_local(p);490			m.complete(p, MEMBER_BIND_STMT);491		} else if p.at(T![assert]) {492			assertion(p);493			asserts.push(m.complete(p, MEMBER_ASSERT_STMT));494		} else {495			field_name(p);496			if p.at(T![+]) {497				p.bump();498			}499			let params = if p.at(T!['(']) {500				params_desc(p);501				visibility(p);502				expr(p);503				true504			} else {505				visibility(p);506				if p.at(T![function]) {507					p.bump_assert(T![function]);508					params_desc(p);509					expr(p);510					true511				} else {512					expr(p);513					false514				}515			};516			elems += 1;517518			if params {519				m.complete(p, MEMBER_FIELD_METHOD)520			} else {521				m.complete(p, MEMBER_FIELD_NORMAL)522			};523		}524		while p.at_ts(COMPSPEC) {525			compspecs.push(compspec(p));526		}527		if comma_with_alternatives(p, TS![;]) {528			continue;529		}530		p.expect(R_BRACE);531		break;532	}533534	if elems > 1 && !compspecs.is_empty() {535		for errored in compspecs {536			errored.wrap_error(537				p,538				"compspec may only be used if there is only one object element",539				true,540			);541		}542		m.complete(p, OBJ_BODY_MEMBER_LIST);543	} else if !compspecs.is_empty() {544		for errored in asserts {545			errored.wrap_error(p, "asserts can't be used in object comprehensions", true);546		}547		m.complete(p, OBJ_BODY_COMP);548	} else {549		m.complete(p, OBJ_BODY_MEMBER_LIST);550	}551	m_t.complete(p, EXPR_OBJECT)552}553fn param(p: &mut Parser) {554	let m = p.start();555	destruct(p);556	if p.at(T![=]) {557		p.bump();558		expr(p);559	}560	m.complete(p, PARAM);561}562fn params_desc(p: &mut Parser) -> CompletedMarker {563	let m = p.start();564	p.bump_assert(T!['(']);565566	loop {567		if p.at(T![')']) {568			p.bump();569			break;570		}571		param(p);572		if comma(p) {573			continue;574		}575		p.expect(T![')']);576		break;577	}578579	m.complete(p, PARAMS_DESC)580}581fn args_desc(p: &mut Parser) {582	let m = p.start();583	p.bump_assert(T!['(']);584585	let started_named = Cell::new(false);586	let mut unnamed_after_named = Vec::new();587588	loop {589		if p.at(T![')']) {590			break;591		}592593		let m = p.start();594		if p.at(IDENT) && p.nth_at(1, T![=]) {595			name(p);596			p.bump();597			expr(p);598			m.complete(p, ARG);599			started_named.set(true);600		} else {601			expr(p);602			let arg = m.complete(p, ARG);603			if started_named.get() {604				unnamed_after_named.push(arg);605			}606		}607		if comma(p) {608			continue;609		}610		break;611	}612	p.expect(T![')']);613	if p.at(T![tailstrict]) {614		p.bump();615	}616617	for errored in unnamed_after_named {618		errored.wrap_error(p, "can't use positional arguments after named", true);619	}620621	m.complete(p, ARGS_DESC);622}623624fn array(p: &mut Parser) -> CompletedMarker {625	// Start the list node626	let m = p.start();627	p.bump_assert(T!['[']);628629	let mut compspecs = Vec::new();630	let mut elems = 0;631632	loop {633		if p.at(T![']']) {634			p.bump();635			break;636		}637		if elems != 0 && p.at_ts(TS![for]) {638			while p.at_ts(COMPSPEC) {639				compspecs.push(compspec(p));640			}641			if comma(p) {642				continue;643			}644			p.expect(T![']']);645			break;646		}647		expr(p);648		elems += 1;649		while p.at_ts(COMPSPEC) {650			compspecs.push(compspec(p));651		}652		if comma(p) {653			continue;654		}655		p.expect(T![']']);656		break;657	}658659	if elems > 1 && !compspecs.is_empty() {660		for spec in compspecs {661			spec.wrap_error(662				p,663				"compspec may only be used if there is only one array element",664				true,665			);666		}667668		m.complete(p, EXPR_ARRAY)669	} else if !compspecs.is_empty() {670		m.complete(p, EXPR_ARRAY_COMP)671	} else {672		m.complete(p, EXPR_ARRAY)673	}674}675/// Returns true if it was slice, false if just index676#[must_use]677fn slice_desc_or_index(p: &mut Parser) -> bool {678	let m = p.start();679	p.bump();680	// Start681	if !p.at(T![:]) {682		expr(p);683	}684	if p.at(T![:]) {685		p.bump();686		// End687		if !p.at_ts(TS![']' :]) {688			expr(p).wrap(p, SLICE_DESC_END, true);689		}690		if p.at(T![:]) {691			p.bump();692			// Step693			if !p.at(T![']']) {694				expr(p).wrap(p, SLICE_DESC_STEP, true);695			}696		}697	} else {698		// It was not a slice699		p.expect(T![']']);700		m.forget(p);701		return false;702	}703	p.expect(T![']']);704	m.complete(p, SLICE_DESC);705	true706}707708fn suffix(p: &mut Parser) {709	loop {710		let start = p.start();711		let _marker: CompletedMarker = if p.at(T![?]) {712			p.bump();713			p.expect(T![.]);714			if p.at(IDENT) {715				name(p);716				start.complete(p, SUFFIX_INDEX)717			} else if p.at(T!['[']) {718				p.bump();719				expr(p);720				p.expect(T![']']);721				start.complete(p, SUFFIX_INDEX_EXPR)722			} else {723				start.complete_missing(p, ExpectedSyntax::Named("index"))724			}725		} else if p.at(T![.]) {726			p.bump();727			name(p);728			start.complete(p, SUFFIX_INDEX)729		} else if p.at(T!['[']) {730			if slice_desc_or_index(p) {731				start.complete(p, SUFFIX_SLICE)732			} else {733				start.complete(p, SUFFIX_INDEX_EXPR)734			}735		} else if p.at(T!['(']) {736			args_desc(p);737			start.complete(p, SUFFIX_APPLY)738		} else {739			start.forget(p);740			break;741		};742	}743}744745fn lhs(p: &mut Parser) -> Result<CompletedMarker, CompletedMarker> {746	let lhs = lhs_basic(p)?;747748	suffix(p);749750	Ok(lhs)751}752fn name(p: &mut Parser) {753	let m = p.start();754	p.expect(IDENT);755	m.complete(p, NAME);756}757fn destruct_rest(p: &mut Parser) {758	let m = p.start();759	p.bump_assert(T![...]);760	if p.at(IDENT) {761		p.bump();762	}763	m.complete(p, DESTRUCT_REST);764}765fn destruct_object_field(p: &mut Parser) {766	let m = p.start();767	name(p);768	if p.at(T![:]) {769		p.bump();770		destruct(p);771	}772	if p.at(T![=]) {773		p.bump();774		expr(p);775	}776	m.complete(p, DESTRUCT_OBJECT_FIELD);777}778fn obj_local(p: &mut Parser) {779	let m = p.start();780	p.bump_assert(T![local]);781	bind(p);782	m.complete(p, OBJ_LOCAL);783}784fn destruct(p: &mut Parser) -> CompletedMarker {785	let m = p.start();786	let _ex = p.expected_syntax_name("destruction specifier");787	if p.at(T![?]) {788		p.bump();789		m.complete(p, DESTRUCT_SKIP)790	} else if p.at(T!['[']) {791		p.bump();792		// let mut had_rest = false;793		loop {794			if p.at(T![']']) {795				p.bump();796				break;797			} else if p.at(T![...]) {798				// let m_err = p.start_ranger();799				destruct_rest(p);800			// if had_rest {801			// 	p.custom_error(m_err.finish(p), "only one rest can be present in array");802			// }803			// had_rest = true;804			} else {805				destruct(p);806			}807			if p.at(T![,]) {808				p.bump();809				continue;810			}811			p.expect(T![']']);812			break;813		}814		m.complete(p, DESTRUCT_ARRAY)815	} else if p.at(T!['{']) {816		p.bump();817		let mut had_rest = false;818		loop {819			if p.at(T!['}']) {820				p.bump();821				break;822			} else if p.at(T![...]) {823				// let m_err = p.start_ranger();824				destruct_rest(p);825				// if had_rest {826				// 	p.custom_error(m_err.finish(p), "only one rest can be present in object");827				// }828				had_rest = true;829			} else {830				if had_rest {831					p.error_with_recovery_set(TS![]);832				}833				destruct_object_field(p);834			}835			if p.at(T![,]) {836				p.bump();837				continue;838			}839			p.expect(T!['}']);840			break;841		}842		m.complete(p, DESTRUCT_OBJECT)843	} else if p.at(IDENT) {844		name(p);845		m.complete(p, DESTRUCT_FULL)846	} else {847		m.forget(p);848		p.error_with_recovery_set(TS![; , '}', '(', :])849	}850}851fn bind(p: &mut Parser) {852	let m = p.start();853	if p.at(IDENT) && p.nth_at(1, T!['(']) {854		name(p);855		params_desc(p);856		p.expect(T![=]);857		expr(p);858		m.complete(p, BIND_FUNCTION)859	} else if p.at(IDENT) && p.nth_at(1, T![=]) && p.nth_at(2, T![function]) {860		name(p);861		p.expect(T![=]);862		p.expect(T![function]);863		params_desc(p);864		expr(p);865		m.complete(p, BIND_FUNCTION)866	} else {867		destruct(p);868		p.expect(T![=]);869		expr(p);870		m.complete(p, BIND_DESTRUCT)871	};872}873fn text(p: &mut Parser) {874	assert!(Text::can_cast(p.current()));875	p.bump();876}877fn number(p: &mut Parser) {878	assert!(Number::can_cast(p.current()));879	p.bump();880}881fn literal(p: &mut Parser) {882	assert!(Literal::can_cast(p.current()));883	p.bump();884}885fn lhs_basic(p: &mut Parser) -> Result<CompletedMarker, CompletedMarker> {886	let _e = p.expected_syntax_name("expression");887	Ok(if Literal::can_cast(p.current()) {888		let m = p.start();889		literal(p);890		m.complete(p, EXPR_LITERAL)891	} else if Text::can_cast(p.current()) {892		let m = p.start();893		text(p);894		m.complete(p, EXPR_STRING)895	} else if Number::can_cast(p.current()) {896		let m = p.start();897		number(p);898		m.complete(p, EXPR_NUMBER)899	} else if p.at(IDENT) {900		let m = p.start();901		name(p);902		m.complete(p, EXPR_VAR)903	} else if p.at(T![if]) {904		let m = p.start();905		p.bump();906		expr(p);907		p.expect(T![then]);908		expr(p).wrap(p, TRUE_EXPR, true);909		if p.at(T![else]) {910			p.bump();911			expr(p).wrap(p, FALSE_EXPR, true);912		}913		m.complete(p, EXPR_IF_THEN_ELSE)914	} else if p.at(T!['[']) {915		array(p)916	} else if p.at(T!['{']) {917		object(p)918	} else if p.at(T![function]) {919		let m = p.start();920		p.bump();921		params_desc(p);922		expr(p);923		m.complete(p, EXPR_FUNCTION)924	} else if p.at(T![error]) {925		let m = p.start();926		p.bump();927		expr(p);928		m.complete(p, EXPR_ERROR)929	} else if p.at(T![import]) || p.at(T![importstr]) || p.at(T![importbin]) {930		let m = p.start();931		p.bump();932		text(p);933		m.complete(p, EXPR_IMPORT)934	} else if let Some(op) = UnaryOperatorKind::cast(p.current()) {935		let ((), right_binding_power) = op.binding_power();936937		let m = p.start();938		p.bump();939		let _ = expr_binding_power(p, right_binding_power).map(|v| v.precede(p).complete(p, EXPR));940		m.complete(p, EXPR_UNARY)941	} else if p.at(T!['(']) {942		let m = p.start();943		p.bump();944		expr(p);945		p.expect(T![')']);946		m.complete(p, EXPR_PARENED)947	} else {948		return Err(p.error_with_no_skip());949	})950}951952impl Parse {953	pub fn syntax(&self) -> SyntaxNode {954		SyntaxNode::new_root(self.green_node.clone())955	}956}
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__for_obj_spec_force_visible.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__for_obj_spec_force_visible.snap
@@ -0,0 +1,51 @@
+---
+source: crates/jrsonnet-rowan-parser/src/tests.rs
+expression: "{ [k]: v for [k]::: v in obj }\n"
+---
+SOURCE_FILE@0..31
+  EXPR@0..30
+    EXPR_OBJECT@0..30
+      OBJ_BODY_COMP@0..30
+        L_BRACE@0..1 "{"
+        WHITESPACE@1..2 " "
+        MEMBER_FIELD_NORMAL@2..8
+          FIELD_NAME_DYNAMIC@2..5
+            L_BRACK@2..3 "["
+            EXPR@3..4
+              EXPR_VAR@3..4
+                NAME@3..4
+                  IDENT@3..4 "k"
+            R_BRACK@4..5 "]"
+          VISIBILITY@5..6
+            COLON@5..6 ":"
+          WHITESPACE@6..7 " "
+          EXPR@7..8
+            EXPR_VAR@7..8
+              NAME@7..8
+                IDENT@7..8 "v"
+        WHITESPACE@8..9 " "
+        FOR_OBJ_SPEC@9..28
+          FOR_KW@9..12 "for"
+          WHITESPACE@12..13 " "
+          L_BRACK@13..14 "["
+          NAME@14..15
+            IDENT@14..15 "k"
+          R_BRACK@15..16 "]"
+          VISIBILITY@16..19
+            COLON@16..17 ":"
+            COLON@17..18 ":"
+            COLON@18..19 ":"
+          WHITESPACE@19..20 " "
+          DESTRUCT_FULL@20..21
+            NAME@20..21
+              IDENT@20..21 "v"
+          WHITESPACE@21..22 " "
+          IN_KW@22..24 "in"
+          WHITESPACE@24..25 " "
+          EXPR@25..28
+            EXPR_VAR@25..28
+              NAME@25..28
+                IDENT@25..28 "obj"
+        WHITESPACE@28..29 " "
+        R_BRACE@29..30 "}"
+  WHITESPACE@30..31 "\n"
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__for_obj_spec_hidden.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__for_obj_spec_hidden.snap
@@ -0,0 +1,50 @@
+---
+source: crates/jrsonnet-rowan-parser/src/tests.rs
+expression: "{ [k]: v for [k]:: v in obj }\n"
+---
+SOURCE_FILE@0..30
+  EXPR@0..29
+    EXPR_OBJECT@0..29
+      OBJ_BODY_COMP@0..29
+        L_BRACE@0..1 "{"
+        WHITESPACE@1..2 " "
+        MEMBER_FIELD_NORMAL@2..8
+          FIELD_NAME_DYNAMIC@2..5
+            L_BRACK@2..3 "["
+            EXPR@3..4
+              EXPR_VAR@3..4
+                NAME@3..4
+                  IDENT@3..4 "k"
+            R_BRACK@4..5 "]"
+          VISIBILITY@5..6
+            COLON@5..6 ":"
+          WHITESPACE@6..7 " "
+          EXPR@7..8
+            EXPR_VAR@7..8
+              NAME@7..8
+                IDENT@7..8 "v"
+        WHITESPACE@8..9 " "
+        FOR_OBJ_SPEC@9..27
+          FOR_KW@9..12 "for"
+          WHITESPACE@12..13 " "
+          L_BRACK@13..14 "["
+          NAME@14..15
+            IDENT@14..15 "k"
+          R_BRACK@15..16 "]"
+          VISIBILITY@16..18
+            COLON@16..17 ":"
+            COLON@17..18 ":"
+          WHITESPACE@18..19 " "
+          DESTRUCT_FULL@19..20
+            NAME@19..20
+              IDENT@19..20 "v"
+          WHITESPACE@20..21 " "
+          IN_KW@21..23 "in"
+          WHITESPACE@23..24 " "
+          EXPR@24..27
+            EXPR_VAR@24..27
+              NAME@24..27
+                IDENT@24..27 "obj"
+        WHITESPACE@27..28 " "
+        R_BRACE@28..29 "}"
+  WHITESPACE@29..30 "\n"
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__for_obj_spec_value_destruct.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__for_obj_spec_value_destruct.snap
@@ -0,0 +1,66 @@
+---
+source: crates/jrsonnet-rowan-parser/src/tests.rs
+expression: "{ [k]: a + b for [k]: [a, b] in obj }\n"
+---
+SOURCE_FILE@0..38
+  EXPR@0..37
+    EXPR_OBJECT@0..37
+      OBJ_BODY_COMP@0..37
+        L_BRACE@0..1 "{"
+        WHITESPACE@1..2 " "
+        MEMBER_FIELD_NORMAL@2..12
+          FIELD_NAME_DYNAMIC@2..5
+            L_BRACK@2..3 "["
+            EXPR@3..4
+              EXPR_VAR@3..4
+                NAME@3..4
+                  IDENT@3..4 "k"
+            R_BRACK@4..5 "]"
+          VISIBILITY@5..6
+            COLON@5..6 ":"
+          WHITESPACE@6..7 " "
+          EXPR@7..12
+            EXPR_BINARY@7..12
+              EXPR@7..8
+                EXPR_VAR@7..8
+                  NAME@7..8
+                    IDENT@7..8 "a"
+              WHITESPACE@8..9 " "
+              PLUS@9..10 "+"
+              WHITESPACE@10..11 " "
+              EXPR@11..12
+                EXPR_VAR@11..12
+                  NAME@11..12
+                    IDENT@11..12 "b"
+        WHITESPACE@12..13 " "
+        FOR_OBJ_SPEC@13..35
+          FOR_KW@13..16 "for"
+          WHITESPACE@16..17 " "
+          L_BRACK@17..18 "["
+          NAME@18..19
+            IDENT@18..19 "k"
+          R_BRACK@19..20 "]"
+          VISIBILITY@20..21
+            COLON@20..21 ":"
+          WHITESPACE@21..22 " "
+          DESTRUCT_ARRAY@22..28
+            L_BRACK@22..23 "["
+            DESTRUCT_FULL@23..24
+              NAME@23..24
+                IDENT@23..24 "a"
+            COMMA@24..25 ","
+            WHITESPACE@25..26 " "
+            DESTRUCT_FULL@26..27
+              NAME@26..27
+                IDENT@26..27 "b"
+            R_BRACK@27..28 "]"
+          WHITESPACE@28..29 " "
+          IN_KW@29..31 "in"
+          WHITESPACE@31..32 " "
+          EXPR@32..35
+            EXPR_VAR@32..35
+              NAME@32..35
+                IDENT@32..35 "obj"
+        WHITESPACE@35..36 " "
+        R_BRACE@36..37 "}"
+  WHITESPACE@37..38 "\n"
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__for_obj_spec_visible.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__for_obj_spec_visible.snap
@@ -0,0 +1,49 @@
+---
+source: crates/jrsonnet-rowan-parser/src/tests.rs
+expression: "{ [k]: v for [k]: v in obj }\n"
+---
+SOURCE_FILE@0..29
+  EXPR@0..28
+    EXPR_OBJECT@0..28
+      OBJ_BODY_COMP@0..28
+        L_BRACE@0..1 "{"
+        WHITESPACE@1..2 " "
+        MEMBER_FIELD_NORMAL@2..8
+          FIELD_NAME_DYNAMIC@2..5
+            L_BRACK@2..3 "["
+            EXPR@3..4
+              EXPR_VAR@3..4
+                NAME@3..4
+                  IDENT@3..4 "k"
+            R_BRACK@4..5 "]"
+          VISIBILITY@5..6
+            COLON@5..6 ":"
+          WHITESPACE@6..7 " "
+          EXPR@7..8
+            EXPR_VAR@7..8
+              NAME@7..8
+                IDENT@7..8 "v"
+        WHITESPACE@8..9 " "
+        FOR_OBJ_SPEC@9..26
+          FOR_KW@9..12 "for"
+          WHITESPACE@12..13 " "
+          L_BRACK@13..14 "["
+          NAME@14..15
+            IDENT@14..15 "k"
+          R_BRACK@15..16 "]"
+          VISIBILITY@16..17
+            COLON@16..17 ":"
+          WHITESPACE@17..18 " "
+          DESTRUCT_FULL@18..19
+            NAME@18..19
+              IDENT@18..19 "v"
+          WHITESPACE@19..20 " "
+          IN_KW@20..22 "in"
+          WHITESPACE@22..23 " "
+          EXPR@23..26
+            EXPR_VAR@23..26
+              NAME@23..26
+                IDENT@23..26 "obj"
+        WHITESPACE@26..27 " "
+        R_BRACE@27..28 "}"
+  WHITESPACE@28..29 "\n"
modifiedcrates/jrsonnet-rowan-parser/src/tests.rsdiffbeforeafterboth
--- a/crates/jrsonnet-rowan-parser/src/tests.rs
+++ b/crates/jrsonnet-rowan-parser/src/tests.rs
@@ -228,6 +228,19 @@
 	local_in_binop_rhs => r#"
 		a + local x = 1; x
 	"#
+
+	for_obj_spec_visible => r#"
+		{ [k]: v for [k]: v in obj }
+	"#
+	for_obj_spec_hidden => r#"
+		{ [k]: v for [k]:: v in obj }
+	"#
+	for_obj_spec_force_visible => r#"
+		{ [k]: v for [k]::: v in obj }
+	"#
+	for_obj_spec_value_destruct => r#"
+		{ [k]: a + b for [k]: [a, b] in obj }
+	"#
 );
 
 #[test]