How to avoid parsing the same input multiple times? #92
-
Hello, I have managed to create a parser for mathematical expression. It's been very easy with petitparser! This is the code I'm using right now: import 'dart:math' as math;
import 'package:petitparser/petitparser.dart';
/// Parses mathematical expressions with support for variables.
class ExpressionParser {
/// Builds a new expression parser accepting strings with a single `x` variable.
/// For example, valid expressions are:
///
/// - `2 + x`
/// - `3 * x - 6`
/// - `x^2 + cos(x / 2)`
const ExpressionParser();
/// Evaluates the parsed expression using the given [value].
num evaluate(String expression, double value) {
final builder = ExpressionBuilder();
// Definition of operator groups. This defines how numbers are parsed and how
// variables are recognized.
builder.group()
..primitive(digit()
.plus()
.seq(char('.').seq(digit().plus()).optional())
.flatten()
.trim()
.map(num.tryParse))
..primitive(
char('x').map((value) => value))
..primitive(
char('e').map((value) => math.e))
..primitive(
string('pi').map((value) => math.pi))
..wrapper(char('(').trim(), char(')').trim(), (_, num a,__) => a)
..wrapper(string('sqrt(').trim(), char(')').trim(), (_, num a,__) => math.sqrt(a))
..wrapper(string('sin(').trim(), char(')').trim(), (_, num a,__) => math.sin(a))
..wrapper(string('cos(').trim(), char(')').trim(), (_, num a,__) => math.cos(a))
..wrapper(string('exp(').trim(), char(')').trim(), (_, num a,__) => math.exp(a))
..wrapper(string('log(').trim(), char(')').trim(), (_, num a,__) => math.log(a))
..wrapper(string('acos(').trim(), char(')').trim(), (_, num a,__) => math.acos(a))
..wrapper(string('asin(').trim(), char(')').trim(), (_, num a,__) => math.asin(a))
..wrapper(string('atan(').trim(), char(')').trim(), (_, num a,__) => math.atan(a));
// Defining operations among operators.
builder.group()
..prefix(char('-').trim(), (String op, num a) => -a);
builder.group()
..right(char('^').trim(), (num a, String op, num b) => math.pow(a, b));
// multiplication and addition are left-associative
builder.group()
..left(char('*').trim(), (num a, String op, num b) => a * b)
..left(char('/').trim(), (num a, String op, num b) => a / b);
builder.group()
..left(char('+').trim(), (num a, String op, num b) => a + b)
..left(char('-').trim(), (num a, String op, num b) => a - b);
return builder
.build()
.end()
.parse(expression)
.value as num;
}
} While this works, I don't really like this at all. To be more precise, this is the line giving me issues:
Consider this example: const expr = ExpressionParser();
for(var value in List<double>.generate(100.0, (i) => i)) {
expr.evaluate("x+3*sqrt(x)", i);
} In this case, evaluate is called 100 times adn I'm building a parser using In other words, how can I avoid building multiple times the parser? Basically, a part from Thank you! |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments
-
There are different ways of solving your problem, each with different performance characteristics of performance, flexibility and required coding: The typical approach described in compiler-construction books is to build an abstract syntax tree (AST), a tree of objects that represent your expression. To evaluate your expression you can then traverse the tree (or print, simplify, or optimize it). However, this comes at the cost of creating a lot of classes for each node type (unary operator, binary operator, value, variable, ...); but is likely the best way in terms of execution speed and flexibility. A quick and dirty alternative for your use-case is to make you parser build functions of type |
Beta Was this translation helpful? Give feedback.
-
Added an example that shows the second approach with c6fdc5e. |
Beta Was this translation helpful? Give feedback.
There are different ways of solving your problem, each with different performance characteristics of performance, flexibility and required coding:
The typical approach described in compiler-construction books is to build an abstract syntax tree (AST), a tree of objects that represent your expression. To evaluate your expression you can then traverse the tree (or print, simplify, or optimize it). However, this comes at the cost of creating a lot of classes for each node type (unary operator, binary operator, value, variable, ...); but is likely the best way in terms of execution speed and flexibility.
A quick and dirty alternative for your use-case is to make you parser build functions of …