1use std::error::Error;
15use std::ops::Range;
16
17use increparse::{
18 CancelToken, Engine, NodeId, Outcome, ParseTree, Pass, Schedule, SerialExecutor, Session, Span,
19 Status,
20};
21use increparse_nom::{nom_pass, NomChildren};
22use nom::branch::alt;
23use nom::bytes::complete::{tag, take, take_till, take_while1};
24use nom::character::complete::{anychar, multispace0};
25use nom::combinator::{map, opt, recognize};
26use nom::multi::many0;
27use nom::IResult;
28use nom::Parser;
29use nom_locate::LocatedSpan;
30
31type Located<'a> = LocatedSpan<&'a str>;
32
33#[derive(Clone, Debug, PartialEq, Eq)]
34enum LangCtx {
35 File,
36 Function { name: String, params: Vec<String> },
37 Return { function: String },
38}
39
40fn ws(i: Located) -> IResult<Located, Located> {
41 recognize(multispace0).parse(i)
42}
43
44fn ident(i: Located) -> IResult<Located, Located> {
45 let (i, s) = take_while1(|c: char| c.is_ascii_alphanumeric() || c == '_').parse(i)?;
46 if s.fragment()
47 .chars()
48 .next()
49 .is_some_and(|c| c.is_ascii_alphabetic() || c == '_')
50 {
51 Ok((i, s))
52 } else {
53 Err(nom::Err::Error(nom::error::Error::new(
54 i,
55 nom::error::ErrorKind::Verify,
56 )))
57 }
58}
59
60fn braced(i: Located) -> IResult<Located, Range<usize>> {
63 let base = i.location_offset();
64 let bytes = i.fragment().as_bytes();
65 if bytes.first() != Some(&b'{') {
66 return Err(nom::Err::Error(nom::error::Error::new(
67 i,
68 nom::error::ErrorKind::Tag,
69 )));
70 }
71 let mut depth = 0usize;
72 let mut len = None;
73 for (k, &b) in bytes.iter().enumerate() {
74 match b {
75 b'{' => depth += 1,
76 b'}' => {
77 depth -= 1;
78 if depth == 0 {
79 len = Some(k + 1);
80 break;
81 }
82 }
83 _ => {}
84 }
85 }
86 let Some(len) = len else {
87 return Err(nom::Err::Error(nom::error::Error::new(
88 i,
89 nom::error::ErrorKind::TakeWhile1,
90 )));
91 };
92 let (i, _) = take(len).parse(i)?;
93 Ok((i, base..base + len))
94}
95
96fn param_list(i: Located) -> IResult<Located, Vec<String>> {
97 let (i, text) = take_till(|c| c == ')' || c == '\n')(i)?;
98 let mut params = Vec::new();
99 for part in text.split(',') {
100 let part = part.trim();
101 if part.is_empty() {
102 continue;
103 }
104 if ident(LocatedSpan::new(part)).is_err() {
105 return Err(nom::Err::Error(nom::error::Error::new(
106 i,
107 nom::error::ErrorKind::Verify,
108 )));
109 }
110 params.push(part.to_string());
111 }
112 Ok((i, params))
113}
114
115fn function_def(i: Located) -> IResult<Located, (Range<usize>, LangCtx)> {
117 let start = i.location_offset();
118 let (i, _) = tag("def").parse(i)?;
119 let (i, name_loc) = recognize((ws, ident, multispace0)).parse(i)?;
120 let name = name_loc.fragment().trim().to_string();
121 let (i, _) = tag("(").parse(i)?;
122 let (i, params) = param_list.parse(i)?;
123 let (i, _) = tag(")").parse(i)?;
124 let (i, _) = recognize((multispace0, opt(tag(";")))).parse(i)?;
125 let (i, body) = braced.parse(i)?;
126 let end = body.end;
127 Ok((
128 i,
129 (
130 start..end,
131 LangCtx::Function {
132 name: name.trim_end().to_string(),
133 params,
134 },
135 ),
136 ))
137}
138
139fn functions_pass_fn(i: Located) -> IResult<Located, NomChildren<LangCtx>> {
143 let (i, defs) = many0(alt((map(function_def, Some), map(anychar, |_| None)))).parse(i)?;
144 Ok((i, defs.into_iter().flatten().collect()))
145}
146
147struct FunctionsPass;
148
149impl Pass for FunctionsPass {
150 type Ctx = LangCtx;
151
152 fn parse(&self, source: &str, span: Span, ctx: &LangCtx) -> Outcome<LangCtx> {
153 if !matches!(ctx, LangCtx::File) {
154 return Outcome::Failed;
155 }
156 nom_pass(functions_pass_fn).parse(source, span, ctx)
157 }
158}
159
160struct BodyPass;
161
162impl Pass for BodyPass {
163 type Ctx = LangCtx;
164
165 fn parse(&self, source: &str, span: Span, ctx: &LangCtx) -> Outcome<LangCtx> {
166 let LangCtx::Function { name, .. } = ctx else {
167 return Outcome::Failed;
168 };
169 let bytes = source.as_bytes();
170 let mut children = Vec::new();
171 let mut i = span.start;
172 while i < span.end {
173 i = skip_ws(bytes, i);
174 if source[i..].starts_with("return") {
175 let Some(semi) = (i..span.end).find(|&j| bytes[j] == b';') else {
176 break;
177 };
178 children.push((
179 Span::new(i, semi + 1, span.rev),
180 LangCtx::Return {
181 function: name.clone(),
182 },
183 ));
184 i = semi + 1;
185 } else {
186 i += 1;
187 }
188 }
189 Outcome::Expand(children)
190 }
191}
192
193fn skip_ws(bytes: &[u8], mut i: usize) -> usize {
194 while i < bytes.len() && bytes[i].is_ascii_whitespace() {
195 i += 1;
196 }
197 i
198}
199
200struct ReturnPass;
201
202impl Pass for ReturnPass {
203 type Ctx = LangCtx;
204
205 fn parse(&self, source: &str, span: Span, ctx: &LangCtx) -> Outcome<LangCtx> {
206 let LangCtx::Return { function: _ } = ctx else {
207 return Outcome::Failed;
208 };
209 let text = source[span.to_range()].trim();
210 let expr = text
211 .strip_prefix("return")
212 .and_then(|rest| rest.strip_suffix(';'))
213 .map(str::trim)
214 .unwrap_or("");
215 if expr.is_empty() {
216 Outcome::Failed
217 } else {
218 Outcome::Done
219 }
220 }
221}
222
223fn function_ids(tree: &ParseTree<LangCtx>) -> Vec<(String, NodeId)> {
224 tree.nodes()
225 .filter_map(|id| match tree.ctx(id) {
226 LangCtx::Function { name, .. } => Some((name.clone(), id)),
227 _ => None,
228 })
229 .collect()
230}
231
232fn dump(tree: &ParseTree<LangCtx>, source: &str, id: NodeId, depth: usize) {
233 let indent = " ".repeat(depth);
234 let text = tree.text(source, id).replace('\n', "\\n");
235 let text = if text.len() > 40 {
236 format!("{}...", &text[..40])
237 } else {
238 text
239 };
240 println!(
241 "{indent}{} {:?} {} {:?} {:?}",
242 depth,
243 tree.status(id),
244 tree.span(id),
245 tree.ctx(id),
246 text
247 );
248 for child in tree.children(id) {
249 dump(tree, source, *child, depth + 1);
250 }
251}
252
253fn main() -> Result<(), Box<dyn Error>> {
254 let source = r#"
255def add(a, b) { return a + b; }
256def bad(x) { return; }
257def noise( { return broken;
258def zero() { return 0; }
259"#;
260
261 let mut schedule = Schedule::new();
262 schedule.push(FunctionsPass);
263 schedule.push(BodyPass);
264 schedule.push(ReturnPass);
265 let engine = Engine::new(schedule);
266
267 let mut session: Session<LangCtx> =
268 Session::new(0, Span::new(0, source.len(), 0), LangCtx::File);
269 let report = session.run(&engine, source, &SerialExecutor, &CancelToken::new());
270 println!("report: {report:?}\n");
271 dump(session.tree(), source, session.tree().root(), 0);
272
273 assert!(
274 function_ids(session.tree())
275 .iter()
276 .map(|(n, _)| n.as_str())
277 .collect::<Vec<_>>()
278 == ["add", "bad", "zero"],
279 "three well-formed functions captured via nom"
280 );
281 assert!(
282 session
283 .tree()
284 .nodes()
285 .any(|id| session.tree().status(id) == Status::Failed),
286 "bad's empty return failed"
287 );
288
289 let mut source = source.to_string();
291 let appended = "\ndef ten() { return 10; }\n";
292 session.edit(increparse::Edit::insert(source.len(), appended.len()));
293 source.push_str(appended);
294 let report = session.run(&engine, &source, &SerialExecutor, &CancelToken::new());
295 println!("\nafter append: {report:?}");
296 assert_eq!(report.nodes_processed, 3, "root + `ten` + its return only");
297
298 Ok(())
299}