1use std::error::Error;
21
22use chumsky::prelude::*;
23use increparse::{
24 CancelToken, Engine, NodeId, Outcome, ParseTree, Pass, Schedule, SerialExecutor, Session, Span,
25};
26use increparse_chumsky::{chumsky_pass, ChumChildren};
27
28#[derive(Clone, Debug, PartialEq, Eq)]
29enum LangCtx {
30 File,
31 Function { name: String, params: Vec<String> },
32 Return { function: String },
33}
34
35fn functions_parser<'a>(
40) -> impl Parser<'a, &'a str, ChumChildren<LangCtx>, extra::Err<Rich<'a, char>>> {
41 let ident = text::ident().map(|s: &str| s.to_string());
42 let params = ident
43 .padded()
44 .separated_by(just(',').padded())
45 .allow_trailing()
46 .collect::<Vec<String>>()
47 .delimited_by(just('('), just(')'))
48 .padded();
49 let body = none_of('}').repeated().to_slice();
51
52 just("def")
53 .ignore_then(ident.padded())
54 .then(params)
55 .then(just('{').ignore_then(body).then_ignore(just('}')))
56 .map_with(
57 |((name, params), _body): ((String, Vec<String>), &str), e| {
58 let span: SimpleSpan = e.span();
59 vec![(span, LangCtx::Function { name, params })]
60 },
61 )
62 .padded()
63 .or(any().to(Vec::new()))
64 .repeated()
65 .collect::<Vec<Vec<(SimpleSpan, LangCtx)>>>()
66 .map(|defs| {
67 defs.into_iter()
68 .flatten()
69 .collect::<Vec<(SimpleSpan, LangCtx)>>()
70 })
71 .then_ignore(any().repeated())
72}
73
74struct FunctionsPass;
75
76impl Pass for FunctionsPass {
77 type Ctx = LangCtx;
78
79 fn parse(&self, source: &str, span: Span, ctx: &LangCtx) -> Outcome<LangCtx> {
80 if !matches!(ctx, LangCtx::File) {
81 return Outcome::Failed;
82 }
83 chumsky_pass(|slice: &str| functions_parser().parse(slice)).parse(source, span, ctx)
84 }
85}
86
87struct BodyPass;
89
90impl Pass for BodyPass {
91 type Ctx = LangCtx;
92
93 fn parse(&self, source: &str, span: Span, ctx: &LangCtx) -> Outcome<LangCtx> {
94 let LangCtx::Function { name, .. } = ctx else {
95 return Outcome::Failed;
96 };
97 let bytes = source.as_bytes();
98 let mut children = Vec::new();
99 let mut i = span.start;
100 while i < span.end {
101 while i < span.end && bytes[i].is_ascii_whitespace() {
102 i += 1;
103 }
104 if source[i..].starts_with("return") {
105 let Some(semi) = (i..span.end).find(|&j| bytes[j] == b';') else {
106 break;
107 };
108 children.push((
109 Span::new(i, semi + 1, span.rev),
110 LangCtx::Return {
111 function: name.clone(),
112 },
113 ));
114 i = semi + 1;
115 } else {
116 i += 1;
117 }
118 }
119 Outcome::Expand(children)
120 }
121}
122
123struct ReturnPass;
125
126impl Pass for ReturnPass {
127 type Ctx = LangCtx;
128
129 fn parse(&self, source: &str, span: Span, ctx: &LangCtx) -> Outcome<LangCtx> {
130 let LangCtx::Return { function: _ } = ctx else {
131 return Outcome::Failed;
132 };
133 let text = source[span.to_range()].trim();
134 let expr = text
135 .strip_prefix("return")
136 .and_then(|rest| rest.strip_suffix(';'))
137 .map(str::trim)
138 .unwrap_or("");
139 if expr.is_empty() {
140 Outcome::Failed
141 } else {
142 Outcome::Done
143 }
144 }
145}
146
147fn function_ids(tree: &ParseTree<LangCtx>) -> Vec<(String, NodeId)> {
148 tree.nodes()
149 .filter_map(|id| match tree.ctx(id) {
150 LangCtx::Function { name, .. } => Some((name.clone(), id)),
151 _ => None,
152 })
153 .collect()
154}
155
156fn dump(tree: &ParseTree<LangCtx>, source: &str, id: NodeId, depth: usize) {
157 let indent = " ".repeat(depth);
158 let text = tree.text(source, id).replace('\n', "\\n");
159 let text = if text.len() > 40 {
160 format!("{}...", &text[..40])
161 } else {
162 text
163 };
164 println!(
165 "{indent}{} {:?} {} {:?} {:?}",
166 depth,
167 tree.status(id),
168 tree.span(id),
169 tree.ctx(id),
170 text
171 );
172 for child in tree.children(id) {
173 dump(tree, source, *child, depth + 1);
174 }
175}
176
177fn main() -> Result<(), Box<dyn Error>> {
178 let source = r#"
179def add(a, b) { return a + b; }
180def bad(x) { return; }
181def noise( { return broken;
182def zero() { return 0; }
183"#;
184
185 let mut schedule = Schedule::new();
186 schedule.push(FunctionsPass);
187 schedule.push(BodyPass);
188 schedule.push(ReturnPass);
189 let engine = Engine::new(schedule);
190
191 let mut session: Session<LangCtx> =
192 Session::new(0, Span::new(0, source.len(), 0), LangCtx::File);
193 let report = session.run(&engine, source, &SerialExecutor, &CancelToken::new());
194 println!("report: {report:?}\n");
195 dump(session.tree(), source, session.tree().root(), 0);
196
197 assert!(
198 function_ids(session.tree())
199 .iter()
200 .map(|(n, _)| n.as_str())
201 .collect::<Vec<_>>()
202 == ["add", "bad", "zero"],
203 "three well-formed functions captured via chumsky"
204 );
205
206 let mut source = source.to_string();
208 let appended = "\ndef ten() { return 10; }\n";
209 session.edit(increparse::Edit::insert(source.len(), appended.len()));
210 source.push_str(appended);
211 let report = session.run(&engine, &source, &SerialExecutor, &CancelToken::new());
212 println!("\nafter append: {report:?}");
213 assert_eq!(report.nodes_processed, 3, "root + `ten` + its return only");
214
215 Ok(())
216}