Вот, посмотрите как удобно в Дарте через перегрузку операторов сделать программный EBNF (библиотека Petit)
final term = undefined();
final prod = undefined();
final prim = undefined();
final add = (prod & char('+').trim() & term)
.map((values) => values[0] + values[2]);
term.set(add | prod);
final mul = (prim & char('*').trim() & prod)
.map((values) => values[0] * values[2]);
prod.set(mul | prim);
final parens = (char('(').trim() & term & char(')').trim())
.map((values) => values[1]);
final number = digit().plus().flatten().trim().map(int.parse);
prim.set(parens | number);