git.delta.rocks / jrsonnet / refs/commits / 93f08ca017e4

difftreelog

refactor split lexer from rowan parser

tzxnlqzsYaroslav Bolyukin2026-03-22parent: #c6d0d24.patch.diff
in: master

15 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -716,6 +716,13 @@
 ]
 
 [[package]]
+name = "jrsonnet-lexer"
+version = "0.5.0-pre97"
+dependencies = [
+ "logos",
+]
+
+[[package]]
 name = "jrsonnet-macros"
 version = "0.5.0-pre97"
 dependencies = [
@@ -744,7 +751,7 @@
  "hi-doc",
  "indoc",
  "insta",
- "logos",
+ "jrsonnet-lexer",
  "rowan",
  "strip-ansi-escapes",
  "thiserror",
modifiedCargo.tomldiffbeforeafterboth
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -81,9 +81,6 @@
 itertools = "0.14.0"
 xshell = "0.2.7"
 
-lsp-server = "0.7.9"
-lsp-types = "0.97.0"
-
 regex = "1.12"
 lru = "0.16.3"
 
addedcrates/jrsonnet-lexer/Cargo.tomldiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-lexer/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "jrsonnet-lexer"
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+version.workspace = true
+
+[dependencies]
+logos.workspace = true
+
+[lints]
+workspace = true
addedcrates/jrsonnet-lexer/src/generated/mod.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-lexer/src/generated/mod.rs
@@ -0,0 +1 @@
+pub mod syntax_kinds;
addedcrates/jrsonnet-lexer/src/generated/syntax_kinds.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-lexer/src/generated/syntax_kinds.rs
@@ -0,0 +1,210 @@
+//! This is a generated file, please do not edit manually. Changes can be
+//! made in codegeneration that lives in `xtask` top-level dir.
+
+#![allow(
+	bad_style,
+	missing_docs,
+	unreachable_pub,
+	clippy::manual_non_exhaustive,
+	clippy::match_like_matches_macro
+)]
+#[doc = r" The kind of syntax node, e.g. `IDENT`, `USE_KW`, or `STRUCT`."]
+#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, logos :: Logos)]
+#[repr(u16)]
+pub enum SyntaxKind {
+	#[doc(hidden)]
+	TOMBSTONE,
+	#[doc(hidden)]
+	EOF,
+	#[token("||")]
+	OR,
+	#[token("??")]
+	NULL_COAELSE,
+	#[token("&&")]
+	AND,
+	#[token("|")]
+	BIT_OR,
+	#[token("^")]
+	BIT_XOR,
+	#[token("&")]
+	BIT_AND,
+	#[token("==")]
+	EQ,
+	#[token("!=")]
+	NE,
+	#[token("<")]
+	LT,
+	#[token(">")]
+	GT,
+	#[token("<=")]
+	LE,
+	#[token(">=")]
+	GE,
+	#[token("<<")]
+	LHS,
+	#[token(">>")]
+	RHS,
+	#[token("+")]
+	PLUS,
+	#[token("-")]
+	MINUS,
+	#[token("*")]
+	MUL,
+	#[token("/")]
+	DIV,
+	#[token("%")]
+	MODULO,
+	#[token("!")]
+	NOT,
+	#[token("~")]
+	BIT_NOT,
+	#[token("[")]
+	L_BRACK,
+	#[token("]")]
+	R_BRACK,
+	#[token("(")]
+	L_PAREN,
+	#[token(")")]
+	R_PAREN,
+	#[token("{")]
+	L_BRACE,
+	#[token("}")]
+	R_BRACE,
+	#[token(":")]
+	COLON,
+	#[token("::")]
+	COLONCOLON,
+	#[token(":::")]
+	COLONCOLONCOLON,
+	#[token(";")]
+	SEMI,
+	#[token(".")]
+	DOT,
+	#[token("...")]
+	DOTDOTDOT,
+	#[token(",")]
+	COMMA,
+	#[token("$")]
+	DOLLAR,
+	#[token("=")]
+	ASSIGN,
+	#[token("?")]
+	QUESTION_MARK,
+	#[regex("(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?(?:[eE][+-]?[0-9]+)?")]
+	FLOAT,
+	#[regex("(?:0|[1-9][0-9]*)\\.[^0-9]")]
+	ERROR_FLOAT_JUNK_AFTER_POINT,
+	#[regex("(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?[eE][^+\\-0-9]")]
+	ERROR_FLOAT_JUNK_AFTER_EXPONENT,
+	#[regex("(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?[eE][+-][^0-9]")]
+	ERROR_FLOAT_JUNK_AFTER_EXPONENT_SIGN,
+	#[regex("\"(?s:[^\"\\\\]|\\\\.)*\"")]
+	STRING_DOUBLE,
+	#[regex("\"(?s:[^\"\\\\]|\\\\.)*")]
+	ERROR_STRING_DOUBLE_UNTERMINATED,
+	#[regex("'(?s:[^'\\\\]|\\\\.)*'")]
+	STRING_SINGLE,
+	#[regex("'(?s:[^'\\\\]|\\\\.)*")]
+	ERROR_STRING_SINGLE_UNTERMINATED,
+	#[regex("@\"(?:[^\"]|\"\")*\"")]
+	STRING_DOUBLE_VERBATIM,
+	#[regex("@\"(?:[^\"]|\"\")*")]
+	ERROR_STRING_DOUBLE_VERBATIM_UNTERMINATED,
+	#[regex("@'(?:[^']|'')*'")]
+	STRING_SINGLE_VERBATIM,
+	#[regex("@'(?:[^']|'')*")]
+	ERROR_STRING_SINGLE_VERBATIM_UNTERMINATED,
+	#[regex("@[^\"'\\s]\\S+")]
+	ERROR_STRING_VERBATIM_MISSING_QUOTES,
+	#[regex("\\|\\|\\|", crate::string_block::lex_str_block_test)]
+	STRING_BLOCK,
+	ERROR_STRING_BLOCK_UNEXPECTED_END,
+	ERROR_STRING_BLOCK_MISSING_NEW_LINE,
+	ERROR_STRING_BLOCK_MISSING_TERMINATION,
+	ERROR_STRING_BLOCK_MISSING_INDENT,
+	#[regex("[_a-zA-Z][_a-zA-Z0-9]*")]
+	IDENT,
+	#[regex("[ \\t\\n\\r]+")]
+	WHITESPACE,
+	#[regex("//[^\\r\\n]*?(\\r\\n|\\n)?")]
+	SINGLE_LINE_SLASH_COMMENT,
+	#[regex("#[^\\r\\n]*?(\\r\\n|\\n)?")]
+	SINGLE_LINE_HASH_COMMENT,
+	#[regex("/\\*([^*]|\\*[^/])*\\*/")]
+	MULTI_LINE_COMMENT,
+	#[regex("/\\*/")]
+	ERROR_COMMENT_TOO_SHORT,
+	#[regex("/\\*([^*/]|\\*[^/])+")]
+	ERROR_COMMENT_UNTERMINATED,
+	#[token("tailstrict")]
+	TAILSTRICT_KW,
+	#[token("local")]
+	LOCAL_KW,
+	#[token("importstr")]
+	IMPORTSTR_KW,
+	#[token("importbin")]
+	IMPORTBIN_KW,
+	#[token("import")]
+	IMPORT_KW,
+	#[token("if")]
+	IF_KW,
+	#[token("then")]
+	THEN_KW,
+	#[token("else")]
+	ELSE_KW,
+	#[token("function")]
+	FUNCTION_KW,
+	#[token("error")]
+	ERROR_KW,
+	#[token("in")]
+	IN_KW,
+	META_OBJECT_APPLY,
+	ERROR_NO_OPERATOR,
+	#[token("null")]
+	NULL_KW,
+	#[token("true")]
+	TRUE_KW,
+	#[token("false")]
+	FALSE_KW,
+	#[token("self")]
+	SELF_KW,
+	#[token("super")]
+	SUPER_KW,
+	#[token("for")]
+	FOR_KW,
+	#[token("assert")]
+	ASSERT_KW,
+	ERROR_MISSING_TOKEN,
+	ERROR_UNEXPECTED_TOKEN,
+	ERROR_CUSTOM,
+	LEXING_ERROR,
+	__LAST_TOKEN,
+	#[doc(hidden)]
+	__LAST,
+}
+use self::SyntaxKind::*;
+impl SyntaxKind {
+	pub fn is_keyword(self) -> bool {
+		match self {
+			OR | NULL_COAELSE | 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 | TAILSTRICT_KW
+			| LOCAL_KW | IMPORTSTR_KW | IMPORTBIN_KW | IMPORT_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 from_raw(r: u16) -> Self {
+		assert!(r < Self::__LAST as u16);
+		unsafe { std::mem::transmute(r) }
+	}
+	pub fn into_raw(self) -> u16 {
+		self as u16
+	}
+}
+#[macro_export]
+macro_rules ! T { [||] => { $ crate :: SyntaxKind :: OR } ; [??] => { $ crate :: SyntaxKind :: NULL_COAELSE } ; [&&] => { $ 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 } ; [local] => { $ crate :: SyntaxKind :: LOCAL_KW } ; [importstr] => { $ crate :: SyntaxKind :: IMPORTSTR_KW } ; [importbin] => { $ crate :: SyntaxKind :: IMPORTBIN_KW } ; [import] => { $ crate :: SyntaxKind :: IMPORT_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 } }
+#[allow(unused_imports)]
+pub use T;
addedcrates/jrsonnet-lexer/src/lex.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-lexer/src/lex.rs
@@ -0,0 +1,78 @@
+use core::ops::Range;
+
+use logos::Logos;
+// use rowan::{TextRange, TextSize};
+
+use crate::{
+	generated::syntax_kinds::SyntaxKind,
+	string_block::{lex_str_block, StringBlockError},
+	Span,
+};
+
+pub struct Lexer<'a> {
+	inner: logos::Lexer<'a, SyntaxKind>,
+}
+
+impl<'a> Lexer<'a> {
+	pub fn new(input: &'a str) -> Self {
+		Self {
+			inner: SyntaxKind::lexer(input),
+		}
+	}
+}
+
+impl<'a> Iterator for Lexer<'a> {
+	type Item = Lexeme<'a>;
+
+	fn next(&mut self) -> Option<Self::Item> {
+		use SyntaxKind::*;
+
+		let mut kind = self.inner.next()?;
+		let text = self.inner.slice();
+
+		if kind == Ok(STRING_BLOCK) {
+			// We use custom lexer, which skips enough bytes, but not returns error
+			// Instead we should call lexer again to verify if there is something wrong with string block
+			let mut lexer = logos::Lexer::<SyntaxKind>::new(text);
+			// In kinds, string blocks is parsed at least as `|||`
+			lexer.bump(3);
+			let res = lex_str_block(&mut lexer);
+			let next = lexer.next();
+			assert!(next.is_none(), "str_block is lexed");
+			match res {
+				Ok(()) => {}
+				Err(e) => {
+					kind = Ok(match e {
+						StringBlockError::UnexpectedEnd => ERROR_STRING_BLOCK_UNEXPECTED_END,
+						StringBlockError::MissingNewLine => ERROR_STRING_BLOCK_MISSING_NEW_LINE,
+						StringBlockError::MissingTermination => {
+							ERROR_STRING_BLOCK_MISSING_TERMINATION
+						}
+						StringBlockError::MissingIndent => ERROR_STRING_BLOCK_MISSING_INDENT,
+					});
+				}
+			}
+		}
+
+		Some(Self::Item {
+			kind: kind.unwrap_or(SyntaxKind::LEXING_ERROR),
+			text,
+			range: {
+				let Range { start, end } = self.inner.span();
+
+				Span(start as u32, end as u32)
+			},
+		})
+	}
+}
+
+#[derive(Clone, Copy, Debug)]
+pub struct Lexeme<'s> {
+	pub kind: SyntaxKind,
+	pub text: &'s str,
+	pub range: Span,
+}
+
+pub fn lex(input: &str) -> Vec<Lexeme<'_>> {
+	Lexer::new(input).collect()
+}
addedcrates/jrsonnet-lexer/src/lib.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-lexer/src/lib.rs
@@ -0,0 +1,8 @@
+mod generated;
+mod lex;
+mod string_block;
+
+#[derive(Clone, Copy, Debug)]
+pub struct Span(pub u32, pub u32);
+
+pub use lex::{Lexeme, Lexer};
addedcrates/jrsonnet-lexer/src/string_block.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-lexer/src/string_block.rs
@@ -0,0 +1,282 @@
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum StringBlockError {
+	UnexpectedEnd,
+	MissingNewLine,
+	MissingTermination,
+	MissingIndent,
+}
+
+use logos::Lexer;
+use StringBlockError::*;
+
+use crate::generated::syntax_kinds::SyntaxKind;
+
+pub(crate) fn lex_str_block_test(lex: &mut Lexer<'_, SyntaxKind>) {
+	let _ = lex_str_block(lex);
+}
+
+pub(crate) struct Context<'a> {
+	source: &'a str,
+	index: usize,
+}
+
+impl<'a> Context<'a> {
+	fn rest(&self) -> &'a str {
+		&self.source[self.index..]
+	}
+
+	fn next(&mut self) -> Option<char> {
+		if self.index == self.source.len() {
+			return None;
+		}
+
+		match self.rest().chars().next() {
+			None => None,
+			Some(c) => {
+				self.index += c.len_utf8();
+				Some(c)
+			}
+		}
+	}
+
+	fn peek(&self) -> Option<char> {
+		if self.index == self.source.len() {
+			return None;
+		}
+
+		self.rest().chars().next()
+	}
+
+	fn eat_if(&mut self, f: impl Fn(char) -> bool) -> usize {
+		if self.peek().is_some_and(f) {
+			self.index += 1;
+			return 1;
+		}
+		0
+	}
+
+	fn eat_while(&mut self, f: impl Fn(char) -> bool) -> usize {
+		if self.index == self.source.len() {
+			return 0;
+		}
+
+		let next_char = self.rest().char_indices().find(|(_, c)| !f(*c));
+
+		match next_char {
+			None => {
+				let diff = self.source.len() - self.index;
+				self.index = self.source.len();
+				diff
+			}
+			Some((idx, _)) => {
+				self.index += idx;
+				idx
+			}
+		}
+	}
+
+	fn skip(&mut self, len: usize) {
+		self.index = match self.index + len {
+			n if n > self.source.len() => self.source.len(),
+			n => n,
+		};
+	}
+}
+
+// Check that b has at least the same whitespace prefix as a and returns the
+// amount of this whitespace, otherwise returns 0.  If a has no whitespace
+// prefix than return 0.
+fn check_whitespace(a: &str, b: &str) -> usize {
+	let a = a.as_bytes();
+	let b = b.as_bytes();
+
+	for i in 0..a.len() {
+		if a[i] != b' ' && a[i] != b'\t' {
+			// a has run out of whitespace and b matched up to this point. Return result.
+			return i;
+		}
+
+		if i >= b.len() {
+			// We ran off the edge of b while a still has whitespace. Return 0 as failure.
+			return 0;
+		}
+
+		if a[i] != b[i] {
+			// a has whitespace but b does not. Return 0 as failure.
+			return 0;
+		}
+	}
+
+	// We ran off the end of a and b kept up
+	a.len()
+}
+
+pub(crate) trait StrBlockLexCtx<'d> {
+	fn remainder(&self) -> &'d str;
+	fn eat_error(&mut self, ctx: &Context<'d>);
+	fn bump_pos(&mut self, s: usize);
+	fn mark_truncating(&mut self);
+	fn mark_line(&mut self, line: &'d str);
+}
+
+impl<'d> StrBlockLexCtx<'d> for Lexer<'d, SyntaxKind> {
+	fn remainder(&self) -> &'d str {
+		self.remainder()
+	}
+	fn eat_error(&mut self, ctx: &Context<'d>) {
+		let end_index = ctx
+			.rest()
+			.find("|||")
+			.map_or_else(|| ctx.rest().len(), |v| v + 3);
+		self.bump(ctx.index + end_index);
+	}
+	fn bump_pos(&mut self, s: usize) {
+		self.bump(s);
+	}
+	fn mark_truncating(&mut self) {
+		// Lexer test doesn't collect anything
+	}
+	fn mark_line(&mut self, _line: &'d str) {
+		// Lexer test doesn't collect anything
+	}
+}
+
+pub fn collect_lexed_str_block(input: &str) -> Result<CollectStrBlock<'_>, StringBlockError> {
+	let mut collect = CollectStrBlock {
+		truncate: false,
+		lines: vec![],
+		input,
+		offset: 0,
+	};
+	lex_str_block(&mut collect)?;
+	Ok(collect)
+}
+
+pub struct CollectStrBlock<'s> {
+	pub truncate: bool,
+	pub lines: Vec<&'s str>,
+	input: &'s str,
+	offset: usize,
+}
+
+impl<'d> StrBlockLexCtx<'d> for CollectStrBlock<'d> {
+	fn remainder(&self) -> &'d str {
+		self.input
+	}
+
+	fn eat_error(&mut self, _ctx: &Context<'d>) {
+		// Error will be returned, no need to record it here
+	}
+
+	fn bump_pos(&mut self, s: usize) {
+		self.offset += s;
+	}
+
+	fn mark_truncating(&mut self) {
+		self.truncate = true;
+	}
+
+	fn mark_line(&mut self, line: &'d str) {
+		self.lines.push(line);
+	}
+}
+
+pub(crate) fn lex_str_block<'a>(lex: &mut impl StrBlockLexCtx<'a>) -> Result<(), StringBlockError> {
+	// debug_assert_eq!(lex.slice(), "|||");
+	let mut ctx = Context::<'a> {
+		source: lex.remainder(),
+		index: 0,
+	};
+
+	if ctx.eat_if(|v| v == '-') != 0 {
+		lex.mark_truncating();
+	}
+
+	// Skip whitespaces
+	ctx.eat_while(|r| r == ' ' || r == '\t' || r == '\r');
+
+	// Skip \n
+	match ctx.next() {
+		Some('\n') => (),
+		None => {
+			lex.eat_error(&ctx);
+			return Err(UnexpectedEnd);
+		}
+		// Text block requires new line after |||.
+		Some(_) => {
+			lex.eat_error(&ctx);
+			return Err(MissingNewLine);
+		}
+	}
+
+	// Process leading blank lines before calculating string block indent
+	while ctx.peek() == Some('\n') {
+		ctx.next();
+	}
+
+	let mut num_whitespace = check_whitespace(ctx.rest(), ctx.rest());
+	let str_block_indent = &ctx.rest()[..num_whitespace];
+
+	if num_whitespace == 0 {
+		// Text block's first line must start with whitespace
+		lex.eat_error(&ctx);
+		return Err(MissingIndent);
+	}
+
+	loop {
+		debug_assert_ne!(num_whitespace, 0, "Unexpected value for num_whitespace");
+		ctx.skip(num_whitespace);
+
+		let line_start = ctx.index;
+		let mut line_size = 0;
+		loop {
+			match ctx.next() {
+				None => {
+					lex.eat_error(&ctx);
+					return Err(UnexpectedEnd);
+				}
+				Some('\n') => {
+					lex.mark_line(&ctx.source[line_start..line_start + line_size]);
+					break;
+				}
+				Some(c) => {
+					line_size += c.len_utf8();
+				}
+			}
+		}
+
+		// Skip any blank lines
+		while ctx.peek() == Some('\n') {
+			lex.mark_line("");
+			ctx.next();
+		}
+
+		// Look at the next line
+		num_whitespace = check_whitespace(str_block_indent, ctx.rest());
+		if num_whitespace == 0 {
+			// End of the text block
+			// let mut term_indent = String::with_capacity(num_whitespace);
+			while let Some(' ' | '\t') = ctx.peek() {
+				// term_indent.push(
+				ctx.next().unwrap();
+				// );
+			}
+
+			if !ctx.rest().starts_with("|||") {
+				if ctx.rest().is_empty() {
+					lex.bump_pos(ctx.index);
+					return Err(UnexpectedEnd);
+				}
+				lex.eat_error(&ctx);
+				return Err(MissingTermination);
+			}
+
+			// Skip '|||'
+			ctx.skip(3);
+			break;
+		}
+	}
+
+	lex.bump_pos(ctx.index);
+	Ok(())
+}
modifiedcrates/jrsonnet-rowan-parser/Cargo.tomldiffbeforeafterboth
--- a/crates/jrsonnet-rowan-parser/Cargo.toml
+++ b/crates/jrsonnet-rowan-parser/Cargo.toml
@@ -14,7 +14,7 @@
 drop_bomb.workspace = true
 hi-doc.workspace = true
 indoc.workspace = true
