Lots of owork on a calculator example, transparency, etc.
This commit is contained in:
207
lib/reactivity_parsing/examples/tokentree_reactive_parser.rs
Normal file
207
lib/reactivity_parsing/examples/tokentree_reactive_parser.rs
Normal file
@@ -0,0 +1,207 @@
|
||||
use std::env;
|
||||
use std::rc::Rc;
|
||||
|
||||
use ruin_reactivity::effect_in;
|
||||
use ruin_reactivity_parsing::{
|
||||
ReactiveRope, ReactiveTokenTree, RopeEdit, TextRange, TokenTreeNode, TokenTreeParse,
|
||||
TokenTreeUpdate,
|
||||
};
|
||||
use ruin_runtime::{async_main, fs, stdio};
|
||||
|
||||
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 tokentree_reactive_parser -- <path/to/file>");
|
||||
let initial_text = fs::read_to_string(&path)
|
||||
.await
|
||||
.expect("source file should be readable");
|
||||
|
||||
let reactor = ruin_reactivity::Reactor::new();
|
||||
let rope = ReactiveRope::from_text(&reactor, &initial_text);
|
||||
let parser = Rc::new(ReactiveTokenTree::new(&rope));
|
||||
|
||||
effect_in(&reactor, {
|
||||
let parser = Rc::clone(&parser);
|
||||
let rope = rope.clone();
|
||||
move || {
|
||||
parser.with_snapshot(|snapshot| {
|
||||
let last_update = parser.last_update();
|
||||
print_snapshot(snapshot, &rope, last_update.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 = parser.snapshot();
|
||||
let last_update = parser.last_update();
|
||||
print_snapshot(&snapshot, &rope, last_update.as_ref());
|
||||
}
|
||||
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())
|
||||
}
|
||||
|
||||
fn print_snapshot(snapshot: &TokenTreeParse, rope: &ReactiveRope, update: Option<&TokenTreeUpdate>) {
|
||||
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.stats.total_nodes,
|
||||
snapshot.stats.reused_nodes
|
||||
);
|
||||
if let Some(update) = update {
|
||||
println!(
|
||||
"Last edit: [{}..{}) -> {:?}",
|
||||
update.delta.range.start,
|
||||
update.delta.range.end,
|
||||
update.delta.inserted_text
|
||||
);
|
||||
println!(
|
||||
"Diagnostics changed={}, trivia changed={}",
|
||||
update.diagnostics_changed,
|
||||
update.trivia_changed
|
||||
);
|
||||
}
|
||||
println!("Parse tree:");
|
||||
print_tree(snapshot, rope, &snapshot.root, 0);
|
||||
if !snapshot.diagnostics.is_empty() {
|
||||
println!("Diagnostics:");
|
||||
for diagnostic in &snapshot.diagnostics {
|
||||
println!(
|
||||
" {:?} [{}..{}): {}",
|
||||
diagnostic.kind, diagnostic.range.start, diagnostic.range.end, diagnostic.message
|
||||
);
|
||||
}
|
||||
}
|
||||
if !snapshot.trivia.comments().is_empty() {
|
||||
println!("Comments:");
|
||||
for comment in snapshot.trivia.comments() {
|
||||
println!(
|
||||
" {:?} [{}..{}): {}",
|
||||
comment.kind, comment.range.start, comment.range.end, comment.text
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn print_tree(snapshot: &TokenTreeParse, rope: &ReactiveRope, node: &TokenTreeNode, depth: usize) {
|
||||
let indent = " ".repeat(depth);
|
||||
let (start, end) = snapshot
|
||||
.absolute_span(rope, node.id)
|
||||
.expect("node span should be available");
|
||||
println!(
|
||||
"{}- id={} kind={} span=({}:{})..({}:{})",
|
||||
indent, node.id, node.kind, start.line, start.column, end.line, end.column
|
||||
);
|
||||
for child in &node.children {
|
||||
print_tree(snapshot, rope, child, depth + 1);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user