Skip to main content

dune_netlist/
expr.rs

1//! B-source expressions: a port of the 3f5 parse-tree front end
2//! (`corpus/spice3f5/src/lib/inp/inpptree.c`: `PTlexer`, the
3//! operator-precedence `PTparse`, `mkfnode`/`mksnode`/`mkbnode` and
4//! `PTcheck`). The result is the undifferentiated tree; derivatives,
5//! folding and evaluation live in `dune-core` (`asrc.rs`).
6//!
7//! Grammar (operator precedence, `prectable` in the C): `+ -` bind
8//! loosest and left-associate, then `* /`, then `^` (right-
9//! associative), then unary `-`; parentheses group; `name(expr)`
10//! calls one of the functions of `ptfuncs.c`; `v(n)`, `v(n1,n2)` and
11//! `i(vname)` read node voltages and branch currents; `e` and `pi`
12//! are constants. There is no unary `+`. The expression ends at the
13//! end of the card or where a value would follow a value (`2 3`,
14//! `v(1) i=…`), and the caller goes on reading card parameters from
15//! there, as `INPdevParse` does.
16//!
17//! Deliberate differences from the C (all on inputs the C rejects or
18//! mangles; see `docs/dune-netlist.md`):
19//! - a number's scale factor is consumed (`2k`): the C's lexer stops
20//!   on the `k`, which then fails as an unknown card parameter;
21//! - inside `v(…)`/`i(…)` a name runs up to blank, `,`, `(` or `)`,
22//!   as in ngspice (`v(1a)`, `v(n-1)`, `v(e)` name nodes; the C read
23//!   `v(e)` as `v(2)` and `v(1.0)` as `v(1)`);
24//! - the tree is bounded ([`MAX_DEPTH`], [`MAX_NODES`]) so hostile
25//!   input cannot exhaust the stack of the recursive passes.
26
27use crate::ir::{Expr, ExprFunc, ExprOp};
28use crate::scan::evaluate_prefix;
29
30/// Deepest tree accepted (operands nested inside operators).
31pub const MAX_DEPTH: u32 = 512;
32/// Largest tree accepted, in nodes.
33pub const MAX_NODES: u32 = 10_000;
34/// `PT_STACKSIZE`: the parser stack of the C.
35const PT_STACKSIZE: usize = 200;
36
37/// A malformed expression: message and byte range inside the text
38/// given to the parser.
39#[derive(Clone, Debug, PartialEq, Eq)]
40pub struct ExprError {
41    pub message: String,
42    pub start: usize,
43    pub end: usize,
44}
45
46/// Parse a whole B-source expression (`v(1)*2`, `exp(i(vin)/1m)`).
47/// Names are lowercased, as the 3f5 lowercases the whole deck; text
48/// left after a complete expression is an error.
49pub fn parse_expr(text: &str) -> Result<Expr, ExprError> {
50    let (res, used) = parse_prefix(text);
51    let expr = res?;
52    let rest = &text[used..];
53    if let Some(off) = rest.find(|c: char| !matches!(c, ' ' | '\t')) {
54        return Err(ExprError {
55            message: format!("unexpected `{}` after the expression", rest[off..].trim()),
56            start: used + off,
57            end: text.len(),
58        });
59    }
60    Ok(expr)
61}
62
63/// Parse an expression at the start of `text` as far as it goes
64/// (`INPgetTree`), returning it and the byte length consumed — the
65/// card scan resumes there.
66pub(crate) fn parse_prefix(text: &str) -> (Result<Expr, ExprError>, usize) {
67    let lower = text.to_ascii_lowercase();
68    let mut lx = Lexer {
69        s: lower.as_bytes(),
70        text: &lower,
71        pos: 0,
72        last: END,
73        last_num: false,
74        prev_vi: false,
75        name_mode: false,
76    };
77    let res = parse(&mut lx).and_then(check);
78    (res, lx.pos)
79}
80
81// Token numbers (`TOK_*`), also the `prectable` indices.
82const END: u8 = 0;
83const PLUS: u8 = 1;
84const MINUS: u8 = 2;
85const TIMES: u8 = 3;
86const DIVIDE: u8 = 4;
87const POWER: u8 = 5;
88const UMINUS: u8 = 6;
89const LPAREN: u8 = 7;
90const RPAREN: u8 = 8;
91const VALUE: u8 = 9;
92const COMMA: u8 = 10;
93
94const G: u8 = 1;
95const L: u8 = 2;
96const E: u8 = 3;
97const R: u8 = 4;
98
99/// `prectable[11][11]` of `inpptree.c`, rows = top terminal of the
100/// stack, columns = incoming token.
101#[rustfmt::skip]
102const PREC: [[u8; 11]; 11] = [
103    /*        $  +  -  *  /  ^  u- (  )  v  , */
104    /* $ */ [ R, L, L, L, L, L, L, L, R, L, R ],
105    /* + */ [ G, G, G, L, L, L, L, L, G, L, G ],
106    /* - */ [ G, G, G, L, L, L, L, L, G, L, G ],
107    /* * */ [ G, G, G, G, G, L, L, L, G, L, G ],
108    /* / */ [ G, G, G, G, G, L, L, L, G, L, G ],
109    /* ^ */ [ G, G, G, G, G, L, L, L, G, L, G ],
110    /* u-*/ [ G, G, G, G, G, G, G, L, G, L, R ],
111    /* ( */ [ R, L, L, L, L, L, L, L, E, L, L ],
112    /* ) */ [ G, G, G, G, G, G, G, R, G, R, G ],
113    /* v */ [ G, G, G, G, G, G, G, G, G, R, G ],
114    /* , */ [ G, L, L, L, L, L, L, L, G, L, G ],
115];
116
117/// An intermediate parse node (`INPparseNode` before `PTcheck`):
118/// placeholders and commas are legal only as `v()`/`i()` arguments.
119#[derive(Debug)]
120enum P {
121    Num(f64),
122    V(String),
123    I(String),
124    Bin(ExprOp, Box<Pn>, Box<Pn>),
125    Comma(Box<Pn>, Box<Pn>),
126    Func(ExprFunc, Box<Pn>),
127    /// `PT_PLACEHOLDER`: a bare name, waiting to be a `v`/`i` argument.
128    Placeholder(String),
129}
130
131#[derive(Debug)]
132struct Pn {
133    p: P,
134    depth: u32,
135    nodes: u32,
136    start: usize,
137    end: usize,
138}
139
140impl Pn {
141    fn leaf(p: P, start: usize, end: usize) -> Pn {
142        Pn {
143            p,
144            depth: 1,
145            nodes: 1,
146            start,
147            end,
148        }
149    }
150}
151
152#[derive(Debug)]
153enum Val {
154    Num(f64),
155    /// `TYP_STRING`: a bare word.
156    Str(String),
157    /// A `v(…)`/`i(…)` argument read as a name (Dune/ngspice).
158    Name(String),
159    /// `TYP_PNODE`: an already-reduced subtree.
160    Node(Pn),
161}
162
163#[derive(Debug)]
164struct El {
165    tok: u8,
166    val: Option<Val>,
167    start: usize,
168    end: usize,
169}
170
171fn err(message: impl Into<String>, start: usize, end: usize) -> ExprError {
172    ExprError {
173        message: message.into(),
174        start,
175        end,
176    }
177}
178
179struct Lexer<'a> {
180    s: &'a [u8],
181    text: &'a str,
182    pos: usize,
183    /// `lasttoken`/`lasttype` of the C (static there; reset per tree
184    /// here — a successful parse always leaves `TOK_END` behind).
185    last: u8,
186    last_num: bool,
187    /// The last token was the word `v` or `i`.
188    prev_vi: bool,
189    /// Inside the parentheses of `v(`/`i(`.
190    name_mode: bool,
191}
192
193impl Lexer<'_> {
194    fn at_end(&self) -> bool {
195        self.pos >= self.s.len() || self.s[self.pos] == 0
196    }
197
198    /// `PTlexer`.
199    fn next(&mut self) -> El {
200        while self.pos < self.s.len() && matches!(self.s[self.pos], b' ' | b'\t' | b'=') {
201            self.pos += 1;
202        }
203        let start = self.pos;
204        let after_value = self.last == VALUE || self.last == RPAREN;
205        let mut val = None;
206        let tok = if self.at_end() {
207            END
208        } else {
209            let c = self.s[self.pos];
210            if self.name_mode && !after_value && !matches!(c, b',' | b'(' | b')') {
211                let mut e = self.pos;
212                while e < self.s.len()
213                    && !matches!(self.s[e], b' ' | b'\t' | b',' | b'(' | b')' | 0)
214                {
215                    e += 1;
216                }
217                val = Some(Val::Name(self.text[self.pos..e].to_string()));
218                self.pos = e;
219                VALUE
220            } else {
221                match c {
222                    b',' => {
223                        self.pos += 1;
224                        COMMA
225                    }
226                    b'-' => {
227                        self.pos += 1;
228                        if after_value { MINUS } else { UMINUS }
229                    }
230                    b'+' => {
231                        self.pos += 1;
232                        PLUS
233                    }
234                    b'*' => {
235                        self.pos += 1;
236                        TIMES
237                    }
238                    b'/' => {
239                        self.pos += 1;
240                        DIVIDE
241                    }
242                    b'^' => {
243                        self.pos += 1;
244                        POWER
245                    }
246                    b'(' => {
247                        if (self.last == VALUE && self.last_num) || self.last == RPAREN {
248                            END
249                        } else {
250                            self.pos += 1;
251                            if self.prev_vi {
252                                self.name_mode = true;
253                            }
254                            LPAREN
255                        }
256                    }
257                    b')' => {
258                        self.pos += 1;
259                        self.name_mode = false;
260                        RPAREN
261                    }
262                    _ if after_value => END,
263                    _ => {
264                        if let Some((v, n)) = evaluate_prefix(&self.s[self.pos..]) {
265                            self.pos += n;
266                            val = Some(Val::Num(v));
267                        } else {
268                            let mut e = self.pos;
269                            while e < self.s.len()
270                                && !matches!(
271                                    self.s[e],
272                                    b' ' | b'\t'
273                                        | b'('
274                                        | b')'
275                                        | b'^'
276                                        | b'+'
277                                        | b'-'
278                                        | b'*'
279                                        | b'/'
280                                        | b','
281                                        | 0
282                                )
283                            {
284                                e += 1;
285                            }
286                            val = Some(Val::Str(self.text[self.pos..e].to_string()));
287                            self.pos = e;
288                        }
289                        VALUE
290                    }
291                }
292            }
293        };
294        self.last = tok;
295        self.last_num = matches!(val, Some(Val::Num(_)));
296        self.prev_vi = matches!(&val, Some(Val::Str(s)) if s == "v" || s == "i");
297        El {
298            tok,
299            val,
300            start,
301            end: self.pos.max(start),
302        }
303    }
304}
305
306/// `PTparse`: operator-precedence parse of one expression.
307fn parse(lx: &mut Lexer) -> Result<Pn, ExprError> {
308    let mut stack: Vec<El> = vec![El {
309        tok: END,
310        val: None,
311        start: 0,
312        end: 0,
313    }];
314    let mut next = lx.next();
315    loop {
316        let sp = stack.len() - 1;
317        if !(sp > 1 || next.tok != END) {
318            break;
319        }
320        // The top-most terminal (stack[0] is `$`, so this stops).
321        let mut i = sp;
322        while stack[i].tok == VALUE {
323            i -= 1;
324        }
325        match PREC[usize::from(stack[i].tok)][usize::from(next.tok)] {
326            L | E => {
327                if sp == PT_STACKSIZE - 1 {
328                    return Err(err(
329                        "expression too deeply nested (parser stack overflow)",
330                        next.start,
331                        next.end,
332                    ));
333                }
334                stack.push(next);
335                next = lx.next();
336            }
337            G => reduce(&mut stack)?,
338            _ => {
339                let (s, e) = if next.tok == END {
340                    (stack[sp].start, stack[sp].end.max(next.end))
341                } else {
342                    (next.start, next.end)
343                };
344                return Err(err("syntax error in expression", s, e));
345            }
346        }
347    }
348    match stack.get_mut(1) {
349        Some(el) => {
350            let (s, e) = (el.start, el.end);
351            makepnode(el)?.ok_or_else(|| err("syntax error in expression", s, e))
352        }
353        None => Err(err("empty expression", next.start, next.end)),
354    }
355}
356
357/// The `G` branch of `PTparse`: find the handle and replace it with
358/// one reduced value.
359fn reduce(stack: &mut Vec<El>) -> Result<(), ExprError> {
360    let st = stack.len() - 1;
361    let mut sp = st;
362    let syntax = |stack: &[El]| {
363        let s = stack.get(1).map_or(0, |e| e.start);
364        let e = stack.last().map_or(0, |e| e.end);
365        err("syntax error in expression", s, e)
366    };
367    if stack[sp].tok == VALUE {
368        sp -= 1;
369    }
370    while sp > 0 {
371        let i = if stack[sp - 1].tok == VALUE { 2 } else { 1 };
372        if sp < i {
373            return Err(syntax(stack));
374        }
375        if PREC[usize::from(stack[sp - i].tok)][usize::from(stack[sp].tok)] == L {
376            break;
377        }
378        sp -= i;
379    }
380    if sp >= 1 && stack[sp - 1].tok == VALUE {
381        sp -= 1;
382    }
383    if sp == 0 {
384        return Err(syntax(stack));
385    }
386    let (hs, he) = (stack[sp].start, stack[st].end);
387    let pn = if st == sp {
388        take_node(stack, st)?
389    } else if stack[sp].tok == UMINUS && st == sp + 1 {
390        let arg = take_node(stack, st)?;
391        mkfnode("-", arg, hs, he)?
392    } else if stack[sp].tok == LPAREN && stack[st].tok == RPAREN {
393        take_node(stack, sp + 1)?
394    } else if stack[sp + 1].tok == LPAREN && stack[st].tok == RPAREN {
395        let arg = take_node(stack, sp + 2)?;
396        let fname = match &stack[sp].val {
397            Some(Val::Str(s) | Val::Name(s)) if stack[sp].tok == VALUE => s.clone(),
398            _ => return Err(syntax(stack)),
399        };
400        mkfnode(&fname, arg, hs, he)?
401    } else {
402        let op = stack[sp + 1].tok;
403        let l = take_node(stack, sp)?;
404        let r = take_node(stack, st)?;
405        mkbnode(op, l, r, hs, he)?
406    };
407    stack.truncate(sp);
408    stack.push(El {
409        tok: VALUE,
410        val: Some(Val::Node(pn)),
411        start: hs,
412        end: he,
413    });
414    Ok(())
415}
416
417/// `makepnode` on `stack[i]`, which must be a value; anything else is
418/// the C's `goto err`.
419fn take_node(stack: &mut [El], i: usize) -> Result<Pn, ExprError> {
420    let el = &mut stack[i];
421    let (s, e) = (el.start, el.end);
422    makepnode(el)?.ok_or_else(|| err("syntax error in expression", s, e))
423}
424
425/// `makepnode`: a value element becomes a parse node (`None` for a
426/// non-value, the C's NULL).
427fn makepnode(el: &mut El) -> Result<Option<Pn>, ExprError> {
428    if el.tok != VALUE {
429        return Ok(None);
430    }
431    let (s, e) = (el.start, el.end);
432    Ok(match el.val.take() {
433        Some(Val::Num(v)) => Some(Pn::leaf(P::Num(v), s, e)),
434        Some(Val::Str(w)) => Some(mksnode(w, s, e)?),
435        Some(Val::Name(w)) => Some(Pn::leaf(P::Placeholder(w), s, e)),
436        Some(Val::Node(pn)) => Some(pn),
437        None => None,
438    })
439}
440
441/// `mksnode`: a bare word is a constant (`e`, `pi`), the special
442/// signal `time`, or a placeholder for a `v()`/`i()` argument.
443fn mksnode(w: String, s: usize, e: usize) -> Result<Pn, ExprError> {
444    match w.as_str() {
445        // `specSigList` of the 3f5 (`bin/config.c`): the parser
446        // accepts it, but `ASRCsetup`/`ASRCload` have no case for an
447        // `IF_STRING` variable and the load returns E_BADPARM.
448        "time" => Err(err(
449            "`time` is not available in a 3f5 B source (its load rejects it); \
450             ngspice's time-dependent B sources are not ported",
451            s,
452            e,
453        )),
454        "e" => Ok(Pn::leaf(P::Num(std::f64::consts::E), s, e)),
455        "pi" => Ok(Pn::leaf(P::Num(std::f64::consts::PI), s, e)),
456        _ => Ok(Pn::leaf(P::Placeholder(w), s, e)),
457    }
458}
459
460/// `mkbnode`.
461fn mkbnode(op: u8, l: Pn, r: Pn, s: usize, e: usize) -> Result<Pn, ExprError> {
462    let depth = 1 + l.depth.max(r.depth);
463    let nodes = 1 + l.nodes + r.nodes;
464    let (l, r) = (Box::new(l), Box::new(r));
465    let node = match op {
466        PLUS => P::Bin(ExprOp::Plus, l, r),
467        MINUS => P::Bin(ExprOp::Minus, l, r),
468        TIMES => P::Bin(ExprOp::Times, l, r),
469        DIVIDE => P::Bin(ExprOp::Divide, l, r),
470        POWER => P::Bin(ExprOp::Power, l, r),
471        COMMA => P::Comma(l, r),
472        _ => return Err(err("syntax error in expression", s, e)),
473    };
474    finish(node, depth, nodes, s, e)
475}
476
477fn finish(p: P, depth: u32, nodes: u32, s: usize, e: usize) -> Result<Pn, ExprError> {
478    if depth > MAX_DEPTH {
479        return Err(err(
480            format!("expression nested deeper than {MAX_DEPTH} levels"),
481            s,
482            e,
483        ));
484    }
485    if nodes > MAX_NODES {
486        return Err(err(
487            format!("expression larger than {MAX_NODES} terms"),
488            s,
489            e,
490        ));
491    }
492    Ok(Pn {
493        p,
494        depth,
495        nodes,
496        start: s,
497        end: e,
498    })
499}
500
501/// `mkfnode`: `v(…)`, `i(…)` or a function call.
502fn mkfnode(fname: &str, arg: Pn, s: usize, e: usize) -> Result<Pn, ExprError> {
503    match fname {
504        "v" => match arg.p {
505            P::Placeholder(n) => Ok(Pn::leaf(P::V(n), s, e)),
506            P::Num(c) => Ok(Pn::leaf(P::V(c_int(c).to_string()), s, e)),
507            // `v(a,b)` → `v(a) - v(b)` (recursively, so `v(a,b,c)`
508            // is `v(a) - v(b) - v(c)` as in the C).
509            P::Comma(a, b) => {
510                let (sa, ea, sb, eb) = (a.start, a.end, b.start, b.end);
511                let l = mkfnode("v", *a, sa, ea)?;
512                let r = mkfnode("v", *b, sb, eb)?;
513                mkbnode(MINUS, l, r, s, e)
514            }
515            _ => Err(err("badly formed node voltage", s, e)),
516        },
517        "i" => match arg.p {
518            P::Placeholder(n) => Ok(Pn::leaf(P::I(n), s, e)),
519            P::Num(c) => Ok(Pn::leaf(P::I(c_int(c).to_string()), s, e)),
520            _ => Err(err("badly formed branch current", s, e)),
521        },
522        "-" => {
523            let (d, n) = (1 + arg.depth, 1 + arg.nodes);
524            finish(P::Func(ExprFunc::Neg, Box::new(arg)), d, n, s, e)
525        }
526        _ => match ExprFunc::TABLE.iter().find(|(n, _)| *n == fname) {
527            Some(&(_, f)) => {
528                let (d, n) = (1 + arg.depth, 1 + arg.nodes);
529                finish(P::Func(f, Box::new(arg)), d, n, s, e)
530            }
531            None => Err(err(format!("no such function `{fname}`"), s, e)),
532        },
533    }
534}
535
536/// The C's `(int) constant` for `sprintf("%d")` (saturating where the
537/// C would be undefined).
538fn c_int(c: f64) -> i64 {
539    if c.is_nan() {
540        0
541    } else {
542        c.trunc().clamp(-2.147_483_648e9, 2.147_483_647e9) as i64
543    }
544}
545
546/// `PTcheck` + conversion: placeholders and commas left anywhere but
547/// inside `v()`/`i()` make the tree invalid.
548fn check(pn: Pn) -> Result<Expr, ExprError> {
549    let (s, e) = (pn.start, pn.end);
550    Ok(match pn.p {
551        P::Num(v) => Expr::Num(v),
552        P::V(n) => Expr::V(n),
553        P::I(n) => Expr::I(n),
554        P::Bin(op, l, r) => Expr::Bin(op, Box::new(check(*l)?), Box::new(check(*r)?)),
555        P::Func(f, a) => Expr::Call(f, Box::new(check(*a)?)),
556        P::Comma(..) => return Err(err("misplaced `,` in expression", s, e)),
557        P::Placeholder(w) => {
558            return Err(err(
559                format!(
560                    "unknown name `{w}` in expression (expected a number, e, pi, v(…), i(…) or a function call)"
561                ),
562                s,
563                e,
564            ));
565        }
566    })
567}
568
569#[cfg(test)]
570mod tests {
571    use super::*;
572    use Expr::*;
573
574    fn p(s: &str) -> Expr {
575        parse_expr(s).unwrap_or_else(|e| panic!("{s}: {e:?}"))
576    }
577
578    fn bin(op: ExprOp, l: Expr, r: Expr) -> Expr {
579        Bin(op, Box::new(l), Box::new(r))
580    }
581
582    fn call(f: ExprFunc, a: Expr) -> Expr {
583        Call(f, Box::new(a))
584    }
585
586    #[test]
587    fn precedence_and_associativity() {
588        use ExprOp::*;
589        // + and - left-associative, below * and /.
590        assert_eq!(
591            p("1-2+3*4"),
592            bin(
593                Plus,
594                bin(Minus, Num(1.0), Num(2.0)),
595                bin(Times, Num(3.0), Num(4.0))
596            )
597        );
598        // ^ binds tighter than * and is right-associative.
599        assert_eq!(
600            p("2*3^4^5"),
601            bin(
602                Times,
603                Num(2.0),
604                bin(Power, Num(3.0), bin(Power, Num(4.0), Num(5.0)))
605            )
606        );
607        // Unary minus binds tighter than ^: -2^2 is (-2)^2.
608        assert_eq!(
609            p("-2^2"),
610            bin(Power, call(ExprFunc::Neg, Num(2.0)), Num(2.0))
611        );
612        assert_eq!(
613            p("2^-x(1)".replace("x", "v").as_str()),
614            bin(Power, Num(2.0), call(ExprFunc::Neg, V("1".into())))
615        );
616    }
617
618    #[test]
619    fn functions_and_references() {
620        use ExprOp::*;
621        assert_eq!(
622            p("cos(v(1))+sin(v(2))"),
623            bin(
624                Plus,
625                call(ExprFunc::Cos, V("1".into())),
626                call(ExprFunc::Sin, V("2".into()))
627            )
628        );
629        assert_eq!(p("v(a,b)"), bin(Minus, V("a".into()), V("b".into())));
630        assert_eq!(
631            p("v(a,b,c)"),
632            bin(
633                Minus,
634                bin(Minus, V("a".into()), V("b".into())),
635                V("c".into())
636            )
637        );
638        assert_eq!(p("I(VDD)"), I("vdd".into()));
639        assert_eq!(
640            p("exp(pi^i(vdd))"),
641            call(
642                ExprFunc::Exp,
643                bin(Power, Num(std::f64::consts::PI), I("vdd".into()))
644            )
645        );
646        assert_eq!(p("2k*v(1)"), bin(Times, Num(2000.0), V("1".into())));
647        // Names inside v()/i() run to blank , ( ).
648        assert_eq!(p("v(1a)"), V("1a".into()));
649        assert_eq!(p("v(e)"), V("e".into()));
650        assert_eq!(p("v( x1.n-2 )"), V("x1.n-2".into()));
651        assert_eq!(p("e"), Num(std::f64::consts::E));
652    }
653
654    #[test]
655    fn prefix_stops_where_a_value_follows_a_value() {
656        let (r, used) = parse_prefix("v(1)*2 i=3");
657        assert!(r.is_ok());
658        assert_eq!(&"v(1)*2 i=3"[used..], "i=3");
659        let (r, used) = parse_prefix("2(3)");
660        assert_eq!(r, Ok(Num(2.0)));
661        assert_eq!(used, 1);
662    }
663
664    #[test]
665    fn errors_have_spans_and_never_panic() {
666        for bad in [
667            "",
668            "+1",
669            "1+",
670            "(",
671            ")",
672            "()",
673            "sin()",
674            "foo(1)",
675            "v()",
676            "v(1)+",
677            "v(sin(1))",
678            "i(1,2)",
679            "x",
680            "1,2",
681            "time",
682            "v(",
683            "((((",
684            "1 + * 2",
685            "-",
686            "sin 1",
687            "v(a,)",
688        ] {
689            let e = parse_expr(bad).expect_err(bad);
690            assert!(
691                e.start <= e.end && e.end <= bad.len().max(e.end),
692                "{bad}: {e:?}"
693            );
694        }
695        // Hostile depth / size.
696        let deep = "(".repeat(5000) + "1" + &")".repeat(5000);
697        assert!(parse_expr(&deep).is_err());
698        let long = vec!["1"; 20_000].join("+");
699        assert!(parse_expr(&long).is_err());
700        let neg = "-".repeat(5000) + "1";
701        assert!(parse_expr(&neg).is_err());
702        let pow = vec!["2"; 3000].join("^");
703        assert!(parse_expr(&pow).is_err());
704    }
705}