-logos.workspace = true
+jrsonnet-lexer = { version = "0.5.0-pre97", path = "../jrsonnet-lexer" }
 rowan.workspace = true
 thiserror.workspace = true
 
modifiedcrates/jrsonnet-rowan-parser/src/generated/syntax_kinds.rsdiffbeforeafterboth
before · crates/jrsonnet-rowan-parser/src/generated/syntax_kinds.rs
1//! This is a generated file, please do not edit manually. Changes can be2//! made in codegeneration that lives in `xtask` top-level dir.34#![allow(5	bad_style,6	missing_docs,7	unreachable_pub,8	clippy::manual_non_exhaustive,9	clippy::match_like_matches_macro10)]11use logos::Logos;12#[doc = r" The kind of syntax node, e.g. `IDENT`, `USE_KW`, or `STRUCT`."]13#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Logos)]14#[repr(u16)]15pub enum SyntaxKind {16	#[doc(hidden)]17	TOMBSTONE,18	#[doc(hidden)]19	EOF,20	#[token("||")]21	OR,22	#[token("??")]23	NULL_COAELSE,24	#[token("&&")]25	AND,26	#[token("|")]27	BIT_OR,28	#[token("^")]29	BIT_XOR,30	#[token("&")]31	BIT_AND,32	#[token("==")]33	EQ,34	#[token("!=")]35	NE,36	#[token("<")]37	LT,38	#[token(">")]39	GT,40	#[token("<=")]41	LE,42	#[token(">=")]43	GE,44	#[token("<<")]45	LHS,46	#[token(">>")]47	RHS,48	#[token("+")]49	PLUS,50	#[token("-")]51	MINUS,52	#[token("*")]53	MUL,54	#[token("/")]55	DIV,56	#[token("%")]57	MODULO,58	#[token("!")]59	NOT,60	#[token("~")]61	BIT_NOT,62	#[token("[")]63	L_BRACK,64	#[token("]")]65	R_BRACK,66	#[token("(")]67	L_PAREN,68	#[token(")")]69	R_PAREN,70	#[token("{")]71	L_BRACE,72	#[token("}")]73	R_BRACE,74	#[token(":")]75	COLON,76	#[token("::")]77	COLONCOLON,78	#[token(":::")]79	COLONCOLONCOLON,80	#[token(";")]81	SEMI,82	#[token(".")]83	DOT,84	#[token("...")]85	DOTDOTDOT,86	#[token(",")]87	COMMA,88	#[token("$")]89	DOLLAR,90	#[token("=")]91	ASSIGN,92	#[token("?")]93	QUESTION_MARK,94	#[regex("(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?(?:[eE][+-]?[0-9]+)?")]95	FLOAT,96	#[regex("(?:0|[1-9][0-9]*)\\.[^0-9]")]97	ERROR_FLOAT_JUNK_AFTER_POINT,98	#[regex("(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?[eE][^+\\-0-9]")]99	ERROR_FLOAT_JUNK_AFTER_EXPONENT,100	#[regex("(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?[eE][+-][^0-9]")]101	ERROR_FLOAT_JUNK_AFTER_EXPONENT_SIGN,102	#[regex("\"(?s:[^\"\\\\]|\\\\.)*\"")]103	STRING_DOUBLE,104	#[regex("\"(?s:[^\"\\\\]|\\\\.)*")]105	ERROR_STRING_DOUBLE_UNTERMINATED,106	#[regex("'(?s:[^'\\\\]|\\\\.)*'")]107	STRING_SINGLE,108	#[regex("'(?s:[^'\\\\]|\\\\.)*")]109	ERROR_STRING_SINGLE_UNTERMINATED,110	#[regex("@\"(?:[^\"]|\"\")*\"")]111	STRING_DOUBLE_VERBATIM,112	#[regex("@\"(?:[^\"]|\"\")*")]113	ERROR_STRING_DOUBLE_VERBATIM_UNTERMINATED,114	#[regex("@'(?:[^']|'')*'")]115	STRING_SINGLE_VERBATIM,116	#[regex("@'(?:[^']|'')*")]117	ERROR_STRING_SINGLE_VERBATIM_UNTERMINATED,118	#[regex("@[^\"'\\s]\\S+")]119	ERROR_STRING_VERBATIM_MISSING_QUOTES,120	#[regex("\\|\\|\\|", crate::string_block::lex_str_block_test)]121	STRING_BLOCK,122	ERROR_STRING_BLOCK_UNEXPECTED_END,123	ERROR_STRING_BLOCK_MISSING_NEW_LINE,124	ERROR_STRING_BLOCK_MISSING_TERMINATION,125	ERROR_STRING_BLOCK_MISSING_INDENT,126	#[regex("[_a-zA-Z][_a-zA-Z0-9]*")]127	IDENT,128	#[regex("[ \\t\\n\\r]+")]129	WHITESPACE,130	#[regex("//[^\\r\\n]*?(\\r\\n|\\n)?")]131	SINGLE_LINE_SLASH_COMMENT,132	#[regex("#[^\\r\\n]*?(\\r\\n|\\n)?")]133	SINGLE_LINE_HASH_COMMENT,134	#[regex("/\\*([^*]|\\*[^/])*\\*/")]135	MULTI_LINE_COMMENT,136	#[regex("/\\*/")]137	ERROR_COMMENT_TOO_SHORT,138	#[regex("/\\*([^*/]|\\*[^/])+")]139	ERROR_COMMENT_UNTERMINATED,140	#[token("tailstrict")]141	TAILSTRICT_KW,142	#[token("local")]143	LOCAL_KW,144	#[token("importstr")]145	IMPORTSTR_KW,146	#[token("importbin")]147	IMPORTBIN_KW,148	#[token("import")]149	IMPORT_KW,150	#[token("if")]151	IF_KW,152	#[token("then")]153	THEN_KW,154	#[token("else")]155	ELSE_KW,156	#[token("function")]157	FUNCTION_KW,158	#[token("error")]159	ERROR_KW,160	#[token("in")]161	IN_KW,162	META_OBJECT_APPLY,163	ERROR_NO_OPERATOR,164	#[token("null")]165	NULL_KW,166	#[token("true")]167	TRUE_KW,168	#[token("false")]169	FALSE_KW,170	#[token("self")]171	SELF_KW,172	#[token("super")]173	SUPER_KW,174	#[token("for")]175	FOR_KW,176	#[token("assert")]177	ASSERT_KW,178	ERROR_MISSING_TOKEN,179	ERROR_UNEXPECTED_TOKEN,180	ERROR_CUSTOM,181	LEXING_ERROR,182	__LAST_TOKEN,183	SOURCE_FILE,184	EXPR,185	SUFFIX_INDEX,186	NAME,187	SUFFIX_INDEX_EXPR,188	SUFFIX_SLICE,189	SLICE_DESC,190	SUFFIX_APPLY,191	ARGS_DESC,192	STMT_LOCAL,193	STMT_ASSERT,194	ASSERTION,195	EXPR_BINARY,196	EXPR_UNARY,197	EXPR_OBJ_EXTEND,198	EXPR_PARENED,199	EXPR_LITERAL,200	EXPR_STRING,201	EXPR_NUMBER,202	EXPR_ARRAY,203	EXPR_OBJECT,204	EXPR_ARRAY_COMP,205	EXPR_IMPORT,206	EXPR_VAR,207	EXPR_IF_THEN_ELSE,208	TRUE_EXPR,209	FALSE_EXPR,210	EXPR_FUNCTION,211	PARAMS_DESC,212	EXPR_ERROR,213	SLICE_DESC_END,214	SLICE_DESC_STEP,215	ARG,216	OBJ_BODY_COMP,217	OBJ_BODY_MEMBER_LIST,218	MEMBER_BIND_STMT,219	OBJ_LOCAL,220	MEMBER_ASSERT_STMT,221	MEMBER_FIELD_NORMAL,222	MEMBER_FIELD_METHOD,223	FIELD_NAME_FIXED,224	FIELD_NAME_DYNAMIC,225	FOR_SPEC,226	IF_SPEC,227	BIND_DESTRUCT,228	BIND_FUNCTION,229	PARAM,230	DESTRUCT_FULL,231	DESTRUCT_SKIP,232	DESTRUCT_ARRAY,233	DESTRUCT_OBJECT,234	DESTRUCT_OBJECT_FIELD,235	DESTRUCT_REST,236	DESTRUCT_ARRAY_ELEMENT,237	SUFFIX,238	BIND,239	STMT,240	OBJ_BODY,241	COMP_SPEC,242	EXPR_BASE,243	MEMBER_COMP,244	MEMBER,245	FIELD_NAME,246	DESTRUCT,247	DESTRUCT_ARRAY_PART,248	BINARY_OPERATOR,249	UNARY_OPERATOR,250	LITERAL,251	TEXT,252	NUMBER,253	IMPORT_KIND,254	VISIBILITY,255	TRIVIA,256	CUSTOM_ERROR,257	#[doc(hidden)]258	__LAST,259}260use self::SyntaxKind::*;261impl SyntaxKind {262	pub fn is_keyword(self) -> bool {263		match self {264			OR | NULL_COAELSE | AND | BIT_OR | BIT_XOR | BIT_AND | EQ | NE | LT | GT | LE | GE265			| LHS | RHS | PLUS | MINUS | MUL | DIV | MODULO | NOT | BIT_NOT | L_BRACK | R_BRACK266			| L_PAREN | R_PAREN | L_BRACE | R_BRACE | COLON | COLONCOLON | COLONCOLONCOLON267			| SEMI | DOT | DOTDOTDOT | COMMA | DOLLAR | ASSIGN | QUESTION_MARK | TAILSTRICT_KW268			| LOCAL_KW | IMPORTSTR_KW | IMPORTBIN_KW | IMPORT_KW | IF_KW | THEN_KW | ELSE_KW269			| FUNCTION_KW | ERROR_KW | IN_KW | NULL_KW | TRUE_KW | FALSE_KW | SELF_KW270			| SUPER_KW | FOR_KW | ASSERT_KW => true,271			_ => false,272		}273	}274	pub fn is_enum(self) -> bool {275		match self {276			SUFFIX | BIND | STMT | OBJ_BODY | COMP_SPEC | EXPR_BASE | MEMBER_COMP | MEMBER277			| FIELD_NAME | DESTRUCT | DESTRUCT_ARRAY_PART | BINARY_OPERATOR | UNARY_OPERATOR278			| LITERAL | TEXT | NUMBER | IMPORT_KIND | VISIBILITY | TRIVIA | CUSTOM_ERROR => true,279			_ => false,280		}281	}282	pub fn from_raw(r: u16) -> Self {283		assert!(r < Self::__LAST as u16);284		unsafe { std::mem::transmute(r) }285	}286	pub fn into_raw(self) -> u16 {287		self as u16288	}289}290#[macro_export]291macro_rules ! T { [||] => { $ crate :: SyntaxKind :: OR } ; [??] => { $ crate :: SyntaxKind :: NULL_COAELSE } ; [&&] => { $ 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 } ; [local] => { $ crate :: SyntaxKind :: LOCAL_KW } ; [importstr] => { $ crate :: SyntaxKind :: IMPORTSTR_KW } ; [importbin] => { $ crate :: SyntaxKind :: IMPORTBIN_KW } ; [import] => { $ crate :: SyntaxKind :: IMPORT_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 } }292#[allow(unused_imports)]293pub use T;
after · crates/jrsonnet-rowan-parser/src/generated/syntax_kinds.rs
1//! This is a generated file, please do not edit manually. Changes can be2//! made in codegeneration that lives in `xtask` top-level dir.34#![allow(5	bad_style,6	missing_docs,7	unreachable_pub,8	clippy::manual_non_exhaustive,9	clippy::match_like_matches_macro10)]11#[doc = r" The kind of syntax node, e.g. `IDENT`, `USE_KW`, or `STRUCT`."]12#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]13#[repr(u16)]14pub enum SyntaxKind {15	#[doc(hidden)]16	TOMBSTONE,17	#[doc(hidden)]18	EOF,19	OR,20	NULL_COAELSE,21	AND,22	BIT_OR,23	BIT_XOR,24	BIT_AND,25	EQ,26	NE,27	LT,28	GT,29	LE,30	GE,31	LHS,32	RHS,33	PLUS,34	MINUS,35	MUL,36	DIV,37	MODULO,38	NOT,39	BIT_NOT,40	L_BRACK,41	R_BRACK,42	L_PAREN,43	R_PAREN,44	L_BRACE,45	R_BRACE,46	COLON,47	COLONCOLON,48	COLONCOLONCOLON,49	SEMI,50	DOT,51	DOTDOTDOT,52	COMMA,53	DOLLAR,54	ASSIGN,55	QUESTION_MARK,56	FLOAT,57	ERROR_FLOAT_JUNK_AFTER_POINT,58	ERROR_FLOAT_JUNK_AFTER_EXPONENT,59	ERROR_FLOAT_JUNK_AFTER_EXPONENT_SIGN,60	STRING_DOUBLE,61	ERROR_STRING_DOUBLE_UNTERMINATED,62	STRING_SINGLE,63	ERROR_STRING_SINGLE_UNTERMINATED,64	STRING_DOUBLE_VERBATIM,65	ERROR_STRING_DOUBLE_VERBATIM_UNTERMINATED,66	STRING_SINGLE_VERBATIM,67	ERROR_STRING_SINGLE_VERBATIM_UNTERMINATED,68	ERROR_STRING_VERBATIM_MISSING_QUOTES,69	STRING_BLOCK,70	ERROR_STRING_BLOCK_UNEXPECTED_END,71	ERROR_STRING_BLOCK_MISSING_NEW_LINE,72	ERROR_STRING_BLOCK_MISSING_TERMINATION,73	ERROR_STRING_BLOCK_MISSING_INDENT,74	IDENT,75	WHITESPACE,76	SINGLE_LINE_SLASH_COMMENT,77	SINGLE_LINE_HASH_COMMENT,78	MULTI_LINE_COMMENT,79	ERROR_COMMENT_TOO_SHORT,80	ERROR_COMMENT_UNTERMINATED,81	TAILSTRICT_KW,82	LOCAL_KW,83	IMPORTSTR_KW,84	IMPORTBIN_KW,85	IMPORT_KW,86	IF_KW,87	THEN_KW,88	ELSE_KW,89	FUNCTION_KW,90	ERROR_KW,91	IN_KW,92	META_OBJECT_APPLY,93	ERROR_NO_OPERATOR,94	NULL_KW,95	TRUE_KW,96	FALSE_KW,97	SELF_KW,98	SUPER_KW,99	FOR_KW,100	ASSERT_KW,101	ERROR_MISSING_TOKEN,102	ERROR_UNEXPECTED_TOKEN,103	ERROR_CUSTOM,104	LEXING_ERROR,105	__LAST_TOKEN,106	SOURCE_FILE,107	EXPR,108	SUFFIX_INDEX,109	NAME,110	SUFFIX_INDEX_EXPR,111	SUFFIX_SLICE,112	SLICE_DESC,113	SUFFIX_APPLY,114	ARGS_DESC,115	STMT_LOCAL,116	STMT_ASSERT,117	ASSERTION,118	EXPR_BINARY,119	EXPR_UNARY,120	EXPR_OBJ_EXTEND,121	EXPR_PARENED,122	EXPR_LITERAL,123	EXPR_STRING,124	EXPR_NUMBER,125	EXPR_ARRAY,126	EXPR_OBJECT,127	EXPR_ARRAY_COMP,128	EXPR_IMPORT,129	EXPR_VAR,130	EXPR_IF_THEN_ELSE,131	TRUE_EXPR,132	FALSE_EXPR,133	EXPR_FUNCTION,134	PARAMS_DESC,135	EXPR_ERROR,136	SLICE_DESC_END,137	SLICE_DESC_STEP,138	ARG,139	OBJ_BODY_COMP,140	OBJ_BODY_MEMBER_LIST,141	MEMBER_BIND_STMT,142	OBJ_LOCAL,143	MEMBER_ASSERT_STMT,144	MEMBER_FIELD_NORMAL,145	MEMBER_FIELD_METHOD,146	FIELD_NAME_FIXED,147	FIELD_NAME_DYNAMIC,148	FOR_SPEC,149	IF_SPEC,150	BIND_DESTRUCT,151	BIND_FUNCTION,152	PARAM,153	DESTRUCT_FULL,154	DESTRUCT_SKIP,155	DESTRUCT_ARRAY,156	DESTRUCT_OBJECT,157	DESTRUCT_OBJECT_FIELD,158	DESTRUCT_REST,159	DESTRUCT_ARRAY_ELEMENT,160	SUFFIX,161	BIND,162	STMT,163	OBJ_BODY,164	COMP_SPEC,165	EXPR_BASE,166	MEMBER_COMP,167	MEMBER,168	FIELD_NAME,169	DESTRUCT,170	DESTRUCT_ARRAY_PART,171	BINARY_OPERATOR,172	UNARY_OPERATOR,173	LITERAL,174	TEXT,175	NUMBER,176	IMPORT_KIND,177	VISIBILITY,178	TRIVIA,179	CUSTOM_ERROR,180	#[doc(hidden)]181	__LAST,182}183use self::SyntaxKind::*;184impl SyntaxKind {185	pub fn is_keyword(self) -> bool {186		match self {187			OR | NULL_COAELSE | AND | BIT_OR | BIT_XOR | BIT_AND | EQ | NE | LT | GT | LE | GE188			| LHS | RHS | PLUS | MINUS | MUL | DIV | MODULO | NOT | BIT_NOT | L_BRACK | R_BRACK189			| L_PAREN | R_PAREN | L_BRACE | R_BRACE | COLON | COLONCOLON | COLONCOLONCOLON190			| SEMI | DOT | DOTDOTDOT | COMMA | DOLLAR | ASSIGN | QUESTION_MARK | TAILSTRICT_KW191			| LOCAL_KW | IMPORTSTR_KW | IMPORTBIN_KW | IMPORT_KW | IF_KW | THEN_KW | ELSE_KW192			| FUNCTION_KW | ERROR_KW | IN_KW | NULL_KW | TRUE_KW | FALSE_KW | SELF_KW193			| SUPER_KW | FOR_KW | ASSERT_KW => true,194			_ => false,195		}196	}197	pub fn is_enum(self) -> bool {198		match self {199			SUFFIX | BIND | STMT | OBJ_BODY | COMP_SPEC | EXPR_BASE | MEMBER_COMP | MEMBER200			| FIELD_NAME | DESTRUCT | DESTRUCT_ARRAY_PART | BINARY_OPERATOR | UNARY_OPERATOR201			| LITERAL | TEXT | NUMBER | IMPORT_KIND | VISIBILITY | TRIVIA | CUSTOM_ERROR => true,202			_ => false,203		}204	}205	pub fn from_raw(r: u16) -> Self {206		assert!(r < Self::__LAST as u16);207		unsafe { std::mem::transmute(r) }208	}209	pub fn into_raw(self) -> u16 {210		self as u16211	}212}213#[macro_export]214macro_rules ! T { [||] => { $ crate :: SyntaxKind :: OR } ; [??] => { $ crate :: SyntaxKind :: NULL_COAELSE } ; [&&] => { $ 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 } ; [local] => { $ crate :: SyntaxKind :: LOCAL_KW } ; [importstr] => { $ crate :: SyntaxKind :: IMPORTSTR_KW } ; [importbin] => { $ crate :: SyntaxKind :: IMPORTBIN_KW } ; [import] => { $ crate :: SyntaxKind :: IMPORT_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 } }215#[allow(unused_imports)]216pub use T;
modifiedcrates/jrsonnet-rowan-parser/src/lex.rsdiffbeforeafterboth
--- a/crates/jrsonnet-rowan-parser/src/lex.rs
+++ b/crates/jrsonnet-rowan-parser/src/lex.rs
@@ -1,81 +1,19 @@
-use core::ops::Range;
-use std::convert::TryFrom;
-
-use logos::Logos;
+use jrsonnet_lexer::Lexer;
 use rowan::{TextRange, TextSize};
 
