git.delta.rocks / jrsonnet / refs/commits / 26a3b24cc9cf

difftreelog

refactor(rowan-parser) remove intrinsic syntax

Yaroslav Bolyukin2022-12-14parent: #a6892a9.patch.diff
in: master

13 files changed

modifiedcmds/jrsonnet-fmt/src/children.rsdiffbeforeafterboth
--- a/cmds/jrsonnet-fmt/src/children.rs
+++ b/cmds/jrsonnet-fmt/src/children.rs
@@ -3,13 +3,11 @@
 use std::{fmt::Debug, mem};
 
 use jrsonnet_rowan_parser::{
-	nodes::{Trivia, TriviaKind},
-	AstNode, AstToken, SyntaxElement,
-	SyntaxKind::*,
-	SyntaxNode, TS,
+	nodes::{CustomError, Trivia, TriviaKind},
+	AstNode, AstToken, SyntaxElement, SyntaxNode, TS,
 };
 
-pub type ChildTrivia = Vec<Trivia>;
+pub type ChildTrivia = Vec<Result<Trivia, String>>;
 
 /// Node should have no non-trivia tokens before element
 pub fn trivia_before(node: SyntaxNode, end: Option<&SyntaxElement>) -> ChildTrivia {
@@ -20,12 +18,14 @@
 		}
 
 		if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {
-			out.push(trivia);
+			out.push(Ok(trivia));
+		} else if CustomError::can_cast(item.kind()) {
+			out.push(Err(item.to_string()));
 		} else if end.is_none() {
 			break;
 		} else {
 			assert!(
-				TS![, ;].contains(item.kind()) || item.kind() == ERROR,
+				TS![, ;].contains(item.kind()),
 				"silently eaten token: {:?}",
 				item.kind()
 			)
@@ -46,10 +46,12 @@
 	let mut out = Vec::new();
 	for item in iter {
 		if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {
-			out.push(trivia);
+			out.push(Ok(trivia));
+		} else if CustomError::can_cast(item.kind()) {
+			out.push(Err(item.to_string()))
 		} else {
 			assert!(
-				TS![, ;].contains(item.kind()) || item.kind() == ERROR,
+				TS![, ;].contains(item.kind()),
 				"silently eaten token: {:?}",
 				item.kind()
 			)
@@ -74,12 +76,14 @@
 	let mut out = Vec::new();
 	for item in iter.take_while(|i| Some(i) != end) {
 		if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {
-			out.push(trivia);
+			out.push(Ok(trivia));
+		} else if CustomError::can_cast(item.kind()) {
+			out.push(Err(item.to_string()))
 		} else if loose {
 			break;
 		} else {
 			assert!(
-				TS![, ;].contains(item.kind()) || item.kind() == ERROR,
+				TS![, ;].contains(item.kind()),
 				"silently eaten token: {:?}",
 				item.kind()
 			)
@@ -120,11 +124,16 @@
 fn count_newlines_before(tt: &ChildTrivia) -> usize {
 	let mut nl_count = 0;
 	for t in tt {
-		match t.kind() {
-			TriviaKind::Whitespace => {
-				nl_count += t.text().bytes().filter(|b| *b == b'\n').count();
+		match t {
+			Ok(t) => match t.kind() {
+				TriviaKind::Whitespace => {
+					nl_count += t.text().bytes().filter(|b| *b == b'\n').count();
+				}
+				_ => break,
+			},
+			Err(_) => {
+				nl_count += 1;
 			}
-			_ => break,
 		}
 	}
 	nl_count
@@ -132,19 +141,22 @@
 fn count_newlines_after(tt: &ChildTrivia) -> usize {
 	let mut nl_count = 0;
 	for t in tt.iter().rev() {
-		match t.kind() {
-			TriviaKind::Whitespace => {
-				nl_count += t.text().bytes().filter(|b| *b == b'\n').count();
-			}
-			TriviaKind::SingleLineHashComment => {
-				nl_count += 1;
-				break;
-			}
-			TriviaKind::SingleLineSlashComment => {
-				nl_count += 1;
-				break;
-			}
-			_ => {}
+		match t {
+			Ok(t) => match t.kind() {
+				TriviaKind::Whitespace => {
+					nl_count += t.text().bytes().filter(|b| *b == b'\n').count();
+				}
+				TriviaKind::SingleLineHashComment => {
+					nl_count += 1;
+					break;
+				}
+				TriviaKind::SingleLineSlashComment => {
+					nl_count += 1;
+					break;
+				}
+				_ => {}
+			},
+			Err(_) => nl_count += 1,
 		}
 	}
 	nl_count
@@ -187,16 +199,18 @@
 				|| current_child.is_none()
 				|| trivia.text().contains('\n') && !is_single_line_comment
 			{
-				next.push(trivia.clone());
+				next.push(Ok(trivia.clone()));
 				started_next = true;
 			} else {
 				let cur = current_child.as_mut().expect("checked not none");
-				cur.inline_trivia.push(trivia);
+				cur.inline_trivia.push(Ok(trivia));
 				if is_single_line_comment {
 					started_next = true;
 				}
 			}
 			had_some = true;
+		} else if CustomError::can_cast(item.kind()) {
+			next.push(Err(item.to_string()))
 		} else if loose {
 			if had_some {
 				break;
@@ -204,7 +218,7 @@
 			started_next = true;
 		} else {
 			assert!(
-				TS![, ;].contains(item.kind()) || item.kind() == ERROR,
+				TS![, ;].contains(item.kind()),
 				"silently eaten token: {:?}",
 				item.kind()
 			)
modifiedcmds/jrsonnet-fmt/src/comments.rsdiffbeforeafterboth
--- a/cmds/jrsonnet-fmt/src/comments.rs
+++ b/cmds/jrsonnet-fmt/src/comments.rs
@@ -17,6 +17,24 @@
 	let mut pi = p!(new:);
 
 	for c in comments {
+		let Ok(c) = c else {
+			let mut text = c.as_ref().unwrap_err() as &str;
+			while !text.is_empty() {
+				let pos = text.find(|c| c == '\n' || c == '\t').unwrap_or(text.len());
+				let sliced = &text[..pos];
+				p!(pi: string(sliced.to_string()));
+				text = &text[pos..];
+				if! text.is_empty(){
+					match text.as_bytes()[0] {
+						b'\n' => p!(pi: nl),
+						b'\t' => p!(pi: tab),
+						_ => unreachable!()
+					}
+					text = &text[1..];
+				}
+			}
+			continue;
+		};
 		match c.kind() {
 			TriviaKind::Whitespace => {}
 			TriviaKind::MultiLineComment => {
@@ -37,7 +55,7 @@
 				let mut immediate_start = true;
 				let mut lines = text
 					.split('\n')
-					.map(|l| l.trim_end())
+					.map(|l| l.trim_end().to_string())
 					.skip_while(|l| {
 						if l.is_empty() {
 							immediate_start = false;
@@ -51,7 +69,7 @@
 					lines.pop();
 				}
 				if lines.len() == 1 && !doc {
-					p!(pi: str("/* ") str(lines[0].trim()) str(" */") nl)
+					p!(pi: str("/* ") string(lines[0].trim().to_string()) str(" */") nl)
 				} else if !lines.is_empty() {
 					fn common_ws_prefix<'a>(a: &'a str, b: &str) -> &'a str {
 						let offset = a
@@ -62,17 +80,18 @@
 						&a[..offset]
 					}
 					// First line is not empty, extract ws prefix of it
-					let mut common_ws_padding = if immediate_start && lines.len() > 1 {
-						common_ws_prefix(lines[1], lines[1])
+					let mut common_ws_padding = (if immediate_start && lines.len() > 1 {
+						common_ws_prefix(&lines[1], &lines[1])
 					} else {
-						common_ws_prefix(lines[0], lines[0])
-					};
+						common_ws_prefix(&lines[0], &lines[0])
+					})
+					.to_string();
 					for line in lines
 						.iter()
 						.skip(if immediate_start { 2 } else { 1 })
 						.filter(|l| !l.is_empty())
 					{
-						common_ws_padding = common_ws_prefix(common_ws_padding, line);
+						common_ws_padding = common_ws_prefix(&common_ws_padding, line).to_string();
 					}
 					for line in lines
 						.iter_mut()
@@ -80,8 +99,9 @@
 						.filter(|l| !l.is_empty())
 					{
 						*line = line
-							.strip_prefix(common_ws_padding)
-							.expect("all non-empty lines start with this padding");
+							.strip_prefix(&common_ws_padding)
+							.expect("all non-empty lines start with this padding")
+							.to_string();
 					}
 
 					p!(pi: str("/*"));
@@ -105,9 +125,9 @@
 								} else {
 									p!(pi: tab);
 								}
-								line = new_line;
+								line = new_line.to_string();
 							}
-							p!(pi: str(line) nl)
+							p!(pi: string(line.to_string()) nl)
 						}
 					}
 					if doc {
@@ -136,7 +156,7 @@
 				if matches!(loc, CommentLocation::ItemInline) {
 					p!(pi: str(" "))
 				}
-				p!(pi: str("# ") str(c.text().strip_prefix('#').expect("hash comment starts with #").trim()));
+				p!(pi: str("# ") string(c.text().strip_prefix('#').expect("hash comment starts with #").trim().to_string()));
 				if !matches!(loc, CommentLocation::ItemInline) {
 					p!(pi: nl)
 				}
@@ -145,14 +165,14 @@
 				if matches!(loc, CommentLocation::ItemInline) {
 					p!(pi: str(" "))
 				}
-				p!(pi: str("// ") str(c.text().strip_prefix("//").expect("comment starts with //").trim()));
+				p!(pi: str("// ") string(c.text().strip_prefix("//").expect("comment starts with //").trim().to_string()));
 				if !matches!(loc, CommentLocation::ItemInline) {
 					p!(pi: nl)
 				}
 			}
 			// Garbage in - garbage out
 			TriviaKind::ErrorCommentTooShort => p!(pi: str("/*/")),
-			TriviaKind::ErrorCommentUnterminated => p!(pi: str(c.text())),
+			TriviaKind::ErrorCommentUnterminated => p!(pi: string(c.text().to_string())),
 		}
 	}
 
modifiedcmds/jrsonnet-fmt/src/main.rsdiffbeforeafterboth
before · cmds/jrsonnet-fmt/src/main.rs
1use std::any::type_name;23use children::{children_between, trivia_before};4use dprint_core::formatting::{PrintItems, PrintOptions};5use jrsonnet_rowan_parser::{6	nodes::{7		ArgsDesc, Assertion, BinaryOperator, Bind, CompSpec, Destruct, DestructArrayPart,8		DestructRest, Expr, Field, FieldName, ForSpec, IfSpec, ImportKind, LhsExpr, Literal,9		Member, Name, Number, ObjBody, ObjLocal, ParamsDesc, SliceDesc, SourceFile, Text,10		UnaryOperator,11	},12	AstNode, AstToken, SyntaxToken,13};1415use crate::{16	children::{trivia_after, trivia_between},17	comments::{format_comments, CommentLocation},18};1920mod children;21mod comments;22#[cfg(test)]23mod tests;2425pub trait Printable {26	fn print(&self) -> PrintItems;27}2829macro_rules! pi {30	(@i; $($t:tt)*) => {{31		#[allow(unused_mut)]32		let mut o = dprint_core::formatting::PrintItems::new();33		pi!(@s; o: $($t)*);34		o35	}};36	(@s; $o:ident: str($e:expr $(,)?) $($t:tt)*) => {{37		$o.push_str($e);38		pi!(@s; $o: $($t)*);39	}};40	(@s; $o:ident: nl $($t:tt)*) => {{41		$o.push_signal(dprint_core::formatting::Signal::NewLine);42		pi!(@s; $o: $($t)*);43	}};44	(@s; $o:ident: tab $($t:tt)*) => {{45		$o.push_signal(dprint_core::formatting::Signal::Tab);46		pi!(@s; $o: $($t)*);47	}};48	(@s; $o:ident: >i $($t:tt)*) => {{49		$o.push_signal(dprint_core::formatting::Signal::StartIndent);50		pi!(@s; $o: $($t)*);51	}};52	(@s; $o:ident: <i $($t:tt)*) => {{53		$o.push_signal(dprint_core::formatting::Signal::FinishIndent);54		pi!(@s; $o: $($t)*);55	}};56	(@s; $o:ident: {$expr:expr} $($t:tt)*) => {{57		$o.extend($expr.print());58		pi!(@s; $o: $($t)*);59	}};60	(@s; $o:ident: items($expr:expr) $($t:tt)*) => {{61		$o.extend($expr);62		pi!(@s; $o: $($t)*);63	}};64	(@s; $o:ident: if ($e:expr)($($then:tt)*) $($t:tt)*) => {{65		if $e {66			pi!(@s; $o: $($then)*);67		}68		pi!(@s; $o: $($t)*);69	}};70	(@s; $o:ident: ifelse ($e:expr)($($then:tt)*)($($else:tt)*) $($t:tt)*) => {{71		if $e {72			pi!(@s; $o: $($then)*);73		} else {74			pi!(@s; $o: $($else)*);75		}76		pi!(@s; $o: $($t)*);77	}};78	(@s; $i:ident:) => {}79}80macro_rules! p {81	(new: $($t:tt)*) => {82		pi!(@i; $($t)*)83	};84	($o:ident: $($t:tt)*) => {85		pi!(@s; $o: $($t)*)86	};87}88pub(crate) use p;89pub(crate) use pi;9091impl<P> Printable for Option<P>92where93	P: Printable,94{95	fn print(&self) -> PrintItems {96		if let Some(v) = self {97			v.print()98		} else {99			p!(new: str(100				&format!(101					"/*missing {}*/",102					type_name::<P>().replace("jrsonnet_rowan_parser::generated::nodes::", "")103				),104			))105		}106	}107}108109impl Printable for SyntaxToken {110	fn print(&self) -> PrintItems {111		p!(new: str(&self.to_string()))112	}113}114115impl Printable for Text {116	fn print(&self) -> PrintItems {117		p!(new: str(&format!("{}", self)))118	}119}120impl Printable for Number {121	fn print(&self) -> PrintItems {122		p!(new: str(&format!("{}", self)))123	}124}125126impl Printable for Name {127	fn print(&self) -> PrintItems {128		p!(new: {self.ident_lit()})129	}130}131132impl Printable for DestructRest {133	fn print(&self) -> PrintItems {134		let mut pi = p!(new: str("..."));135		if let Some(name) = self.into() {136			p!(pi: {name});137		}138		pi139	}140}141142impl Printable for Destruct {143	fn print(&self) -> PrintItems {144		let mut pi = p!(new:);145		match self {146			Destruct::DestructFull(f) => {147				p!(pi: {f.name()})148			}149			Destruct::DestructSkip(_) => p!(pi: str("?")),150			Destruct::DestructArray(a) => {151				p!(pi: str("[") >i nl);152				for el in a.destruct_array_parts() {153					match el {154						DestructArrayPart::DestructArrayElement(e) => {155							p!(pi: {e.destruct()} str(",") nl)156						}157						DestructArrayPart::DestructRest(d) => {158							p!(pi: {d} str(",") nl)159						}160					}161				}162				p!(pi: <i str("]"));163			}164			Destruct::DestructObject(o) => {165				p!(pi: str("{") >i nl);166				for item in o.destruct_object_fields() {167					p!(pi: {item.field()});168					if let Some(des) = item.destruct() {169						p!(pi: str(": ") {des})170					}171					if let Some(def) = item.expr() {172						p!(pi: str(" = ") {def});173					}174					p!(pi: str(",") nl);175				}176				if let Some(rest) = o.destruct_rest() {177					p!(pi: {rest} nl)178				}179				p!(pi: <i str("}"));180			}181		}182		pi183	}184}185186impl Printable for FieldName {187	fn print(&self) -> PrintItems {188		match self {189			FieldName::FieldNameFixed(f) => {190				if let Some(id) = f.id() {191					p!(new: {id})192				} else if let Some(str) = f.text() {193					p!(new: {str})194				} else {195					p!(new: str("/*missing FieldName*/"))196				}197			}198			FieldName::FieldNameDynamic(d) => {199				p!(new: str("[") {d.expr()} str("]"))200			}201		}202	}203}204impl Printable for Field {205	fn print(&self) -> PrintItems {206		let mut pi = p!(new:);207		match self {208			Field::FieldNormal(n) => {209				p!(pi: {n.field_name()});210				if n.plus_token().is_some() {211					p!(pi: str("+"));212				}213				p!(pi: str(": ") {n.expr()});214			}215			Field::FieldMethod(m) => {216				p!(pi: {m.field_name()} {m.params_desc()} str(": ") {m.expr()});217			}218		}219		pi220	}221}222223impl Printable for ObjLocal {224	fn print(&self) -> PrintItems {225		p!(new: str("local ") {self.bind()})226	}227}228229impl Printable for Assertion {230	fn print(&self) -> PrintItems {231		let mut pi = p!(new: str("assert ") {self.condition()});232		if self.colon_token().is_some() || self.message().is_some() {233			p!(pi: str(": ") {self.message()})234		}235		pi236	}237}238239impl Printable for ParamsDesc {240	fn print(&self) -> PrintItems {241		let mut pi = p!(new: str("(") >i nl);242		for param in self.params() {243			p!(pi: {param.destruct()});244			if param.assign_token().is_some() || param.expr().is_some() {245				p!(pi: str(" = ") {param.expr()})246			}247			p!(pi: str(",") nl)248		}249		p!(pi: <i str(")"));250		pi251	}252}253impl Printable for ArgsDesc {254	fn print(&self) -> PrintItems {255		let mut pi = p!(new: str("(") >i nl);256		for arg in self.args() {257			if arg.name().is_some() || arg.assign_token().is_some() {258				p!(pi: {arg.name()} str(" = "));259			}260			p!(pi: {arg.expr()} str(",") nl)261		}262		p!(pi: <i str(")"));263		pi264	}265}266impl Printable for SliceDesc {267	fn print(&self) -> PrintItems {268		let mut pi = p!(new: str("["));269		if self.from().is_some() {270			p!(pi: {self.from()});271		}272		p!(pi: str(":"));273		if self.end().is_some() {274			p!(pi: {self.end().map(|e|e.expr())})275		}276		// Keep only one : in case if we don't need step277		if self.step().is_some() {278			p!(pi: str(":") {self.step().map(|e|e.expr())});279		}280		p!(pi: str("]"));281		pi282	}283}284285impl Printable for ObjBody {286	fn print(&self) -> PrintItems {287		match self {288			ObjBody::ObjBodyComp(_) => todo!(),289			ObjBody::ObjBodyMemberList(l) => {290				let mut pi = p!(new: str("{") >i nl);291				let (children, end_comments) = children_between::<Member>(292					l.syntax().clone(),293					l.l_brace_token().map(Into::into).as_ref(),294					l.r_brace_token().map(Into::into).as_ref(),295				);296				for mem in children.into_iter() {297					if mem.should_start_with_newline {298						p!(pi: nl);299					}300					p!(pi: items(format_comments(&mem.before_trivia, CommentLocation::AboveItem)));301					match mem.value {302						Member::MemberBindStmt(b) => {303							p!(pi: {b.obj_local()})304						}305						Member::MemberAssertStmt(ass) => {306							p!(pi: {ass.assertion()})307						}308						Member::MemberField(f) => {309							p!(pi: {f.field()})310						}311					}312					p!(pi: str(","));313					p!(pi: items(format_comments(&mem.inline_trivia, CommentLocation::ItemInline)));314					p!(pi: nl)315				}316317				if end_comments.should_start_with_newline {318					p!(pi: nl);319				}320				p!(pi: items(format_comments(&end_comments.trivia, CommentLocation::EndOfItems)));321				p!(pi: <i str("}"));322				pi323			}324		}325	}326}327impl Printable for UnaryOperator {328	fn print(&self) -> PrintItems {329		p!(new: str(self.text()))330	}331}332impl Printable for BinaryOperator {333	fn print(&self) -> PrintItems {334		p!(new: str(self.text()))335	}336}337impl Printable for Bind {338	fn print(&self) -> PrintItems {339		match self {340			Bind::BindDestruct(d) => {341				p!(new: {d.into()} str(" = ") {d.value()})342			}343			Bind::BindFunction(f) => {344				p!(new: str("function") {f.params()} str(" = ") {f.value()})345			}346		}347	}348}349impl Printable for Literal {350	fn print(&self) -> PrintItems {351		p!(new: str(&self.syntax().to_string()))352	}353}354impl Printable for ImportKind {355	fn print(&self) -> PrintItems {356		p!(new: str(&self.syntax().to_string()))357	}358}359impl Printable for LhsExpr {360	fn print(&self) -> PrintItems {361		p!(new: {self.expr()})362	}363}364impl Printable for ForSpec {365	fn print(&self) -> PrintItems {366		p!(new: str("for ") {self.bind()} str(" in ") {self.expr()})367	}368}369impl Printable for IfSpec {370	fn print(&self) -> PrintItems {371		p!(new: str("if ") {self.expr()})372	}373}374impl Printable for CompSpec {375	fn print(&self) -> PrintItems {376		match self {377			CompSpec::ForSpec(f) => f.print(),378			CompSpec::IfSpec(i) => i.print(),379		}380	}381}382impl Printable for Expr {383	fn print(&self) -> PrintItems {384		match self {385			Expr::ExprBinary(b) => {386				p!(new: {b.lhs()} str(" ") {b.binary_operator()} str(" ") {b.rhs()})387			}388			Expr::ExprUnary(u) => p!(new: {u.unary_operator()} {u.rhs()}),389			Expr::ExprSlice(s) => {390				p!(new: {s.expr()} {s.slice_desc()})391			}392			Expr::ExprIndex(i) => {393				p!(new: {i.expr()} str(".") {i.index()})394			}395			Expr::ExprIndexExpr(i) => p!(new: {i.base()} str("[") {i.index()} str("]")),396			Expr::ExprApply(a) => {397				let mut pi = p!(new: {a.expr()} {a.args_desc()});398				if a.tailstrict_kw_token().is_some() {399					p!(pi: str(" tailstrict"));400				}401				pi402			}403			Expr::ExprObjExtend(ex) => {404				p!(new: {ex.lhs_expr()} str(" ") {ex.expr()})405			}406			Expr::ExprParened(p) => {407				p!(new: str("(") {p.expr()} str(")"))408			}409			Expr::ExprIntrinsicThisFile(_) => p!(new: str("$intrinsicThisFile")),410			Expr::ExprIntrinsicId(_) => p!(new: str("$intrinsicId")),411			Expr::ExprIntrinsic(i) => p!(new: str("$intrinsic(") {i.name()} str(")")),412			Expr::ExprString(s) => p!(new: {s.text()}),413			Expr::ExprNumber(n) => p!(new: {n.number()}),414			Expr::ExprArray(a) => {415				let mut pi = p!(new: str("[") >i nl);416				for el in a.exprs() {417					p!(pi: {el} str(",") nl);418				}419				p!(pi: <i str("]"));420				pi421			}422			Expr::ExprObject(o) => {423				p!(new: {o.obj_body()})424			}425			Expr::ExprArrayComp(arr) => {426				let mut pi = p!(new: str("[") {arr.expr()});427				for spec in arr.comp_specs() {428					p!(pi: str(" ") {spec});429				}430				p!(pi: str("]"));431				pi432			}433			Expr::ExprImport(v) => {434				p!(new: {v.import_kind()} str(" ") {v.text()})435			}436			Expr::ExprVar(n) => p!(new: {n.name()}),437			Expr::ExprLocal(l) => {438				let mut pi = p!(new:);439				let (binds, end_comments) = children_between::<Bind>(440					l.syntax().clone(),441					l.local_kw_token().map(Into::into).as_ref(),442					l.semi_token().map(Into::into).as_ref(),443				);444				if binds.len() == 1 {445					let bind = &binds[0];446					p!(pi: items(format_comments(&bind.before_trivia, CommentLocation::AboveItem)));447					p!(pi: str("local ") {bind.value});448				// TODO: keep end_comments, child.inline_trivia somehow, force multiple locals formatting in case of presence?449				} else {450					p!(pi: str("local") >i nl);451					for bind in binds {452						if bind.should_start_with_newline {453							p!(pi: nl);454						}455						p!(pi: items(format_comments(&bind.before_trivia, CommentLocation::AboveItem)));456						p!(pi: {bind.value} str(";"));457						p!(pi: items(format_comments(&bind.inline_trivia, CommentLocation::ItemInline)) nl);458					}459					if end_comments.should_start_with_newline {460						p!(pi: nl)461					}462					p!(pi: items(format_comments(&end_comments.trivia, CommentLocation::EndOfItems)));463					p!(pi: <i);464				}465				p!(pi: str(";") nl);466467				let expr_comments = trivia_between(468					l.syntax().clone(),469					l.semi_token().map(Into::into).as_ref(),470					l.expr()471						.map(|e| e.syntax().clone())472						.map(Into::into)473						.as_ref(),474				);475476				if expr_comments.should_start_with_newline {477					p!(pi: nl);478				}479				p!(pi: items(format_comments(&expr_comments.trivia, CommentLocation::AboveItem)));480				p!(pi: {l.expr()});481				pi482			}483			Expr::ExprIfThenElse(ite) => {484				let mut pi =485					p!(new: str("if ") {ite.cond()} str(" then ") {ite.then().map(|t| t.expr())});486				if ite.else_kw_token().is_some() || ite.else_().is_some() {487					p!(pi: str(" else ") {ite.else_().map(|t| t.expr())})488				}489				pi490			}491			Expr::ExprFunction(f) => p!(new: str("function") {f.params_desc()} str(" ") {f.expr()}),492			Expr::ExprAssert(a) => p!(new: {a.assertion()} str("; ") {a.expr()}),493			Expr::ExprError(e) => p!(new: str("error ") {e.expr()}),494			Expr::ExprLiteral(l) => {495				p!(new: {l.literal()})496			}497		}498	}499}500501impl Printable for SourceFile {502	fn print(&self) -> PrintItems {503		let mut pi = p!(new:);504		let before = trivia_before(505			self.syntax().clone(),506			self.expr()507				.map(|e| e.syntax().clone())508				.map(Into::into)509				.as_ref(),510		);511		let after = trivia_after(512			self.syntax().clone(),513			self.expr()514				.map(|e| e.syntax().clone())515				.map(Into::into)516				.as_ref(),517		);518		p!(pi: items(format_comments(&before, CommentLocation::AboveItem)));519		p!(pi: {self.expr()} nl);520		p!(pi: items(format_comments(&after, CommentLocation::EndOfItems)));521		pi522	}523}524525fn main() {526	let (parsed, _errors) = jrsonnet_rowan_parser::parse(527		r#"528529530		# Edit me!531		local b = import "b.libsonnet";  # comment532		local a = import "a.libsonnet";533534			 local f(x,y)=x+y;535536		local {a: [b, ..., c], d, ...e} = null;537538		local ass = assert false : false; false;539540		local fn = function(a, b, c = 3) 4;541542		local comp = [a for b in c if d == e];543		local ocomp = {[k]: 1 for k in v};544545		local ? = skip;546547		local intr = $intrinsic(test);548		local intrId = $intrinsicId;549		local intrThisFile = $intrinsicThisFile;550551		local ie = a[expr];552553		local unary = !a;554555		local556			//   I am comment557			singleLocalWithItemComment = 1,558		;559560		// Comment between local and expression561562		local563			a = 1, //   Inline564			// Comment above b565			b = 4,566567			// c needs some space568			c = 5,569570			// Comment after everything571		;572573574		local Template = {z: "foo"};575576		{577						local578579					h = 3,580					assert self.a == 1581582					: "error",583		"f": ((((((3)))))) ,584		"g g":585		f(4,2),586		arr: [[587		  1, 2,588		  ],589		  3,590		  {591			  b: {592				  c: {593					  k: [16]594				  }595			  }596		  }597		  ],598		  m: a[1::],599		  m: b[::],600601		  comments: {602			_: '',603			//     Plain comment604			a: '',605606			#    Plain comment with empty line before607			b: '',608			/*Single-line multiline comment609610			*/611			c: '',612613			/**Single-line multiline doc comment614615			*/616			c: '',617618			/**multiline doc comment619			s620			*/621			c: '',622623			/*624625	Multi-line626627	comment628			*/629			d: '',630631			e: '', // Inline comment632633			k: '',634635			// Text after everything636		  },637		  comments2: {638			k: '',639			// Text after everything, but no newline above640		  },641		  k: if a         == b    then642643644		  2645646		  else Template {}647		} + Template648649650		// Comment after everything651"#,652	);653654	// dbg!(errors);655	dbg!(&parsed);656657	let o = dprint_core::formatting::format(658		|| parsed.print(),659		PrintOptions {660			indent_width: 2,661			max_width: 100,662			use_tabs: false,663			new_line_text: "\n",664		},665	);666	println!("{}", o);667}
after · cmds/jrsonnet-fmt/src/main.rs
1use std::any::type_name;23use children::{children_between, trivia_before};4use dprint_core::formatting::{PrintItems, PrintOptions};5use jrsonnet_rowan_parser::{6	nodes::{7		ArgsDesc, Assertion, BinaryOperator, Bind, CompSpec, Destruct, DestructArrayPart,8		DestructRest, Expr, FieldName, ForSpec, IfSpec, ImportKind, LhsExpr, Literal, Member, Name,9		Number, ObjBody, ObjLocal, ParamsDesc, SliceDesc, SourceFile, Text, UnaryOperator,10		Visibility, VisibilityKind,11	},12	rowan::NodeOrToken,13	AstNode, AstToken, SyntaxToken,14};1516use crate::{17	children::{trivia_after, trivia_between},18	comments::{format_comments, CommentLocation},19};2021mod children;22mod comments;23#[cfg(test)]24mod tests;2526pub trait Printable {27	fn print(&self) -> PrintItems;28}2930macro_rules! pi {31	(@i; $($t:tt)*) => {{32		#[allow(unused_mut)]33		let mut o = dprint_core::formatting::PrintItems::new();34		pi!(@s; o: $($t)*);35		o36	}};37	(@s; $o:ident: str($e:expr $(,)?) $($t:tt)*) => {{38		$o.push_str($e);39		pi!(@s; $o: $($t)*);40	}};41	(@s; $o:ident: string($e:expr $(,)?) $($t:tt)*) => {{42		$o.push_string($e);43		pi!(@s; $o: $($t)*);44	}};45	(@s; $o:ident: nl $($t:tt)*) => {{46		$o.push_signal(dprint_core::formatting::Signal::NewLine);47		pi!(@s; $o: $($t)*);48	}};49	(@s; $o:ident: tab $($t:tt)*) => {{50		$o.push_signal(dprint_core::formatting::Signal::Tab);51		pi!(@s; $o: $($t)*);52	}};53	(@s; $o:ident: >i $($t:tt)*) => {{54		$o.push_signal(dprint_core::formatting::Signal::StartIndent);55		pi!(@s; $o: $($t)*);56	}};57	(@s; $o:ident: <i $($t:tt)*) => {{58		$o.push_signal(dprint_core::formatting::Signal::FinishIndent);59		pi!(@s; $o: $($t)*);60	}};61	(@s; $o:ident: {$expr:expr} $($t:tt)*) => {{62		$o.extend($expr.print());63		pi!(@s; $o: $($t)*);64	}};65	(@s; $o:ident: items($expr:expr) $($t:tt)*) => {{66		$o.extend($expr);67		pi!(@s; $o: $($t)*);68	}};69	(@s; $o:ident: if ($e:expr)($($then:tt)*) $($t:tt)*) => {{70		if $e {71			pi!(@s; $o: $($then)*);72		}73		pi!(@s; $o: $($t)*);74	}};75	(@s; $o:ident: ifelse ($e:expr)($($then:tt)*)($($else:tt)*) $($t:tt)*) => {{76		if $e {77			pi!(@s; $o: $($then)*);78		} else {79			pi!(@s; $o: $($else)*);80		}81		pi!(@s; $o: $($t)*);82	}};83	(@s; $i:ident:) => {}84}85macro_rules! p {86	(new: $($t:tt)*) => {87		pi!(@i; $($t)*)88	};89	($o:ident: $($t:tt)*) => {90		pi!(@s; $o: $($t)*)91	};92}93pub(crate) use p;94pub(crate) use pi;9596impl<P> Printable for Option<P>97where98	P: Printable,99{100	fn print(&self) -> PrintItems {101		if let Some(v) = self {102			v.print()103		} else {104			p!(new: string(105				format!(106					"/*missing {}*/",107					type_name::<P>().replace("jrsonnet_rowan_parser::generated::nodes::", "")108				),109			))110		}111	}112}113114impl Printable for SyntaxToken {115	fn print(&self) -> PrintItems {116		p!(new: string(self.to_string()))117	}118}119120impl Printable for Text {121	fn print(&self) -> PrintItems {122		p!(new: string(format!("{}", self)))123	}124}125impl Printable for Number {126	fn print(&self) -> PrintItems {127		p!(new: string(format!("{}", self)))128	}129}130131impl Printable for Name {132	fn print(&self) -> PrintItems {133		p!(new: {self.ident_lit()})134	}135}136137impl Printable for DestructRest {138	fn print(&self) -> PrintItems {139		let mut pi = p!(new: str("..."));140		if let Some(name) = self.into() {141			p!(pi: {name});142		}143		pi144	}145}146147impl Printable for Destruct {148	fn print(&self) -> PrintItems {149		let mut pi = p!(new:);150		match self {151			Destruct::DestructFull(f) => {152				p!(pi: {f.name()})153			}154			Destruct::DestructSkip(_) => p!(pi: str("?")),155			Destruct::DestructArray(a) => {156				p!(pi: str("[") >i nl);157				for el in a.destruct_array_parts() {158					match el {159						DestructArrayPart::DestructArrayElement(e) => {160							p!(pi: {e.destruct()} str(",") nl)161						}162						DestructArrayPart::DestructRest(d) => {163							p!(pi: {d} str(",") nl)164						}165					}166				}167				p!(pi: <i str("]"));168			}169			Destruct::DestructObject(o) => {170				p!(pi: str("{") >i nl);171				for item in o.destruct_object_fields() {172					p!(pi: {item.field()});173					if let Some(des) = item.destruct() {174						p!(pi: str(": ") {des})175					}176					if let Some(def) = item.expr() {177						p!(pi: str(" = ") {def});178					}179					p!(pi: str(",") nl);180				}181				if let Some(rest) = o.destruct_rest() {182					p!(pi: {rest} nl)183				}184				p!(pi: <i str("}"));185			}186		}187		pi188	}189}190191impl Printable for FieldName {192	fn print(&self) -> PrintItems {193		match self {194			FieldName::FieldNameFixed(f) => {195				if let Some(id) = f.id() {196					p!(new: {id})197				} else if let Some(str) = f.text() {198					p!(new: {str})199				} else {200					p!(new: str("/*missing FieldName*/"))201				}202			}203			FieldName::FieldNameDynamic(d) => {204				p!(new: str("[") {d.expr()} str("]"))205			}206		}207	}208}209210impl Printable for Visibility {211	fn print(&self) -> PrintItems {212		p!(new: string(self.to_string()))213	}214}215216impl Printable for ObjLocal {217	fn print(&self) -> PrintItems {218		p!(new: str("local ") {self.bind()})219	}220}221222impl Printable for Assertion {223	fn print(&self) -> PrintItems {224		let mut pi = p!(new: str("assert ") {self.condition()});225		if self.colon_token().is_some() || self.message().is_some() {226			p!(pi: str(": ") {self.message()})227		}228		pi229	}230}231232impl Printable for ParamsDesc {233	fn print(&self) -> PrintItems {234		let mut pi = p!(new: str("(") >i nl);235		for param in self.params() {236			p!(pi: {param.destruct()});237			if param.assign_token().is_some() || param.expr().is_some() {238				p!(pi: str(" = ") {param.expr()})239			}240			p!(pi: str(",") nl)241		}242		p!(pi: <i str(")"));243		pi244	}245}246impl Printable for ArgsDesc {247	fn print(&self) -> PrintItems {248		let mut pi = p!(new: str("(") >i nl);249		for arg in self.args() {250			if arg.name().is_some() || arg.assign_token().is_some() {251				p!(pi: {arg.name()} str(" = "));252			}253			p!(pi: {arg.expr()} str(",") nl)254		}255		p!(pi: <i str(")"));256		pi257	}258}259impl Printable for SliceDesc {260	fn print(&self) -> PrintItems {261		let mut pi = p!(new: str("["));262		if self.from().is_some() {263			p!(pi: {self.from()});264		}265		p!(pi: str(":"));266		if self.end().is_some() {267			p!(pi: {self.end().map(|e|e.expr())})268		}269		// Keep only one : in case if we don't need step270		if self.step().is_some() {271			p!(pi: str(":") {self.step().map(|e|e.expr())});272		}273		p!(pi: str("]"));274		pi275	}276}277278impl Printable for Member {279	fn print(&self) -> PrintItems {280		match self {281			Member::MemberBindStmt(b) => {282				p!(new: {b.obj_local()})283			}284			Member::MemberAssertStmt(ass) => {285				p!(new: {ass.assertion()})286			}287			Member::MemberFieldNormal(n) => {288				p!(new: {n.field_name()} if(n.plus_token().is_some())({n.plus_token()}) {n.visibility()} str(" ") {n.expr()})289			}290			Member::MemberFieldMethod(_) => todo!(),291		}292	}293}294295impl Printable for ObjBody {296	fn print(&self) -> PrintItems {297		match self {298			ObjBody::ObjBodyComp(l) => {299				let mut pi = p!(new: str("{") >i nl);300				let (children, end_comments) = children_between::<Member>(301					l.syntax().clone(),302					l.l_brace_token().map(Into::into).as_ref(),303					Some(304						&(l.comp_specs()305							.next()306							.expect("at least one spec is defined")307							.syntax()308							.clone())309						.into(),310					),311				);312				for mem in children.into_iter() {313					if mem.should_start_with_newline {314						p!(pi: nl);315					}316					p!(pi: items(format_comments(&mem.before_trivia, CommentLocation::AboveItem)));317					p!(pi: {mem.value} str(","));318					p!(pi: items(format_comments(&mem.inline_trivia, CommentLocation::ItemInline)));319					p!(pi: nl)320				}321322				if end_comments.should_start_with_newline {323					p!(pi: nl);324				}325				p!(pi: items(format_comments(&end_comments.trivia, CommentLocation::EndOfItems)));326327				let (compspecs, end_comments) = children_between::<CompSpec>(328					l.syntax().clone(),329					l.member_comps()330						.last()331						.map(|m| m.syntax().clone())332						.map(Into::into)333						.or_else(|| l.l_brace_token().map(Into::into))334						.as_ref(),335					l.r_brace_token().map(Into::into).as_ref(),336				);337				for mem in compspecs.into_iter() {338					if mem.should_start_with_newline {339						p!(pi: nl);340					}341					p!(pi: items(format_comments(&mem.before_trivia, CommentLocation::AboveItem)));342					p!(pi: {mem.value});343					p!(pi: items(format_comments(&mem.inline_trivia, CommentLocation::ItemInline)));344					p!(pi: nl)345				}346				if end_comments.should_start_with_newline {347					p!(pi: nl);348				}349				p!(pi: items(format_comments(&end_comments.trivia, CommentLocation::EndOfItems)));350351				p!(pi: <i str("}"));352				pi353			}354			ObjBody::ObjBodyMemberList(l) => {355				let mut pi = p!(new: str("{") >i nl);356				let (children, end_comments) = children_between::<Member>(357					l.syntax().clone(),358					l.l_brace_token().map(Into::into).as_ref(),359					l.r_brace_token().map(Into::into).as_ref(),360				);361				for mem in children.into_iter() {362					if mem.should_start_with_newline {363						p!(pi: nl);364					}365					p!(pi: items(format_comments(&mem.before_trivia, CommentLocation::AboveItem)));366					p!(pi: {mem.value} str(","));367					p!(pi: items(format_comments(&mem.inline_trivia, CommentLocation::ItemInline)));368					p!(pi: nl)369				}370371				if end_comments.should_start_with_newline {372					p!(pi: nl);373				}374				p!(pi: items(format_comments(&end_comments.trivia, CommentLocation::EndOfItems)));375				p!(pi: <i str("}"));376				pi377			}378		}379	}380}381impl Printable for UnaryOperator {382	fn print(&self) -> PrintItems {383		p!(new: string(self.text().to_string()))384	}385}386impl Printable for BinaryOperator {387	fn print(&self) -> PrintItems {388		p!(new: string(self.text().to_string()))389	}390}391impl Printable for Bind {392	fn print(&self) -> PrintItems {393		match self {394			Bind::BindDestruct(d) => {395				p!(new: {d.into()} str(" = ") {d.value()})396			}397			Bind::BindFunction(f) => {398				p!(new: str("function") {f.params()} str(" = ") {f.value()})399			}400		}401	}402}403impl Printable for Literal {404	fn print(&self) -> PrintItems {405		p!(new: string(self.syntax().to_string()))406	}407}408impl Printable for ImportKind {409	fn print(&self) -> PrintItems {410		p!(new: string(self.syntax().to_string()))411	}412}413impl Printable for LhsExpr {414	fn print(&self) -> PrintItems {415		p!(new: {self.expr()})416	}417}418impl Printable for ForSpec {419	fn print(&self) -> PrintItems {420		p!(new: str("for ") {self.bind()} str(" in ") {self.expr()})421	}422}423impl Printable for IfSpec {424	fn print(&self) -> PrintItems {425		p!(new: str("if ") {self.expr()})426	}427}428impl Printable for CompSpec {429	fn print(&self) -> PrintItems {430		match self {431			CompSpec::ForSpec(f) => f.print(),432			CompSpec::IfSpec(i) => i.print(),433		}434	}435}436impl Printable for Expr {437	fn print(&self) -> PrintItems {438		match self {439			Expr::ExprBinary(b) => {440				p!(new: {b.lhs()} str(" ") {b.binary_operator()} str(" ") {b.rhs()})441			}442			Expr::ExprUnary(u) => p!(new: {u.unary_operator()} {u.rhs()}),443			Expr::ExprSlice(s) => {444				p!(new: {s.expr()} {s.slice_desc()})445			}446			Expr::ExprIndex(i) => {447				p!(new: {i.expr()} str(".") {i.index()})448			}449			Expr::ExprIndexExpr(i) => p!(new: {i.base()} str("[") {i.index()} str("]")),450			Expr::ExprApply(a) => {451				let mut pi = p!(new: {a.expr()} {a.args_desc()});452				if a.tailstrict_kw_token().is_some() {453					p!(pi: str(" tailstrict"));454				}455				pi456			}457			Expr::ExprObjExtend(ex) => {458				p!(new: {ex.lhs_expr()} str(" ") {ex.expr()})459			}460			Expr::ExprParened(p) => {461				p!(new: str("(") {p.expr()} str(")"))462			}463			Expr::ExprString(s) => p!(new: {s.text()}),464			Expr::ExprNumber(n) => p!(new: {n.number()}),465			Expr::ExprArray(a) => {466				let mut pi = p!(new: str("[") >i nl);467				for el in a.exprs() {468					p!(pi: {el} str(",") nl);469				}470				p!(pi: <i str("]"));471				pi472			}473			Expr::ExprObject(o) => {474				p!(new: {o.obj_body()})475			}476			Expr::ExprArrayComp(arr) => {477				let mut pi = p!(new: str("[") {arr.expr()});478				for spec in arr.comp_specs() {479					p!(pi: str(" ") {spec});480				}481				p!(pi: str("]"));482				pi483			}484			Expr::ExprImport(v) => {485				p!(new: {v.import_kind()} str(" ") {v.text()})486			}487			Expr::ExprVar(n) => p!(new: {n.name()}),488			Expr::ExprLocal(l) => {489				let mut pi = p!(new:);490				let (binds, end_comments) = children_between::<Bind>(491					l.syntax().clone(),492					l.local_kw_token().map(Into::into).as_ref(),493					l.semi_token().map(Into::into).as_ref(),494				);495				if binds.len() == 1 {496					let bind = &binds[0];497					p!(pi: items(format_comments(&bind.before_trivia, CommentLocation::AboveItem)));498					p!(pi: str("local ") {bind.value});499				// TODO: keep end_comments, child.inline_trivia somehow, force multiple locals formatting in case of presence?500				} else {501					p!(pi: str("local") >i nl);502					for bind in binds {503						if bind.should_start_with_newline {504							p!(pi: nl);505						}506						p!(pi: items(format_comments(&bind.before_trivia, CommentLocation::AboveItem)));507						p!(pi: {bind.value} str(";"));508						p!(pi: items(format_comments(&bind.inline_trivia, CommentLocation::ItemInline)) nl);509					}510					if end_comments.should_start_with_newline {511						p!(pi: nl)512					}513					p!(pi: items(format_comments(&end_comments.trivia, CommentLocation::EndOfItems)));514					p!(pi: <i);515				}516				p!(pi: str(";") nl);517518				let expr_comments = trivia_between(519					l.syntax().clone(),520					l.semi_token().map(Into::into).as_ref(),521					l.expr()522						.map(|e| e.syntax().clone())523						.map(Into::into)524						.as_ref(),525				);526527				if expr_comments.should_start_with_newline {528					p!(pi: nl);529				}530				p!(pi: items(format_comments(&expr_comments.trivia, CommentLocation::AboveItem)));531				p!(pi: {l.expr()});532				pi533			}534			Expr::ExprIfThenElse(ite) => {535				let mut pi =536					p!(new: str("if ") {ite.cond()} str(" then ") {ite.then().map(|t| t.expr())});537				if ite.else_kw_token().is_some() || ite.else_().is_some() {538					p!(pi: str(" else ") {ite.else_().map(|t| t.expr())})539				}540				pi541			}542			Expr::ExprFunction(f) => p!(new: str("function") {f.params_desc()} str(" ") {f.expr()}),543			Expr::ExprAssert(a) => p!(new: {a.assertion()} str("; ") {a.expr()}),544			Expr::ExprError(e) => p!(new: str("error ") {e.expr()}),545			Expr::ExprLiteral(l) => {546				p!(new: {l.literal()})547			}548		}549	}550}551552impl Printable for SourceFile {553	fn print(&self) -> PrintItems {554		let mut pi = p!(new:);555		let before = trivia_before(556			self.syntax().clone(),557			self.expr()558				.map(|e| e.syntax().clone())559				.map(Into::into)560				.as_ref(),561		);562		let after = trivia_after(563			self.syntax().clone(),564			self.expr()565				.map(|e| e.syntax().clone())566				.map(Into::into)567				.as_ref(),568		);569		p!(pi: items(format_comments(&before, CommentLocation::AboveItem)));570		p!(pi: {self.expr()} nl);571		p!(pi: items(format_comments(&after, CommentLocation::EndOfItems)));572		pi573	}574}575576fn main() {577	let (parsed, _errors) = jrsonnet_rowan_parser::parse(578		r#"579580581		# Edit me!582		local b = import "b.libsonnet";  # comment583		local a = import "a.libsonnet";584585			 local f(x,y)=x+y;586587		local {a: [b, ..., c], d, ...e} = null;588589		local ass = assert false : false; false;590591		local fn = function(a, b, c = 3) 4;592593		local comp = [a for b in c if d == e];594		local ocomp = {[k]: 1 for k in v};595596		local ? = skip;597598		local ie = a[expr];599600		local unary = !a;601602		local603			//   I am comment604			singleLocalWithItemComment = 1,605		;606607		// Comment between local and expression608609		local610			a = 1, //   Inline611			// Comment above b612			b = 4,613614			// c needs some space615			c = 5,616617			// Comment after everything618		;619620621		local Template = {z: "foo"};622623		{624						local625626					h = 3,627					assert self.a == 1628629					: "error",630		"f": ((((((3)))))) ,631		"g g":632		f(4,2),633		arr: [[634		  1, 2,635		  ],636		  3,637		  {638			  b: {639				  c: {640					  k: [16]641				  }642			  }643		  }644		  ],645		  m: a[1::],646		  m: b[::],647648		  comments: {649			_: '',650			//     Plain comment651			a: '',652653			#    Plain comment with empty line before654			b: '',655			/*Single-line multiline comment656657			*/658			c: '',659660			/**Single-line multiline doc comment661662			*/663			c: '',664665			/**multiline doc comment666			s667			*/668			c: '',669670			/*671672	Multi-line673674	comment675			*/676			d: '',677678			e: '', // Inline comment679680			k: '',681682			// Text after everything683		  },684		  comments2: {685			k: '',686			// Text after everything, but no newline above687		  },688		  k: if a         == b    then689690691		  2692693		  else Template {},694695		  compspecs: {696			obj_with_no_item: {for i in [1, 2, 3]},697			obj_with_2_items: {a:1, b:2, for i in [1,2,3]},698		  }699700		} + Template701702703		// Comment after everything704"#,705	);706707	// dbg!(errors);708	dbg!(&parsed);709710	let o = dprint_core::formatting::format(711		|| parsed.print(),712		PrintOptions {713			indent_width: 2,714			max_width: 100,715			use_tabs: false,716			new_line_text: "\n",717		},718	);719	println!("{}", o);720}
modifiedcrates/jrsonnet-rowan-parser/jsonnet.ungramdiffbeforeafterboth
--- a/crates/jrsonnet-rowan-parser/jsonnet.ungram
+++ b/crates/jrsonnet-rowan-parser/jsonnet.ungram
@@ -38,15 +38,6 @@
 
 ExprLiteral =
     Literal
-ExprIntrinsicThisFile =
-    '$intrinsicThisFile'
-ExprIntrinsicId =
-    '$intrinsicId'
-ExprIntrinsic =
-    '$intrinsic'
-    '('
-    name:Name
-    ')'
 ExprString =
     Text
 ExprNumber =
@@ -110,9 +101,6 @@
 |   ExprApply
 |   ExprObjExtend
 |   ExprParened
-|   ExprIntrinsicThisFile
-|   ExprIntrinsicId
-|   ExprIntrinsic
 |   ExprString
 |   ExprNumber
 |   ExprLiteral
@@ -167,14 +155,7 @@
 
 ObjBodyComp =
     '{'
-    pre:ObjLocalPostComma*
-    '['
-    key:LhsExpr
-    ']'
-    '+'?
-    ':'
-    value:Expr
-    post:ObjLocalPreComma*
+    (MemberComp (',' MemberComp)* ','?)?
     CompSpec*
     '}'
 ObjBodyMemberList =
@@ -185,13 +166,6 @@
     ObjBodyComp
 |   ObjBodyMemberList
 
-ObjLocalPostComma =
-    ObjLocal
-    ','
-ObjLocalPreComma =
-    ','
-    ObjLocal
-
 MemberBindStmt = ObjLocal
 MemberAssertStmt = Assertion
 MemberFieldNormal =
@@ -204,6 +178,10 @@
     ParamsDesc
     Visibility
     Expr
+MemberComp =
+    MemberBindStmt
+|    MemberFieldNormal
+|   MemberFieldMethod
 Member =
     MemberBindStmt
 |   MemberAssertStmt
@@ -367,7 +345,7 @@
 |   'LIT_SINGLE_LINE_HASH_COMMENT!'
 |   'LIT_SINGLE_LINE_SLASH_COMMENT!'
 
-ParsingError =
+CustomError =
     'ERROR_MISSING_TOKEN!'
 |   'ERROR_UNEXPECTED_TOKEN!'
 |   'ERROR_CUSTOM!'
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
@@ -212,45 +212,6 @@
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub struct ExprIntrinsicThisFile {
-	pub(crate) syntax: SyntaxNode,
-}
-impl ExprIntrinsicThisFile {
-	pub fn intrinsic_this_file_token(&self) -> Option<SyntaxToken> {
-		support::token(&self.syntax, T!["$intrinsicThisFile"])
-	}
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub struct ExprIntrinsicId {
-	pub(crate) syntax: SyntaxNode,
-}
-impl ExprIntrinsicId {
-	pub fn intrinsic_id_token(&self) -> Option<SyntaxToken> {
-		support::token(&self.syntax, T!["$intrinsicId"])
-	}
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub struct ExprIntrinsic {
-	pub(crate) syntax: SyntaxNode,
-}
-impl ExprIntrinsic {
-	pub fn intrinsic_token(&self) -> Option<SyntaxToken> {
-		support::token(&self.syntax, T!["$intrinsic"])
-	}
-	pub fn l_paren_token(&self) -> Option<SyntaxToken> {
-		support::token(&self.syntax, T!['('])
-	}
-	pub fn name(&self) -> Option<Name> {
-		support::child(&self.syntax)
-	}
-	pub fn r_paren_token(&self) -> Option<SyntaxToken> {
-		support::token(&self.syntax, T![')'])
-	}
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct ExprString {
 	pub(crate) syntax: SyntaxNode,
 }
@@ -535,28 +496,7 @@
 	pub fn l_brace_token(&self) -> Option<SyntaxToken> {
 		support::token(&self.syntax, T!['{'])
 	}
-	pub fn pre(&self) -> AstChildren<ObjLocalPostComma> {
-		support::children(&self.syntax)
-	}
-	pub fn l_brack_token(&self) -> Option<SyntaxToken> {
-		support::token(&self.syntax, T!['['])
-	}
-	pub fn key(&self) -> Option<LhsExpr> {
-		support::child(&self.syntax)
-	}
-	pub fn r_brack_token(&self) -> Option<SyntaxToken> {
-		support::token(&self.syntax, T![']'])
-	}
-	pub fn plus_token(&self) -> Option<SyntaxToken> {
-		support::token(&self.syntax, T![+])
-	}
-	pub fn colon_token(&self) -> Option<SyntaxToken> {
-		support::token(&self.syntax, T![:])
-	}
-	pub fn value(&self) -> Option<Expr> {
-		support::child(&self.syntax)
-	}
-	pub fn post(&self) -> AstChildren<ObjLocalPreComma> {
+	pub fn member_comps(&self) -> AstChildren<MemberComp> {
 		support::children(&self.syntax)
 	}
 	pub fn comp_specs(&self) -> AstChildren<CompSpec> {
@@ -568,32 +508,6 @@
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub struct ObjLocalPostComma {
-	pub(crate) syntax: SyntaxNode,
-}
-impl ObjLocalPostComma {
-	pub fn obj_local(&self) -> Option<ObjLocal> {
-		support::child(&self.syntax)
-	}
-	pub fn comma_token(&self) -> Option<SyntaxToken> {
-		support::token(&self.syntax, T![,])
-	}
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub struct ObjLocalPreComma {
-	pub(crate) syntax: SyntaxNode,
-}
-impl ObjLocalPreComma {
-	pub fn comma_token(&self) -> Option<SyntaxToken> {
-		support::token(&self.syntax, T![,])
-	}
-	pub fn obj_local(&self) -> Option<ObjLocal> {
-		support::child(&self.syntax)
-	}
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct ObjBodyMemberList {
 	pub(crate) syntax: SyntaxNode,
 }
@@ -610,24 +524,24 @@
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub struct ObjLocal {
+pub struct MemberBindStmt {
 	pub(crate) syntax: SyntaxNode,
 }
-impl ObjLocal {
-	pub fn local_kw_token(&self) -> Option<SyntaxToken> {
-		support::token(&self.syntax, T![local])
-	}
-	pub fn bind(&self) -> Option<Bind> {
+impl MemberBindStmt {
+	pub fn obj_local(&self) -> Option<ObjLocal> {
 		support::child(&self.syntax)
 	}
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub struct MemberBindStmt {
+pub struct ObjLocal {
 	pub(crate) syntax: SyntaxNode,
 }
-impl MemberBindStmt {
-	pub fn obj_local(&self) -> Option<ObjLocal> {
+impl ObjLocal {
+	pub fn local_kw_token(&self) -> Option<SyntaxToken> {
+		support::token(&self.syntax, T![local])
+	}
+	pub fn bind(&self) -> Option<Bind> {
 		support::child(&self.syntax)
 	}
 }
@@ -905,9 +819,6 @@
 	ExprApply(ExprApply),
 	ExprObjExtend(ExprObjExtend),
 	ExprParened(ExprParened),
-	ExprIntrinsicThisFile(ExprIntrinsicThisFile),
-	ExprIntrinsicId(ExprIntrinsicId),
-	ExprIntrinsic(ExprIntrinsic),
 	ExprString(ExprString),
 	ExprNumber(ExprNumber),
 	ExprLiteral(ExprLiteral),
@@ -942,6 +853,13 @@
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub enum MemberComp {
+	MemberBindStmt(MemberBindStmt),
+	MemberFieldNormal(MemberFieldNormal),
+	MemberFieldMethod(MemberFieldMethod),
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub enum Member {
 	MemberBindStmt(MemberBindStmt),
 	MemberAssertStmt(MemberAssertStmt),
@@ -1110,13 +1028,13 @@
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub struct ParsingError {
+pub struct CustomError {
 	syntax: SyntaxToken,
-	kind: ParsingErrorKind,
+	kind: CustomErrorKind,
 }
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-pub enum ParsingErrorKind {
+pub enum CustomErrorKind {
 	ErrorMissingToken,
 	ErrorUnexpectedToken,
 	ErrorCustom,
@@ -1319,36 +1237,6 @@
 impl AstNode for ExprLiteral {
 	fn can_cast(kind: SyntaxKind) -> bool {
 		kind == EXPR_LITERAL
-	}
-	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 ExprIntrinsicThisFile {
-	fn can_cast(kind: SyntaxKind) -> bool {
-		kind == EXPR_INTRINSIC_THIS_FILE
-	}
-	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 ExprIntrinsicId {
-	fn can_cast(kind: SyntaxKind) -> bool {
-		kind == EXPR_INTRINSIC_ID
 	}
 	fn cast(syntax: SyntaxNode) -> Option<Self> {
 		if Self::can_cast(syntax.kind()) {
@@ -1361,21 +1249,6 @@
 		&self.syntax
 	}
 }
-impl AstNode for ExprIntrinsic {
-	fn can_cast(kind: SyntaxKind) -> bool {
-		kind == EXPR_INTRINSIC
-	}
-	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 ExprString {
 	fn can_cast(kind: SyntaxKind) -> bool {
 		kind == EXPR_STRING
@@ -1676,36 +1549,6 @@
 		&self.syntax
 	}
 }
-impl AstNode for ObjLocalPostComma {
-	fn can_cast(kind: SyntaxKind) -> bool {
-		kind == OBJ_LOCAL_POST_COMMA
-	}
-	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 ObjLocalPreComma {
-	fn can_cast(kind: SyntaxKind) -> bool {
-		kind == OBJ_LOCAL_PRE_COMMA
-	}
-	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 ObjBodyMemberList {
 	fn can_cast(kind: SyntaxKind) -> bool {
 		kind == OBJ_BODY_MEMBER_LIST
@@ -1721,9 +1564,9 @@
 		&self.syntax
 	}
 }
-impl AstNode for ObjLocal {
+impl AstNode for MemberBindStmt {
 	fn can_cast(kind: SyntaxKind) -> bool {
-		kind == OBJ_LOCAL
+		kind == MEMBER_BIND_STMT
 	}
 	fn cast(syntax: SyntaxNode) -> Option<Self> {
 		if Self::can_cast(syntax.kind()) {
@@ -1736,9 +1579,9 @@
 		&self.syntax
 	}
 }
-impl AstNode for MemberBindStmt {
+impl AstNode for ObjLocal {
 	fn can_cast(kind: SyntaxKind) -> bool {
-		kind == MEMBER_BIND_STMT
+		kind == OBJ_LOCAL
 	}
 	fn cast(syntax: SyntaxNode) -> Option<Self> {
 		if Self::can_cast(syntax.kind()) {
@@ -2044,21 +1887,6 @@
 impl From<ExprParened> for Expr {
 	fn from(node: ExprParened) -> Expr {
 		Expr::ExprParened(node)
-	}
-}
-impl From<ExprIntrinsicThisFile> for Expr {
-	fn from(node: ExprIntrinsicThisFile) -> Expr {
-		Expr::ExprIntrinsicThisFile(node)
-	}
-}
-impl From<ExprIntrinsicId> for Expr {
-	fn from(node: ExprIntrinsicId) -> Expr {
-		Expr::ExprIntrinsicId(node)
-	}
-}
-impl From<ExprIntrinsic> for Expr {
-	fn from(node: ExprIntrinsic) -> Expr {
-		Expr::ExprIntrinsic(node)
 	}
 }
 impl From<ExprString> for Expr {
@@ -2129,30 +1957,10 @@
 impl AstNode for Expr {
 	fn can_cast(kind: SyntaxKind) -> bool {
 		match kind {
-			EXPR_BINARY
-			| EXPR_UNARY
-			| EXPR_SLICE
-			| EXPR_INDEX
-			| EXPR_INDEX_EXPR
-			| EXPR_APPLY
-			| EXPR_OBJ_EXTEND
-			| EXPR_PARENED
-			| EXPR_INTRINSIC_THIS_FILE
-			| EXPR_INTRINSIC_ID
-			| EXPR_INTRINSIC
-			| EXPR_STRING
-			| EXPR_NUMBER
-			| EXPR_LITERAL
-			| EXPR_ARRAY
-			| EXPR_OBJECT
-			| EXPR_ARRAY_COMP
-			| EXPR_IMPORT
-			| EXPR_VAR
-			| EXPR_LOCAL
-			| EXPR_IF_THEN_ELSE
-			| EXPR_FUNCTION
-			| EXPR_ASSERT
-			| EXPR_ERROR => true,
+			EXPR_BINARY | EXPR_UNARY | EXPR_SLICE | EXPR_INDEX | EXPR_INDEX_EXPR | EXPR_APPLY
+			| EXPR_OBJ_EXTEND | EXPR_PARENED | EXPR_STRING | EXPR_NUMBER | EXPR_LITERAL
+			| EXPR_ARRAY | EXPR_OBJECT | EXPR_ARRAY_COMP | EXPR_IMPORT | EXPR_VAR | EXPR_LOCAL
+			| EXPR_IF_THEN_ELSE | EXPR_FUNCTION | EXPR_ASSERT | EXPR_ERROR => true,
 			_ => false,
 		}
 	}
@@ -2166,11 +1974,6 @@
 			EXPR_APPLY => Expr::ExprApply(ExprApply { syntax }),
 			EXPR_OBJ_EXTEND => Expr::ExprObjExtend(ExprObjExtend { syntax }),
 			EXPR_PARENED => Expr::ExprParened(ExprParened { syntax }),
-			EXPR_INTRINSIC_THIS_FILE => {
-				Expr::ExprIntrinsicThisFile(ExprIntrinsicThisFile { syntax })
-			}
-			EXPR_INTRINSIC_ID => Expr::ExprIntrinsicId(ExprIntrinsicId { syntax }),
-			EXPR_INTRINSIC => Expr::ExprIntrinsic(ExprIntrinsic { syntax }),
 			EXPR_STRING => Expr::ExprString(ExprString { syntax }),
 			EXPR_NUMBER => Expr::ExprNumber(ExprNumber { syntax }),
 			EXPR_LITERAL => Expr::ExprLiteral(ExprLiteral { syntax }),
@@ -2198,9 +2001,6 @@
 			Expr::ExprApply(it) => &it.syntax,
 			Expr::ExprObjExtend(it) => &it.syntax,
 			Expr::ExprParened(it) => &it.syntax,
-			Expr::ExprIntrinsicThisFile(it) => &it.syntax,
-			Expr::ExprIntrinsicId(it) => &it.syntax,
-			Expr::ExprIntrinsic(it) => &it.syntax,
 			Expr::ExprString(it) => &it.syntax,
 			Expr::ExprNumber(it) => &it.syntax,
 			Expr::ExprLiteral(it) => &it.syntax,
@@ -2313,6 +2113,45 @@
 		}
 	}
 }
+impl From<MemberBindStmt> for MemberComp {
+	fn from(node: MemberBindStmt) -> MemberComp {
+		MemberComp::MemberBindStmt(node)
+	}
+}
+impl From<MemberFieldNormal> for MemberComp {
+	fn from(node: MemberFieldNormal) -> MemberComp {
+		MemberComp::MemberFieldNormal(node)
+	}
+}
+impl From<MemberFieldMethod> for MemberComp {
+	fn from(node: MemberFieldMethod) -> MemberComp {
+		MemberComp::MemberFieldMethod(node)
+	}
+}
+impl AstNode for MemberComp {
+	fn can_cast(kind: SyntaxKind) -> bool {
+		match kind {
+			MEMBER_BIND_STMT | MEMBER_FIELD_NORMAL | MEMBER_FIELD_METHOD => true,
+			_ => false,
+		}
+	}
+	fn cast(syntax: SyntaxNode) -> Option<Self> {
+		let res = match syntax.kind() {
+			MEMBER_BIND_STMT => MemberComp::MemberBindStmt(MemberBindStmt { syntax }),
+			MEMBER_FIELD_NORMAL => MemberComp::MemberFieldNormal(MemberFieldNormal { syntax }),
+			MEMBER_FIELD_METHOD => MemberComp::MemberFieldMethod(MemberFieldMethod { syntax }),
+			_ => return None,
+		};
+		Some(res)
+	}
+	fn syntax(&self) -> &SyntaxNode {
+		match self {
+			MemberComp::MemberBindStmt(it) => &it.syntax,
+			MemberComp::MemberFieldNormal(it) => &it.syntax,
+			MemberComp::MemberFieldMethod(it) => &it.syntax,
+		}
+	}
+}
 impl From<MemberBindStmt> for Member {
 	fn from(node: MemberBindStmt) -> Member {
 		Member::MemberBindStmt(node)
@@ -2847,19 +2686,19 @@
 		std::fmt::Display::fmt(self.syntax(), f)
 	}
 }
-impl AstToken for ParsingError {
+impl AstToken for CustomError {
 	fn can_cast(kind: SyntaxKind) -> bool {
-		ParsingErrorKind::can_cast(kind)
+		CustomErrorKind::can_cast(kind)
 	}
 	fn cast(syntax: SyntaxToken) -> Option<Self> {
-		let kind = ParsingErrorKind::cast(syntax.kind())?;
-		Some(ParsingError { syntax, kind })
+		let kind = CustomErrorKind::cast(syntax.kind())?;
+		Some(CustomError { syntax, kind })
 	}
 	fn syntax(&self) -> &SyntaxToken {
 		&self.syntax
 	}
 }
-impl ParsingErrorKind {
+impl CustomErrorKind {
 	fn can_cast(kind: SyntaxKind) -> bool {
 		match kind {
 			ERROR_MISSING_TOKEN | ERROR_UNEXPECTED_TOKEN | ERROR_CUSTOM => true,
@@ -2876,12 +2715,12 @@
 		Some(res)
 	}
 }
-impl ParsingError {
-	pub fn kind(&self) -> ParsingErrorKind {
+impl CustomError {
+	pub fn kind(&self) -> CustomErrorKind {
 		self.kind
 	}
 }
-impl std::fmt::Display for ParsingError {
+impl std::fmt::Display for CustomError {
 	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 		std::fmt::Display::fmt(self.syntax(), f)
 	}
@@ -2906,6 +2745,11 @@
 		std::fmt::Display::fmt(self.syntax(), f)
 	}
 }
+impl std::fmt::Display for MemberComp {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		std::fmt::Display::fmt(self.syntax(), f)
+	}
+}
 impl std::fmt::Display for Member {
 	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 		std::fmt::Display::fmt(self.syntax(), f)
@@ -2996,21 +2840,6 @@
 		std::fmt::Display::fmt(self.syntax(), f)
 	}
 }
-impl std::fmt::Display for ExprIntrinsicThisFile {
-	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-		std::fmt::Display::fmt(self.syntax(), f)
-	}
-}
-impl std::fmt::Display for ExprIntrinsicId {
-	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-		std::fmt::Display::fmt(self.syntax(), f)
-	}
-}
-impl std::fmt::Display for ExprIntrinsic {
-	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-		std::fmt::Display::fmt(self.syntax(), f)
-	}
-}
 impl std::fmt::Display for ExprString {
 	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 		std::fmt::Display::fmt(self.syntax(), f)
@@ -3111,27 +2940,17 @@
 		std::fmt::Display::fmt(self.syntax(), f)
 	}
 }
-impl std::fmt::Display for ObjLocalPostComma {
-	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-		std::fmt::Display::fmt(self.syntax(), f)
-	}
-}
-impl std::fmt::Display for ObjLocalPreComma {
+impl std::fmt::Display for ObjBodyMemberList {
 	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 		std::fmt::Display::fmt(self.syntax(), f)
 	}
 }
-impl std::fmt::Display for ObjBodyMemberList {
+impl std::fmt::Display for MemberBindStmt {
 	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 		std::fmt::Display::fmt(self.syntax(), f)
 	}
 }
 impl std::fmt::Display for ObjLocal {
-	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-		std::fmt::Display::fmt(self.syntax(), f)
-	}
-}
-impl std::fmt::Display for MemberBindStmt {
 	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
@@ -89,12 +89,6 @@
 	ASSIGN,
 	#[token("?")]
 	QUESTION_MARK,
-	#[token("$intrinsicThisFile")]
-	INTRINSIC_THIS_FILE,
-	#[token("$intrinsicId")]
-	INTRINSIC_ID,
-	#[token("$intrinsic")]
-	INTRINSIC,
 	#[regex("(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?(?:[eE][+-]?[0-9]+)?")]
 	FLOAT,
 	#[regex("(?:0|[1-9][0-9]*)\\.[^0-9]")]
@@ -199,9 +193,6 @@
 	EXPR_OBJ_EXTEND,
 	EXPR_PARENED,
 	EXPR_LITERAL,
-	EXPR_INTRINSIC_THIS_FILE,
-	EXPR_INTRINSIC_ID,
-	EXPR_INTRINSIC,
 	EXPR_STRING,
 	EXPR_NUMBER,
 	EXPR_ARRAY,
@@ -222,11 +213,9 @@
 	SLICE_DESC_STEP,
 	ARG,
 	OBJ_BODY_COMP,
-	OBJ_LOCAL_POST_COMMA,
-	OBJ_LOCAL_PRE_COMMA,
 	OBJ_BODY_MEMBER_LIST,
-	OBJ_LOCAL,
 	MEMBER_BIND_STMT,
+	OBJ_LOCAL,
 	MEMBER_ASSERT_STMT,
 	MEMBER_FIELD_NORMAL,
 	MEMBER_FIELD_METHOD,
@@ -248,6 +237,7 @@
 	OBJ_BODY,
 	COMP_SPEC,
 	BIND,
+	MEMBER_COMP,
 	MEMBER,
 	FIELD_NAME,
 	DESTRUCT,
@@ -260,7 +250,7 @@
 	IMPORT_KIND,
 	VISIBILITY,
 	TRIVIA,
-	PARSING_ERROR,
+	CUSTOM_ERROR,
 	#[doc(hidden)]
 	__LAST,
 }
@@ -271,18 +261,18 @@
 			OR | AND | BIT_OR | BIT_XOR | BIT_AND | EQ | NE | LT | GT | LE | GE | LHS | RHS
 			| PLUS | MINUS | MUL | DIV | MODULO | NOT | BIT_NOT | L_BRACK | R_BRACK | L_PAREN
 			| R_PAREN | L_BRACE | R_BRACE | COLON | COLONCOLON | COLONCOLONCOLON | SEMI | DOT
-			| DOTDOTDOT | COMMA | DOLLAR | ASSIGN | QUESTION_MARK | INTRINSIC_THIS_FILE
-			| INTRINSIC_ID | INTRINSIC | TAILSTRICT_KW | IMPORTSTR_KW | IMPORTBIN_KW
-			| IMPORT_KW | LOCAL_KW | IF_KW | THEN_KW | ELSE_KW | FUNCTION_KW | ERROR_KW | IN_KW
-			| NULL_KW | TRUE_KW | FALSE_KW | SELF_KW | SUPER_KW | FOR_KW | ASSERT_KW => true,
+			| DOTDOTDOT | COMMA | DOLLAR | ASSIGN | QUESTION_MARK | TAILSTRICT_KW
+			| IMPORTSTR_KW | IMPORTBIN_KW | IMPORT_KW | LOCAL_KW | IF_KW | THEN_KW | ELSE_KW
+			| FUNCTION_KW | ERROR_KW | IN_KW | NULL_KW | TRUE_KW | FALSE_KW | SELF_KW
+			| SUPER_KW | FOR_KW | ASSERT_KW => true,
 			_ => false,
 		}
 	}
 	pub fn is_enum(self) -> bool {
 		match self {
-			EXPR | OBJ_BODY | COMP_SPEC | BIND | MEMBER | FIELD_NAME | DESTRUCT
+			EXPR | OBJ_BODY | COMP_SPEC | BIND | MEMBER_COMP | MEMBER | FIELD_NAME | DESTRUCT
 			| DESTRUCT_ARRAY_PART | BINARY_OPERATOR | UNARY_OPERATOR | LITERAL | TEXT | NUMBER
-			| IMPORT_KIND | VISIBILITY | TRIVIA | PARSING_ERROR => true,
+			| IMPORT_KIND | VISIBILITY | TRIVIA | CUSTOM_ERROR => true,
 			_ => false,
 		}
 	}
@@ -295,5 +285,5 @@
 	}
 }
 #[macro_export]
-macro_rules ! T { [||] => { $ crate :: SyntaxKind :: OR } ; [&&] => { $ crate :: SyntaxKind :: AND } ; [|] => { $ crate :: SyntaxKind :: BIT_OR } ; [^] => { $ crate :: SyntaxKind :: BIT_XOR } ; [&] => { $ crate :: SyntaxKind :: BIT_AND } ; [==] => { $ crate :: SyntaxKind :: EQ } ; [!=] => { $ crate :: SyntaxKind :: NE } ; [<] => { $ crate :: SyntaxKind :: LT } ; [>] => { $ crate :: SyntaxKind :: GT } ; [<=] => { $ crate :: SyntaxKind :: LE } ; [>=] => { $ crate :: SyntaxKind :: GE } ; [<<] => { $ crate :: SyntaxKind :: LHS } ; [>>] => { $ crate :: SyntaxKind :: RHS } ; [+] => { $ crate :: SyntaxKind :: PLUS } ; [-] => { $ crate :: SyntaxKind :: MINUS } ; [*] => { $ crate :: SyntaxKind :: MUL } ; [/] => { $ crate :: SyntaxKind :: DIV } ; [%] => { $ crate :: SyntaxKind :: MODULO } ; [!] => { $ crate :: SyntaxKind :: NOT } ; [~] => { $ crate :: SyntaxKind :: BIT_NOT } ; ['['] => { $ crate :: SyntaxKind :: L_BRACK } ; [']'] => { $ crate :: SyntaxKind :: R_BRACK } ; ['('] => { $ crate :: SyntaxKind :: L_PAREN } ; [')'] => { $ crate :: SyntaxKind :: R_PAREN } ; ['{'] => { $ crate :: SyntaxKind :: L_BRACE } ; ['}'] => { $ crate :: SyntaxKind :: R_BRACE } ; [:] => { $ crate :: SyntaxKind :: COLON } ; [::] => { $ crate :: SyntaxKind :: COLONCOLON } ; [:::] => { $ crate :: SyntaxKind :: COLONCOLONCOLON } ; [;] => { $ crate :: SyntaxKind :: SEMI } ; [.] => { $ crate :: SyntaxKind :: DOT } ; [...] => { $ crate :: SyntaxKind :: DOTDOTDOT } ; [,] => { $ crate :: SyntaxKind :: COMMA } ; ['$'] => { $ crate :: SyntaxKind :: DOLLAR } ; [=] => { $ crate :: SyntaxKind :: ASSIGN } ; [?] => { $ crate :: SyntaxKind :: QUESTION_MARK } ; ["$intrinsicThisFile"] => { $ crate :: SyntaxKind :: INTRINSIC_THIS_FILE } ; ["$intrinsicId"] => { $ crate :: SyntaxKind :: INTRINSIC_ID } ; ["$intrinsic"] => { $ crate :: SyntaxKind :: INTRINSIC } ; [tailstrict] => { $ crate :: SyntaxKind :: TAILSTRICT_KW } ; [importstr] => { $ crate :: SyntaxKind :: IMPORTSTR_KW } ; [importbin] => { $ crate :: SyntaxKind :: IMPORTBIN_KW } ; [import] => { $ crate :: SyntaxKind :: IMPORT_KW } ; [local] => { $ crate :: SyntaxKind :: LOCAL_KW } ; [if] => { $ crate :: SyntaxKind :: IF_KW } ; [then] => { $ crate :: SyntaxKind :: THEN_KW } ; [else] => { $ crate :: SyntaxKind :: ELSE_KW } ; [function] => { $ crate :: SyntaxKind :: FUNCTION_KW } ; [error] => { $ crate :: SyntaxKind :: ERROR_KW } ; [in] => { $ crate :: SyntaxKind :: IN_KW } ; [null] => { $ crate :: SyntaxKind :: NULL_KW } ; [true] => { $ crate :: SyntaxKind :: TRUE_KW } ; [false] => { $ crate :: SyntaxKind :: FALSE_KW } ; [self] => { $ crate :: SyntaxKind :: SELF_KW } ; [super] => { $ crate :: SyntaxKind :: SUPER_KW } ; [for] => { $ crate :: SyntaxKind :: FOR_KW } ; [assert] => { $ crate :: SyntaxKind :: ASSERT_KW } }
+macro_rules ! T { [||] => { $ crate :: SyntaxKind :: OR } ; [&&] => { $ crate :: SyntaxKind :: AND } ; [|] => { $ crate :: SyntaxKind :: BIT_OR } ; [^] => { $ crate :: SyntaxKind :: BIT_XOR } ; [&] => { $ crate :: SyntaxKind :: BIT_AND } ; [==] => { $ crate :: SyntaxKind :: EQ } ; [!=] => { $ crate :: SyntaxKind :: NE } ; [<] => { $ crate :: SyntaxKind :: LT } ; [>] => { $ crate :: SyntaxKind :: GT } ; [<=] => { $ crate :: SyntaxKind :: LE } ; [>=] => { $ crate :: SyntaxKind :: GE } ; [<<] => { $ crate :: SyntaxKind :: LHS } ; [>>] => { $ crate :: SyntaxKind :: RHS } ; [+] => { $ crate :: SyntaxKind :: PLUS } ; [-] => { $ crate :: SyntaxKind :: MINUS } ; [*] => { $ crate :: SyntaxKind :: MUL } ; [/] => { $ crate :: SyntaxKind :: DIV } ; [%] => { $ crate :: SyntaxKind :: MODULO } ; [!] => { $ crate :: SyntaxKind :: NOT } ; [~] => { $ crate :: SyntaxKind :: BIT_NOT } ; ['['] => { $ crate :: SyntaxKind :: L_BRACK } ; [']'] => { $ crate :: SyntaxKind :: R_BRACK } ; ['('] => { $ crate :: SyntaxKind :: L_PAREN } ; [')'] => { $ crate :: SyntaxKind :: R_PAREN } ; ['{'] => { $ crate :: SyntaxKind :: L_BRACE } ; ['}'] => { $ crate :: SyntaxKind :: R_BRACE } ; [:] => { $ crate :: SyntaxKind :: COLON } ; [::] => { $ crate :: SyntaxKind :: COLONCOLON } ; [:::] => { $ crate :: SyntaxKind :: COLONCOLONCOLON } ; [;] => { $ crate :: SyntaxKind :: SEMI } ; [.] => { $ crate :: SyntaxKind :: DOT } ; [...] => { $ crate :: SyntaxKind :: DOTDOTDOT } ; [,] => { $ crate :: SyntaxKind :: COMMA } ; ['$'] => { $ crate :: SyntaxKind :: DOLLAR } ; [=] => { $ crate :: SyntaxKind :: ASSIGN } ; [?] => { $ crate :: SyntaxKind :: QUESTION_MARK } ; [tailstrict] => { $ crate :: SyntaxKind :: TAILSTRICT_KW } ; [importstr] => { $ crate :: SyntaxKind :: IMPORTSTR_KW } ; [importbin] => { $ crate :: SyntaxKind :: IMPORTBIN_KW } ; [import] => { $ crate :: SyntaxKind :: IMPORT_KW } ; [local] => { $ crate :: SyntaxKind :: LOCAL_KW } ; [if] => { $ crate :: SyntaxKind :: IF_KW } ; [then] => { $ crate :: SyntaxKind :: THEN_KW } ; [else] => { $ crate :: SyntaxKind :: ELSE_KW } ; [function] => { $ crate :: SyntaxKind :: FUNCTION_KW } ; [error] => { $ crate :: SyntaxKind :: ERROR_KW } ; [in] => { $ crate :: SyntaxKind :: IN_KW } ; [null] => { $ crate :: SyntaxKind :: NULL_KW } ; [true] => { $ crate :: SyntaxKind :: TRUE_KW } ; [false] => { $ crate :: SyntaxKind :: FALSE_KW } ; [self] => { $ crate :: SyntaxKind :: SELF_KW } ; [super] => { $ crate :: SyntaxKind :: SUPER_KW } ; [for] => { $ crate :: SyntaxKind :: FOR_KW } ; [assert] => { $ crate :: SyntaxKind :: ASSERT_KW } }
 pub use T;
modifiedcrates/jrsonnet-rowan-parser/src/parser.rsdiffbeforeafterboth
--- a/crates/jrsonnet-rowan-parser/src/parser.rs
+++ b/crates/jrsonnet-rowan-parser/src/parser.rs
@@ -114,7 +114,13 @@
 	pub fn parse(mut self) -> Vec<Event> {
 		let m = self.start();
 		expr(&mut self);
-		self.expect(EOF);
+		if !self.at(EOF) {
+			let m = self.start();
+			while !self.at(EOF) {
+				self.bump();
+			}
+			m.complete_error(&mut self, "unexpected tokens after end");
+		}
 		m.complete(&mut self, SOURCE_FILE);
 
 		self.events
@@ -832,21 +838,6 @@
 		let m = p.start();
 		name(p);
 		m.complete(p, EXPR_VAR)
-	} else if p.at(INTRINSIC_THIS_FILE) {
-		let m = p.start();
-		p.bump();
-		m.complete(p, EXPR_INTRINSIC_THIS_FILE)
-	} else if p.at(INTRINSIC_ID) {
-		let m = p.start();
-		p.bump();
-		m.complete(p, EXPR_INTRINSIC_ID)
-	} else if p.at(INTRINSIC) {
-		let m = p.start();
-		p.bump();
-		p.expect(T!['(']);
-		name(p);
-		p.expect(T![')']);
-		m.complete(p, EXPR_INTRINSIC)
 	} else if p.at(T![if]) {
 		let m = p.start();
 		p.bump();
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__continue_after_total_failure.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__continue_after_total_failure.snap
@@ -0,0 +1,74 @@
+---
+source: crates/jrsonnet-rowan-parser/src/tests.rs
+expression: "local intr = $intrinsic(test);\n\nlocal a = 1, b = 2, c = a + b;\n\n[c]\n"
+---
+SOURCE_FILE@0..68
+  EXPR_LOCAL@0..29
+    LOCAL_KW@0..5 "local"
+    WHITESPACE@5..6 " "
+    BIND_DESTRUCT@6..14
+      DESTRUCT_FULL@6..10
+        NAME@6..10
+          IDENT@6..10 "intr"
+      WHITESPACE@10..11 " "
+      ASSIGN@11..12 "="
+      WHITESPACE@12..13 " "
+      EXPR_LITERAL@13..14
+        DOLLAR@13..14 "$"
+    ERROR_UNEXPECTED_TOKEN@14..23
+      IDENT@14..23 "intrinsic"
+    EXPR_PARENED@23..29
+      L_PAREN@23..24 "("
+      EXPR_VAR@24..28
+        NAME@24..28
+          IDENT@24..28 "test"
+      R_PAREN@28..29 ")"
+  ERROR_CUSTOM@29..67
+    SEMI@29..30 ";"
+    WHITESPACE@30..32 "\n\n"
+    LOCAL_KW@32..37 "local"
+    WHITESPACE@37..38 " "
+    IDENT@38..39 "a"
+    WHITESPACE@39..40 " "
+    ASSIGN@40..41 "="
+    WHITESPACE@41..42 " "
+    FLOAT@42..43 "1"
+    COMMA@43..44 ","
+    WHITESPACE@44..45 " "
+    IDENT@45..46 "b"
+    WHITESPACE@46..47 " "
+    ASSIGN@47..48 "="
+    WHITESPACE@48..49 " "
+    FLOAT@49..50 "2"
+    COMMA@50..51 ","
+    WHITESPACE@51..52 " "
+    IDENT@52..53 "c"
+    WHITESPACE@53..54 " "
+    ASSIGN@54..55 "="
+    WHITESPACE@55..56 " "
+    IDENT@56..57 "a"
+    WHITESPACE@57..58 " "
+    PLUS@58..59 "+"
+    WHITESPACE@59..60 " "
+    IDENT@60..61 "b"
+    SEMI@61..62 ";"
+    WHITESPACE@62..64 "\n\n"
+    L_BRACK@64..65 "["
+    IDENT@65..66 "c"
+    R_BRACK@66..67 "]"
+  WHITESPACE@67..68 "\n"
+===
+LocatedSyntaxError { error: Unexpected { expected: Unnamed(SyntaxKindSet([L_BRACK, L_PAREN, L_BRACE, SEMI, DOT, COMMA])), found: IDENT }, range: 14..23 }
+LocatedSyntaxError { error: Custom { error: "unexpected tokens after end" }, range: 29..67 }
+===
+  x syntax error
+   ,-[1:1]
+ 1 | ,-> local intr = $intrinsic(test);
+   : ||              ^^^^|^^^^
+   : ||                  `-- expected L_BRACK, L_PAREN, L_BRACE, SEMI, DOT or COMMA, found IDENT
+ 2 | |
+ 3 | |   local a = 1, b = 2, c = a + b;
+ 4 | |
+ 5 | |-> [c]
+   : `---- unexpected tokens after end
+   `----
modifiedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__no_lhs.snapdiffbeforeafterboth
--- a/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__no_lhs.snap
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__no_lhs.snap
@@ -2,19 +2,21 @@
 source: crates/jrsonnet-rowan-parser/src/tests.rs
 expression: "+ 2\n"
 ---
-SOURCE_FILE@0..2
+SOURCE_FILE@0..4
   ERROR_MISSING_TOKEN@0..0
-  ERROR_UNEXPECTED_TOKEN@0..1
+  ERROR_CUSTOM@0..3
     PLUS@0..1 "+"
-  WHITESPACE@1..2 " "
+    WHITESPACE@1..2 " "
+    FLOAT@2..3 "2"
+  WHITESPACE@3..4 "\n"
 ===
 LocatedSyntaxError { error: Missing { expected: Named("expression") }, range: 0..0 }
-LocatedSyntaxError { error: Unexpected { expected: Unnamed(SyntaxKindSet([EOF])), found: PLUS }, range: 0..1 }
+LocatedSyntaxError { error: Custom { error: "unexpected tokens after end" }, range: 0..3 }
 ===
   x syntax error
    ,----
  1 | + 2
-   : ^|
-   : |`-- expected EOF, found PLUS
+   : ^^|
+   : | `-- unexpected tokens after end
    : `-- missing expression
    `----
modifiedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__no_operator.snapdiffbeforeafterboth
--- a/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__no_operator.snap
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__no_operator.snap
@@ -6,15 +6,15 @@
   EXPR_NUMBER@0..1
     FLOAT@0..1 "2"
   WHITESPACE@1..2 " "
-  ERROR_UNEXPECTED_TOKEN@2..3
+  ERROR_CUSTOM@2..3
     FLOAT@2..3 "2"
   WHITESPACE@3..4 "\n"
 ===
-LocatedSyntaxError { error: Unexpected { expected: Unnamed(SyntaxKindSet([EOF, L_BRACK, L_PAREN, L_BRACE, DOT])), found: FLOAT }, range: 2..3 }
+LocatedSyntaxError { error: Custom { error: "unexpected tokens after end" }, range: 2..3 }
 ===
   x syntax error
    ,----
  1 | 2 2
    :   |
-   :   `-- expected EOF, L_BRACK, L_PAREN, L_BRACE or DOT, found FLOAT
+   :   `-- unexpected tokens after end
    `----
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,14 @@
 			a: function(x) x,
 		}
 	"#
+
+	continue_after_total_failure => r#"
+		local intr = $intrinsic(test);
+
+		local a = 1, b = 2, c = a + b;
+
+		[c]
+	"#
 );
 
 #[test]
modifiedcrates/jrsonnet-rowan-parser/src/token_set.rsdiffbeforeafterboth
--- a/crates/jrsonnet-rowan-parser/src/token_set.rs
+++ b/crates/jrsonnet-rowan-parser/src/token_set.rs
@@ -27,7 +27,10 @@
 		SyntaxKindSet(self.0 | mask(kind))
 	}
 
-	pub const fn contains(&self, kind: SyntaxKind) -> bool {
+	pub fn contains(&self, kind: SyntaxKind) -> bool {
+		if !is_token(kind) {
+			return false;
+		}
 		self.0 & mask(kind) != 0
 	}
 }
@@ -74,6 +77,9 @@
 }
 
 const fn mask(kind: SyntaxKind) -> u128 {
+	if kind as u32 > 128 {
+		panic!("mask for not a token kind")
+	}
 	1u128 << (kind as u128)
 }
 
@@ -95,3 +101,6 @@
 		"can't keep KindSet as bitset"
 	);
 }
+fn is_token(kind: SyntaxKind) -> bool {
+	(kind as u32) < 127
+}
modifiedxtask/src/sourcegen/kinds.rsdiffbeforeafterboth
--- a/xtask/src/sourcegen/kinds.rs
+++ b/xtask/src/sourcegen/kinds.rs
@@ -247,9 +247,6 @@
 		"$" => "DOLLAR";
 		"=" => "ASSIGN";
 		"?" => "QUESTION_MARK";
-		"$intrinsicThisFile" => "INTRINSIC_THIS_FILE";
-		"$intrinsicId" => "INTRINSIC_ID";
-		"$intrinsic" => "INTRINSIC";
 		// Literals
 		lit("FLOAT") => r"(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?";
 		error("FLOAT_JUNK_AFTER_POINT") => r"(?:0|[1-9][0-9]*)\.[^0-9]";