blob: 272fa751e93b14e9a6e29299fd682577c6f05bdf [file] [log] [blame]
part of petitparser;
/**
* A builder that allows the simple definition of expression grammars with
* prefix, postfix, and left- and right-associative infix operators.
*
* The following code creates the empty expression builder:
*
* var builder = new ExpressionBuilder();
*
* Then we define the operator-groups in descending precedence. The highest
* precedence have the literal numbers themselves:
*
* builder.group()
* ..primitive(digit().plus()
* .seq(char('.').seq(digit().plus()).optional())
* .flatten().trim().map((a) => double.parse(a)));
*
* Then come the normal arithmetic operators. Note, that the action blocks receive
* both, the terms and the parsed operator in the order they appear in the parsed
* input.
*
* // negation is a prefix operator
* builder.group()
* ..prefix(char('-').trim(), (op, a) => -a);
*
* // power is right-associative
* builder.group()
* ..right(char('^').trim(), (a, op, b) => math.pow(a, b));
*
* // multiplication and addition is left-associative
* builder.group()
* ..left(char('*').trim(), (a, op, b) => a * b)
* ..left(char('/').trim(), (a, op, b) => a / b);
* builder.group()
* ..left(char('+').trim(), (a, op, b) => a + b)
* ..left(char('-').trim(), (a, op, b) => a - b);
*
* Finally we can build the parser:
*
* var parser = builder.build();
*
* After executing the above code we get an efficient parser that correctly
* evaluates expressions like:
*
* parser.parse('-8'); // -8
* parser.parse('1+2*3'); // 7
* parser.parse('1*2+3'); // 5
* parser.parse('8/4/2'); // 2
* parser.parse('2^2^3'); // 256
*/
class ExpressionBuilder {
final List<ExpressionGroup> _groups = new List();
/**
* Creates a new group of operators that share the same priority.
*/
ExpressionGroup group() {
var group = new ExpressionGroup();
_groups.add(group);
return group;
}
/**
* Builds the expression parser.
*/
Parser build() => _groups.fold(
failure('Highest priority group should define a primitive parser.'),
(a, b) => b._build(a));
}
/**
* Models a group of operators of the same precedence.
*/
class ExpressionGroup {
/**
* Defines a new primitive or literal [parser].
*/
void primitive(Parser parser) {
_primitives.add(parser);
}
Parser _build_primitive(Parser inner) {
return _build_choice(_primitives, inner);
}
final List<Parser> _primitives = new List();
/**
* Adds a prefix operator [parser]. Evaluates the optional [action] with the
* parsed `operator` and `value`.
*/
void prefix(Parser parser, [action(operator, value)]) {
if (action == null) action = (operator, value) => [operator, value];
_prefix
.add(parser.map((operator) => new _ExpressionResult(operator, action)));
}
Parser _build_prefix(Parser inner) {
if (_prefix.isEmpty) {
return inner;
} else {
return new SequenceParser([_build_choice(_prefix).star(), inner]).map(
(tuple) {
return tuple.first.reversed.fold(tuple.last, (value, result) {
return result.action(result.operator, value);
});
});
}
}
final List<Parser> _prefix = new List();
/**
* Adds a postfix operator [parser]. Evaluates the optional [action] with the
* parsed `value` and `operator`.
*/
void postfix(Parser parser, [action(value, operator)]) {
if (action == null) action = (value, operator) => [value, operator];
_postfix
.add(parser.map((operator) => new _ExpressionResult(operator, action)));
}
Parser _build_postfix(Parser inner) {
if (_postfix.isEmpty) {
return inner;
} else {
return new SequenceParser([inner, _build_choice(_postfix).star()]).map(
(tuple) {
return tuple.last.fold(tuple.first, (value, result) {
return result.action(value, result.operator);
});
});
}
}
final List<Parser> _postfix = new List();
/**
* Adds a right-associative operator [parser]. Evaluates the optional [action] with
* the parsed `left` term, `operator`, and `right` term.
*/
void right(Parser parser, [action(left, operator, right)]) {
if (action == null) action =
(left, operator, right) => [left, operator, right];
_right
.add(parser.map((operator) => new _ExpressionResult(operator, action)));
}
Parser _build_right(Parser inner) {
if (_right.isEmpty) {
return inner;
} else {
return inner.separatedBy(_build_choice(_right)).map((sequence) {
var result = sequence.last;
for (var i = sequence.length - 2; i > 0; i -= 2) {
result =
sequence[i].action(sequence[i - 1], sequence[i].operator, result);
}
return result;
});
}
}
final List<Parser> _right = new List();
/**
* Adds a left-associative operator [parser]. Evaluates the optional [action] with
* the parsed `left` term, `operator`, and `right` term.
*/
void left(Parser parser, [action(left, operator, right)]) {
if (action == null) action =
(left, operator, right) => [left, operator, right];
_left
.add(parser.map((operator) => new _ExpressionResult(operator, action)));
}
Parser _build_left(Parser inner) {
if (_left.isEmpty) {
return inner;
} else {
return inner.separatedBy(_build_choice(_left)).map((sequence) {
var result = sequence.first;
for (var i = 1; i < sequence.length; i += 2) {
result =
sequence[i].action(result, sequence[i].operator, sequence[i + 1]);
}
return result;
});
}
}
final List<Parser> _left = new List();
// helper to build an optimal choice parser
Parser _build_choice(List<Parser> parsers, [Parser otherwise]) {
if (parsers.isEmpty) {
return otherwise;
} else if (parsers.length == 1) {
return parsers.first;
} else {
return new ChoiceParser(parsers);
}
}
// helper to build the group of parsers
Parser _build(Parser inner) {
return _build_left(
_build_right(_build_postfix(_build_prefix(_build_primitive(inner)))));
}
}
// helper class to associate operators and actions
class _ExpressionResult {
final operator;
final Function action;
_ExpressionResult(this.operator, this.action);
String toString() => operator.toString();
}