-use crate::{
-	string_block::{lex_str_block, StringBlockError},
-	SyntaxKind,
-};
-
-pub struct Lexer<'a> {
-	inner: logos::Lexer<'a, SyntaxKind>,
-}
-
-impl<'a> Lexer<'a> {
-	pub fn new(input: &'a str) -> Self {
-		Self {
-			inner: SyntaxKind::lexer(input),
-		}
-	}
-}
-
-impl<'a> Iterator for Lexer<'a> {
-	type Item = Lexeme<'a>;
-
-	fn next(&mut self) -> Option<Self::Item> {
-		use SyntaxKind::*;
-
-		let mut kind = self.inner.next()?;
-		let text = self.inner.slice();
-
-		if kind == Ok(STRING_BLOCK) {
-			// We use custom lexer, which skips enough bytes, but not returns error
-			// Instead we should call lexer again to verify if there is something wrong with string block
-			let mut lexer = logos::Lexer::<SyntaxKind>::new(text);
-			// In kinds, string blocks is parsed at least as `|||`
-			lexer.bump(3);
-			let res = lex_str_block(&mut lexer);
-			let next = lexer.next();
-			assert!(next.is_none(), "str_block is lexed");
-			match res {
-				Ok(()) => {}
-				Err(e) => {
-					kind = Ok(match e {
-						StringBlockError::UnexpectedEnd => ERROR_STRING_BLOCK_UNEXPECTED_END,
-						StringBlockError::MissingNewLine => ERROR_STRING_BLOCK_MISSING_NEW_LINE,
-						StringBlockError::MissingTermination => {
-							ERROR_STRING_BLOCK_MISSING_TERMINATION
-						}
-						StringBlockError::MissingIndent => ERROR_STRING_BLOCK_MISSING_INDENT,
-					});
-				}
-			}
-		}
-
-		Some(Self::Item {
-			kind: kind.unwrap_or(SyntaxKind::LEXING_ERROR),
-			text,
-			range: {
-				let Range { start, end } = self.inner.span();
-
-				TextRange::new(
-					TextSize::try_from(start).unwrap(),
-					TextSize::try_from(end).unwrap(),
-				)
-			},
-		})
-	}
-}
+use crate::SyntaxKind;
 
 #[derive(Clone, Copy, Debug)]
