formulaParser property
Parser
formulaParser
Construct the parser, with caching.
Implementation
Parser get formulaParser {
if (_formulaParser != null) return _formulaParser!;
// Start the arithmetic parser.
// The following is taken from `package:petitparser` README.
final builder = ExpressionBuilder<AstNode>();
// Variables have the highest priority.
// Take every variable and make a string parser from it.
final variableParsers = variables.keys
.map((key) => stringIgnoreCase(key)
.trim()
.map((value) => VariableNode(key, variables[key]!)))
.toList(growable: false);
// Create a choice (OR) parser from all the variable parsers.
// variableParsers =
// variableParsers.toChoiceParser(failureJoiner: selectFarthestJoined);
// Numbers and parenthesis are just a tad down from variables.
final Parser<AstNode> numberParser = digit()
.plus()
.seq(char('.').seq(digit().plus()).optional())
.flatten()
.trim()
.map((a) => NumberNode(double.parse(a)));
// Top group
final topGroup = builder.group();
topGroup.primitive(numberParser);
for (final parser in variableParsers) {
topGroup.primitive(parser);
}
topGroup.wrapper<String, String>(
char('(').trim(),
char(')').trim(),
(l, a, r) => ParensNode(a),
);
// negation is a prefix operator
builder.group().prefix<String>(
char('-').trim(),
(op, a) => NegativeNode(a),
);
// The range operator is left associative and very high-priority.
builder.group().left<String>(char('~').trim(), (a, op, b) {
if (a.isStochastic) {
throw ArgumentError('Cannot create a range with stochastic start: $a');
}
if (b.isStochastic) {
throw ArgumentError('Cannot create a range with stochastic end: $b');
}
final aValue = a.emit();
final bValue = b.emit();
if (aValue == bValue) {
print('Warning: range $aValue~$bValue is auto-converted to just '
'the value $aValue.');
return NumberNode(aValue);
}
final range = Range(aValue, bValue);
return RangeNode(range);
});
// The % postfix (just divides by 100).
builder.group()
..postfix(char('%').trim(), (a, op) => ConstantMultipleNode(a, 1 / 100))
..postfix(stringIgnoreCase('K').trim(),
(a, op) => ConstantMultipleNode(a, 1000))
..postfix(stringIgnoreCase('M').trim(),
(a, op) => ConstantMultipleNode(a, 1000000));
// TODO: add a +- parser
// Math functions.
// TODO: take more from Excel / Google Spreadsheets: MOD(), POWER(),
// CEILING(), FLOOR(), LOG(), LN(), LOG10() EXP(), SIGN(), POW(),
// ABS(), CLAMP()
// FV(), PV(), PMT(), PPMT(), NPER(), RATE(), EFFECT(),
// NOMINAL(), SLN()
// PI, E, TAU
builder.group()
..wrapper(stringIgnoreCase('sqrt(').trim(), char(')').trim(),
(l, a, r) => SquareRootFunctionNode(a))
..wrapper(stringIgnoreCase('sin(').trim(), char(')').trim(),
(l, a, r) => SineFunctionNode(a))
..wrapper(stringIgnoreCase('cos(').trim(), char(')').trim(),
(l, a, r) => CosineFunctionNode(a))
..wrapper(stringIgnoreCase('tan(').trim(), char(')').trim(),
(l, a, r) => TangentFunctionNode(a));
// power is right-associative
builder.group().right<String>(
[char('^'), string('**')]
.toChoiceParser(failureJoiner: selectFarthestJoined)
.trim(),
(a, op, b) => MathPowerNode(a, b),
);
// multiplication and addition are left-associative
builder.group()
..left<String>(
[char('*'), char('×'), char('·')]
.toChoiceParser(failureJoiner: selectFarthestJoined)
.trim(),
(a, op, b) => MultiplicationNode(a, b),
)
..left<String>(
[char('/'), char('÷')]
.toChoiceParser(failureJoiner: selectFarthestJoined)
.trim(),
(a, op, b) => DivisionNode(a, b),
);
builder.group()
..left<String>(
char('+').trim(),
(a, op, b) => AdditionNode(a, b),
)
..left<String>(
char('-').trim(),
(a, op, b) => SubtractionNode(a, b),
);
_formulaParser = builder.build().end();
return _formulaParser!;
}