Lots of owork on a calculator example, transparency, etc.
This commit is contained in:
745
lib/reactivity_parsing/examples/json_reactive_parser.rs
Normal file
745
lib/reactivity_parsing/examples/json_reactive_parser.rs
Normal file
@@ -0,0 +1,745 @@
|
||||
use std::env;
|
||||
use std::fmt;
|
||||
use std::rc::Rc;
|
||||
|
||||
use ruin_reactivity::{cell_in, effect_in};
|
||||
use ruin_reactivity_parsing::{
|
||||
Anchor, AnchorBias, EditDelta, ReactiveRope, RopeEdit, TextPoint, TextRange,
|
||||
};
|
||||
use ruin_runtime::{async_main, fs, stdio};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct ParseSnapshot {
|
||||
text: String,
|
||||
root_anchor: Anchor,
|
||||
root: AstNode,
|
||||
reused_nodes: usize,
|
||||
total_nodes: usize,
|
||||
diagnostics: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct AstNode {
|
||||
id: u64,
|
||||
kind: JsonKind,
|
||||
local_range: TextRange,
|
||||
children: Vec<AstNode>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum JsonKind {
|
||||
Object,
|
||||
Array,
|
||||
Pair,
|
||||
String,
|
||||
Number,
|
||||
True,
|
||||
False,
|
||||
Null,
|
||||
}
|
||||
|
||||
impl fmt::Display for JsonKind {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let label = match self {
|
||||
Self::Object => "object",
|
||||
Self::Array => "array",
|
||||
Self::Pair => "pair",
|
||||
Self::String => "string",
|
||||
Self::Number => "number",
|
||||
Self::True => "true",
|
||||
Self::False => "false",
|
||||
Self::Null => "null",
|
||||
};
|
||||
write!(f, "{label}")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct ParsedNode {
|
||||
kind: JsonKind,
|
||||
range: TextRange,
|
||||
children: Vec<ParsedNode>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct JsonParser {
|
||||
next_id: u64,
|
||||
}
|
||||
|
||||
struct Cursor<'a> {
|
||||
text: &'a str,
|
||||
offset: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum Command {
|
||||
Insert { offset: usize, text: String },
|
||||
Replace { start: usize, end: usize, text: String },
|
||||
ReplaceAll { text: String },
|
||||
Delete { start: usize, end: usize },
|
||||
Print,
|
||||
Help,
|
||||
Quit,
|
||||
}
|
||||
|
||||
#[async_main]
|
||||
async fn main() {
|
||||
let path = env::args()
|
||||
.nth(1)
|
||||
.expect("usage: cargo run -p ruin-reactivity-parsing --example json_reactive_parser -- <path/to/file.json>");
|
||||
let initial_text = fs::read_to_string(&path)
|
||||
.await
|
||||
.expect("json file should be readable");
|
||||
|
||||
let reactor = ruin_reactivity::Reactor::new();
|
||||
let rope = ReactiveRope::from_text(&reactor, &initial_text);
|
||||
let parser = Rc::new(std::cell::RefCell::new(JsonParser::default()));
|
||||
|
||||
let initial_snapshot = parser
|
||||
.borrow_mut()
|
||||
.parse_initial(&rope)
|
||||
.expect("initial JSON should parse");
|
||||
let parse_state = cell_in(&reactor, initial_snapshot);
|
||||
let last_delta = cell_in(&reactor, None::<EditDelta>);
|
||||
|
||||
let _parser_subscription = {
|
||||
let rope = rope.clone();
|
||||
let parser = Rc::clone(&parser);
|
||||
let parse_state = parse_state.clone();
|
||||
let last_delta = last_delta.clone();
|
||||
rope.edit_events().subscribe(move |delta| {
|
||||
let previous = parse_state.get();
|
||||
match parser.borrow_mut().parse_incremental(&rope, &previous, delta) {
|
||||
Ok(next) => {
|
||||
last_delta.replace(Some(delta.clone()));
|
||||
parse_state.replace(next);
|
||||
}
|
||||
Err(error) => {
|
||||
eprintln!("parse error after edit: {error}");
|
||||
eprintln!(
|
||||
"note: current text length is {} bytes; use `replace_all <text>` to replace the full document without manual offsets",
|
||||
rope.len_bytes()
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
effect_in(&reactor, {
|
||||
let rope = rope.clone();
|
||||
let parse_state = parse_state.clone();
|
||||
let last_delta = last_delta.clone();
|
||||
move || {
|
||||
parse_state.with(|snapshot| print_snapshot(snapshot, &rope, last_delta.with(Clone::clone).as_ref()));
|
||||
}
|
||||
})
|
||||
.leak();
|
||||
|
||||
println!("Commands:");
|
||||
println!(" insert <byte_offset> <text>");
|
||||
println!(" replace <start> <end> <text>");
|
||||
println!(" replace_all <text>");
|
||||
println!(" delete <start> <end>");
|
||||
println!(" print");
|
||||
println!(" help");
|
||||
println!(" quit");
|
||||
|
||||
let mut input = stdio::stdin().expect("stdin should be available");
|
||||
while let Some(line) = input.read_line().await.expect("stdin read should succeed") {
|
||||
let trimmed = line.trim_end();
|
||||
if trimmed.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
match parse_command(trimmed) {
|
||||
Ok(Command::Insert { offset, text }) => {
|
||||
let _ = rope.edit(RopeEdit::insert(offset, text));
|
||||
}
|
||||
Ok(Command::Replace { start, end, text }) => {
|
||||
let _ = rope.edit(RopeEdit::replace(TextRange::new(start, end), text));
|
||||
}
|
||||
Ok(Command::ReplaceAll { text }) => {
|
||||
let _ = rope.edit(RopeEdit::replace(TextRange::new(0, rope.len_bytes()), text));
|
||||
}
|
||||
Ok(Command::Delete { start, end }) => {
|
||||
let _ = rope.edit(RopeEdit::delete(TextRange::new(start, end)));
|
||||
}
|
||||
Ok(Command::Print) => {
|
||||
let snapshot = parse_state.get();
|
||||
print_snapshot(&snapshot, &rope, None);
|
||||
}
|
||||
Ok(Command::Help) => {
|
||||
println!("Commands:");
|
||||
println!(" insert <byte_offset> <text>");
|
||||
println!(" replace <start> <end> <text>");
|
||||
println!(" replace_all <text>");
|
||||
println!(" delete <start> <end>");
|
||||
println!(" print");
|
||||
println!(" help");
|
||||
println!(" quit");
|
||||
}
|
||||
Ok(Command::Quit) => break,
|
||||
Err(error) => eprintln!("{error}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_command(input: &str) -> Result<Command, String> {
|
||||
if input == "quit" {
|
||||
return Ok(Command::Quit);
|
||||
}
|
||||
if input == "print" {
|
||||
return Ok(Command::Print);
|
||||
}
|
||||
if input == "help" {
|
||||
return Ok(Command::Help);
|
||||
}
|
||||
if let Some(rest) = input.strip_prefix("insert ") {
|
||||
let (offset, text) = rest
|
||||
.split_once(' ')
|
||||
.ok_or_else(|| "insert expects: insert <offset> <text>".to_string())?;
|
||||
return Ok(Command::Insert {
|
||||
offset: offset.parse().map_err(|_| "invalid insert offset".to_string())?,
|
||||
text: text.to_string(),
|
||||
});
|
||||
}
|
||||
if let Some(rest) = input.strip_prefix("replace ") {
|
||||
let mut parts = rest.splitn(3, ' ');
|
||||
let start = parts
|
||||
.next()
|
||||
.ok_or_else(|| "replace expects: replace <start> <end> <text>".to_string())?;
|
||||
let end = parts
|
||||
.next()
|
||||
.ok_or_else(|| "replace expects: replace <start> <end> <text>".to_string())?;
|
||||
let text = parts
|
||||
.next()
|
||||
.ok_or_else(|| "replace expects: replace <start> <end> <text>".to_string())?;
|
||||
return Ok(Command::Replace {
|
||||
start: start.parse().map_err(|_| "invalid replace start".to_string())?,
|
||||
end: end.parse().map_err(|_| "invalid replace end".to_string())?,
|
||||
text: text.to_string(),
|
||||
});
|
||||
}
|
||||
if let Some(text) = input.strip_prefix("replace_all ") {
|
||||
return Ok(Command::ReplaceAll {
|
||||
text: text.to_string(),
|
||||
});
|
||||
}
|
||||
if let Some(rest) = input.strip_prefix("delete ") {
|
||||
let (start, end) = rest
|
||||
.split_once(' ')
|
||||
.ok_or_else(|| "delete expects: delete <start> <end>".to_string())?;
|
||||
return Ok(Command::Delete {
|
||||
start: start.parse().map_err(|_| "invalid delete start".to_string())?,
|
||||
end: end.parse().map_err(|_| "invalid delete end".to_string())?,
|
||||
});
|
||||
}
|
||||
Err("unknown command; type `help`".to_string())
|
||||
}
|
||||
|
||||
impl JsonParser {
|
||||
fn parse_initial(&mut self, rope: &ReactiveRope) -> Result<ParseSnapshot, String> {
|
||||
let text = read_rope_text(rope);
|
||||
let parsed = parse_json(&text)?;
|
||||
let root = self.materialize_fresh(&parsed, 0);
|
||||
Ok(ParseSnapshot {
|
||||
text,
|
||||
root_anchor: Anchor::new(0, AnchorBias::Left),
|
||||
total_nodes: count_nodes(&root),
|
||||
reused_nodes: 0,
|
||||
diagnostics: Vec::new(),
|
||||
root,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_incremental(
|
||||
&mut self,
|
||||
rope: &ReactiveRope,
|
||||
previous: &ParseSnapshot,
|
||||
delta: &EditDelta,
|
||||
) -> Result<ParseSnapshot, String> {
|
||||
let text = read_rope_text(rope);
|
||||
let parsed = parse_json(&text)?;
|
||||
let mut root_anchor = previous.root_anchor;
|
||||
root_anchor.map_through(delta);
|
||||
let mut reused = 0usize;
|
||||
let root = self.reuse_node(
|
||||
&parsed,
|
||||
Some(&previous.root),
|
||||
previous.root_anchor.byte,
|
||||
root_anchor.byte,
|
||||
&previous.text,
|
||||
&text,
|
||||
delta,
|
||||
&mut reused,
|
||||
);
|
||||
Ok(ParseSnapshot {
|
||||
text,
|
||||
root_anchor,
|
||||
total_nodes: count_nodes(&root),
|
||||
reused_nodes: reused,
|
||||
diagnostics: Vec::new(),
|
||||
root,
|
||||
})
|
||||
}
|
||||
|
||||
fn materialize_fresh(&mut self, parsed: &ParsedNode, parent_start: usize) -> AstNode {
|
||||
AstNode {
|
||||
id: self.alloc_id(),
|
||||
kind: parsed.kind,
|
||||
local_range: localize(parsed.range, parent_start),
|
||||
children: parsed
|
||||
.children
|
||||
.iter()
|
||||
.map(|child| self.materialize_fresh(child, parsed.range.start))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn reuse_node(
|
||||
&mut self,
|
||||
parsed: &ParsedNode,
|
||||
old: Option<&AstNode>,
|
||||
old_parent_start: usize,
|
||||
new_parent_start: usize,
|
||||
old_text: &str,
|
||||
new_text: &str,
|
||||
delta: &EditDelta,
|
||||
reused: &mut usize,
|
||||
) -> AstNode {
|
||||
let parsed_local = localize(parsed.range, new_parent_start);
|
||||
if let Some(old) = old
|
||||
&& old.kind == parsed.kind
|
||||
&& map_range(absolute_range(old, old_parent_start), delta) == parsed.range
|
||||
&& old_text
|
||||
.get(absolute_range(old, old_parent_start).start..absolute_range(old, old_parent_start).end)
|
||||
== new_text.get(parsed.range.start..parsed.range.end)
|
||||
{
|
||||
*reused += count_nodes(old);
|
||||
return AstNode {
|
||||
id: old.id,
|
||||
kind: old.kind,
|
||||
local_range: parsed_local,
|
||||
children: old.children.clone(),
|
||||
};
|
||||
}
|
||||
|
||||
let children = parsed
|
||||
.children
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, child)| {
|
||||
let previous_child = old.and_then(|node| node.children.get(index));
|
||||
let next_old_parent_start = old
|
||||
.map(|node| absolute_range(node, old_parent_start).start)
|
||||
.unwrap_or(old_parent_start);
|
||||
self.reuse_node(
|
||||
child,
|
||||
previous_child,
|
||||
next_old_parent_start,
|
||||
parsed.range.start,
|
||||
old_text,
|
||||
new_text,
|
||||
delta,
|
||||
reused,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
AstNode {
|
||||
id: self.alloc_id(),
|
||||
kind: parsed.kind,
|
||||
local_range: parsed_local,
|
||||
children,
|
||||
}
|
||||
}
|
||||
|
||||
fn alloc_id(&mut self) -> u64 {
|
||||
self.next_id = self.next_id.wrapping_add(1);
|
||||
self.next_id
|
||||
}
|
||||
}
|
||||
|
||||
fn read_rope_text(rope: &ReactiveRope) -> String {
|
||||
let mut cursor = rope.cursor(0);
|
||||
let mut text = String::new();
|
||||
while let Some(chunk) = cursor.next_chunk() {
|
||||
text.push_str(chunk.text());
|
||||
}
|
||||
text
|
||||
}
|
||||
|
||||
fn localize(range: TextRange, parent_start: usize) -> TextRange {
|
||||
TextRange::new(
|
||||
range.start.saturating_sub(parent_start),
|
||||
range.end.saturating_sub(parent_start),
|
||||
)
|
||||
}
|
||||
|
||||
fn absolute_range(node: &AstNode, parent_start: usize) -> TextRange {
|
||||
TextRange::new(
|
||||
parent_start + node.local_range.start,
|
||||
parent_start + node.local_range.end,
|
||||
)
|
||||
}
|
||||
|
||||
fn map_range(range: TextRange, delta: &EditDelta) -> TextRange {
|
||||
if range.end <= delta.range.start {
|
||||
return range;
|
||||
}
|
||||
if range.start >= delta.range.end {
|
||||
let shift = delta.net_byte_delta();
|
||||
return TextRange::new(
|
||||
range.start.saturating_add_signed(shift),
|
||||
range.end.saturating_add_signed(shift),
|
||||
);
|
||||
}
|
||||
TextRange::new(usize::MAX, usize::MAX)
|
||||
}
|
||||
|
||||
fn parse_json(text: &str) -> Result<ParsedNode, String> {
|
||||
let mut cursor = Cursor { text, offset: 0 };
|
||||
cursor.skip_ws();
|
||||
let node = cursor.parse_value()?;
|
||||
cursor.skip_ws();
|
||||
if !cursor.is_eof() {
|
||||
return Err(format!("unexpected trailing data at byte {}", cursor.offset));
|
||||
}
|
||||
Ok(node)
|
||||
}
|
||||
|
||||
impl<'a> Cursor<'a> {
|
||||
fn is_eof(&self) -> bool {
|
||||
self.offset >= self.text.len()
|
||||
}
|
||||
|
||||
fn skip_ws(&mut self) {
|
||||
while let Some(ch) = self.peek_char() {
|
||||
if ch.is_ascii_whitespace() {
|
||||
self.offset += ch.len_utf8();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn peek_char(&self) -> Option<char> {
|
||||
self.text[self.offset..].chars().next()
|
||||
}
|
||||
|
||||
fn bump_char(&mut self) -> Option<char> {
|
||||
let ch = self.peek_char()?;
|
||||
self.offset += ch.len_utf8();
|
||||
Some(ch)
|
||||
}
|
||||
|
||||
fn expect_char(&mut self, expected: char) -> Result<(), String> {
|
||||
match self.bump_char() {
|
||||
Some(ch) if ch == expected => Ok(()),
|
||||
Some(ch) => Err(format!(
|
||||
"expected `{expected}` at byte {}, found `{ch}`",
|
||||
self.offset.saturating_sub(ch.len_utf8())
|
||||
)),
|
||||
None => Err(format!("expected `{expected}` at end of file")),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_value(&mut self) -> Result<ParsedNode, String> {
|
||||
self.skip_ws();
|
||||
match self.peek_char() {
|
||||
Some('{') => self.parse_object(),
|
||||
Some('[') => self.parse_array(),
|
||||
Some('"') => self.parse_string(),
|
||||
Some('t') => self.parse_literal("true", JsonKind::True),
|
||||
Some('f') => self.parse_literal("false", JsonKind::False),
|
||||
Some('n') => self.parse_literal("null", JsonKind::Null),
|
||||
Some('-' | '0'..='9') => self.parse_number(),
|
||||
Some(other) => Err(format!("unexpected `{other}` at byte {}", self.offset)),
|
||||
None => Err("unexpected end of JSON input".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_object(&mut self) -> Result<ParsedNode, String> {
|
||||
let start = self.offset;
|
||||
self.expect_char('{')?;
|
||||
self.skip_ws();
|
||||
let mut children = Vec::new();
|
||||
if self.peek_char() == Some('}') {
|
||||
self.bump_char();
|
||||
return Ok(ParsedNode {
|
||||
kind: JsonKind::Object,
|
||||
range: TextRange::new(start, self.offset),
|
||||
children,
|
||||
});
|
||||
}
|
||||
|
||||
loop {
|
||||
self.skip_ws();
|
||||
let key = self.parse_string()?;
|
||||
self.skip_ws();
|
||||
self.expect_char(':')?;
|
||||
self.skip_ws();
|
||||
let value = self.parse_value()?;
|
||||
let pair = ParsedNode {
|
||||
kind: JsonKind::Pair,
|
||||
range: TextRange::new(key.range.start, value.range.end),
|
||||
children: vec![key, value],
|
||||
};
|
||||
children.push(pair);
|
||||
self.skip_ws();
|
||||
match self.peek_char() {
|
||||
Some(',') => {
|
||||
self.bump_char();
|
||||
}
|
||||
Some('}') => {
|
||||
self.bump_char();
|
||||
break;
|
||||
}
|
||||
Some(other) => {
|
||||
return Err(format!("expected `,` or `}}` at byte {}, found `{other}`", self.offset));
|
||||
}
|
||||
None => return Err("unterminated object".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ParsedNode {
|
||||
kind: JsonKind::Object,
|
||||
range: TextRange::new(start, self.offset),
|
||||
children,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_array(&mut self) -> Result<ParsedNode, String> {
|
||||
let start = self.offset;
|
||||
self.expect_char('[')?;
|
||||
self.skip_ws();
|
||||
let mut children = Vec::new();
|
||||
if self.peek_char() == Some(']') {
|
||||
self.bump_char();
|
||||
return Ok(ParsedNode {
|
||||
kind: JsonKind::Array,
|
||||
range: TextRange::new(start, self.offset),
|
||||
children,
|
||||
});
|
||||
}
|
||||
|
||||
loop {
|
||||
let value = self.parse_value()?;
|
||||
children.push(value);
|
||||
self.skip_ws();
|
||||
match self.peek_char() {
|
||||
Some(',') => {
|
||||
self.bump_char();
|
||||
self.skip_ws();
|
||||
}
|
||||
Some(']') => {
|
||||
self.bump_char();
|
||||
break;
|
||||
}
|
||||
Some(other) => {
|
||||
return Err(format!("expected `,` or `]` at byte {}, found `{other}`", self.offset));
|
||||
}
|
||||
None => return Err("unterminated array".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ParsedNode {
|
||||
kind: JsonKind::Array,
|
||||
range: TextRange::new(start, self.offset),
|
||||
children,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_string(&mut self) -> Result<ParsedNode, String> {
|
||||
let start = self.offset;
|
||||
self.expect_char('"')?;
|
||||
loop {
|
||||
match self.bump_char() {
|
||||
Some('"') => {
|
||||
return Ok(ParsedNode {
|
||||
kind: JsonKind::String,
|
||||
range: TextRange::new(start, self.offset),
|
||||
children: Vec::new(),
|
||||
});
|
||||
}
|
||||
Some('\\') => match self.bump_char() {
|
||||
Some('"') | Some('\\') | Some('/') | Some('b') | Some('f') | Some('n')
|
||||
| Some('r') | Some('t') => {}
|
||||
Some('u') => {
|
||||
for _ in 0..4 {
|
||||
match self.bump_char() {
|
||||
Some(ch) if ch.is_ascii_hexdigit() => {}
|
||||
_ => return Err(format!("invalid unicode escape at byte {}", self.offset)),
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => return Err(format!("invalid escape sequence at byte {}", self.offset)),
|
||||
},
|
||||
Some(ch) if ch >= '\u{20}' => {}
|
||||
Some(_) => return Err(format!("control character in string at byte {}", self.offset)),
|
||||
None => return Err("unterminated string".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_number(&mut self) -> Result<ParsedNode, String> {
|
||||
let start = self.offset;
|
||||
if self.peek_char() == Some('-') {
|
||||
self.bump_char();
|
||||
}
|
||||
|
||||
match self.peek_char() {
|
||||
Some('0') => {
|
||||
self.bump_char();
|
||||
}
|
||||
Some('1'..='9') => {
|
||||
self.bump_char();
|
||||
while matches!(self.peek_char(), Some('0'..='9')) {
|
||||
self.bump_char();
|
||||
}
|
||||
}
|
||||
_ => return Err(format!("invalid number at byte {}", self.offset)),
|
||||
}
|
||||
|
||||
if self.peek_char() == Some('.') {
|
||||
self.bump_char();
|
||||
let mut digits = 0usize;
|
||||
while matches!(self.peek_char(), Some('0'..='9')) {
|
||||
digits += 1;
|
||||
self.bump_char();
|
||||
}
|
||||
if digits == 0 {
|
||||
return Err(format!("expected fraction digits at byte {}", self.offset));
|
||||
}
|
||||
}
|
||||
|
||||
if matches!(self.peek_char(), Some('e' | 'E')) {
|
||||
self.bump_char();
|
||||
if matches!(self.peek_char(), Some('+' | '-')) {
|
||||
self.bump_char();
|
||||
}
|
||||
let mut digits = 0usize;
|
||||
while matches!(self.peek_char(), Some('0'..='9')) {
|
||||
digits += 1;
|
||||
self.bump_char();
|
||||
}
|
||||
if digits == 0 {
|
||||
return Err(format!("expected exponent digits at byte {}", self.offset));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ParsedNode {
|
||||
kind: JsonKind::Number,
|
||||
range: TextRange::new(start, self.offset),
|
||||
children: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_literal(&mut self, literal: &str, kind: JsonKind) -> Result<ParsedNode, String> {
|
||||
let start = self.offset;
|
||||
if !self.text[self.offset..].starts_with(literal) {
|
||||
return Err(format!("expected `{literal}` at byte {}", self.offset));
|
||||
}
|
||||
self.offset += literal.len();
|
||||
Ok(ParsedNode {
|
||||
kind,
|
||||
range: TextRange::new(start, self.offset),
|
||||
children: Vec::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn print_snapshot(snapshot: &ParseSnapshot, rope: &ReactiveRope, delta: Option<&EditDelta>) {
|
||||
let metrics = rope.snapshot_metrics();
|
||||
println!();
|
||||
println!("Document revision {}", metrics.revision);
|
||||
println!(
|
||||
"Text bytes={}, leaves={}, nodes={}, reused_on_last_edit={}",
|
||||
metrics.text.bytes, metrics.leaves, snapshot.total_nodes, snapshot.reused_nodes
|
||||
);
|
||||
if let Some(delta) = delta {
|
||||
println!(
|
||||
"Last edit: [{}..{}) -> {:?}",
|
||||
delta.range.start, delta.range.end, delta.inserted_text
|
||||
);
|
||||
let focus = delta.range.start.min(snapshot.text.len().saturating_sub(1));
|
||||
let path = path_for_offset(&snapshot.root, snapshot.root_anchor.byte, focus);
|
||||
println!("Changed path:");
|
||||
for (node, range) in path {
|
||||
let start = rope.point_at(range.start);
|
||||
let end = rope.point_at(range.end);
|
||||
println!(
|
||||
" id={} kind={} span={}",
|
||||
node.id,
|
||||
node.kind,
|
||||
format_span(start, end)
|
||||
);
|
||||
}
|
||||
}
|
||||
if !snapshot.diagnostics.is_empty() {
|
||||
println!("Diagnostics:");
|
||||
for diagnostic in &snapshot.diagnostics {
|
||||
println!(" - {diagnostic}");
|
||||
}
|
||||
}
|
||||
println!("Root id={} kind={}", snapshot.root.id, snapshot.root.kind);
|
||||
println!("Parse tree:");
|
||||
print_tree(&snapshot.root, snapshot.root_anchor.byte, rope, 0);
|
||||
println!("Current text:");
|
||||
println!("{}", snapshot.text);
|
||||
}
|
||||
|
||||
fn print_tree(node: &AstNode, parent_start: usize, rope: &ReactiveRope, depth: usize) {
|
||||
let range = absolute_range(node, parent_start);
|
||||
let start = rope.point_at(range.start);
|
||||
let end = rope.point_at(range.end);
|
||||
let indent = " ".repeat(depth);
|
||||
println!(
|
||||
"{indent}- id={} kind={} span={}",
|
||||
node.id,
|
||||
node.kind,
|
||||
format_span(start, end)
|
||||
);
|
||||
for child in &node.children {
|
||||
print_tree(child, range.start, rope, depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
fn path_for_offset(root: &AstNode, root_start: usize, offset: usize) -> Vec<(&AstNode, TextRange)> {
|
||||
fn walk<'a>(
|
||||
node: &'a AstNode,
|
||||
parent_start: usize,
|
||||
offset: usize,
|
||||
out: &mut Vec<(&'a AstNode, TextRange)>,
|
||||
) -> bool {
|
||||
let range = absolute_range(node, parent_start);
|
||||
if offset < range.start || offset >= range.end {
|
||||
return false;
|
||||
}
|
||||
out.push((node, range));
|
||||
for child in &node.children {
|
||||
if walk(child, range.start, offset, out) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
let mut path = Vec::new();
|
||||
let _ = walk(root, root_start, offset, &mut path);
|
||||
path
|
||||
}
|
||||
|
||||
fn format_span(start: TextPoint, end: TextPoint) -> String {
|
||||
format!(
|
||||
"{}:{}..{}:{}",
|
||||
start.line + 1,
|
||||
start.column + 1,
|
||||
end.line + 1,
|
||||
end.column + 1
|
||||
)
|
||||
}
|
||||
|
||||
fn count_nodes(node: &AstNode) -> usize {
|
||||
1 + node.children.iter().map(count_nodes).sum::<usize>()
|
||||
}
|
||||
Reference in New Issue
Block a user