-pub struct Lexeme<'i> {
+pub struct Lexeme<'s> {
 	pub kind: SyntaxKind,
-	pub text: &'i str,
+	pub text: &'s str,
 	pub range: TextRange,
 }
 
 pub fn lex(input: &str) -> Vec<Lexeme<'_>> {
-	Lexer::new(input).collect()
+	Lexer::new(input).map(|l| Lexeme {
+		kind: SyntaxKind::from_raw(l.kind.into_raw()),
+		text: l.text,
+		range: TextRange::new(TextSize::from(l.range.0), TextSize::from(l.range.1)),
+	}).collect()
 }
modifiedcrates/jrsonnet-rowan-parser/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-rowan-parser/src/lib.rs
+++ b/crates/jrsonnet-rowan-parser/src/lib.rs
@@ -2,7 +2,6 @@
 
 use event::Sink;
 use generated::nodes::{SourceFile, Trivia};
-use lex::lex;
 use parser::{LocatedSyntaxError, Parser};
 pub use rowan;
 
@@ -14,14 +13,12 @@
 mod marker;
 mod parser;
 mod precedence;
-mod string_block;
 mod tests;
 mod token_set;
 
 pub use ast::{AstChildren, AstNode, AstToken};
 pub use generated::{nodes, syntax_kinds::SyntaxKind};
 pub use language::*;
