git.delta.rocks / jrsonnet / refs/commits / 5382799864e9

difftreelog

feat lsp prototype

Yaroslav Bolyukin2022-03-29parent: #609e5ef.patch.diff
in: master

29 files changed

deletedCargo.lockdiffbeforeafterboth

no changes

modifiedCargo.tomldiffbeforeafterboth
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,7 +1,7 @@
 [workspace]
 package.version = "0.5.0-pre95"
 package.repository = "https://github.com/CertainLach/jrsonnet"
-members = ["crates/*", "bindings/jsonnet", "cmds/jrsonnet", "tests"]
+members = ["crates/*", "bindings/jsonnet", "cmds/*", "tests"]
 default-members = ["cmds/jrsonnet"]
 resolver = "2"
 
addedcrates/jrsonnet-rowan-parser/Cargo.tomldiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/Cargo.toml
@@ -0,0 +1,20 @@
+[package]
+name = "jrsonnet-rowan-parser"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+anyhow = "1.0.51"
+backtrace = "0.3.63"
+drop_bomb = "0.1.5"
+indoc = "1.0.3"
+logos = "0.12.0"
+miette = { version = "4.2.1", features = ["fancy"] }
+rowan = "0.15.0"
+text-size = "1.1.0"
+thiserror = "1.0.30"
+
+[dev-dependencies]
+backtrace = "0.3.63"
+indoc = "1.0.3"
+insta = "1.10.0"
addedcrates/jrsonnet-rowan-parser/src/binary.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/binary.rs
@@ -0,0 +1,47 @@
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum BinaryOperator {
+	Mul,
+	Div,
+	Mod,
+	Plus,
+	Minus,
+	ShiftLeft,
+	ShiftRight,
+	LessThan,
+	GreaterThan,
+	LessThanOrEqual,
+	GreaterThanOrEqual,
+	Equal,
+	NotEqual,
+	BitAnd,
+	BitXor,
+	BitOr,
+	And,
+	Or,
+	In,
+	ObjectApply,
+	Invalid,
+}
+
+impl BinaryOperator {
+	pub fn binding_power(&self) -> (u8, u8) {
+		match self {
+			Self::ObjectApply => (22, 23),
+			Self::Mul | Self::Div | Self::Mod => (20, 21),
+			Self::Plus | Self::Minus => (18, 19),
+			Self::ShiftLeft | Self::ShiftRight => (16, 17),
+			Self::LessThan
+			| Self::GreaterThan
+			| Self::LessThanOrEqual
+			| Self::GreaterThanOrEqual
+			| Self::In => (14, 15),
+			Self::Equal | Self::NotEqual => (12, 13),
+			Self::BitAnd => (10, 11),
+			Self::BitXor => (8, 9),
+			Self::BitOr => (6, 7),
+			Self::And => (4, 5),
+			Self::Or => (2, 3),
+			Self::Invalid => (0, 1),
+		}
+	}
+}
addedcrates/jrsonnet-rowan-parser/src/event.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/event.rs
@@ -0,0 +1,107 @@
+use std::mem;
+
+use rowan::{GreenNode, GreenNodeBuilder, Language};
+
+use crate::{
+	lex::{Lang, Lexeme, SyntaxKind},
+	parser::{Parse, SyntaxError},
+};
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum Event {
+	Start {
+		kind: SyntaxKind,
+		forward_parent: Option<usize>,
+	},
+	Token,
+	Finish,
+	Placeholder,
+	Error(SyntaxError),
+}
+
+pub(super) struct Sink<'i> {
+	pub builder: GreenNodeBuilder<'static>,
+	lexemes: &'i [Lexeme<'i>],
+	offset: usize,
+	events: Vec<Event>,
+	pub errors: Vec<SyntaxError>,
+}
+
+impl<'i> Sink<'i> {
+	pub(super) fn new(events: Vec<Event>, lexemes: &'i [Lexeme<'i>]) -> Self {
+		Self {
+			builder: GreenNodeBuilder::new(),
+			lexemes,
+			offset: 0,
+			events,
+			errors: vec![],
+		}
+	}
+
+	pub(super) fn finish(mut self) -> Parse {
+		for idx in 0..self.events.len() {
+			match mem::replace(&mut self.events[idx], Event::Placeholder) {
+				Event::Start {
+					kind,
+					forward_parent,
+				} => {
+					let mut kinds = vec![kind];
+
+					let mut idx = idx;
+					let mut forward_parent = forward_parent;
+
+					// Walk through the forward parent of the forward parent, and the forward parent
+					// of that, and of that, etc. until we reach a StartNode event without a forward
+					// parent.
+					while let Some(fp) = forward_parent {
+						idx += fp;
+
+						forward_parent = if let Event::Start {
+							kind,
+							forward_parent,
+						} = mem::replace(&mut self.events[idx], Event::Placeholder)
+						{
+							kinds.push(kind);
+							forward_parent
+						} else {
+							unreachable!()
+						};
+					}
+
+					for kind in kinds.into_iter().rev() {
+						self.builder.start_node(Lang::kind_to_raw(kind));
+					}
+				}
+				Event::Token => self.token(),
+				Event::Finish => {
+					self.builder.finish_node();
+				}
+				Event::Placeholder => {}
+				Event::Error(e) => {
+					self.errors.push(e);
+				}
+			}
+			self.skip_whitespace();
+		}
+
+		Parse {
+			green_node: self.builder.finish(),
+			errors: self.errors,
+		}
+	}
+	fn token(&mut self) {
+		let lexeme = self.lexemes[self.offset];
+		self.builder
+			.token(Lang::kind_to_raw(lexeme.kind), lexeme.text);
+		self.offset += 1;
+	}
+	fn skip_whitespace(&mut self) {
+		while let Some(lexeme) = self.lexemes.get(self.offset) {
+			if !lexeme.kind.is_trivia() {
+				break;
+			}
+
+			self.token();
+		}
+	}
+}
addedcrates/jrsonnet-rowan-parser/src/lex.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/lex.rs
@@ -0,0 +1,315 @@
+use crate::string_block::lex_str_block_test;
+use core::ops::Range;
+use logos::Logos;
+use rowan::{Checkpoint, TextRange, TextSize};
+use std::{convert::TryFrom, iter::Peekable};
+
+#[derive(Logos, Debug, PartialEq, Hash, Eq, PartialOrd, Ord, Clone, Copy)]
+#[repr(u16)]
+pub enum SyntaxKind {
+	#[token("assert")]
+	KeywordAssert = 0,
+
+	#[token("else")]
+	KeywordElse,
+
+	#[token("error")]
+	KeywordError,
+
+	#[token("false")]
+	KeywordFalse,
+
+	#[token("for")]
+	KeywordFor,
+
+	#[token("function")]
+	KeywordFunction,
+
+	#[token("if")]
+	KeywordIf,
+
+	#[token("import")]
+	KeywordImport,
+
+	#[token("importstr")]
+	KeywordImportStr,
+
+	#[token("local")]
+	KeywordLocal,
+
+	#[token("null")]
+	KeywordNull,
+
+	#[token("tailstrict")]
+	KeywordTailStrict,
+
+	#[token("then")]
+	KeywordThen,
+
+	#[token("self")]
+	KeywordSelf,
+
+	#[token("super")]
+	KeywordSuper,
+
+	#[token("true")]
+	KeywordTrue,
+
+	#[regex(r"[_a-zA-Z][_a-zA-Z0-9]*")]
+	Ident,
+
+	#[regex(r"(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?")]
+	Number,
+
+	#[regex(r"(?:0|[1-9][0-9]*)\.[^0-9]")]
+	ErrorNumJunkAfterDecimalPoint,
+
+	#[regex(r"(?:0|[1-9][0-9]*)(?:\.[0-9]+)?[eE][^+\-0-9]")]
+	ErrorNumJunkAfterExponent,
+
+	#[regex(r"(?:0|[1-9][0-9]*)(?:\.[0-9]+)?[eE][+-][^0-9]")]
+	ErrorNumJunkAfterExponentSign,
+
+	#[token("{")]
+	SymbolLeftBrace,
+
+	#[token("}")]
+	SymbolRightBrace,
+
+	#[token("[")]
+	SymbolLeftBracket,
+
+	#[token("]")]
+	SymbolRightBracket,
+
+	#[token(",")]
+	SymbolComma,
+
+	#[token(".")]
+	SymbolDot,
+
+	#[token("(")]
+	LParen,
+
+	#[token(")")]
+	RParen,
+
+	#[token(";")]
+	SymbolSemi,
+	#[token(":")]
+	SymbolColon,
+
+	#[token("$")]
+	SymbolDollar,
+
+	#[token("*")]
+	OpMul,
+	#[token("/")]
+	OpDiv,
+	#[token("%")]
+	OpMod,
+	#[token("+")]
+	OpPlus,
+	#[token("-")]
+	OpMinus,
+	#[token("<<")]
+	OpShiftLeft,
+	#[token(">>")]
+	OpShiftRight,
+	#[token("<")]
+	OpLessThan,
+	#[token(">")]
+	OpGreaterThan,
+	#[token("<=")]
+	OpLessThanOrEqual,
+	#[token(">=")]
+	OpGreaterThanOrEqual,
+	#[token("==")]
+	OpEqual,
+	#[token("!=")]
+	OpNotEqual,
+	#[token("&")]
+	OpBitAnd,
+	#[token("^")]
+	OpBitXor,
+	#[token("|")]
+	OpBitOr,
+	#[token("&&")]
+	OpAnd,
+	#[token("||")]
+	OpOr,
+	#[token("in")]
+	OpIn,
+	#[token("!")]
+	OpNot,
+	#[token("~")]
+	OpBitNegate,
+	#[token("=")]
+	SymbolAssign,
+
+	#[regex("\"(?s:[^\"\\\\]|\\\\.)*\"")]
+	StringDoubleQuoted,
+
+	#[regex("'(?s:[^'\\\\]|\\\\.)*'")]
+	StringSingleQuoted,
+
+	#[regex("@\"(?:[^\"]|\"\")*\"")]
+	StringDoubleVerbatim,
+
+	#[regex("@'(?:[^']|'')*'")]
+	StringSingleVerbatim,
+
+	#[regex(r"\|\|\|", lex_str_block_test)]
+	StringBlock, //(StringBlockToken),
+
+	#[regex("\"(?s:[^\"\\\\]|\\\\.)*")]
+	ErrorStringDoubleQuotedUnterminated,
+
+	#[regex("'(?s:[^'\\\\]|\\\\.)*")]
+	ErrorStringSingleQuotedUnterminated,
+
+	#[regex("@\"(?:[^\"]|\"\")*")]
+	ErrorStringDoubleVerbatimUnterminated,
+
+	#[regex("@'(?:[^']|'')*")]
+	ErrorStringSingleVerbatimUnterminated,
+
+	#[regex("@[^\"'\\s]\\S+")]
+	ErrorStringMissingQuotes,
+
+	#[token("/*/")]
+	ErrorCommentTooShort,
+
+	#[regex(r"/\*([^*]|\*[^/])+")]
+	ErrorCommentUnterminated,
+
+	#[regex(r"[ \t\n\r]+")]
+	Whitespace,
+
+	#[regex(r"//[^\r\n]*(\r\n|\n)?")]
+	SingelLineSlashComment,
+
+	#[regex(r"#[^\r\n]*(\r\n|\n)?")]
+	SingleLineHashComment,
+
+	#[regex(r"/\*([^*]|\*[^/])*\*/")]
+	MultiLineComment,
+
+	#[error]
+	Error,
+
+	ErrorPositionalAfterNamed,
+
+	Literal,
+	Expr,
+	Array,
+	ArrayElem,
+	Object,
+	Field,
+
+	CompspecFor,
+	CompspecIf,
+
+	Slice,
+	FieldAccess,
+	ObjectApply,
+	FunctionCall,
+	FunctionDef,
+	BodyDef,
+
+	BinOp,
+	UnaryOp,
+	Local,
+	ExprError,
+	ExprAssert,
+	ExprImport,
+
+	DefParam,
+	DefParams,
+
+	DefArgs,
+	DefNamedArg,
+	DefPositionalArg,
+
+	Parened,
+
+	Root,
+}
+
+impl SyntaxKind {
+	pub fn is_trivia(self) -> bool {
+		matches!(
+			self,
+			Self::Whitespace
+				| Self::MultiLineComment
+				| Self::SingelLineSlashComment
+				| Self::SingleLineHashComment
+		)
+	}
+}
+
+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> {
+		let kind = self.inner.next()?;
+		let text = self.inner.slice();
+
+		Some(Self::Item {
+			kind,
+			text,
+			range: {
+				let Range { start, end } = self.inner.span();
+
+				TextRange::new(
+					TextSize::try_from(start).unwrap(),
+					TextSize::try_from(end).unwrap(),
+				)
+			},
+		})
+	}
+}
+
+#[derive(Clone, Copy)]
+pub struct Lexeme<'i> {
+	pub kind: SyntaxKind,
+	pub text: &'i str,
+	pub range: TextRange,
+}
+
+pub fn lex(input: &str) -> Vec<Lexeme<'_>> {
+	Lexer::new(input).collect()
+}
+
+impl From<SyntaxKind> for rowan::SyntaxKind {
+	fn from(kind: SyntaxKind) -> Self {
+		Self(kind as u16)
+	}
+}
+
+use SyntaxKind::*;
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum Lang {}
+impl rowan::Language for Lang {
+	type Kind = SyntaxKind;
+	fn kind_from_raw(raw: rowan::SyntaxKind) -> Self::Kind {
+		assert!(raw.0 <= Root as u16);
+		unsafe { std::mem::transmute::<u16, SyntaxKind>(raw.0) }
+	}
+	fn kind_to_raw(kind: Self::Kind) -> rowan::SyntaxKind {
+		kind.into()
+	}
+}
addedcrates/jrsonnet-rowan-parser/src/lib.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/lib.rs
@@ -0,0 +1,139 @@
+#![deny(unused_must_use)]
+
+mod binary;
+mod event;
+mod lex;
+mod marker;
+mod parser;
+mod string_block;
+mod token_set;
+mod unary;
+
+#[cfg(test)]
+mod tests {
+	use miette::{Diagnostic, GraphicalReportHandler, LabeledSpan};
+	use thiserror::Error;
+
+	use crate::parser::parse;
+
+	#[derive(Debug, Error)]
+	#[error("syntax error")]
+	struct MyDiagnostic {
+		code: String,
+		spans: Vec<LabeledSpan>,
+	}
+	impl Diagnostic for MyDiagnostic {
+		fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
+			None
+		}
+
+		fn severity(&self) -> Option<miette::Severity> {
+			None
+		}
+
+		fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
+			None
+		}
+
+		fn url<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
+			None
+		}
+
+		fn source_code(&self) -> Option<&dyn miette::SourceCode> {
+			Some(&self.code)
+		}
+
+		fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
+			Some(Box::new(self.spans.clone().into_iter()))
+		}
+
+		fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
+			None
+		}
+	}
+
+	fn process(text: &str) -> String {
+		use std::fmt::Write;
+		let mut out = String::new();
+		let node = parse(text);
+		write!(out, "{:#?}", node.syntax()).unwrap();
+		if !node.errors.is_empty() && !text.is_empty() {
+			writeln!(out, "===").unwrap();
+			for err in &node.errors {
+				writeln!(out, "{:?}", err).unwrap();
+			}
+			let diag = MyDiagnostic {
+				code: text.to_string(),
+				spans: node.errors.into_iter().map(|e| e.into()).collect(),
+			};
+
+			let handler = GraphicalReportHandler::new();
+
+			write!(out, "===").unwrap();
+			handler.render_report(&mut out, &diag).unwrap();
+		}
+		out
+	}
+	macro_rules! mk_test {
+		($($name:ident => $test:expr)+) => {$(
+			#[test]
+			fn $name() {
+				let src = indoc::indoc!($test);
+				let result = process(&src);
+				insta::assert_snapshot!(stringify!($name), result, src);
+
+			}
+		)+};
+	}
+	mk_test!(
+		empty => r#" "#
+		function => r#"
+			function(a, b = 1) a + b
+		"#
+		function_error_no_value => r#"
+			function(a, b = ) a + b
+		"#
+		function_error_rparen => r#"
+			function(a, b
+		"#
+		function_error_body => r#"
+			function(a, b)
+		"#
+		local_novalue => r#"
+			local a =
+		"#
+		local_no_value_recovery => r#"
+			local a =
+			local b = 3;
+			1
+		"#
+
+		array_comp => r#"
+			[a for a in [1, 2, 3]]
+		"#
+		array_comp_incompatible_with_multiple_elems => r#"
+			[a for a in [1, 2, 3], b]
+		"#
+
+		no_rhs => r#"
+			a +
+		"#
+		no_lhs => r#"
+			+ 2
+		"#
+		no_operator => "
+			2 2
+		"
+
+		named_before_positional => "
+			a(1, 2, b=4, 3, 5, k = 12, 6)
+		"
+
+		wrong_field_end => "
+			{
+				a: 1;
+				b: 2;
+			}
+		"
+	);
+}
addedcrates/jrsonnet-rowan-parser/src/marker.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/marker.rs
@@ -0,0 +1,114 @@
+use drop_bomb::DropBomb;
+use rowan::TextRange;
+
+use crate::{event::Event, lex::SyntaxKind, parser::Parser};
+
+pub struct Ranger {
+	pub pos: usize,
+}
+impl Ranger {
+	pub fn finish(mut self, p: &Parser) -> FinishedRanger {
+		FinishedRanger {
+			start_token: self.pos,
+			end_token: self.pos.max(p.offset.saturating_sub(1)),
+		}
+	}
+}
+
+pub struct FinishedRanger {
+	pub start_token: usize,
+	pub end_token: usize,
+}
+impl FinishedRanger {
+	pub fn had_error_since(&self, p: &Parser) -> bool {
+		p.last_error_token >= self.start_token
+	}
+}
+
+#[must_use]
+pub struct Marker {
+	pub start_event_idx: usize,
+	pub token: usize,
+	bomb: DropBomb,
+}
+impl Marker {
+	pub fn new(pos: usize, token: usize) -> Self {
+		Self {
+			start_event_idx: pos,
+			token,
+			bomb: DropBomb::new("marked dropped while not completed"),
+		}
+	}
+	pub fn complete(mut self, p: &mut Parser, kind: SyntaxKind) -> CompletedMarker {
+		self.bomb.defuse();
+		let event_at_pos = &mut p.events[self.start_event_idx];
+		assert_eq!(*event_at_pos, Event::Placeholder);
+
+		*event_at_pos = Event::Start {
+			kind,
+			forward_parent: None,
+		};
+
+		p.events.push(Event::Finish);
+		p.entered -= 1;
+		p.clear_outdated_hints();
+		CompletedMarker {
+			start_event_idx: self.start_event_idx,
+			start_token: self.token,
+			end_token: self.token.max(p.offset.saturating_sub(1)),
+		}
+	}
+}
+pub struct CompletedMarker {
+	start_event_idx: usize,
+	pub start_token: usize,
+	pub end_token: usize,
+}
+impl CompletedMarker {
+	pub(super) fn precede(self, p: &mut Parser) -> Marker {
+		let mut new_m = p.start();
+		new_m.token = self.start_token;
+
+		if let Event::Start {
+			ref mut forward_parent,
+			..
+		} = p.events[self.start_event_idx]
+		{
+			*forward_parent = Some(new_m.start_event_idx - self.start_event_idx);
+		} else {
+			unreachable!();
+		}
+
+		new_m
+	}
+}
+
+pub trait AsRange {
+	fn as_range(&self, p: &Parser) -> TextRange;
+	fn end_token(&self) -> usize;
+}
+
+impl AsRange for CompletedMarker {
+	fn as_range(&self, p: &Parser) -> TextRange {
+		TextRange::new(
+			p.start_of_token(self.start_token),
+			p.end_of_token(self.end_token),
+		)
+	}
+	fn end_token(&self) -> usize {
+		self.end_token
+	}
+}
+
+impl AsRange for FinishedRanger {
+	fn as_range(&self, p: &Parser) -> TextRange {
+		TextRange::new(
+			p.start_of_token(self.start_token),
+			p.end_of_token(self.end_token),
+		)
+	}
+
+	fn end_token(&self) -> usize {
+		self.end_token
+	}
+}
addedcrates/jrsonnet-rowan-parser/src/parser.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/parser.rs
@@ -0,0 +1,809 @@
+use std::cell::Cell;
+use std::fmt::Display;
+use std::rc::Rc;
+
+use miette::Diagnostic;
+use miette::LabeledSpan;
+use miette::SourceOffset;
+use miette::SourceSpan;
+use rowan::GreenNode;
+
+use rowan::TextRange;
+use rowan::TextSize;
+use thiserror::Error;
+
+use crate::binary::BinaryOperator;
+use crate::event::Event;
+use crate::event::Sink;
+use crate::lex::lex;
+use crate::lex::Lang;
+use crate::lex::Lexeme;
+use crate::lex::SyntaxKind;
+use crate::lex::SyntaxKind::*;
+use crate::marker::AsRange;
+use crate::marker::CompletedMarker;
+use crate::marker::FinishedRanger;
+use crate::marker::Marker;
+use crate::marker::Ranger;
+use crate::token_set::TokenSet;
+use crate::unary::UnaryOperator;
+
+pub struct Parse {
+	pub green_node: GreenNode,
+	pub errors: Vec<SyntaxError>,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub enum ExpectedSyntax {
+	Named(&'static str),
+	Unnamed(SyntaxKind),
+}
+impl Display for ExpectedSyntax {
+	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+		match self {
+			ExpectedSyntax::Named(n) => write!(f, "{}", n),
+			ExpectedSyntax::Unnamed(u) => write!(f, "{:?}", u),
+		}
+	}
+}
+
+pub struct Parser<'i> {
+	lexemes: &'i [Lexeme<'i>],
+	pub offset: usize,
+	pub events: Vec<Event>,
+	pub entered: u32,
+	pub hints: Vec<(u32, TextRange, String)>,
+	pub last_error_token: usize,
+	expected_syntax: Option<ExpectedSyntax>,
+	expected_syntax_tracking_state: Rc<Cell<ExpectedSyntaxTrackingState>>,
+}
+
+const DEFAULT_RECOVERY_SET: TokenSet = TokenSet::new(&[
+	SymbolSemi,
+	RParen,
+	SymbolRightBracket,
+	SymbolRightBrace,
+	KeywordLocal,
+]);
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum SyntaxError {
+	Unexpected {
+		expected: ExpectedSyntax,
+		found: SyntaxKind,
+		range: TextRange,
+	},
+	Missing {
+		expected: ExpectedSyntax,
+		offset: TextSize,
+	},
+	Custom {
+		error: String,
+		range: TextRange,
+	},
+	Hint {
+		error: String,
+		range: TextRange,
+	},
+}
+
+impl Into<LabeledSpan> for SyntaxError {
+	fn into(self) -> LabeledSpan {
+		match self {
+			SyntaxError::Unexpected {
+				expected,
+				found,
+				range,
+			} => LabeledSpan::new_with_span(
+				Some(format!("expected {}, found {:?}", expected, found)),
+				SourceSpan::new(
+					SourceOffset::from(usize::from(range.start())),
+					SourceOffset::from(usize::from(range.end() - range.start())),
+				),
+			),
+			SyntaxError::Missing { expected, offset } => LabeledSpan::new_with_span(
+				Some(format!("missing {}", expected)),
+				SourceSpan::new(
+					SourceOffset::from(usize::from(offset)),
+					SourceOffset::from(0),
+				),
+			),
+			SyntaxError::Custom { error, range } | SyntaxError::Hint { error, range } => {
+				LabeledSpan::new_with_span(
+					Some(format!("{}", error)),
+					SourceSpan::new(
+						SourceOffset::from(usize::from(range.start())),
+						SourceOffset::from(usize::from(range.end() - range.start())),
+					),
+				)
+			}
+		}
+	}
+}
+
+impl<'i> Parser<'i> {
+	fn new(lexemes: &'i [Lexeme<'i>]) -> Self {
+		Self {
+			lexemes,
+			offset: 0,
+			events: vec![],
+			entered: 0,
+			last_error_token: 0,
+			hints: vec![],
+			expected_syntax: None,
+			expected_syntax_tracking_state: Rc::new(Cell::new(
+				ExpectedSyntaxTrackingState::Unnamed,
+			)),
+		}
+	}
+	pub fn clear_outdated_hints(&mut self) {
+		let amount = self
+			.hints
+			.iter()
+			.rev()
+			.take_while(|h| h.0 > self.entered)
+			.count();
+		self.hints.truncate(self.hints.len() - amount)
+	}
+	fn clear_expected_syntaxes(&mut self) {
+		self.expected_syntax = None;
+		self.expected_syntax_tracking_state
+			.set(ExpectedSyntaxTrackingState::Unnamed);
+	}
+	pub fn start(&mut self) -> Marker {
+		let start_event_idx = self.events.len();
+		self.events.push(Event::Placeholder);
+		self.entered += 1;
+		Marker::new(start_event_idx, self.offset)
+	}
+	pub fn start_ranger(&mut self) -> Ranger {
+		let pos = self.offset;
+		Ranger { pos }
+	}
+	fn parse(mut self) -> Vec<Event> {
+		let m = self.start();
+		expr(&mut self);
+		if !self.at_end() {
+			let ranger = self.start_ranger();
+
+			while self.peek().is_some() {
+				self.bump()
+			}
+			let end = ranger.finish(&self);
+			self.custom_error(end, "unexpected input after expression");
+		}
+		m.complete(&mut self, Root);
+
+		self.events
+	}
+
+	pub(crate) fn expect(&mut self, kind: SyntaxKind) {
+		self.expect_with_recovery_set(kind, TokenSet::default())
+	}
+
+	pub(crate) fn expect_with_recovery_set(&mut self, kind: SyntaxKind, recovery_set: TokenSet) {
+		if self.at(kind) {
+			self.bump();
+		} else {
+			self.error_with_recovery_set(recovery_set);
+		}
+	}
+
+	pub(crate) fn expect_with_no_skip(&mut self, kind: SyntaxKind) {
+		if self.at(kind) {
+			self.bump();
+		} else {
+			self.error_with_no_skip();
+		}
+	}
+	pub(crate) fn last_token_range(&self) -> Option<TextRange> {
+		self.lexemes.last().map(|Lexeme { range, .. }| *range)
+	}
+	fn current_token(&self) -> Lexeme<'i> {
+		self.lexemes[self.offset]
+	}
+	fn previous_token(&mut self) -> Option<Lexeme<'i>> {
+		if self.offset == 0 {
+			return None;
+		}
+		let mut previous_token_idx = self.offset - 1;
+		while self
+			.lexemes
+			.get(previous_token_idx)
+			.map_or(false, |l| l.kind.is_trivia())
+			&& previous_token_idx != 0
+		{
+			previous_token_idx -= 1;
+		}
+
+		Some(self.lexemes[previous_token_idx])
+	}
+	pub fn start_of_token(&self, mut idx: usize) -> TextSize {
+		while self.lexemes[idx].kind.is_trivia() {
+			idx += 1;
+		}
+		self.lexemes[idx].range.start()
+	}
+	pub fn end_of_token(&self, mut idx: usize) -> TextSize {
+		while self.lexemes[idx].kind.is_trivia() {
+			idx -= 1;
+		}
+		self.lexemes[idx].range.end()
+	}
+	pub(crate) fn custom_error(&mut self, marker: impl AsRange, error: impl AsRef<str>) {
+		self.last_error_token = marker.end_token();
+		self.events.push(Event::Error(SyntaxError::Custom {
+			error: error.as_ref().to_string(),
+			range: marker.as_range(self),
+		}));
+	}
+	pub(crate) fn error_with_recovery_set(
+		&mut self,
+		recovery_set: TokenSet,
+	) -> Option<CompletedMarker> {
+		self.error_with_recovery_set_no_default(recovery_set.union(DEFAULT_RECOVERY_SET))
+	}
+	pub fn error_with_no_skip(&mut self) -> Option<CompletedMarker> {
+		self.error_with_recovery_set_no_default(TokenSet::ALL)
+	}
+
+	pub fn error_with_recovery_set_no_default(
+		&mut self,
+		recovery_set: TokenSet,
+	) -> Option<CompletedMarker> {
+		let expected_syntax = self.expected_syntax.take().unwrap();
+		self.expected_syntax_tracking_state
+			.set(ExpectedSyntaxTrackingState::Unnamed);
+
+		if self.at_end() || self.at_set(recovery_set) {
+			let range = self
+				.previous_token()
+				.map(|t| t.range)
+				.unwrap_or(TextRange::at(TextSize::from(0), TextSize::from(0)));
+
+			self.events.push(Event::Error(SyntaxError::Missing {
+				expected: expected_syntax,
+				offset: range.end(),
+			}));
+			return None;
+		}
+
+		let current_token = self.current_token();
+
+		self.events.push(Event::Error(SyntaxError::Unexpected {
+			expected: expected_syntax.clone(),
+			found: current_token.kind,
+			range: current_token.range,
+		}));
+		self.clear_expected_syntaxes();
+		self.last_error_token = self.offset;
+
+		let m = self.start();
+		self.bump();
+		Some(m.complete(self, SyntaxKind::Error))
+	}
+
+	fn bump(&mut self) {
+		self.skip_trivia();
+		assert_ne!(self.offset, self.lexemes.len(), "already at end");
+		self.events.push(Event::Token);
+		self.offset += 1;
+		self.clear_expected_syntaxes();
+	}
+	fn peek(&mut self) -> Option<SyntaxKind> {
+		self.skip_trivia();
+		self.peek_raw()
+	}
+	pub fn peek_token(&mut self) -> Option<&Lexeme<'i>> {
+		self.skip_trivia();
+		self.peek_token_raw()
+	}
+	fn skip_trivia(&mut self) {
+		while self.peek_raw().map(|c| c.is_trivia()).unwrap_or(false) {
+			self.offset += 1;
+		}
+	}
+	fn peek_raw(&mut self) -> Option<SyntaxKind> {
+		self.lexemes.get(self.offset).map(|l| l.kind)
+	}
+	fn peek_token_raw(&mut self) -> Option<&Lexeme<'i>> {
+		self.lexemes.get(self.offset)
+	}
+	#[must_use]
+	pub(crate) fn expected_syntax_name(&mut self, name: &'static str) -> ExpectedSyntaxGuard {
+		self.expected_syntax_tracking_state
+			.set(ExpectedSyntaxTrackingState::Named);
+		self.expected_syntax = Some(ExpectedSyntax::Named(name));
+
+		ExpectedSyntaxGuard::new(Rc::clone(&self.expected_syntax_tracking_state))
+	}
+	pub fn at(&mut self, kind: SyntaxKind) -> bool {
+		if let ExpectedSyntaxTrackingState::Unnamed = self.expected_syntax_tracking_state.get() {
+			self.expected_syntax = Some(ExpectedSyntax::Unnamed(kind));
+		}
+		self.peek() == Some(kind)
+	}
+	pub fn at_set(&mut self, set: TokenSet) -> bool {
+		self.peek().map_or(false, |k| set.contains(k))
+	}
+	pub fn at_end(&mut self) -> bool {
+		self.peek().is_none()
+	}
+}
+pub(crate) struct ExpectedSyntaxGuard {
+	expected_syntax_tracking_state: Rc<Cell<ExpectedSyntaxTrackingState>>,
+}
+
+impl ExpectedSyntaxGuard {
+	fn new(expected_syntax_tracking_state: Rc<Cell<ExpectedSyntaxTrackingState>>) -> Self {
+		Self {
+			expected_syntax_tracking_state,
+		}
+	}
+}
+
+impl Drop for ExpectedSyntaxGuard {
+	fn drop(&mut self) {
+		self.expected_syntax_tracking_state
+			.set(ExpectedSyntaxTrackingState::Unnamed);
+	}
+}
+
+#[derive(Debug, Clone, Copy)]
+enum ExpectedSyntaxTrackingState {
+	Named,
+	Unnamed,
+}
+macro_rules! at_match {
+	($p:ident {
+		$($r:ident => $e:expr,)*
+		_ => $else:expr $(,)?
+	}) => {{
+		$(
+			if $p.at($r) {$e} else
+		)* {
+			$else
+		}
+	}}
+}
+
+fn expr(p: &mut Parser) {
+	expr_binding_power(p, 0);
+}
+fn expr_binding_power(p: &mut Parser, minimum_binding_power: u8) -> Option<CompletedMarker> {
+	let mut lhs = lhs(p)?;
+
+	loop {
+		let op = at_match!(p {
+			OpMul => BinaryOperator::Mul,
+			OpDiv => BinaryOperator::Div,
+			OpMod => BinaryOperator::Mod,
+			OpPlus => BinaryOperator::Plus,
+			OpMinus => BinaryOperator::Minus,
+			OpShiftLeft => BinaryOperator::ShiftLeft,
+			OpShiftRight => BinaryOperator::ShiftRight,
+			OpLessThan => BinaryOperator::LessThan,
+			OpGreaterThan => BinaryOperator::GreaterThan,
+			OpLessThanOrEqual => BinaryOperator::LessThanOrEqual,
+			OpGreaterThanOrEqual => BinaryOperator::GreaterThanOrEqual,
+			OpEqual => BinaryOperator::Equal,
+			OpNotEqual => BinaryOperator::NotEqual,
+			OpBitAnd => BinaryOperator::BitAnd,
+			OpBitXor => BinaryOperator::BitXor,
+			OpBitOr => BinaryOperator::BitOr,
+			OpAnd => BinaryOperator::And,
+			OpOr => BinaryOperator::Or,
+			OpIn => BinaryOperator::In,
+			SymbolLeftBrace => BinaryOperator::ObjectApply,
+			_ => break,
+		});
+		let (left_binding_power, right_binding_power) = op.binding_power();
+		if left_binding_power < minimum_binding_power {
+			break;
+		}
+
+		// Object apply is not a real operator, we dont have something to bump
+		if op != BinaryOperator::ObjectApply {
+			p.bump();
+		}
+
+		let m = lhs.precede(p);
+		let parsed_rhs = expr_binding_power(p, right_binding_power).is_some();
+		lhs = m.complete(
+			p,
+			if op == BinaryOperator::ObjectApply {
+				ObjectApply
+			} else {
+				BinOp
+			},
+		);
+
+		if !parsed_rhs {
+			break;
+		}
+	}
+	Some(lhs)
+}
+fn compspec(p: &mut Parser) {
+	assert!(p.at(KeywordFor) || p.at(KeywordIf));
+	if p.at(KeywordFor) {
+		let m = p.start();
+		p.bump();
+		p.expect(Ident);
+		p.expect(OpIn);
+		expr(p);
+		m.complete(p, CompspecFor);
+	} else if p.at(KeywordIf) {
+		let m = p.start();
+		p.bump();
+		expr(p);
+		m.complete(p, CompspecIf);
+	} else {
+		unreachable!()
+	}
+}
+fn comma(p: &mut Parser) -> bool {
+	if p.at(SymbolComma) {
+		p.bump();
+		true
+	} else {
+		false
+	}
+}
+fn comma_with_alternatives(p: &mut Parser, set: TokenSet) -> bool {
+	if p.at(SymbolComma) {
+		p.bump();
+		true
+	} else if p.at_set(set) {
+		p.expect_with_no_skip(SymbolComma);
+		p.bump();
+		true
+	} else {
+		false
+	}
+}
+fn field_name(p: &mut Parser) {
+	let _e = p.expected_syntax_name("field name");
+	if p.at(SymbolLeftBracket) {
+		p.bump();
+		expr(p);
+		p.expect(SymbolRightBracket);
+	} else if p.at(Ident) {
+		p.bump()
+	} else {
+		p.error_with_recovery_set(TokenSet::new(&[SymbolSemi]));
+	}
+}
+fn object(p: &mut Parser) -> CompletedMarker {
+	assert!(p.at(SymbolLeftBrace));
+	let m = p.start();
+	p.bump();
+
+	loop {
+		if p.at(SymbolRightBrace) {
+			p.bump();
+			break;
+		}
+		let m = p.start();
+		field_name(p);
+		p.expect(SymbolColon);
+		expr(p);
+		while p.at(KeywordFor) || p.at(KeywordIf) {
+			compspec(p)
+		}
+		m.complete(p, Field);
+		if comma_with_alternatives(p, TokenSet::new(&[SymbolAssign])) {
+			continue;
+		}
+		p.expect(SymbolRightBrace);
+		break;
+	}
+
+	m.complete(p, Object)
+}
+
+fn params(p: &mut Parser) -> CompletedMarker {
+	assert!(p.at(LParen));
+	let m = p.start();
+	p.bump();
+
+	loop {
+		if p.at(RParen) {
+			p.bump();
+			break;
+		}
+		let m = p.start();
+		p.expect(Ident);
+		if p.at(SymbolAssign) {
+			p.bump();
+			expr(p);
+		}
+		m.complete(p, DefParam);
+		if comma(p) {
+			continue;
+		}
+		p.expect(RParen);
+		break;
+	}
+
+	m.complete(p, DefParams)
+}
+fn args(p: &mut Parser) {
+	assert!(p.at(LParen));
+	p.bump();
+
+	let mut error_positional_start = None::<Marker>;
+	let mut started_named = Cell::new(false);
+	let mut on_positional = |p: &mut Parser, m: Marker| {
+		let c = m.complete(p, DefPositionalArg);
+		if started_named.get() && error_positional_start.is_none() {
+			error_positional_start = Some(c.precede(p));
+		}
+	};
+	loop {
+		if p.at(RParen) {
+			break;
+		}
+
+		let m = p.start();
+		if p.at(Ident) {
+			p.bump();
+			if p.at(SymbolAssign) {
+				p.bump();
+				expr(p);
+				m.complete(p, DefNamedArg);
+				started_named.set(true);
+			} else {
+				on_positional(p, m);
+			}
+		} else {
+			expr(p);
+			on_positional(p, m);
+		}
+		if comma(p) {
+			continue;
+		}
+		break;
+	}
+	if let Some(error_positional_start) = error_positional_start {
+		let c = error_positional_start.complete(p, ErrorPositionalAfterNamed);
+		p.custom_error(c, "positional arguments can't be placed after named")
+	}
+	p.expect(RParen);
+}
+
+fn array(p: &mut Parser) -> CompletedMarker {
+	assert!(p.at(SymbolLeftBracket));
+	// Start the list node
+	let m = p.start();
+	p.bump(); // '['
+
+	// This vec will have at most one element in case of correct input
+	let mut compspecs = Vec::with_capacity(1);
+	let mut elems = 0;
+
+	loop {
+		if p.at(SymbolRightBracket) {
+			p.bump();
+			break;
+		}
+		elems += 1;
+		let m = p.start();
+		{
+			let m = p.start();
+			expr(p);
+			m.complete(p, BodyDef);
+		}
+		let c = p.start_ranger();
+		let mut had_spec = false;
+		while p.at(KeywordFor) || p.at(KeywordIf) {
+			had_spec = true;
+			compspec(p)
+		}
+		if had_spec {
+			compspecs.push(c.finish(p));
+		}
+		m.complete(p, ArrayElem);
+		if comma(p) {
+			continue;
+		}
+		p.expect(SymbolRightBracket);
+		break;
+	}
+
+	if elems > 1 && !compspecs.is_empty() {
+		for spec in compspecs {
+			p.custom_error(
+				spec,
+				"compspec may only be used if there is only one array element",
+			)
+		}
+	}
+
+	m.complete(p, Array)
+}
+
+fn lhs(p: &mut Parser) -> Option<CompletedMarker> {
+	let mut lhs = lhs_basic(p)?;
+
+	loop {
+		if p.at(SymbolDot) {
+			let m = lhs.precede(p);
+			p.bump();
+			p.expect(Ident);
+			lhs = m.complete(p, FieldAccess);
+		} else if p.at(SymbolLeftBracket) {
+			let m = lhs.precede(p);
+			p.bump();
+			// Start
+			if !p.at(SymbolColon) {
+				expr(p);
+			}
+			if p.at(SymbolColon) {
+				p.bump();
+				// End
+				if !p.at(SymbolRightBracket) && !p.at(SymbolColon) {
+					expr(p);
+				}
+				if p.at(SymbolColon) {
+					p.bump();
+					// Step
+					if !p.at(SymbolRightBracket) {
+						expr(p);
+					}
+				}
+			}
+			p.expect(SymbolRightBracket);
+			lhs = m.complete(p, Slice);
+		} else if p.at(LParen) {
+			let m = lhs.precede(p);
+			args(p);
+			lhs = m.complete(p, FunctionCall);
+		} else {
+			break;
+		}
+	}
+
+	Some(lhs)
+}
+
+fn lhs_basic(p: &mut Parser) -> Option<CompletedMarker> {
+	let _e = p.expected_syntax_name("value");
+	Some(
+		if p.at(Number)
+			|| p.at(StringSingleQuoted)
+			|| p.at(StringDoubleQuoted)
+			|| p.at(StringSingleVerbatim)
+			|| p.at(StringDoubleVerbatim)
+			|| p.at(StringBlock)
+			|| p.at(KeywordNull)
+			|| p.at(SymbolDollar)
+			|| p.at(KeywordSuper)
+			|| p.at(KeywordSelf)
+		{
+			let m = p.start();
+			p.bump();
+			m.complete(p, Literal)
+		} else if p.at(Ident) {
+			let m = p.start();
+			p.bump();
+			m.complete(p, Ident)
+		} else if p.at(SymbolLeftBracket) {
+			array(p)
+		} else if p.at(SymbolLeftBrace) {
+			object(p)
+		} else if p.at(KeywordLocal) {
+			let m = p.start();
+			p.bump();
+			let mut sus_local = None;
+			loop {
+				p.expect_with_recovery_set(
+					Ident,
+					TokenSet::new(&[SymbolAssign, SymbolSemi, KeywordLocal]),
+				);
+				if p.at(LParen) {
+					params(p);
+				}
+
+				let sus_local_candidate = p.start_ranger();
+				p.expect_with_recovery_set(
+					SymbolAssign,
+					TokenSet::new(&[SymbolSemi, KeywordLocal]),
+				);
+
+				sus_local = p.at(KeywordLocal).then(|| sus_local_candidate.finish(p));
+				expr(p);
+
+				if !comma(p) {
+					break;
+				}
+			}
+			p.expect(SymbolSemi);
+			if let Some(sus_local) = sus_local {
+				if sus_local.had_error_since(p) {
+					p.custom_error(sus_local, "unusal local placement, missing ';' ?")
+				}
+			}
+			{
+				let m = p.start();
+				expr(p);
+				m.complete(p, BodyDef);
+			}
+			m.complete(p, Local)
+		} else if p.at(KeywordFunction) {
+			let m = p.start();
+			p.bump();
+			args(p);
+			{
+				let m = p.start();
+				expr(p);
+				m.complete(p, BodyDef);
+			}
+			m.complete(p, FunctionDef)
+		} else if p.at(KeywordError) {
+			let m = p.start();
+			p.bump();
+			expr(p);
+			m.complete(p, ExprError)
+		} else if p.at(KeywordAssert) {
+			let m = p.start();
+			p.bump();
+			expr(p);
+			if p.at(SymbolColon) {
+				p.bump();
+				expr(p);
+			}
+			m.complete(p, ExprAssert)
+		} else if p.at(KeywordImport) || p.at(KeywordImportStr) {
+			let m = p.start();
+			p.bump();
+			expr(p);
+			m.complete(p, ExprImport)
+		} else if p.at(OpMinus) || p.at(OpNot) || p.at(OpBitNegate) {
+			let op = match p.peek().unwrap() {
+				OpMinus => UnaryOperator::Minus,
+				OpNot => UnaryOperator::Not,
+				OpBitNegate => UnaryOperator::BitNegate,
+				_ => unreachable!(),
+			};
+			let ((), right_binding_power) = op.binding_power();
+
+			let m = p.start();
+			p.bump();
+			expr_binding_power(p, right_binding_power);
+			m.complete(p, UnaryOp)
+		} else if p.at(LParen) {
+			let m = p.start();
+			p.bump();
+			expr(p);
+			assert!(p.at(RParen));
+			p.bump();
+			m.complete(p, Parened)
+		} else {
+			p.error_with_no_skip();
+			return None;
+		},
+	)
+}
+
+type SyntaxNode = rowan::SyntaxNode<Lang>;
+#[allow(unused)]
+type SyntaxToken = rowan::SyntaxToken<Lang>;
+#[allow(unused)]
+type SyntaxElement = rowan::NodeOrToken<SyntaxNode, SyntaxToken>;
+
+impl Parse {
+	pub fn syntax(&self) -> SyntaxNode {
+		SyntaxNode::new_root(self.green_node.clone())
+	}
+}
+
+pub fn parse(input: &str) -> Parse {
+	let lexemes = lex(input);
+	let parser = Parser::new(&lexemes);
+	let events = parser.parse();
+	dbg!(&events);
+	let sink = Sink::new(events, &lexemes);
+
+	sink.finish()
+}
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__array_comp.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__array_comp.snap
@@ -0,0 +1,43 @@
+---
+source: crates/jrsonnet-rowan-parser/src/lib.rs
+assertion_line: 88
+expression: "[a for a in [1, 2, 3]]\n"
+
+---
+Root@0..23
+  Array@0..23
+    SymbolLeftBracket@0..1 "["
+    ArrayElem@1..21
+      BodyDef@1..3
+        Ident@1..3
+          Ident@1..2 "a"
+          Whitespace@2..3 " "
+      CompspecFor@3..21
+        KeywordFor@3..6 "for"
+        Whitespace@6..7 " "
+        Ident@7..8 "a"
+        Whitespace@8..9 " "
+        OpIn@9..11 "in"
+        Whitespace@11..12 " "
+        Array@12..21
+          SymbolLeftBracket@12..13 "["
+          ArrayElem@13..14
+            BodyDef@13..14
+              Literal@13..14
+                Number@13..14 "1"
+          SymbolComma@14..15 ","
+          Whitespace@15..16 " "
+          ArrayElem@16..17
+            BodyDef@16..17
+              Literal@16..17
+                Number@16..17 "2"
+          SymbolComma@17..18 ","
+          Whitespace@18..19 " "
+          ArrayElem@19..20
+            BodyDef@19..20
+              Literal@19..20
+                Number@19..20 "3"
+          SymbolRightBracket@20..21 "]"
+    SymbolRightBracket@21..22 "]"
+    Whitespace@22..23 "\n"
+
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__array_comp_incompatible_with_multiple_elems.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__array_comp_incompatible_with_multiple_elems.snap
@@ -0,0 +1,58 @@
+---
+source: crates/jrsonnet-rowan-parser/src/lib.rs
+assertion_line: 88
+expression: "[a for a in [1, 2, 3], b]\n"
+
+---
+Root@0..26
+  Array@0..26
+    SymbolLeftBracket@0..1 "["
+    ArrayElem@1..21
+      BodyDef@1..3
+        Ident@1..3
+          Ident@1..2 "a"
+          Whitespace@2..3 " "
+      CompspecFor@3..21
+        KeywordFor@3..6 "for"
+        Whitespace@6..7 " "
+        Ident@7..8 "a"
+        Whitespace@8..9 " "
+        OpIn@9..11 "in"
+        Whitespace@11..12 " "
+        Array@12..21
+          SymbolLeftBracket@12..13 "["
+          ArrayElem@13..14
+            BodyDef@13..14
+              Literal@13..14
+                Number@13..14 "1"
+          SymbolComma@14..15 ","
+          Whitespace@15..16 " "
+          ArrayElem@16..17
+            BodyDef@16..17
+              Literal@16..17
+                Number@16..17 "2"
+          SymbolComma@17..18 ","
+          Whitespace@18..19 " "
+          ArrayElem@19..20
+            BodyDef@19..20
+              Literal@19..20
+                Number@19..20 "3"
+          SymbolRightBracket@20..21 "]"
+    SymbolComma@21..22 ","
+    Whitespace@22..23 " "
+    ArrayElem@23..24
+      BodyDef@23..24
+        Ident@23..24
+          Ident@23..24 "b"
+    SymbolRightBracket@24..25 "]"
+    Whitespace@25..26 "\n"
+===
+Custom { error: "compspec may only be used if there is only one array element", range: 3..21 }
+===
+  Γ— syntax error
+   ╭────
+ 1 β”‚ [a for a in [1, 2, 3], b]
+   Β·    ─────────┬────────
+   Β·             ╰── compspec may only be used if there is only one array element
+   ╰────
+
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__empty.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__empty.snap
@@ -0,0 +1,18 @@
+---
+source: crates/jrsonnet-rowan-parser/src/lib.rs
+assertion_line: 88
+expression: " "
+
+---
+Root@0..1
+  Whitespace@0..1 " "
+===
+Missing { expected: Named("value"), offset: 1 }
+===
+  Γ— syntax error
+   ╭────
+ 1 β”‚  
+   Β·  β–²
+   Β·  ╰── missing value
+   ╰────
+
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__function.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__function.snap
@@ -0,0 +1,34 @@
+---
+source: crates/jrsonnet-rowan-parser/src/lib.rs
+assertion_line: 88
+expression: "function(a, b = 1) a + b\n"
+
+---
+Root@0..25
+  FunctionDef@0..25
+    KeywordFunction@0..8 "function"
+    LParen@8..9 "("
+    DefPositionalArg@9..10
+      Ident@9..10 "a"
+    SymbolComma@10..11 ","
+    Whitespace@11..12 " "
+    DefNamedArg@12..17
+      Ident@12..13 "b"
+      Whitespace@13..14 " "
+      SymbolAssign@14..15 "="
+      Whitespace@15..16 " "
+      Literal@16..17
+        Number@16..17 "1"
+    RParen@17..18 ")"
+    Whitespace@18..19 " "
+    BodyDef@19..25
+      BinOp@19..25
+        Ident@19..21
+          Ident@19..20 "a"
+          Whitespace@20..21 " "
+        OpPlus@21..22 "+"
+        Whitespace@22..23 " "
+        Ident@23..25
+          Ident@23..24 "b"
+          Whitespace@24..25 "\n"
+
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__function_error_body.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__function_error_body.snap
@@ -0,0 +1,29 @@
+---
+source: crates/jrsonnet-rowan-parser/src/lib.rs
+assertion_line: 88
+expression: "function(a, b)\n"
+
+---
+Root@0..15
+  FunctionDef@0..15
+    KeywordFunction@0..8 "function"
+    LParen@8..9 "("
+    DefPositionalArg@9..10
+      Ident@9..10 "a"
+    SymbolComma@10..11 ","
+    Whitespace@11..12 " "
+    DefPositionalArg@12..13
+      Ident@12..13 "b"
+    RParen@13..14 ")"
+    Whitespace@14..15 "\n"
+    BodyDef@15..15
+===
+Missing { expected: Named("value"), offset: 14 }
+===
+  Γ— syntax error
+   ╭────
+ 1 β”‚ function(a, b)
+   Β·               β–²
+   Β·               ╰── missing value
+   ╰────
+
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__function_error_no_value.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__function_error_no_value.snap
@@ -0,0 +1,41 @@
+---
+source: crates/jrsonnet-rowan-parser/src/lib.rs
+assertion_line: 88
+expression: "function(a, b = ) a + b\n"
+
+---
+Root@0..24
+  FunctionDef@0..24
+    KeywordFunction@0..8 "function"
+    LParen@8..9 "("
+    DefPositionalArg@9..10
+      Ident@9..10 "a"
+    SymbolComma@10..11 ","
+    Whitespace@11..12 " "
+    DefNamedArg@12..16
+      Ident@12..13 "b"
+      Whitespace@13..14 " "
+      SymbolAssign@14..15 "="
+      Whitespace@15..16 " "
+    RParen@16..17 ")"
+    Whitespace@17..18 " "
+    BodyDef@18..24
+      BinOp@18..24
+        Ident@18..20
+          Ident@18..19 "a"
+          Whitespace@19..20 " "
+        OpPlus@20..21 "+"
+        Whitespace@21..22 " "
+        Ident@22..24
+          Ident@22..23 "b"
+          Whitespace@23..24 "\n"
+===
+Missing { expected: Named("value"), offset: 15 }
+===
+  Γ— syntax error
+   ╭────
+ 1 β”‚ function(a, b = ) a + b
+   Β·                β–²
+   Β·                ╰── missing value
+   ╰────
+
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__function_error_rparen.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__function_error_rparen.snap
@@ -0,0 +1,30 @@
+---
+source: crates/jrsonnet-rowan-parser/src/lib.rs
+assertion_line: 88
+expression: "function(a, b\n"
+
+---
+Root@0..14
+  FunctionDef@0..14
+    KeywordFunction@0..8 "function"
+    LParen@8..9 "("
+    DefPositionalArg@9..10
+      Ident@9..10 "a"
+    SymbolComma@10..11 ","
+    Whitespace@11..12 " "
+    DefPositionalArg@12..14
+      Ident@12..13 "b"
+      Whitespace@13..14 "\n"
+    BodyDef@14..14
+===
+Missing { expected: Unnamed(RParen), offset: 13 }
+Missing { expected: Named("value"), offset: 13 }
+===
+  Γ— syntax error
+   ╭────
+ 1 β”‚ function(a, b
+   Β·              β–²
+   Β·              β”‚╰── missing value
+   Β·              ╰── missing RParen
+   ╰────
+
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__local_no_value_recovery.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__local_no_value_recovery.snap
@@ -0,0 +1,47 @@
+---
+source: crates/jrsonnet-rowan-parser/src/lib.rs
+assertion_line: 88
+expression: "local a =\nlocal b = 3;\n1\n"
+
+---
+Root@0..25
+  Local@0..25
+    KeywordLocal@0..5 "local"
+    Whitespace@5..6 " "
+    Ident@6..7 "a"
+    Whitespace@7..8 " "
+    SymbolAssign@8..9 "="
+    Whitespace@9..10 "\n"
+    Local@10..25
+      KeywordLocal@10..15 "local"
+      Whitespace@15..16 " "
+      Ident@16..17 "b"
+      Whitespace@17..18 " "
+      SymbolAssign@18..19 "="
+      Whitespace@19..20 " "
+      Literal@20..21
+        Number@20..21 "3"
+      SymbolSemi@21..22 ";"
+      Whitespace@22..23 "\n"
+      BodyDef@23..25
+        Literal@23..25
+          Number@23..24 "1"
+          Whitespace@24..25 "\n"
+    BodyDef@25..25
+===
+Missing { expected: Unnamed(SymbolSemi), offset: 24 }
+Custom { error: "unusal local placement, missing ';' ?", range: 8..9 }
+Missing { expected: Named("value"), offset: 24 }
+===
+  Γ— syntax error
+   ╭─[1:1]
+ 1 β”‚ local a =
+   Β·         ┬
+   Β·         ╰── unusal local placement, missing ';' ?
+ 2 β”‚ local b = 3;
+ 3 β”‚ 1
+   Β·  β–²
+   Β·  β”‚╰── missing value
+   Β·  ╰── missing SymbolSemi
+   ╰────
+
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__local_no_value_recovery.snap.newdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__local_no_value_recovery.snap.new
@@ -0,0 +1,43 @@
+---
+source: crates/jrsonnet-rowan-parser/src/lib.rs
+assertion_line: 88
+expression: "local a =\nlocal b = 3;\n1\n"
+
+---
+Root@0..25
+  Local@0..25
+    KeywordLocal@0..5 "local"
+    Whitespace@5..6 " "
+    Ident@6..7 "a"
+    Whitespace@7..8 " "
+    SymbolAssign@8..9 "="
+    Whitespace@9..10 "\n"
+    Local@10..25
+      KeywordLocal@10..15 "local"
+      Whitespace@15..16 " "
+      Ident@16..17 "b"
+      Whitespace@17..18 " "
+      SymbolAssign@18..19 "="
+      Whitespace@19..20 " "
+      Literal@20..21
+        Number@20..21 "3"
+      SymbolSemi@21..22 ";"
+      Whitespace@22..23 "\n"
+      BodyDef@23..25
+        Literal@23..25
+          Number@23..24 "1"
+          Whitespace@24..25 "\n"
+    BodyDef@25..25
+===
+Missing { expected: Unnamed(SymbolSemi), offset: 24 }
+Missing { expected: Named("value"), offset: 24 }
+===
+  Γ— syntax error
+   ╭─[2:1]
+ 2 β”‚ local b = 3;
+ 3 β”‚ 1
+   Β·  β–²
+   Β·  β”‚╰── missing value
+   Β·  ╰── missing SymbolSemi
+   ╰────
+
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__local_novalue.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__local_novalue.snap
@@ -0,0 +1,29 @@
+---
+source: crates/jrsonnet-rowan-parser/src/lib.rs
+assertion_line: 88
+expression: "local a =\n"
+
+---
+Root@0..10
+  Local@0..10
+    KeywordLocal@0..5 "local"
+    Whitespace@5..6 " "
+    Ident@6..7 "a"
+    Whitespace@7..8 " "
+    SymbolAssign@8..9 "="
+    Whitespace@9..10 "\n"
+    BodyDef@10..10
+===
+Missing { expected: Named("value"), offset: 9 }
+Missing { expected: Unnamed(SymbolSemi), offset: 9 }
+Missing { expected: Named("value"), offset: 9 }
+===
+  Γ— syntax error
+   ╭────
+ 1 β”‚ local a =
+   Β·          β–²
+   Β·          ╰── missing value
+   Β·          β”‚╰── missing SymbolSemi
+   Β·          ╰── missing value
+   ╰────
+
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__named_before_positional.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__named_before_positional.snap
@@ -0,0 +1,63 @@
+---
+source: crates/jrsonnet-rowan-parser/src/lib.rs
+assertion_line: 88
+expression: "a(1, 2, b=4, 3, 5, k = 12, 6)\n"
+
+---
+Root@0..30
+  FunctionCall@0..30
+    Ident@0..1
+      Ident@0..1 "a"
+    LParen@1..2 "("
+    DefPositionalArg@2..3
+      Literal@2..3
+        Number@2..3 "1"
+    SymbolComma@3..4 ","
+    Whitespace@4..5 " "
+    DefPositionalArg@5..6
+      Literal@5..6
+        Number@5..6 "2"
+    SymbolComma@6..7 ","
+    Whitespace@7..8 " "
+    DefNamedArg@8..11
+      Ident@8..9 "b"
+      SymbolAssign@9..10 "="
+      Literal@10..11
+        Number@10..11 "4"
+    SymbolComma@11..12 ","
+    Whitespace@12..13 " "
+    ErrorPositionalAfterNamed@13..28
+      DefPositionalArg@13..14
+        Literal@13..14
+          Number@13..14 "3"
+      SymbolComma@14..15 ","
+      Whitespace@15..16 " "
+      DefPositionalArg@16..17
+        Literal@16..17
+          Number@16..17 "5"
+      SymbolComma@17..18 ","
+      Whitespace@18..19 " "
+      DefNamedArg@19..25
+        Ident@19..20 "k"
+        Whitespace@20..21 " "
+        SymbolAssign@21..22 "="
+        Whitespace@22..23 " "
+        Literal@23..25
+          Number@23..25 "12"
+      SymbolComma@25..26 ","
+      Whitespace@26..27 " "
+      DefPositionalArg@27..28
+        Literal@27..28
+          Number@27..28 "6"
+    RParen@28..29 ")"
+    Whitespace@29..30 "\n"
+===
+Custom { error: "positional arguments can't be placed after named", range: 13..28 }
+===
+  Γ— syntax error
+   ╭────
+ 1 β”‚ a(1, 2, b=4, 3, 5, k = 12, 6)
+   Β·              ───────┬───────
+   Β·                     ╰── positional arguments can't be placed after named
+   ╰────
+
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__no_lhs.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__no_lhs.snap
@@ -0,0 +1,23 @@
+---
+source: crates/jrsonnet-rowan-parser/src/lib.rs
+assertion_line: 88
+expression: "+ 2\n"
+
+---
+Root@0..4
+  OpPlus@0..1 "+"
+  Whitespace@1..2 " "
+  Number@2..3 "2"
+  Whitespace@3..4 "\n"
+===
+Missing { expected: Named("value"), offset: 0 }
+Custom { error: "unexpected input after expression", range: 0..3 }
+===
+  Γ— syntax error
+   ╭────
+ 1 β”‚ + 2
+   Β· β–²─┬─
+   Β· β”‚╰── unexpected input after expression
+   Β· ╰── missing value
+   ╰────
+
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__no_operator.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__no_operator.snap
@@ -0,0 +1,22 @@
+---
+source: crates/jrsonnet-rowan-parser/src/lib.rs
+assertion_line: 88
+expression: "2 2\n"
+
+---
+Root@0..4
+  Literal@0..2
+    Number@0..1 "2"
+    Whitespace@1..2 " "
+  Number@2..3 "2"
+  Whitespace@3..4 "\n"
+===
+Custom { error: "unexpected input after expression", range: 2..3 }
+===
+  Γ— syntax error
+   ╭────
+ 1 β”‚ 2 2
+   Β·   ┬
+   Β·   ╰── unexpected input after expression
+   ╰────
+
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__no_rhs.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__no_rhs.snap
@@ -0,0 +1,23 @@
+---
+source: crates/jrsonnet-rowan-parser/src/lib.rs
+assertion_line: 88
+expression: "a +\n"
+
+---
+Root@0..4
+  BinOp@0..4
+    Ident@0..2
+      Ident@0..1 "a"
+      Whitespace@1..2 " "
+    OpPlus@2..3 "+"
+    Whitespace@3..4 "\n"
+===
+Missing { expected: Named("value"), offset: 3 }
+===
+  Γ— syntax error
+   ╭────
+ 1 β”‚ a +
+   Β·    β–²
+   Β·    ╰── missing value
+   ╰────
+
addedcrates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__wrong_field_end.snap.newdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/snapshots/jrsonnet_rowan_parser__tests__wrong_field_end.snap.new
@@ -0,0 +1,41 @@
+---
+source: crates/jrsonnet-rowan-parser/src/lib.rs
+assertion_line: 88
+expression: "{\n\ta: 1;\n\tb: 2;\n}\n"
+
+---
+Root@0..18
+  Object@0..7
+    SymbolLeftBrace@0..1 "{"
+    Whitespace@1..3 "\n\t"
+    Field@3..7
+      Ident@3..4 "a"
+      SymbolColon@4..5 ":"
+      Whitespace@5..6 " "
+      Literal@6..7
+        Number@6..7 "1"
+  SymbolSemi@7..8 ";"
+  Whitespace@8..10 "\n\t"
+  Ident@10..11 "b"
+  SymbolColon@11..12 ":"
+  Whitespace@12..13 " "
+  Number@13..14 "2"
+  SymbolSemi@14..15 ";"
+  Whitespace@15..16 "\n"
+  SymbolRightBrace@16..17 "}"
+  Whitespace@17..18 "\n"
+===
+Missing { expected: Unnamed(SymbolRightBrace), offset: 7 }
+Custom { error: "unexpected input after expression", range: 7..17 }
+===
+  Γ— syntax error
+   ╭─[1:1]
+ 1 β”‚     {
+ 2 β”‚ β•­─β–Ά 	a: 1;
+   Β· β”‚β”‚     β–²
+   Β· β”‚β”‚     ╰── missing SymbolRightBrace
+ 3 β”‚ β”‚   	b: 2;
+ 4 β”‚ β”œ─β–Ά }
+   Β· β•°──── unexpected input after expression
+   ╰────
+
addedcrates/jrsonnet-rowan-parser/src/string_block.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/string_block.rs
@@ -0,0 +1,221 @@
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum StringBlockToken {
+	Valid,
+	UnexpectedEndOfString,
+	MissingTextBlockNewLine,
+	MissingTextBlockTermination,
+	MissingTextBlockIndent,
+}
+
+use std::ops::Range;
+
+use StringBlockToken::*;
+
+use crate::lex::SyntaxKind;
+
+pub fn lex_str_block_test<'a>(lex: &mut logos::Lexer<'a, SyntaxKind>) {
+	lex_str_block(lex);
+}
+
+fn lex_str_block<'a>(lex: &mut logos::Lexer<'a, SyntaxKind>) -> StringBlockToken {
+	struct Context<'a> {
+		source: &'a str,
+		index: usize,
+		offset: 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_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,
+			};
+		}
+
+		fn pos(&self) -> Range<usize> {
+			if self.index == self.source.len() {
+				self.offset + self.index..self.offset + self.index
+			} else {
+				// TODO: char size
+				self.offset + self.index..self.offset + self.index + 1
+			}
+		}
+	}
+
+	// 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()
+	}
+
+	fn guess_token_end_and_bump<'a>(lex: &mut logos::Lexer<'a, SyntaxKind>, ctx: &Context<'a>) {
+		let end_index = ctx
+			.rest()
+			.find("|||")
+			.map(|v| v + 3)
+			.unwrap_or_else(|| ctx.rest().len());
+		lex.bump(ctx.index + end_index);
+	}
+
+	debug_assert_eq!(lex.slice(), "|||");
+	let mut ctx = Context {
+		source: lex.remainder(),
+		index: 0,
+		offset: lex.span().end,
+	};
+
+	// Skip whitespaces
+	ctx.eat_while(|r| r == ' ' || r == '\t' || r == '\r');
+
+	// Skip \n
+	match ctx.next() {
+		Some('\n') => (),
+		None => {
+			guess_token_end_and_bump(lex, &ctx);
+			return UnexpectedEndOfString;
+		}
+		// Text block requires new line after |||.
+		Some(_) => {
+			guess_token_end_and_bump(lex, &ctx);
+			return MissingTextBlockNewLine;
+		}
+	}
+
+	// Process leading blank lines before calculating string block indent
+	while let Some('\n') = ctx.peek() {
+		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
+		guess_token_end_and_bump(lex, &ctx);
+		return MissingTextBlockIndent;
+	}
+
+	loop {
+		debug_assert_ne!(num_whitespace, 0, "Unexpected value for num_whitespace");
+		ctx.skip(num_whitespace);
+
+		loop {
+			match ctx.next() {
+				None => {
+					guess_token_end_and_bump(lex, &ctx);
+					return UnexpectedEndOfString;
+				}
+				Some('\n') => break,
+				Some(_) => (),
+			}
+		}
+
+		// Skip any blank lines
+		while let Some('\n') = ctx.peek() {
+			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);
+			loop {
+				match ctx.peek() {
+					Some(' ') | Some('\t') => {
+						term_indent.push(ctx.next().unwrap());
+					}
+					_ => break,
+				}
+			}
+
+			if !ctx.rest().starts_with("|||") {
+				// Text block not terminated with |||
+				let pos = ctx.pos();
+				if pos.len() == 0 {
+					// eof
+					lex.bump(ctx.index);
+					return UnexpectedEndOfString;
+				}
+
+				guess_token_end_and_bump(lex, &ctx);
+				return MissingTextBlockTermination;
+			}
+
+			// Skip '|||'
+			ctx.skip(3);
+			break;
+		}
+	}
+
+	lex.bump(ctx.index);
+	Valid
+}
addedcrates/jrsonnet-rowan-parser/src/token_set.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/token_set.rs
@@ -0,0 +1,31 @@
+use crate::lex::SyntaxKind;
+
+#[derive(Clone, Copy, Default)]
+pub struct TokenSet(u64);
+
+impl TokenSet {
+	pub const EMPTY: Self = Self(0);
+	pub const ALL: Self = Self(u64::MAX);
+
+	pub const fn new(kinds: &[SyntaxKind]) -> TokenSet {
+		let mut res = 0u64;
+		let mut i = 0;
+		while i < kinds.len() {
+			res |= mask(kinds[i]);
+			i += 1
+		}
+		TokenSet(res)
+	}
+
+	pub const fn union(self, other: TokenSet) -> TokenSet {
+		TokenSet(self.0 | other.0)
+	}
+
+	pub const fn contains(&self, kind: SyntaxKind) -> bool {
+		self.0 & mask(kind) != 0
+	}
+}
+
+const fn mask(kind: SyntaxKind) -> u64 {
+	1u64 << (kind as usize)
+}
addedcrates/jrsonnet-rowan-parser/src/unary.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-rowan-parser/src/unary.rs
@@ -0,0 +1,15 @@
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum UnaryOperator {
+	Minus,
+	Not,
+	BitNegate,
+}
+impl UnaryOperator {
+	pub fn binding_power(&self) -> ((), u8) {
+		match self {
+			UnaryOperator::Minus => ((), 20),
+			UnaryOperator::Not => ((), 20),
+			UnaryOperator::BitNegate => ((), 20),
+		}
+	}
+}
addedjrsonnet-lsp/Cargo.tomldiffbeforeafterboth
--- /dev/null
+++ b/jrsonnet-lsp/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "jrsonnet-lsp"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+anyhow = "1.0.48"
+jrsonnet-evaluator = { path = "../jrsonnet-evaluator" }
+jrsonnet-parser = { path = "../jrsonnet-parser" }
+lsp-server = "0.5.2"
+lsp-types = "0.92.0"
+serde = "1.0.130"
+serde_json = "1.0.71"
addedjrsonnet-lsp/src/main.rsdiffbeforeafterboth
--- /dev/null
+++ b/jrsonnet-lsp/src/main.rs
@@ -0,0 +1,211 @@
+use std::{
+	collections::HashMap,
+	fs::File,
+	path::{Path, PathBuf},
+	str::FromStr,
+};
+
+use jrsonnet_evaluator::{EvaluationState, FileImportResolver, Val};
+use jrsonnet_parser::{ExprLocation, ParserSettings};
+use lsp_server::{Connection, ErrorCode, Message, Request, RequestId, Response};
+use lsp_types::{
+	notification::{DidChangeTextDocument, DidOpenTextDocument, Notification},
+	request::{DocumentLinkRequest, HoverRequest},
+	CompletionOptions, DidChangeTextDocumentParams, DidOpenTextDocumentParams, DocumentLink,
+	DocumentLinkOptions, Hover, HoverContents, MarkupContent, MarkupKind, ServerCapabilities,
+	TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions, Url,
+	WorkDoneProgressOptions,
+};
+
+use std::io::Write;
+
+fn main() {
+	let mut log = File::create("test").unwrap();
+	writeln!(log, "start").unwrap();
+	let (connection, io_threads) = Connection::stdio();
+	let capabilities = serde_json::to_value(&ServerCapabilities {
+		completion_provider: Some(CompletionOptions::default()),
+		definition_provider: Some(lsp_types::OneOf::Left(true)),
+		document_link_provider: Some(DocumentLinkOptions {
+			resolve_provider: Some(false),
+			work_done_progress_options: WorkDoneProgressOptions::default(),
+		}),
+		hover_provider: Some(lsp_types::HoverProviderCapability::Simple(true)),
+		text_document_sync: Some(TextDocumentSyncCapability::Options(
+			TextDocumentSyncOptions {
+				change: Some(TextDocumentSyncKind::FULL),
+				open_close: Some(true),
+				..TextDocumentSyncOptions::default()
+			},
+		)),
+		..ServerCapabilities::default()
+	})
+	.expect("failed to convert capabilities to json");
+
+	connection
+		.initialize(capabilities)
+		.expect("failed to initialize connection");
+
+	writeln!(log, "initialized").unwrap();
+
+	main_loop(&mut log, &connection).expect("main loop failed");
+
+	io_threads.join().expect("failed to join io_threads");
+}
+fn main_loop(log: &mut File, connection: &Connection) -> anyhow::Result<()> {
+	let mut es = EvaluationState::default();
+	es.set_import_resolver(Box::new(FileImportResolver::default()));
+
+	let reply = |response: Response| {
+		connection
+			.sender
+			.send(Message::Response(response))
+			.expect("failed to respond");
+	};
+
+	for msg in &connection.receiver {
+		match msg {
+			Message::Response(_) => (),
+			Message::Request(req) => {
+				if connection.handle_shutdown(&req)? {
+					return Ok(());
+				}
+				if let Some((id, params)) = cast::<DocumentLinkRequest>(&req) {
+					reply(Response::new_ok(id, <Vec<DocumentLink>>::new()));
+				} else if let Some((id, params)) = cast::<HoverRequest>(&req) {
+					let pos = params
+						.text_document_position_params
+						.text_document
+						.uri
+						.path();
+					let buf = PathBuf::from_str(pos).unwrap();
+					let pos = es
+						.map_from_source_location(
+							&buf,
+							params.text_document_position_params.position.line as usize + 1,
+							params.text_document_position_params.position.character as usize + 1,
+						)
+						.unwrap();
+					let el = ExprLocation(buf.clone().into(), pos as usize, pos as usize);
+					let es2 = es.clone();
+				// reply(Response::new_ok(
+				// 	id,
+				// 	Some(Hover {
+				// 		range: None,
+				// 		contents: HoverContents::Markup(MarkupContent {
+				// 			kind: MarkupKind::Markdown,
+				// 			value: es
+				// 				.run_in_state_with_breakpoint(el, move || {
+				// 					es2.reset_evaluation_state(&buf);
+				// 					es2.import_file(&PathBuf::new(), &buf)?
+				// 						.to_string()
+				// 						.map(|_| ())
+				// 				})
+				// 				.unwrap()
+				// 				.unwrap_or_else(|| Val::Null)
+				// 				.value_type()
+				// 				.to_string(),
+				// 		}),
+				// 	}),
+				// ));
+				} else
+				/*
+				if let Some((id, params)) = cast::<DocumentLinkRequest>(&req) {
+					 let links = handle_links(&files, params).unwrap_or_default();
+					 reply(Response::new_ok(id, links));
+				} else if let Some((id, params)) = cast::<GotoDefinition>(&req) {
+					 if let Some(loc) = handle_goto(&files, params) {
+						  reply(Response::new_ok(id, loc))
+					 } else {
+						  reply(Response::new_ok(id, ()))
+					 }
+				} else if let Some((id, params)) = cast::<HoverRequest>(&req) {
+					 match handle_hover(&files, params) {
+						  Some((range, markdown)) => {
+								reply(Response::new_ok(
+									 id,
+									 Hover {
+										  contents: HoverContents::Markup(MarkupContent {
+												kind: MarkupKind::Markdown,
+												value: markdown,
+										  }),
+										  range,
+									 },
+								));
+						  }
+						  None => {
+								reply(Response::new_ok(id, ()));
+						  }
+					 }
+				} else if let Some((id, params)) = cast::<Completion>(&req) {
+					 let completions = handle_completion(&files, params.text_document_position)
+						  .unwrap_or_default();
+					 reply(Response::new_ok(id, completions));
+				} else
+				*/
+				{
+					reply(Response::new_err(
+						req.id,
+						ErrorCode::MethodNotFound as i32,
+						format!("unrecognized request {}", req.method),
+					))
+				}
+			}
+			Message::Notification(req) => {
+				let mut handle = |text: String, uri: Url| {
+					writeln!(log, "updated file: {:?}", uri).unwrap();
+					let path = match PathBuf::from_str(uri.path()) {
+						Ok(x) => x,
+						Err(_) => return,
+					};
+					let parsed = match jrsonnet_parser::parse(
+						&text,
+						&ParserSettings {
+							file_name: path.clone().into(),
+						},
+					) {
+						Ok(v) => v,
+						Err(e) => {
+							writeln!(log, "fuck D: {:?}", e).unwrap();
+							return;
+							// connection.sender.send(Message::Notification(Notification::new_err(req.id, ErrorCode::ParseError as i32, format!("Fuck D: {:?}", e))))
+						}
+					};
+					es.add_parsed_file(path.into(), text.into(), parsed)
+						.unwrap();
+					writeln!(log, "parsed: {:?}", uri).unwrap();
+				};
+
+				match &*req.method {
+					DidOpenTextDocument::METHOD => {
+						let params: DidOpenTextDocumentParams =
+							match serde_json::from_value(req.params) {
+								Ok(x) => x,
+								Err(_) => continue,
+							};
+						handle(params.text_document.text, params.text_document.uri);
+					}
+					DidChangeTextDocument::METHOD => {
+						let params: DidChangeTextDocumentParams =
+							match serde_json::from_value(req.params) {
+								Ok(x) => x,
+								Err(_) => continue,
+							};
+						for change in params.content_changes.into_iter() {
+							handle(change.text, params.text_document.uri.clone());
+						}
+					}
+					_ => continue,
+				}
+			}
+		}
+	}
+	Ok(())
+}
+fn cast<R>(req: &Request) -> Option<(RequestId, R::Params)>
+where
+	R: lsp_types::request::Request,
+	R::Params: serde::de::DeserializeOwned,
+{
+	req.clone().extract(R::METHOD).ok()
+}