difftreelog
feat(fmt) basic comment formatting in objects
in: master
21 files changed
.cargo/configdiffbeforeafterboth--- a/.cargo/config
+++ /dev/null
@@ -1,2 +0,0 @@
-[alias]
-xtask = "run --manifest-path ./xtask/Cargo.toml --"
.cargo/config.tomldiffbeforeafterboth--- /dev/null
+++ b/.cargo/config.toml
@@ -0,0 +1,2 @@
+[alias]
+xtask = "run --package xtask --bin xtask --"
cmds/jrsonnet-fmt/Cargo.tomldiffbeforeafterboth--- a/cmds/jrsonnet-fmt/Cargo.toml
+++ b/cmds/jrsonnet-fmt/Cargo.toml
@@ -6,3 +6,5 @@
[dependencies]
dprint-core = "0.58.2"
jrsonnet-rowan-parser = { path = "../../crates/jrsonnet-rowan-parser" }
+insta = "1.15"
+indoc = "1.0"
cmds/jrsonnet-fmt/src/children.rsdiffbeforeafterboth--- /dev/null
+++ b/cmds/jrsonnet-fmt/src/children.rs
@@ -0,0 +1,168 @@
+use std::{fmt::Debug, marker::PhantomData, mem};
+
+use jrsonnet_rowan_parser::{
+ nodes::{Trivia, TriviaKind},
+ AstNode, AstToken, SyntaxElement,
+ SyntaxKind::*,
+ SyntaxNode, TS,
+};
+
+pub type ChildTrivia = Vec<Trivia>;
+
+pub struct ChildIterator<I, T> {
+ inner: I,
+ _marker: PhantomData<T>,
+}
+
+pub fn children_between<T: AstNode + Debug>(
+ node: SyntaxNode,
+ start: Option<&SyntaxElement>,
+ end: Option<&SyntaxElement>,
+) -> (Vec<Child<T>>, ChildTrivia) {
+ let mut iter = node.children_with_tokens().peekable();
+ while iter.peek() == start {
+ iter.next();
+ }
+ children(
+ iter.take_while(|i| Some(i) != end),
+ start.is_none() || end.is_none(),
+ )
+}
+
+pub fn should_start_with_newline(tt: &ChildTrivia) -> bool {
+ // First for previous item end
+ count_newlines_before(&tt) >= 2
+}
+
+fn count_newlines_before(tt: &ChildTrivia) -> usize {
+ let mut nl_count = 0;
+ for t in tt {
+ match t.kind() {
+ TriviaKind::Whitespace => {
+ nl_count += t.text().bytes().filter(|b| *b == b'\n').count();
+ }
+ _ => break,
+ }
+ }
+ nl_count
+}
+fn count_newlines_after(tt: &ChildTrivia) -> usize {
+ let mut nl_count = 0;
+ for t in tt.iter().rev() {
+ match t.kind() {
+ TriviaKind::Whitespace => {
+ nl_count += t.text().bytes().filter(|b| *b == b'\n').count();
+ }
+ TriviaKind::SingleLineHashComment => {
+ nl_count += 1;
+ break;
+ }
+ TriviaKind::SingleLineSlashComment => {
+ nl_count += 1;
+ break;
+ }
+ _ => {}
+ }
+ }
+ nl_count
+}
+
+pub fn children<'a, T: AstNode + Debug>(
+ items: impl Iterator<Item = SyntaxElement>,
+ loose: bool,
+) -> (Vec<Child<T>>, ChildTrivia) {
+ let mut out = Vec::new();
+ let mut current_child = None::<Child<T>>;
+ let mut next = ChildTrivia::new();
+ // Previous element ended, do not add more inline comments
+ let mut started_next = false;
+ let mut had_some = false;
+
+ for item in items {
+ if let Some(value) = item.as_node().cloned().and_then(T::cast) {
+ let before_trivia = mem::take(&mut next);
+ let last_child = current_child.replace(Child {
+ newlines_above: if had_some {
+ count_newlines_before(&before_trivia)
+ + current_child
+ .as_ref()
+ .map(|c| count_newlines_after(&c.inline_trivia))
+ .unwrap_or_default()
+ } else {
+ 0
+ },
+ before_trivia,
+ value,
+ inline_trivia: Vec::new(),
+ });
+ if let Some(last_child) = last_child {
+ out.push(last_child)
+ }
+ had_some = true;
+ started_next = false;
+ } else if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {
+ let is_single_line_comment = trivia.kind() == TriviaKind::SingleLineHashComment
+ || trivia.kind() == TriviaKind::SingleLineSlashComment;
+ if started_next
+ || current_child.is_none()
+ || trivia.text().contains('\n') && !is_single_line_comment
+ {
+ next.push(trivia.clone());
+ started_next = true;
+ } else {
+ let cur = current_child.as_mut().expect("checked not none");
+ cur.inline_trivia.push(trivia);
+ if is_single_line_comment {
+ started_next = true;
+ }
+ }
+ had_some = true;
+ } else if loose {
+ if had_some {
+ break;
+ }
+ started_next = true;
+ } else {
+ assert!(
+ TS![, ;].contains(item.kind()) || item.kind() == ERROR,
+ "silently eaten token: {:?}",
+ item.kind()
+ )
+ }
+ }
+
+ if let Some(current_child) = current_child {
+ out.push(current_child);
+ }
+
+ (out, next)
+}
+
+#[derive(Debug)]
+pub struct Child<T> {
+ newlines_above: usize,
+ /// Comment before item, i.e
+ ///
+ /// ```ignore
+ /// // Comment
+ /// item
+ /// ```
+ pub before_trivia: ChildTrivia,
+ pub value: T,
+ /// Comment after line, but located at same line
+ ///
+ /// ```ignore
+ /// item1, // Inline comment
+ /// // Not inline comment
+ /// item2,
+ /// ```
+ pub inline_trivia: ChildTrivia,
+}
+
+impl<T> Child<T> {
+ /// If this child has two newlines above in source code, so it needs to have it in output
+ pub fn needs_newline_above(&self) -> bool {
+ // First line for end of previous item
+ self.newlines_above >= 2
+ }
+}
cmds/jrsonnet-fmt/src/comments.rsdiffbeforeafterboth--- /dev/null
+++ b/cmds/jrsonnet-fmt/src/comments.rs
@@ -0,0 +1,159 @@
+use dprint_core::formatting::PrintItems;
+use jrsonnet_rowan_parser::{nodes::TriviaKind, AstToken};
+
+use crate::{children::ChildTrivia, p, pi};
+
+pub enum CommentLocation {
+ /// Above local, field, other things
+ AboveItem,
+ /// After item
+ ItemInline,
+ /// After all items in object
+ EndOfItems,
+}
+
+pub fn format_comments(comments: &ChildTrivia, loc: CommentLocation) -> PrintItems {
+ let mut pi = p!(new:);
+
+ for c in comments {
+ match c.kind() {
+ TriviaKind::Whitespace => {}
+ TriviaKind::MultiLineComment => {
+ let mut text = c
+ .text()
+ .strip_prefix("/*")
+ .expect("ml comment starts with /*")
+ .strip_suffix("*/")
+ .expect("ml comment ends with */");
+ // doc-style comment, /**
+ let doc = if text.starts_with('*') {
+ text = &text[1..];
+ true
+ } else {
+ false
+ };
+ // Is comment starts with text immediatly, i.e /*text
+ let mut immediate_start = true;
+ let mut lines = text
+ .split('\n')
+ .map(|l| l.trim_end())
+ .skip_while(|l| {
+ if l.is_empty() {
+ immediate_start = false;
+ true
+ } else {
+ false
+ }
+ })
+ .collect::<Vec<_>>();
+ while lines.last().map(|l| l.is_empty()).unwrap_or(false) {
+ lines.pop();
+ }
+ if lines.len() == 1 && !doc {
+ p!(pi: str("/* ") str(lines[0].trim()) str(" */") nl)
+ } else if !lines.is_empty() {
+ fn common_ws_prefix<'a>(a: &'a str, b: &str) -> &'a str {
+ let offset = a
+ .bytes()
+ .zip(b.bytes())
+ .take_while(|(a, b)| a == b && (a.is_ascii_whitespace() || *a == b'*'))
+ .count();
+ &a[..offset]
+ }
+ // First line is not empty, extract ws prefix of it
+ let mut common_ws_padding = if immediate_start && lines.len() > 1 {
+ common_ws_prefix(lines[1], lines[1])
+ } else {
+ common_ws_prefix(lines[0], lines[0])
+ };
+ for line in lines
+ .iter()
+ .skip(if immediate_start { 2 } else { 1 })
+ .filter(|l| !l.is_empty())
+ {
+ common_ws_padding = common_ws_prefix(common_ws_padding, line);
+ }
+ for line in lines
+ .iter_mut()
+ .skip(if immediate_start { 1 } else { 0 })
+ .filter(|l| !l.is_empty())
+ {
+ *line = line
+ .strip_prefix(common_ws_padding)
+ .expect("all non-empty lines start with this padding");
+ }
+
+ p!(pi: str("/*"));
+ if doc {
+ p!(pi: str("*"));
+ }
+ p!(pi: nl);
+ for mut line in lines {
+ if doc {
+ p!(pi: str(" *"));
+ }
+ if line.is_empty() {
+ p!(pi: nl);
+ } else {
+ if doc {
+ p!(pi: str(" "));
+ }
+ while let Some(new_line) = line.strip_prefix('\t') {
+ if doc {
+ p!(pi: str(" "));
+ } else {
+ p!(pi: tab);
+ }
+ line = new_line;
+ }
+ p!(pi: str(line) nl)
+ }
+ }
+ if doc {
+ p!(pi: str(" "));
+ }
+ p!(pi: str("*/") nl)
+ }
+ }
+ // TODO: Keep common padding for multiple continous lines of single-line comments
+ // I.e
+ // ```
+ // # Line1
+ // # Line2
+ // ```
+ // Should be reformatted as
+ // ```
+ // # Line1
+ // # Line2
+ // ```
+ // But currently comment formatter is not aware of continous comment lines, and reformats it as
+ // ```
+ // # Line1
+ // # Line2
+ // ```
+ TriviaKind::SingleLineHashComment => {
+ if matches!(loc, CommentLocation::ItemInline) {
+ p!(pi: str(" "))
+ }
+ p!(pi: str("# ") str(c.text().strip_prefix('#').expect("hash comment starts with #").trim()));
+ if !matches!(loc, CommentLocation::ItemInline) {
+ p!(pi: nl)
+ }
+ }
+ TriviaKind::SingleLineSlashComment => {
+ if matches!(loc, CommentLocation::ItemInline) {
+ p!(pi: str(" "))
+ }
+ p!(pi: str("// ") str(c.text().strip_prefix("//").expect("comment starts with //").trim()));
+ if !matches!(loc, CommentLocation::ItemInline) {
+ p!(pi: nl)
+ }
+ }
+ // Garbage in - garbage out
+ TriviaKind::ErrorCommentTooShort => p!(pi: str("/*/")),
+ TriviaKind::ErrorCommentUnterminated => p!(pi: str(c.text())),
+ }
+ }
+
+ pi
+}
cmds/jrsonnet-fmt/src/main.rsdiffbeforeafterboth--- a/cmds/jrsonnet-fmt/src/main.rs
+++ b/cmds/jrsonnet-fmt/src/main.rs
@@ -1,6 +1,7 @@
use std::any::type_name;
-use dprint_core::formatting::{PrintItems, PrintOptions, Signal};
+use children::children_between;
+use dprint_core::formatting::{PrintItems, PrintOptions};
use jrsonnet_rowan_parser::{
nodes::{
ArgsDesc, Assertion, BinaryOperator, Bind, CompSpec, Destruct, DestructArrayPart,
@@ -8,9 +9,19 @@
Member, Name, Number, ObjBody, ObjLocal, ParamsDesc, SliceDesc, SourceFile, Text,
UnaryOperator,
},
- AstToken, SyntaxToken,
+ AstNode, AstToken, SyntaxToken,
};
+use crate::{
+ children::should_start_with_newline,
+ comments::{format_comments, CommentLocation},
+};
+
+mod children;
+mod comments;
+#[cfg(test)]
+mod tests;
+
pub trait Printable {
fn print(&self) -> PrintItems;
}
@@ -18,7 +29,7 @@
macro_rules! pi {
(@i; $($t:tt)*) => {{
#[allow(unused_mut)]
- let mut o = PrintItems::new();
+ let mut o = dprint_core::formatting::PrintItems::new();
pi!(@s; o: $($t)*);
o
}};
@@ -27,21 +38,29 @@
pi!(@s; $o: $($t)*);
}};
(@s; $o:ident: nl $($t:tt)*) => {{
- $o.push_signal(Signal::NewLine);
+ $o.push_signal(dprint_core::formatting::Signal::NewLine);
pi!(@s; $o: $($t)*);
}};
+ (@s; $o:ident: tab $($t:tt)*) => {{
+ $o.push_signal(dprint_core::formatting::Signal::Tab);
+ pi!(@s; $o: $($t)*);
+ }};
(@s; $o:ident: >i $($t:tt)*) => {{
- $o.push_signal(Signal::StartIndent);
+ $o.push_signal(dprint_core::formatting::Signal::StartIndent);
pi!(@s; $o: $($t)*);
}};
(@s; $o:ident: <i $($t:tt)*) => {{
- $o.push_signal(Signal::FinishIndent);
+ $o.push_signal(dprint_core::formatting::Signal::FinishIndent);
pi!(@s; $o: $($t)*);
}};
(@s; $o:ident: {$expr:expr} $($t:tt)*) => {{
$o.extend($expr.print());
pi!(@s; $o: $($t)*);
}};
+ (@s; $o:ident: items($expr:expr) $($t:tt)*) => {{
+ $o.extend($expr);
+ pi!(@s; $o: $($t)*);
+ }};
(@s; $o:ident: if ($e:expr)($($then:tt)*) $($t:tt)*) => {{
if $e {
pi!(@s; $o: $($then)*);
@@ -66,6 +85,8 @@
pi!(@s; $o: $($t)*)
};
}
+pub(crate) use p;
+pub(crate) use pi;
impl<P> Printable for Option<P>
where
@@ -266,9 +287,18 @@
match self {
ObjBody::ObjBodyComp(_) => todo!(),
ObjBody::ObjBodyMemberList(l) => {
- let mut pi = p!(new:);
- for mem in l.members() {
- match mem {
+ let mut pi = p!(new: str("{") >i nl);
+ let (children, end_comments) = children_between::<Member>(
+ l.syntax().clone(),
+ l.l_brace_token().map(Into::into).as_ref(),
+ l.r_brace_token().map(Into::into).as_ref(),
+ );
+ for mem in children.into_iter() {
+ if mem.needs_newline_above() {
+ p!(pi: nl);
+ }
+ p!(pi: items(format_comments(&mem.before_trivia, CommentLocation::AboveItem)));
+ match mem.value {
Member::MemberBindStmt(b) => {
p!(pi: {b.obj_local()})
}
@@ -279,8 +309,17 @@
p!(pi: {f.field()})
}
}
- p!(pi: str(",") nl)
+ p!(pi: str(","));
+ p!(pi: items(format_comments(&mem.inline_trivia, CommentLocation::ItemInline)));
+ p!(pi: nl)
}
+
+ // TODO: implement same thing as needs_newline_above, but for end comments
+ if should_start_with_newline(&end_comments) {
+ p!(pi: nl);
+ }
+ p!(pi: items(format_comments(&end_comments, CommentLocation::EndOfItems)));
+ p!(pi: <i str("}"));
pi
}
}
@@ -382,7 +421,7 @@
pi
}
Expr::ExprObject(o) => {
- p!(new: str("{") >i nl {o.obj_body()} <i str("}"))
+ p!(new: {o.obj_body()})
}
Expr::ExprArrayComp(arr) => {
let mut pi = p!(new: str("[") {arr.expr()});
@@ -485,6 +524,47 @@
],
m: a[1::],
m: b[::],
+
+ comments: {
+ _: '',
+ // Plain comment
+ a: '',
+
+ # Plain comment with empty line before
+ b: '',
+ /*Single-line multiline comment
+
+ */
+ c: '',
+
+ /**Single-line multiline doc comment
+
+ */
+ c: '',
+
+ /**multiline doc comment
+ s
+ */
+ c: '',
+
+ /*
+
+ Multi-line
+
+ comment
+ */
+ d: '',
+
+ e: '', // Inline comment
+
+ k: '',
+
+ // Text after everything
+ },
+ comments2: {
+ k: '',
+ // Text after everything, but no newline above
+ },
k: if a == b then
cmds/jrsonnet-fmt/src/snapshots/jrsonnet_fmt__tests__complex_comments_snapshot.snapdiffbeforeafterboth--- /dev/null
+++ b/cmds/jrsonnet-fmt/src/snapshots/jrsonnet_fmt__tests__complex_comments_snapshot.snap
@@ -0,0 +1,53 @@
+---
+source: cmds/jrsonnet-fmt/src/tests.rs
+expression: "reformat(indoc!(\"{\n\t\t comments: {\n\t\t\t_: '',\n\t\t\t// Plain comment\n\t\t\ta: '',\n\n\t\t\t# Plain comment with empty line before\n\t\t\tb: '',\n\t\t\t/*Single-line multiline comment\n\n\t\t\t*/\n\t\t\tc: '',\n\n\t\t\t/**Single-line multiline doc comment\n\n\t\t\t*/\n\t\t\tc: '',\n\n\t\t\t/**Multiline doc\n\t\t\tComment\n\t\t\t*/\n\t\t\tc: '',\n\n\t\t\t/*\n\n\tMulti-line\n\n\tcomment\n\t\t\t*/\n\t\t\td: '',\n\n\t\t\te: '', // Inline comment\n\n\t\t\tk: '',\n\n\t\t\t// Text after everything\n\t\t },\n\t\t comments2: {\n\t\t\tk: '',\n\t\t\t// Text after everything, but no newline above\n\t\t },\n spacing: {\n a: '',\n\n b: '',\n },\n noSpacing: {\n a: '',\n b: '',\n },\n }\"))"
+---
+{
+ comments: {
+ _: '',
+ // Plain comment
+ a: '',
+
+ # Plain comment with empty line before
+ b: '',
+ /* Single-line multiline comment */
+ c: '',
+
+ /**
+ * Single-line multiline doc comment
+ */
+ c: '',
+
+ /**
+ * Multiline doc
+ * Comment
+ */
+ c: '',
+
+ /*
+ Multi-line
+
+ comment
+ */
+ d: '',
+
+ e: '', // Inline comment
+
+ k: '',
+
+ // Text after everything
+ },
+ comments2: {
+ k: '',
+ // Text after everything, but no newline above
+ },
+ spacing: {
+ a: '',
+
+ b: '',
+ },
+ noSpacing: {
+ a: '',
+ b: '',
+ },
+}
cmds/jrsonnet-fmt/src/tests.rsdiffbeforeafterboth--- /dev/null
+++ b/cmds/jrsonnet-fmt/src/tests.rs
@@ -0,0 +1,124 @@
+use dprint_core::formatting::PrintOptions;
+use indoc::indoc;
+
+use crate::Printable;
+
+fn reformat(input: &str) -> String {
+ let (source, _) = jrsonnet_rowan_parser::parse(input);
+
+ dprint_core::formatting::format(
+ || source.print(),
+ PrintOptions {
+ indent_width: 2,
+ max_width: 100,
+ use_tabs: false,
+ new_line_text: "\n",
+ },
+ )
+}
+
+macro_rules! assert_formatted {
+ ($input:literal, $output:literal) => {
+ let formatted = reformat(indoc!($input));
+ let expected = indoc!($output);
+ if formatted != expected {
+ panic!(
+ "bad formatting, expected\n```\n{formatted}\n```\nto be equal to\n```\n{expected}\n```",
+ )
+ }
+ };
+}
+
+#[test]
+fn padding_stripped_for_multiline_comment() {
+ assert_formatted!(
+ "{
+ /*
+ Hello
+ World
+ */
+ _: null,
+ }",
+ "{
+ /*
+ Hello
+ World
+ */
+ _: null,
+ }"
+ );
+}
+
+// Fails
+#[test]
+fn last_comment_respects_spacing_with_inline_comment_above() {
+ assert_formatted!(
+ "{
+ a: '', // Inline
+
+ // Comment
+ }",
+ "{
+ a: '', // Inline
+
+ // Comment
+ }"
+ );
+}
+
+#[test]
+fn complex_comments_snapshot() {
+ insta::assert_display_snapshot!(reformat(indoc!(
+ "{
+ comments: {
+ _: '',
+ // Plain comment
+ a: '',
+
+ # Plain comment with empty line before
+ b: '',
+ /*Single-line multiline comment
+
+ */
+ c: '',
+
+ /**Single-line multiline doc comment
+
+ */
+ c: '',
+
+ /**Multiline doc
+ Comment
+ */
+ c: '',
+
+ /*
+
+ Multi-line
+
+ comment
+ */
+ d: '',
+
+ e: '', // Inline comment
+
+ k: '',
+
+ // Text after everything
+ },
+ comments2: {
+ k: '',
+ // Text after everything, but no newline above
+ },
+ spacing: {
+ a: '',
+
+ b: '',
+ },
+ noSpacing: {
+ a: '',
+ b: '',
+ },
+ }"
+ )))
+}
cmds/jrsonnet-lsp/Cargo.tomldiffbeforeafterboth--- /dev/null
+++ b/cmds/jrsonnet-lsp/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "jrsonnet-lsp"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+anyhow = "1.0.48"
+jrsonnet-evaluator = { path = "../../crates/jrsonnet-evaluator" }
+jrsonnet-rowan-parser = { path = "../../crates/jrsonnet-rowan-parser" }
+lsp-server = "0.6.0"
+lsp-types = "0.93.0"
+serde = "1.0.130"
+serde_json = "1.0.71"
cmds/jrsonnet-lsp/src/main.rsdiffbeforeafterboth--- /dev/null
+++ b/cmds/jrsonnet-lsp/src/main.rs
@@ -0,0 +1,188 @@
+use std::{fs::File, io::Write, path::PathBuf, str::FromStr};
+
+use lsp_server::{Connection, ErrorCode, Message, Request, RequestId, Response};
+use lsp_types::{
+ notification::{DidChangeTextDocument, DidOpenTextDocument, Notification},
+ request::{DocumentLinkRequest, HoverRequest},
+ CompletionOptions, DidChangeTextDocumentParams, DidOpenTextDocumentParams, DocumentLink,
+ DocumentLinkOptions, ServerCapabilities, TextDocumentSyncCapability, TextDocumentSyncKind,
+ TextDocumentSyncOptions, Url, WorkDoneProgressOptions,
+};
+
+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 {
+ reply(Response::new_err(
+ req.id,
+ ErrorCode::MethodNotFound as i32,
+ format!("unrecognized request {}", req.method),
+ ))
+ }
+ /*
+ 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
+ */
+ }
+ 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 (ast, errors) = jrsonnet_rowan_parser::parse(&text);
+ // 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()
+}
crates/jrsonnet-rowan-parser/Cargo.tomldiffbeforeafterboth--- a/crates/jrsonnet-rowan-parser/Cargo.toml
+++ b/crates/jrsonnet-rowan-parser/Cargo.toml
@@ -4,19 +4,19 @@
edition = "2021"
[dependencies]
-anyhow = "1.0.51"
+anyhow = "1.0"
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"
+indoc = "1.0"
+logos = "0.12"
+miette = { version = "4.2", features = ["fancy"] }
+rowan = "0.15"
+text-size = "1.1"
+thiserror = "1.0"
[dev-dependencies]
backtrace = "0.3.63"
-indoc = "1.0.3"
-insta = "1.10.0"
-anyhow = "1.0.57"
+indoc = "1.0"
+insta = "1.15"
+anyhow = "1.0"
jrsonnet-stdlib = { path = "../jrsonnet-stdlib" }
crates/jrsonnet-rowan-parser/jsonnet.ungramdiffbeforeafterboth--- a/crates/jrsonnet-rowan-parser/jsonnet.ungram
+++ b/crates/jrsonnet-rowan-parser/jsonnet.ungram
@@ -56,9 +56,7 @@
(Expr (',' Expr)* ','?)?
']'
ExprObject =
- '{'
ObjBody
- '}'
ExprArrayComp =
'['
Expr
@@ -168,6 +166,7 @@
(name:Name '=')? Expr
ObjBodyComp =
+ '{'
pre:ObjLocalPostComma*
'['
key:LhsExpr
@@ -177,8 +176,11 @@
value:Expr
post:ObjLocalPreComma*
CompSpec*
+ '}'
ObjBodyMemberList =
+ '{'
(Member (',' Member)* ','?)?
+ '}'
ObjBody =
ObjBodyComp
| ObjBodyMemberList
crates/jrsonnet-rowan-parser/src/event.rsdiffbeforeafterboth--- a/crates/jrsonnet-rowan-parser/src/event.rs
+++ b/crates/jrsonnet-rowan-parser/src/event.rs
@@ -24,6 +24,10 @@
Token {
kind: SyntaxKind,
},
+ /// Push token, but do not eat anything,
+ VirtualToken {
+ kind: SyntaxKind,
+ },
/// Position of finished node
Finish {
/// Same as forward_parent of Start, but for wrapping
@@ -105,6 +109,13 @@
self.token(kind);
eat_start_whitespace = true;
}
+ Event::VirtualToken { kind } => {
+ if eat_start_whitespace {
+ self.skip_whitespace();
+ }
+ self.virtual_token(kind);
+ eat_start_whitespace = false;
+ }
Event::Finish { wrapper } => {
self.builder.finish_node();
depth -= 1;
@@ -124,7 +135,7 @@
}
eat_start_whitespace = true;
}
- Event::Pending => panic!("placeholder should not end in events"),
+ Event::Pending => panic!("pending event should not appear in finished events"),
Event::Noop => {}
Event::Error(e) => {
self.errors.push(e);
@@ -137,6 +148,9 @@
errors: self.errors,
}
}
+ fn virtual_token(&mut self, kind: SyntaxKind) {
+ self.builder.token(JsonnetLanguage::kind_to_raw(kind), "")
+ }
fn token(&mut self, kind: SyntaxKind) {
let lexeme = self.lexemes[self.offset];
self.builder
crates/jrsonnet-rowan-parser/src/generated/nodes.rsdiffbeforeafterboth--- a/crates/jrsonnet-rowan-parser/src/generated/nodes.rs
+++ b/crates/jrsonnet-rowan-parser/src/generated/nodes.rs
@@ -291,15 +291,9 @@
pub(crate) syntax: SyntaxNode,
}
impl ExprObject {
- pub fn l_brace_token(&self) -> Option<SyntaxToken> {
- support::token(&self.syntax, T!['{'])
- }
pub fn obj_body(&self) -> Option<ObjBody> {
support::child(&self.syntax)
}
- pub fn r_brace_token(&self) -> Option<SyntaxToken> {
- support::token(&self.syntax, T!['}'])
- }
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -538,6 +532,9 @@
pub(crate) syntax: SyntaxNode,
}
impl ObjBodyComp {
+ pub fn l_brace_token(&self) -> Option<SyntaxToken> {
+ support::token(&self.syntax, T!['{'])
+ }
pub fn pre(&self) -> AstChildren<ObjLocalPostComma> {
support::children(&self.syntax)
}
@@ -565,6 +562,9 @@
pub fn comp_specs(&self) -> AstChildren<CompSpec> {
support::children(&self.syntax)
}
+ pub fn r_brace_token(&self) -> Option<SyntaxToken> {
+ support::token(&self.syntax, T!['}'])
+ }
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -598,9 +598,15 @@
pub(crate) syntax: SyntaxNode,
}
impl ObjBodyMemberList {
+ pub fn l_brace_token(&self) -> Option<SyntaxToken> {
+ support::token(&self.syntax, T!['{'])
+ }
pub fn members(&self) -> AstChildren<Member> {
support::children(&self.syntax)
}
+ pub fn r_brace_token(&self) -> Option<SyntaxToken> {
+ support::token(&self.syntax, T!['}'])
+ }
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
crates/jrsonnet-rowan-parser/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-rowan-parser/src/lib.rs
+++ b/crates/jrsonnet-rowan-parser/src/lib.rs
@@ -1,5 +1,11 @@
#![deny(unused_must_use)]
+use event::Sink;
+use generated::nodes::{SourceFile, Trivia};
+use lex::lex;
+use parser::{Parser, SyntaxError};
+pub use rowan;
+
mod ast;
mod event;
mod generated;
@@ -13,18 +19,18 @@
mod token_set;
pub use ast::{AstChildren, AstNode, AstToken};
-use event::Sink;
-use generated::nodes::SourceFile;
pub use generated::{nodes, syntax_kinds::SyntaxKind};
-pub use language::{
- JsonnetLanguage, PreorderWithTokens, SyntaxElement, SyntaxElementChildren, SyntaxNode,
- SyntaxNodeChildren, SyntaxToken,
-};
-use lex::lex;
-use parser::{Parser, SyntaxError};
+pub use language::*;
+pub use token_set::SyntaxKindSet;
+
pub fn parse(input: &str) -> (SourceFile, Vec<SyntaxError>) {
let lexemes = lex(input);
- let parser = Parser::new(&lexemes);
+ let kinds = lexemes
+ .iter()
+ .map(|l| l.kind)
+ .filter(|k| !Trivia::can_cast(*k))
+ .collect();
+ let parser = Parser::new(kinds);
let events = parser.parse();
let sink = Sink::new(events, &lexemes);
crates/jrsonnet-rowan-parser/src/marker.rsdiffbeforeafterboth--- a/crates/jrsonnet-rowan-parser/src/marker.rs
+++ b/crates/jrsonnet-rowan-parser/src/marker.rs
@@ -113,21 +113,3 @@
completed
}
}
-
-pub trait AsRange {
- fn as_range(&self, p: &Parser) -> TextRange;
- fn end_token(&self) -> usize;
-}
-
-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.rsdiffbeforeafterboth1use std::{cell::Cell, fmt::Display, rc::Rc};23use miette::{LabeledSpan, SourceOffset, SourceSpan};4use rowan::{GreenNode, TextRange, TextSize};56use crate::{7 event::Event,8 lex::Lexeme,9 marker::{AsRange, CompletedMarker, Marker, Ranger},10 nodes::{BinaryOperatorKind, Literal, Number, Text, Trivia, UnaryOperatorKind},11 token_set::SyntaxKindSet,12 AstToken, SyntaxKind,13 SyntaxKind::*,14 SyntaxNode, T, TS,15};1617pub struct Parse {18 pub green_node: GreenNode,19 pub errors: Vec<SyntaxError>,20}2122#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]23pub enum ExpectedSyntax {24 Named(&'static str),25 Unnamed(SyntaxKind),26}27impl Display for ExpectedSyntax {28 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {29 match self {30 ExpectedSyntax::Named(n) => write!(f, "{}", n),31 ExpectedSyntax::Unnamed(u) => write!(f, "{:?}", u),32 }33 }34}3536pub struct Parser<'i> {37 // TODO: remove all trivia before feeding to parser?38 lexemes: &'i [Lexeme<'i>],39 pub offset: usize,40 pub events: Vec<Event>,41 pub entered: u32,42 pub hints: Vec<(u32, TextRange, String)>,43 pub last_error_token: usize,44 expected_syntax: Option<ExpectedSyntax>,45 expected_syntax_tracking_state: Rc<Cell<ExpectedSyntaxTrackingState>>,46 steps: Cell<u64>,47}4849const DEFAULT_RECOVERY_SET: SyntaxKindSet = TS![];5051#[derive(Clone, Debug, PartialEq, Eq)]52pub enum SyntaxError {53 Unexpected {54 expected: ExpectedSyntax,55 found: SyntaxKind,56 range: TextRange,57 },58 Missing {59 expected: ExpectedSyntax,60 offset: TextSize,61 },62 Custom {63 error: String,64 range: TextRange,65 },66 Hint {67 error: String,68 range: TextRange,69 },70}7172impl From<SyntaxError> for LabeledSpan {73 fn from(val: SyntaxError) -> Self {74 match val {75 SyntaxError::Unexpected {76 expected,77 found,78 range,79 } => LabeledSpan::new_with_span(80 Some(format!("expected {}, found {:?}", expected, found)),81 SourceSpan::new(82 SourceOffset::from(usize::from(range.start())),83 SourceOffset::from(usize::from(range.end() - range.start())),84 ),85 ),86 SyntaxError::Missing { expected, offset } => LabeledSpan::new_with_span(87 Some(format!("missing {}", expected)),88 SourceSpan::new(89 SourceOffset::from(usize::from(offset)),90 SourceOffset::from(0),91 ),92 ),93 SyntaxError::Custom { error, range } | SyntaxError::Hint { error, range } => {94 LabeledSpan::new_with_span(95 Some(error),96 SourceSpan::new(97 SourceOffset::from(usize::from(range.start())),98 SourceOffset::from(usize::from(range.end() - range.start())),99 ),100 )101 }102 }103 }104}105106impl<'i> Parser<'i> {107 pub fn new(lexemes: &'i [Lexeme<'i>]) -> Self {108 Self {109 lexemes,110 offset: 0,111 events: vec![],112 entered: 0,113 last_error_token: 0,114 hints: vec![],115 expected_syntax: None,116 expected_syntax_tracking_state: Rc::new(Cell::new(117 ExpectedSyntaxTrackingState::Unnamed,118 )),119 steps: Cell::new(0),120 }121 }122 pub fn clear_outdated_hints(&mut self) {123 let amount = self124 .hints125 .iter()126 .rev()127 .take_while(|h| h.0 > self.entered)128 .count();129 self.hints.truncate(self.hints.len() - amount)130 }131 fn clear_expected_syntaxes(&mut self) {132 self.expected_syntax = None;133 self.expected_syntax_tracking_state134 .set(ExpectedSyntaxTrackingState::Unnamed);135 }136 pub fn start(&mut self) -> Marker {137 self.skip_trivia();138 let start_event_idx = self.events.len();139 self.events.push(Event::Pending);140 self.entered += 1;141 Marker::new(start_event_idx)142 }143 pub fn start_ranger(&mut self) -> Ranger {144 self.skip_trivia();145 let pos = self.offset;146 Ranger { pos }147 }148 pub fn parse(mut self) -> Vec<Event> {149 let m = self.start();150 expr(&mut self);151 self.expect(EOF);152 m.complete(&mut self, SOURCE_FILE);153154 self.events155 }156157 pub(crate) fn expect(&mut self, kind: SyntaxKind) {158 self.expect_with_recovery_set(kind, TS![])159 }160161 pub(crate) fn expect_with_recovery_set(162 &mut self,163 kind: SyntaxKind,164 recovery_set: SyntaxKindSet,165 ) {166 if self.at(kind) {167 if kind != EOF {168 self.bump();169 }170 } else {171 self.error_with_recovery_set(recovery_set);172 }173 }174175 pub(crate) fn expect_with_no_skip(&mut self, kind: SyntaxKind) {176 if self.at(kind) {177 self.bump();178 } else {179 self.error_with_no_skip();180 }181 }182 fn current_token(&self) -> Lexeme<'i> {183 self.lexemes[self.offset]184 }185 fn previous_token(&mut self) -> Option<Lexeme<'i>> {186 if self.offset == 0 {187 return None;188 }189 let mut previous_token_idx = self.offset - 1;190 while self191 .lexemes192 .get(previous_token_idx)193 .map_or(false, |l| Trivia::can_cast(l.kind))194 && previous_token_idx != 0195 {196 previous_token_idx -= 1;197 }198199 Some(self.lexemes[previous_token_idx])200 }201 pub fn start_of_token(&self, mut idx: usize) -> TextSize {202 while Trivia::can_cast(self.lexemes[idx].kind) {203 idx += 1;204 }205 self.lexemes[idx].range.start()206 }207 pub fn end_of_token(&self, mut idx: usize) -> TextSize {208 while Trivia::can_cast(self.lexemes[idx].kind) {209 idx -= 1;210 }211 self.lexemes[idx].range.end()212 }213 pub(crate) fn custom_error(&mut self, marker: impl AsRange, error: impl AsRef<str>) {214 self.last_error_token = marker.end_token();215 self.events.push(Event::Error(SyntaxError::Custom {216 error: error.as_ref().to_string(),217 range: marker.as_range(self),218 }));219 }220 pub(crate) fn error_with_recovery_set(221 &mut self,222 recovery_set: SyntaxKindSet,223 ) -> Option<CompletedMarker> {224 self.error_with_recovery_set_no_default(recovery_set.union(DEFAULT_RECOVERY_SET))225 }226 pub fn error_with_no_skip(&mut self) -> Option<CompletedMarker> {227 self.error_with_recovery_set_no_default(SyntaxKindSet::ALL)228 }229230 pub fn error_with_recovery_set_no_default(231 &mut self,232 recovery_set: SyntaxKindSet,233 ) -> Option<CompletedMarker> {234 let expected_syntax = self235 .expected_syntax236 .take()237 .unwrap_or(ExpectedSyntax::Named("unknown"));238 self.expected_syntax_tracking_state239 .set(ExpectedSyntaxTrackingState::Unnamed);240241 self.skip_trivia();242 if self.at_end() || self.at_ts(recovery_set) {243 let range = self244 .previous_token()245 .map(|t| t.range)246 .unwrap_or_else(|| TextRange::at(TextSize::from(0), TextSize::from(0)));247248 self.events.push(Event::Error(SyntaxError::Missing {249 expected: expected_syntax,250 offset: range.end(),251 }));252 return None;253 }254255 let current_token = self.current_token();256257 self.events.push(Event::Error(SyntaxError::Unexpected {258 expected: expected_syntax,259 found: current_token.kind,260 range: current_token.range,261 }));262 self.clear_expected_syntaxes();263 self.last_error_token = self.offset;264265 let m = self.start();266 self.bump();267 Some(m.complete(self, SyntaxKind::ERROR))268 }269 fn bump_assert(&mut self, kind: SyntaxKind) {270 self.skip_trivia();271 assert!(self.at(kind), "expected {:?}", kind);272 self.bump_remap(self.current());273 }274 fn bump(&mut self) {275 self.skip_trivia();276 self.bump_remap(self.current());277 }278 fn bump_remap(&mut self, kind: SyntaxKind) {279 self.skip_trivia();280 assert_ne!(self.offset, self.lexemes.len(), "already at end");281 self.events.push(Event::Token { kind });282 self.offset += 1;283 self.clear_expected_syntaxes();284 }285 fn step(&self) {286 use std::fmt::Write;287 let steps = self.steps.get();288 if steps >= 15000000 {289 let mut out = "seems like parsing is stuck".to_owned();290 {291 let last = 20;292 write!(out, "\n\nLast {} events:", last).unwrap();293 for (i, event) in self294 .events295 .iter()296 .skip(self.events.len().saturating_sub(last))297 .enumerate()298 {299 write!(out, "\n{i}. {event:?}").unwrap();300 }301 }302 {303 let next = 20;304 write!(out, "\n\nNext {next} tokens:").unwrap();305 for (i, tok) in self.lexemes.iter().skip(self.offset).take(next).enumerate() {306 write!(out, "\n{i}. {tok:?}").unwrap();307 }308 }309 panic!("{out}")310 }311 self.steps.set(steps + 1);312 }313 fn nth(&self, i: usize) -> SyntaxKind {314 self.step();315 let mut offset = self.offset;316 for _ in 0..i {317 while self318 .lexemes319 .get(offset)320 .map(|l| Trivia::can_cast(l.kind))321 .unwrap_or(false)322 {323 offset += 1;324 }325 offset += 1;326 }327 while self328 .lexemes329 .get(offset)330 .map(|l| Trivia::can_cast(l.kind))331 .unwrap_or(false)332 {333 offset += 1;334 }335 self.lexemes.get(offset).map(|l| l.kind).unwrap_or(EOF)336 }337 fn current(&self) -> SyntaxKind {338 self.nth(0)339 }340 fn skip_trivia(&mut self) {341 while Trivia::can_cast(self.peek_raw()) {342 self.offset += 1;343 }344 }345 fn peek_raw(&mut self) -> SyntaxKind {346 self.lexemes347 .get(self.offset)348 .map(|l| l.kind)349 .unwrap_or(SyntaxKind::EOF)350 }351 #[must_use]352 pub(crate) fn expected_syntax_name(&mut self, name: &'static str) -> ExpectedSyntaxGuard {353 self.expected_syntax_tracking_state354 .set(ExpectedSyntaxTrackingState::Named);355 self.expected_syntax = Some(ExpectedSyntax::Named(name));356357 ExpectedSyntaxGuard::new(Rc::clone(&self.expected_syntax_tracking_state))358 }359 pub fn at(&mut self, kind: SyntaxKind) -> bool {360 self.nth_at(0, kind)361 }362 pub fn nth_at(&mut self, n: usize, kind: SyntaxKind) -> bool {363 if let ExpectedSyntaxTrackingState::Unnamed = self.expected_syntax_tracking_state.get() {364 self.expected_syntax = Some(ExpectedSyntax::Unnamed(kind));365 }366 self.nth(n) == kind367 }368 pub fn at_ts(&mut self, set: SyntaxKindSet) -> bool {369 set.contains(self.current())370 }371 pub fn at_end(&mut self) -> bool {372 self.at(EOF)373 }374}375pub(crate) struct ExpectedSyntaxGuard {376 expected_syntax_tracking_state: Rc<Cell<ExpectedSyntaxTrackingState>>,377}378379impl ExpectedSyntaxGuard {380 fn new(expected_syntax_tracking_state: Rc<Cell<ExpectedSyntaxTrackingState>>) -> Self {381 Self {382 expected_syntax_tracking_state,383 }384 }385}386387impl Drop for ExpectedSyntaxGuard {388 fn drop(&mut self) {389 self.expected_syntax_tracking_state390 .set(ExpectedSyntaxTrackingState::Unnamed);391 }392}393394#[derive(Debug, Clone, Copy)]395enum ExpectedSyntaxTrackingState {396 Named,397 Unnamed,398}399400fn expr(p: &mut Parser) -> Option<CompletedMarker> {401 expr_binding_power(p, 0)402}403fn expr_binding_power(p: &mut Parser, minimum_binding_power: u8) -> Option<CompletedMarker> {404 let mut lhs = lhs(p)?;405406 while let Some(op) = BinaryOperatorKind::cast(p.current())407 .or_else(|| p.at(T!['{']).then(|| BinaryOperatorKind::MetaObjectApply))408 {409 let (left_binding_power, right_binding_power) = op.binding_power();410 if left_binding_power < minimum_binding_power {411 break;412 }413414 // Object apply is not a real operator, we dont have something to bump415 if op != BinaryOperatorKind::MetaObjectApply {416 p.bump();417 }418419 let m = lhs.wrap(p, LHS_EXPR).precede(p);420 let parsed_rhs = expr_binding_power(p, right_binding_power).is_some();421 lhs = m.complete(422 p,423 if op == BinaryOperatorKind::MetaObjectApply {424 EXPR_OBJ_EXTEND425 } else {426 EXPR_BINARY427 },428 );429430 if !parsed_rhs {431 break;432 }433 }434 Some(lhs)435}436fn compspec(p: &mut Parser) {437 assert!(p.at(T![for]) || p.at(T![if]));438 if p.at(T![for]) {439 let m = p.start();440 p.bump();441 name(p);442 p.expect(T![in]);443 expr(p);444 m.complete(p, FOR_SPEC);445 } else if p.at(T![if]) {446 let m = p.start();447 p.bump();448 expr(p);449 m.complete(p, IF_SPEC);450 } else {451 unreachable!()452 }453}454fn comma(p: &mut Parser) -> bool {455 if p.at(T![,]) {456 p.bump();457 true458 } else {459 false460 }461}462fn comma_with_alternatives(p: &mut Parser, set: SyntaxKindSet) -> bool {463 if p.at(T![,]) {464 p.bump();465 true466 } else if p.at_ts(set) {467 p.expect_with_no_skip(T![,]);468 p.bump();469 true470 } else {471 false472 }473}474fn field_name(p: &mut Parser) {475 let _e = p.expected_syntax_name("field name");476 let m = p.start();477 if p.at(T!['[']) {478 p.bump();479 expr(p);480 p.expect(T![']']);481 m.complete(p, FIELD_NAME_DYNAMIC);482 } else if p.at(IDENT) {483 name(p);484 m.complete(p, FIELD_NAME_FIXED);485 } else if Text::can_cast(p.current()) {486 text(p);487 m.complete(p, FIELD_NAME_FIXED);488 } else {489 p.error_with_recovery_set(TS![;]);490 }491}492fn visibility(p: &mut Parser) {493 if p.at_ts(TS![: :: :::]) {494 p.bump()495 } else {496 p.error_with_recovery_set(TS![]);497 }498}499fn field(p: &mut Parser) {500 let m = p.start();501 field_name(p);502 let plus = if p.at(T![+]) {503 let r = p.start_ranger();504 p.bump();505 Some(r.finish(p))506 } else {507 None508 };509 let params = if p.at(T!['(']) {510 if let Some(plus) = plus {511 p.custom_error(plus, "can't extend with method");512 }513 params_desc(p);514 if p.at(T![+]) {515 let r = p.start_ranger();516 p.bump();517 p.custom_error(r.finish(p), "can't extend with method");518 }519 true520 } else {521 false522 };523 visibility(p);524 expr(p);525526 if params {527 m.complete(p, FIELD_METHOD)528 } else {529 m.complete(p, FIELD_NORMAL)530 };531}532fn assertion(p: &mut Parser) {533 let m = p.start();534 p.bump_assert(T![assert]);535 expr(p).map(|c| c.wrap(p, LHS_EXPR));536 if p.at(T![:]) {537 p.bump();538 expr(p);539 }540 m.complete(p, ASSERTION);541}542fn object(p: &mut Parser) -> CompletedMarker {543 let m_t = p.start();544 let m = p.start();545 p.bump_assert(T!['{']);546547 loop {548 if p.at(T!['}']) {549 p.bump();550 break;551 }552 let m = p.start();553 if p.at(T![local]) {554 obj_local(p);555 m.complete(p, MEMBER_BIND_STMT)556 } else if p.at(T![assert]) {557 assertion(p);558 m.complete(p, MEMBER_ASSERT_STMT)559 } else {560 field(p);561 while p.at(T![for]) || p.at(T![if]) {562 compspec(p)563 }564 m.complete(p, MEMBER_FIELD)565 };566 if comma_with_alternatives(p, SyntaxKindSet::new(&[T![=]])) {567 continue;568 }569 p.expect(R_BRACE);570 break;571 }572573 m.complete(p, OBJ_BODY_MEMBER_LIST);574 m_t.complete(p, EXPR_OBJECT)575}576fn param(p: &mut Parser) {577 let m = p.start();578 destruct(p);579 if p.at(T![=]) {580 p.bump();581 expr(p);582 }583 m.complete(p, PARAM);584}585fn params_desc(p: &mut Parser) -> CompletedMarker {586 let m = p.start();587 p.bump_assert(T!['(']);588589 loop {590 if p.at(T![')']) {591 p.bump();592 break;593 }594 param(p);595 if comma(p) {596 continue;597 }598 p.expect(T![')']);599 break;600 }601602 m.complete(p, PARAMS_DESC)603}604fn args_desc(p: &mut Parser) {605 let m = p.start();606 p.bump_assert(T!['(']);607608 let started_named = Cell::new(false);609610 loop {611 if p.at(T![')']) {612 break;613 }614615 let m = p.start();616 if p.at(IDENT) && p.nth_at(1, T![=]) {617 name(p);618 p.bump();619 expr(p);620 m.complete(p, ARG);621 started_named.set(true);622 } else {623 expr(p);624 m.complete(p, ARG);625 }626 if comma(p) {627 continue;628 }629 break;630 }631 p.expect(T![')']);632 if p.at(T![tailstrict]) {633 p.bump()634 }635 m.complete(p, ARGS_DESC);636}637638fn array(p: &mut Parser) -> CompletedMarker {639 // Start the list node640 let m = p.start();641 p.bump_assert(T!['[']);642643 // This vec will have at most one element in case of correct input644 let mut compspecs = Vec::with_capacity(1);645 let mut elems = 0;646647 loop {648 if p.at(T![']']) {649 p.bump();650 break;651 }652 elems += 1;653 expr(p);654 let c = p.start_ranger();655 let mut had_spec = false;656 while p.at(T![for]) || p.at(T![if]) {657 had_spec = true;658 compspec(p)659 }660 if had_spec {661 compspecs.push(c.finish(p));662 }663 if comma(p) {664 continue;665 }666 p.expect(T![']']);667 break;668 }669670 if elems > 1 && !compspecs.is_empty() {671 for spec in compspecs {672 p.custom_error(673 spec,674 "compspec may only be used if there is only one array element",675 )676 }677678 m.complete(p, EXPR_ARRAY)679 } else if !compspecs.is_empty() {680 m.complete(p, EXPR_ARRAY_COMP)681 } else {682 m.complete(p, EXPR_ARRAY)683 }684}685/// Returns true if it was slice, false if just index686#[must_use]687fn slice_desc_or_index(p: &mut Parser) -> bool {688 let m = p.start();689 p.bump();690 // TODO: do not treat :, ::, ::: as full tokens?691 // Start692 if !p.at(T![:]) && !p.at(T![::]) {693 expr(p);694 }695 if p.at(T![:]) {696 p.bump();697 // End698 if !p.at(T![']']) {699 expr(p).map(|c| c.wrap(p, SLICE_DESC_END));700 }701 if p.at(T![:]) {702 p.bump();703 // Step704 if !p.at(T![']']) {705 expr(p).map(|c| c.wrap(p, SLICE_DESC_STEP));706 }707 }708 } else if p.at(T![::]) {709 p.bump();710 // End711 if !p.at(T![']']) {712 expr(p).map(|c| c.wrap(p, SLICE_DESC_END));713 }714 } else {715 // It was not a slice716 p.expect(T![']']);717 m.forget(p);718 return false;719 }720 p.expect(T![']']);721 m.complete(p, SLICE_DESC);722 true723}724fn lhs(p: &mut Parser) -> Option<CompletedMarker> {725 let mut lhs = lhs_basic(p)?;726727 loop {728 if p.at(T![.]) {729 let m = lhs.precede(p);730 p.bump();731 name(p);732 lhs = m.complete(p, EXPR_INDEX);733 } else if p.at(T!['[']) {734 if slice_desc_or_index(p) {735 lhs = lhs.precede(p).complete(p, EXPR_SLICE);736 } else {737 lhs = lhs738 .wrap(p, LHS_EXPR)739 .precede(p)740 .complete(p, EXPR_INDEX_EXPR);741 }742 } else if p.at(T!['(']) {743 let m = lhs.precede(p);744 args_desc(p);745 lhs = m.complete(p, EXPR_APPLY);746 } else {747 break;748 }749 }750751 Some(lhs)752}753fn name(p: &mut Parser) {754 let m = p.start();755 p.expect(IDENT);756 m.complete(p, NAME);757}758fn destruct_rest(p: &mut Parser) {759 let m = p.start();760 p.bump_assert(T![...]);761 if p.at(IDENT) {762 p.bump()763 }764 m.complete(p, DESTRUCT_REST);765}766fn destruct_object_field(p: &mut Parser) {767 let m = p.start();768 name(p);769 if p.at(T![:]) {770 p.bump();771 destruct(p);772 };773 if p.at(T![=]) {774 p.bump();775 expr(p);776 }777 m.complete(p, DESTRUCT_OBJECT_FIELD);778}779fn obj_local(p: &mut Parser) {780 let m = p.start();781 p.bump_assert(T![local]);782 bind(p);783 m.complete(p, OBJ_LOCAL);784}785fn destruct(p: &mut Parser) -> CompletedMarker {786 let m = p.start();787 if p.at(T![?]) {788 p.bump();789 m.complete(p, DESTRUCT_SKIP)790 } else if p.at(T!['[']) {791 p.bump();792 let mut had_rest = false;793 loop {794 if p.at(T![']']) {795 p.bump();796 break;797 } else if p.at(T![...]) {798 let m_err = p.start_ranger();799 destruct_rest(p);800 if had_rest {801 p.custom_error(m_err.finish(p), "only one rest can be present in array");802 }803 had_rest = true;804 } else {805 destruct(p);806 }807 if p.at(T![,]) {808 p.bump();809 continue;810 }811 p.expect(T![']']);812 break;813 }814 m.complete(p, DESTRUCT_ARRAY)815 } else if p.at(T!['{']) {816 p.bump();817 let mut had_rest = false;818 loop {819 if p.at(T!['}']) {820 p.bump();821 break;822 } else if p.at(T![...]) {823 let m_err = p.start_ranger();824 destruct_rest(p);825 if had_rest {826 p.custom_error(m_err.finish(p), "only one rest can be present in object");827 }828 had_rest = true;829 } else {830 if had_rest {831 p.error_with_recovery_set(TS![]);832 }833 destruct_object_field(p);834 }835 if p.at(T![,]) {836 p.bump();837 continue;838 }839 p.expect(T!['}']);840 break;841 }842 m.complete(p, DESTRUCT_OBJECT)843 } else if p.at(IDENT) {844 name(p);845 m.complete(p, DESTRUCT_FULL)846 } else {847 m.complete(p, ERROR)848 }849}850fn bind(p: &mut Parser) {851 let m = p.start();852 if p.at(IDENT) && p.nth_at(1, T!['(']) {853 name(p);854 params_desc(p);855 p.expect(T![=]);856 expr(p);857 m.complete(p, BIND_FUNCTION)858 } else {859 destruct(p);860 p.expect(T![=]);861 expr(p);862 m.complete(p, BIND_DESTRUCT)863 };864}865fn text(p: &mut Parser) {866 assert!(Text::can_cast(p.current()));867 p.bump();868}869fn number(p: &mut Parser) {870 assert!(Number::can_cast(p.current()));871 p.bump();872}873fn literal(p: &mut Parser) {874 assert!(Literal::can_cast(p.current()));875 p.bump();876}877fn lhs_basic(p: &mut Parser) -> Option<CompletedMarker> {878 let _e = p.expected_syntax_name("value");879 Some(if Literal::can_cast(p.current()) {880 let m = p.start();881 literal(p);882 m.complete(p, EXPR_LITERAL)883 } else if Text::can_cast(p.current()) {884 let m = p.start();885 text(p);886 m.complete(p, EXPR_STRING)887 } else if Number::can_cast(p.current()) {888 let m = p.start();889 number(p);890 m.complete(p, EXPR_NUMBER)891 } else if p.at(IDENT) {892 let m = p.start();893 name(p);894 m.complete(p, EXPR_VAR)895 } else if p.at(INTRINSIC_THIS_FILE) {896 let m = p.start();897 p.bump();898 m.complete(p, EXPR_INTRINSIC_THIS_FILE)899 } else if p.at(INTRINSIC_ID) {900 let m = p.start();901 p.bump();902 m.complete(p, EXPR_INTRINSIC_ID)903 } else if p.at(INTRINSIC) {904 let m = p.start();905 p.bump();906 p.expect(T!['(']);907 name(p);908 p.expect(T![')']);909 m.complete(p, EXPR_INTRINSIC)910 } else if p.at(T![if]) {911 let m = p.start();912 p.bump();913 expr(p);914 p.expect(T![then]);915 expr(p).map(|c| c.wrap(p, TRUE_EXPR));916 if p.at(T![else]) {917 p.bump();918 expr(p).map(|c| c.wrap(p, FALSE_EXPR));919 }920 m.complete(p, EXPR_IF_THEN_ELSE)921 } else if p.at(T!['[']) {922 array(p)923 } else if p.at(T!['{']) {924 object(p)925 } else if p.at(T![local]) {926 let m = p.start();927 p.bump();928 loop {929 if p.at(T![;]) {930 p.bump();931 break;932 }933 bind(p);934935 if p.at(T![,]) {936 p.bump();937 continue;938 }939 p.expect(T![;]);940 break;941 }942 expr(p);943 m.complete(p, EXPR_LOCAL)944 } else if p.at(T![function]) {945 let m = p.start();946 p.bump();947 params_desc(p);948 expr(p);949 m.complete(p, EXPR_FUNCTION)950 } else if p.at(T![error]) {951 let m = p.start();952 p.bump();953 expr(p);954 m.complete(p, EXPR_ERROR)955 } else if p.at(T![assert]) {956 let m = p.start();957 assertion(p);958 p.expect(T![;]);959 expr(p);960 m.complete(p, EXPR_ASSERT)961 } else if p.at(T![import]) || p.at(T![importstr]) || p.at(T![importbin]) {962 let m = p.start();963 p.bump();964 text(p);965 m.complete(p, EXPR_IMPORT)966 } else if let Some(op) = UnaryOperatorKind::cast(p.current()) {967 let ((), right_binding_power) = op.binding_power();968969 let m = p.start();970 p.bump();971 expr_binding_power(p, right_binding_power);972 m.complete(p, EXPR_UNARY)973 } else if p.at(T!['(']) {974 let m = p.start();975 p.bump();976 expr(p);977 p.expect(T![')']);978 m.complete(p, EXPR_PARENED)979 } else {980 p.error_with_recovery_set(TS![]);981 return None;982 })983}984985impl Parse {986 pub fn syntax(&self) -> SyntaxNode {987 SyntaxNode::new_root(self.green_node.clone())988 }989}crates/jrsonnet-rowan-parser/src/token_set.rsdiffbeforeafterboth--- a/crates/jrsonnet-rowan-parser/src/token_set.rs
+++ b/crates/jrsonnet-rowan-parser/src/token_set.rs
@@ -34,9 +34,9 @@
#[macro_export]
macro_rules! TS {
($($tt:tt)*) => {
- SyntaxKindSet::new(&[
+ $crate::SyntaxKindSet::new(&[
$(
- T![$tt]
+ $crate::T![$tt]
),*
])
};
jrsonnet-lsp/Cargo.tomldiffbeforeafterboth--- a/jrsonnet-lsp/Cargo.toml
+++ /dev/null
@@ -1,15 +0,0 @@
-[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--- a/jrsonnet-lsp/src/main.rs
+++ /dev/null
@@ -1,211 +0,0 @@
-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()
-}
xtask/src/sourcegen/ast.rsdiffbeforeafterboth--- a/xtask/src/sourcegen/ast.rs
+++ b/xtask/src/sourcegen/ast.rs
@@ -151,6 +151,19 @@
if let Some(old) = types.insert(field.ty(), field.method_name(kinds)) {
panic!("{name}.{} has same type as {name}.{}, resolve conflict by wrapping one field: {}", old, field.method_name(kinds), field.ty());
}
+ // TODO: check for assignable field types, i.e you can have
+ // ```
+ // SomeEnum =
+ // SomeItem
+ // | SomeOtherItem
+ // ```
+ // And check above will fail to detect conflict in
+ // ```
+ // SomeStruct =
+ // SomeEnum
+ // SomeItem
+ // ```
+ // Despite generating getters, which will both return SomeEnum
}
res.nodes.push(AstNodeSrc {
doc: Vec::new(),