Lots of owork on a calculator example, transparency, etc.

This commit is contained in:
2026-05-16 16:18:55 -04:00
parent e2c2563b7e
commit 6dcc2d0d00
40 changed files with 5783 additions and 125 deletions

View 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);
}
}