-pub use string_block::{collect_lexed_str_block, CollectStrBlock};
 pub use token_set::SyntaxKindSet;
 
 use self::{
@@ -30,7 +27,7 @@
 };
 
 pub fn parse(input: &str) -> (SourceFile, Vec<LocatedSyntaxError>) {
-	let lexemes = lex(input);
+	let lexemes = lex::lex(input);
 	let kinds = lexemes
 		.iter()
 		.map(|l| l.kind)
deletedcrates/jrsonnet-rowan-parser/src/string_block.rsdiffbeforeafterboth
--- a/crates/jrsonnet-rowan-parser/src/string_block.rs
+++ /dev/null
@@ -1,282 +0,0 @@
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
-pub enum StringBlockError {
-	UnexpectedEnd,
-	MissingNewLine,
-	MissingTermination,
-	MissingIndent,
-}
-
-use logos::Lexer;
-use StringBlockError::*;
-
-use crate::SyntaxKind;
-
-pub(crate) fn lex_str_block_test(lex: &mut Lexer<'_, SyntaxKind>) {
-	let _ = lex_str_block(lex);
-}
-
-pub(crate) struct Context<'a> {
-	source: &'a str,
-	index: usize,
-}
-
-impl<'a> Context<'a> {
-	fn rest(&self) -> &'a str {
-		&self.source[self.index..]
-	}
-
-	fn next(&mut self) -> Option<char> {
-		if self.index == self.source.len() {
-			return None;
-		}
-
-		match self.rest().chars().next() {
-			None => None,
-			Some(c) => {
-				self.index += c.len_utf8();
-				Some(c)
-			}
-		}
-	}
-
-	fn peek(&self) -> Option<char> {
-		if self.index == self.source.len() {
-			return None;
-		}
-
-		self.rest().chars().next()
-	}
-
-	fn eat_if(&mut self, f: impl Fn(char) -> bool) -> usize {
-		if self.peek().is_some_and(f) {
-			self.index += 1;
-			return 1;
-		}
-		0
-	}
-
-	fn eat_while(&mut self, f: impl Fn(char) -> bool) -> usize {
-		if self.index == self.source.len() {
-			return 0;
-		}
-
-		let next_char = self.rest().char_indices().find(|(_, c)| !f(*c));
-
-		match next_char {
-			None => {
-				let diff = self.source.len() - self.index;
-				self.index = self.source.len();
-				diff
-			}
-			Some((idx, _)) => {
-				self.index += idx;
-				idx
-			}
-		}
-	}
-
-	fn skip(&mut self, len: usize) {
-		self.index = match self.index + len {
-			n if n > self.source.len() => self.source.len(),
-			n => n,
-		};
-	}
-}
-
-// Check that b has at least the same whitespace prefix as a and returns the
-// amount of this whitespace, otherwise returns 0.  If a has no whitespace
-// prefix than return 0.
-fn check_whitespace(a: &str, b: &str) -> usize {
-	let a = a.as_bytes();
-	let b = b.as_bytes();
-
-	for i in 0..a.len() {
-		if a[i] != b' ' && a[i] != b'\t' {
-			// a has run out of whitespace and b matched up to this point. Return result.
-			return i;
-		}
-
-		if i >= b.len() {
-			// We ran off the edge of b while a still has whitespace. Return 0 as failure.
-			return 0;
-		}
-
-		if a[i] != b[i] {
-			// a has whitespace but b does not. Return 0 as failure.
-			return 0;
-		}
-	}
-
-	// We ran off the end of a and b kept up
-	a.len()
-}
-
-pub(crate) trait StrBlockLexCtx<'d> {
-	fn remainder(&self) -> &'d str;
-	fn eat_error(&mut self, ctx: &Context<'d>);
-	fn bump_pos(&mut self, s: usize);
-	fn mark_truncating(&mut self);
-	fn mark_line(&mut self, line: &'d str);
-}
-
-impl<'d> StrBlockLexCtx<'d> for Lexer<'d, SyntaxKind> {
-	fn remainder(&self) -> &'d str {
-		self.remainder()
-	}
-	fn eat_error(&mut self, ctx: &Context<'d>) {
-		let end_index = ctx
-			.rest()
-			.find("|||")
-			.map_or_else(|| ctx.rest().len(), |v| v + 3);
-		self.bump(ctx.index + end_index);
-	}
-	fn bump_pos(&mut self, s: usize) {
-		self.bump(s);
-	}
-	fn mark_truncating(&mut self) {
-		// Lexer test doesn't collect anything
-	}
-	fn mark_line(&mut self, _line: &'d str) {
-		// Lexer test doesn't collect anything
-	}
-}
-
-pub fn collect_lexed_str_block(input: &str) -> Result<CollectStrBlock<'_>, StringBlockError> {
-	let mut collect = CollectStrBlock {
-		truncate: false,
-		lines: vec![],
-		input,
-		offset: 0,
-	};
-	lex_str_block(&mut collect)?;
-	Ok(collect)
-}
-
-pub struct CollectStrBlock<'s> {
-	pub truncate: bool,
-	pub lines: Vec<&'s str>,
-	input: &'s str,
-	offset: usize,
-}
-
-impl<'d> StrBlockLexCtx<'d> for CollectStrBlock<'d> {
-	fn remainder(&self) -> &'d str {
-		self.input
-	}
-
-	fn eat_error(&mut self, _ctx: &Context<'d>) {
-		// Error will be returned, no need to record it here
-	}
-
-	fn bump_pos(&mut self, s: usize) {
-		self.offset += s;
-	}
-
-	fn mark_truncating(&mut self) {
-		self.truncate = true;
-	}
-
-	fn mark_line(&mut self, line: &'d str) {
-		self.lines.push(line);
-	}
-}
-
-pub(crate) fn lex_str_block<'a>(lex: &mut impl StrBlockLexCtx<'a>) -> Result<(), StringBlockError> {
-	// debug_assert_eq!(lex.slice(), "|||");
-	let mut ctx = Context::<'a> {
-		source: lex.remainder(),
-		index: 0,
-	};
-
-	if ctx.eat_if(|v| v == '-') != 0 {
-		lex.mark_truncating();
-	}
-
-	// Skip whitespaces
-	ctx.eat_while(|r| r == ' ' || r == '\t' || r == '\r');
-
-	// Skip \n
-	match ctx.next() {
-		Some('\n') => (),
-		None => {
-			lex.eat_error(&ctx);
-			return Err(UnexpectedEnd);
-		}
-		// Text block requires new line after |||.
-		Some(_) => {
-			lex.eat_error(&ctx);
-			return Err(MissingNewLine);
-		}
-	}
-
-	// Process leading blank lines before calculating string block indent
-	while ctx.peek() == Some('\n') {
-		ctx.next();
-	}
-
-	let mut num_whitespace = check_whitespace(ctx.rest(), ctx.rest());
-	let str_block_indent = &ctx.rest()[..num_whitespace];
-
-	if num_whitespace == 0 {
-		// Text block's first line must start with whitespace
-		lex.eat_error(&ctx);
-		return Err(MissingIndent);
-	}
-
-	loop {
-		debug_assert_ne!(num_whitespace, 0, "Unexpected value for num_whitespace");
-		ctx.skip(num_whitespace);
-
-		let line_start = ctx.index;
-		let mut line_size = 0;
-		loop {
-			match ctx.next() {
-				None => {
-					lex.eat_error(&ctx);
-					return Err(UnexpectedEnd);
-				}
-				Some('\n') => {
-					lex.mark_line(&ctx.source[line_start..line_start + line_size]);
-					break;
-				}
-				Some(c) => {
-					line_size += c.len_utf8();
-				}
-			}
-		}
-
-		// Skip any blank lines
-		while ctx.peek() == Some('\n') {
-			lex.mark_line("");
-			ctx.next();
-		}
-
-		// Look at the next line
-		num_whitespace = check_whitespace(str_block_indent, ctx.rest());
-		if num_whitespace == 0 {
-			// End of the text block
-			// let mut term_indent = String::with_capacity(num_whitespace);
-			while let Some(' ' | '\t') = ctx.peek() {
-				// term_indent.push(
-				ctx.next().unwrap();
-				// );
-			}
-
-			if !ctx.rest().starts_with("|||") {
-				if ctx.rest().is_empty() {
-					lex.bump_pos(ctx.index);
-					return Err(UnexpectedEnd);
-				}
-				lex.eat_error(&ctx);
-				return Err(MissingTermination);
-			}
-
-			// Skip '|||'
-			ctx.skip(3);
-			break;
-		}
-	}
-
-	lex.bump_pos(ctx.index);
-	Ok(())
-}
modifiedxtask/src/sourcegen/kinds.rsdiffbeforeafterboth
--- a/xtask/src/sourcegen/kinds.rs
+++ b/xtask/src/sourcegen/kinds.rs
@@ -56,7 +56,7 @@
 			| Self::Error { name, .. } => name,
 		}
 	}
