difftreelog
feat lsp prototype
in: master
29 files changed
Cargo.lockdiffbeforeafterbothno changes
Cargo.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"
crates/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"
crates/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),
+ }
+ }
+}
crates/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();
+ }
+ }
+}
crates/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()
+ }
+}
crates/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;
+ }
+ "
+ );
+}
crates/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
+ }
+}
crates/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()
+}
crates/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"
+
crates/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 }
+===
+ [38;2;255;30;30mΓ[0m syntax error
+ βββββ
+ [2m1[0m β [a for a in [1, 2, 3], b]
+ Β· [38;2;246;87;248m ββββββββββ¬ββββββββ[0m
+ Β· [38;2;246;87;248mβ°ββ [38;2;246;87;248mcompspec may only be used if there is only one array element[0m[0m
+ β°ββββ
+
crates/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 }
+===
+ [38;2;255;30;30mΓ[0m syntax error
+ βββββ
+ [2m1[0m β
+ Β· [38;2;246;87;248m β²[0m
+ Β· [38;2;246;87;248mβ°ββ [38;2;246;87;248mmissing value[0m[0m
+ β°ββββ
+
crates/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"
+
crates/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 }
+===
+ [38;2;255;30;30mΓ[0m syntax error
+ βββββ
+ [2m1[0m β function(a, b)
+ Β· [38;2;246;87;248m β²[0m
+ Β· [38;2;246;87;248mβ°ββ [38;2;246;87;248mmissing value[0m[0m
+ β°ββββ
+
crates/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 }
+===
+ [38;2;255;30;30mΓ[0m syntax error
+ βββββ
+ [2m1[0m β function(a, b = ) a + b
+ Β· [38;2;246;87;248m β²[0m
+ Β· [38;2;246;87;248mβ°ββ [38;2;246;87;248mmissing value[0m[0m
+ β°ββββ
+
crates/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 }
+===
+ [38;2;255;30;30mΓ[0m syntax error
+ βββββ
+ [2m1[0m β function(a, b
+ Β· [38;2;246;87;248m β²[0m
+ Β· [38;2;246;87;248mβ[0m[38;2;30;201;212mβ°ββ [38;2;30;201;212mmissing value[0m[0m
+ Β· [38;2;246;87;248mβ°ββ [38;2;246;87;248mmissing RParen[0m[0m
+ β°ββββ
+
crates/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 }
+===
+ [38;2;255;30;30mΓ[0m syntax error
+ ββ[1:1]
+ [2m1[0m β local a =
+ Β· [38;2;246;87;248m β¬[0m
+ Β· [38;2;246;87;248mβ°ββ [38;2;246;87;248munusal local placement, missing ';' ?[0m[0m
+ [2m2[0m β local b = 3;
+ [2m3[0m β 1
+ Β· [38;2;30;201;212m β²[0m
+ Β· [38;2;30;201;212mβ[0m[38;2;145;246;111mβ°ββ [38;2;145;246;111mmissing value[0m[0m
+ Β· [38;2;30;201;212mβ°ββ [38;2;30;201;212mmissing SymbolSemi[0m[0m
+ β°ββββ
+
crates/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 }
+===
+ [38;2;255;30;30mΓ[0m syntax error
+ ββ[2:1]
+ [2m2[0m β local b = 3;
+ [2m3[0m β 1
+ Β· [38;2;246;87;248m β²[0m
+ Β· [38;2;246;87;248mβ[0m[38;2;30;201;212mβ°ββ [38;2;30;201;212mmissing value[0m[0m
+ Β· [38;2;246;87;248mβ°ββ [38;2;246;87;248mmissing SymbolSemi[0m[0m
+ β°ββββ
+
crates/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 }
+===
+ [38;2;255;30;30mΓ[0m syntax error
+ βββββ
+ [2m1[0m β local a =
+ Β· [38;2;246;87;248m β²[0m
+ Β· [38;2;145;246;111mβ°ββ [38;2;145;246;111mmissing value[0m[0m
+ Β· [38;2;246;87;248mβ[0m[38;2;30;201;212mβ°ββ [38;2;30;201;212mmissing SymbolSemi[0m[0m
+ Β· [38;2;246;87;248mβ°ββ [38;2;246;87;248mmissing value[0m[0m
+ β°ββββ
+
crates/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 }
+===
+ [38;2;255;30;30mΓ[0m syntax error
+ βββββ
+ [2m1[0m β a(1, 2, b=4, 3, 5, k = 12, 6)
+ Β· [38;2;246;87;248m ββββββββ¬βββββββ[0m
+ Β· [38;2;246;87;248mβ°ββ [38;2;246;87;248mpositional arguments can't be placed after named[0m[0m
+ β°ββββ
+
crates/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 }
+===
+ [38;2;255;30;30mΓ[0m syntax error
+ βββββ
+ [2m1[0m β + 2
+ Β· [38;2;246;87;248mβ²[0m[38;2;30;201;212mββ¬β[0m
+ Β· [38;2;246;87;248mβ[0m[38;2;30;201;212mβ°ββ [38;2;30;201;212munexpected input after expression[0m[0m
+ Β· [38;2;246;87;248mβ°ββ [38;2;246;87;248mmissing value[0m[0m
+ β°ββββ
+
crates/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 }
+===
+ [38;2;255;30;30mΓ[0m syntax error
+ βββββ
+ [2m1[0m β 2 2
+ Β· [38;2;246;87;248m β¬[0m
+ Β· [38;2;246;87;248mβ°ββ [38;2;246;87;248munexpected input after expression[0m[0m
+ β°ββββ
+
crates/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 }
+===
+ [38;2;255;30;30mΓ[0m syntax error
+ βββββ
+ [2m1[0m β a +
+ Β· [38;2;246;87;248m β²[0m
+ Β· [38;2;246;87;248mβ°ββ [38;2;246;87;248mmissing value[0m[0m
+ β°ββββ
+
crates/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 }
+===
+ [38;2;255;30;30mΓ[0m syntax error
+ ββ[1:1]
+ [2m1[0m β {
+ [2m2[0m β [38;2;246;87;248mβ[0m[38;2;246;87;248mβ[0m[38;2;246;87;248mβΆ[0m a: 1;
+ Β· [38;2;246;87;248mβ[0m[38;2;30;201;212mβ[0m[38;2;246;87;248m β²[0m
+ Β· [38;2;246;87;248mβ[0m[38;2;30;201;212mβ[0m [38;2;246;87;248mβ°ββ [38;2;246;87;248mmissing SymbolRightBrace[0m[0m
+ [2m3[0m β [38;2;30;201;212mβ[0m b: 2;
+ [2m4[0m β [38;2;30;201;212mβ[0m[38;2;30;201;212mβ[0m[38;2;30;201;212mβΆ[0m }
+ Β· [38;2;30;201;212mβ°[0m[38;2;30;201;212mβββ[0m[38;2;30;201;212mβ[0m [38;2;30;201;212munexpected input after expression[0m
+ β°ββββ
+
crates/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
+}
crates/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)
+}
crates/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),
+ }
+ }
+}
jrsonnet-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"
jrsonnet-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()
+}