blob: 0f40e08f016aa9554880320a8e8877642bc9d4e0 [file] [log] [blame]
import 'package:meta/meta.dart';
import '../core/parser.dart';
import '../definition/resolve.dart';
import '../parser/combinator/settable.dart';
import 'group.dart';
import 'utils.dart';
/// 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 producing values of
/// type [num]:
///
/// final builder = ExpressionBuilder<num>();
///
/// Every [ExpressionBuilder] needs to define at least one primitive type to
/// parse. In this example these are the literal numbers. The mapping function
/// converts the string input into an actual number.
///
/// builder.primitive(digit()
/// .plus()
/// .seq(char('.').seq(digit().plus()).optional())
/// .flatten()
/// .trim()
/// .map(num.parse));
///
/// Then we define the operator-groups in descending precedence. The highest
/// precedence have parentheses. The mapping function receives both the opening
/// parenthesis, the value, and the closing parenthesis as arguments:
///
/// builder.group().wrapper(
/// char('(').trim(), char(')').trim(), (left, value, right) => value);
///
/// Then come the normal arithmetic operators. We are using [cascade
/// notation](https://dart.dev/guides/language/language-tour#cascade-notation)
/// to define multiple operators on the same precedence-group. The mapping
/// functions 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(), (operator, value) => -value);
///
/// // Power is right-associative.
/// builder.group().right(char('^').trim(), (left, operator, right) => math.pow(left, right));
///
/// // Multiplication and addition are left-associative, multiplication has
/// // higher priority than addition.
/// builder.group()
/// ..left(char('*').trim(), (left, operator, right) => left * right)
/// ..left(char('/').trim(), (left, operator, right) => left / right);
/// builder.group()
/// ..left(char('+').trim(), (left, operator, right) => left + right)
/// ..left(char('-').trim(), (left, operator, right) => left - right);
///
/// Finally we can build the parser:
///
/// final 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<T> {
final List<Parser<T>> _primitives = [];
final List<ExpressionGroup<T>> _groups = [];
final SettableParser<T> _loopback = undefined();
/// Defines a new primitive, value, or literal) [parser].
void primitive(Parser<T> parser) => _primitives.add(parser);
/// Creates a new group of operators that share the same priority.
@useResult
ExpressionGroup<T> group() {
final group = ExpressionGroup<T>(_loopback);
_groups.add(group);
return group;
}
/// Builds the expression parser.
@useResult
Parser<T> build() {
final primitives = <Parser<T>>[
..._primitives,
..._groups.expand((group) => group.primitives),
];
assert(primitives.isNotEmpty, 'At least one primitive parser expected');
final parser = _groups.fold<Parser<T>>(
buildChoice(primitives),
(parser, group) => group.build(parser),
);
_loopback.set(parser);
return resolve(parser);
}
}