-	pub fn expand_kind(&self) -> TokenStream {
+	pub fn expand_kind(&self, lexer: bool) -> TokenStream {
 		let name = format_ident!("{}", self.name());
 		let attr = match self {
 			Self::Keyword { code, .. } => quote! {#[token(#code)]},
@@ -75,6 +75,11 @@
 			}
 			_ => quote! {},
 		};
+		let attr = if lexer {
+			attr
+		} else {
+			quote! {}
+		};
 		quote! {
 			#attr
 			#name
modifiedxtask/src/sourcegen/mod.rsdiffbeforeafterboth
--- a/xtask/src/sourcegen/mod.rs
+++ b/xtask/src/sourcegen/mod.rs
@@ -89,7 +89,7 @@
 		kinds.define_node(&name);
 	}
 
-	let syntax_kinds = generate_syntax_kinds(&kinds, &ast)?;
+	let syntax_kinds = generate_syntax_kinds(&kinds, &ast, false)?;
 
 	let nodes = generate_nodes(&kinds, &ast)?;
 	ensure_file_contents(
@@ -106,12 +106,21 @@
 		)),
 		&nodes,
 	);
+
+	let lexer_syntax_kinds = generate_syntax_kinds(&kinds, &ast, true)?;
+	ensure_file_contents(
+		&PathBuf::from(concat!(
+			env!("CARGO_MANIFEST_DIR"),
+			"/../crates/jrsonnet-lexer/src/generated/syntax_kinds.rs",
+		)),
+		&lexer_syntax_kinds,
+	);
 	Ok(())
 }
 
-fn generate_syntax_kinds(kinds: &KindsSrc, grammar: &AstSrc) -> Result<String> {
+fn generate_syntax_kinds(kinds: &KindsSrc, grammar: &AstSrc, lexer: bool) -> Result<String> {
 	let t_macros = kinds.tokens().filter_map(TokenKind::expand_t_macros);
-	let token_kinds = kinds.tokens().map(TokenKind::expand_kind);
+	let token_kinds = kinds.tokens().map(|t| t.expand_kind(lexer));
 
 	let keywords = kinds
 		.tokens()
@@ -119,12 +128,16 @@
 		.map(TokenKind::name)
 		.map(|n| format_ident!("{n}"));
 
-	let nodes = kinds
+	let mut nodes = kinds
 		.nodes
 		.iter()
 		.map(|name| format_ident!("{}", name))
 		.collect::<Vec<_>>();
 
+	if lexer {
+		nodes.clear();
+	}
+
 	let enums = grammar
 		.enums
 		.iter()
@@ -134,14 +147,34 @@
 				.token_enums
 				.iter()
 				.map(|e| format_ident!("{}", to_upper_snake_case(&e.name))),
-		);
+		)
+		.collect::<Vec<_>>();
+	let is_enum = if lexer {
+		quote! {}
+	} else {
+		quote! {
+			pub fn is_enum(self) -> bool {
+				match self {
+					#(#enums)|* => true,
+					_ => false,
+				}
+			}
+		}
+	};
 
+	let derive_logos = if lexer {
+		quote! {
+			, logos::Logos
+		}
+	} else {
+		quote! {}
+	};
+
 	let ast = quote! {
 		#![allow(bad_style, missing_docs, unreachable_pub, clippy::manual_non_exhaustive, clippy::match_like_matches_macro)]
-		use logos::Logos;
 
 		/// The kind of syntax node, e.g. `IDENT`, `USE_KW`, or `STRUCT`.
-		#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Logos)]
+		#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug #derive_logos)]
 		#[repr(u16)]
 		pub enum SyntaxKind {
 			#[doc(hidden)]
@@ -164,13 +197,9 @@
 					_ => false,
 				}
 			}
-			pub fn is_enum(self) -> bool {
-				match self {
-					#(#enums)|* => true,
-					_ => false,
-				}
-			}
 
+			#is_enum
+
 			pub fn from_raw(r: u16) -> Self {
 				assert!(r < Self::__LAST as u16);
 				unsafe { std::mem::transmute(r) }