% a simple dcg expression evaluator
%    checks the validity of expressions on integer
%       values using the binary operators + - * /
%    if the expression is valid then the result of the
%       expression is returned, following standard rules
%       for precedence but right-to-left associativity
%
% the query format is:  evaluate(ExpressionText, Result).
%    e.g.    evaluate("3 + 44 *6", R).
% and the query response will be false if the source syntax is invalid,
%     or will succeed, unifying Result with the expression result
%
% most code segments are required to be whitespace delimited

% -----------------------------------------------------------------------
% evaluate(Expression,Result)
% ---------------------------
% attempts to evaluate the expression
evaluate(Expression,Result) :- string_codes(Expression,L), phrase(expr(Result), L, []).

% -----------------------------------------------------------------------

% an expression is either simple subexpression
%    or an addition/subtraction of other terms
expr(Result) --> addexpr(Result).

addexpr(Result) --> mulexpr(Result).
addexpr(Result) --> mulexpr(M), wspace, "+", wspace, addexpr(A), { Result is M + A }.
addexpr(Result) --> mulexpr(M), wspace, "-", wspace, addexpr(A), { Result is M - A }.

% a subexpression is either an integer or a product/division
%   of other terms
mulexpr(Result) --> unary(Result).
mulexpr(Result) --> unary(N), wspace, "*", wspace, mulexpr(M), { Result is N * M }.
mulexpr(Result) --> unary(N), wspace, "/", wspace, mulexpr(M), { Result is N / M }.

% a unary expression is either a basic expression or a minus sign
%   followed by a unary expression
unary(Result) --> basic(Result).
unary(Result) --> "-", wspace, basic(NegRes), { Result is 0 - NegRes }.

% an basic expression is either an integer or an expression in brackets
basic(Result) --> integer(Result).
basic(Result) --> "(", wspace, expr(Result), wspace, ")".

% an integer is one or more digits
integer(Result) --> digits(0,Result).

% recognize digit sequences
digits(Previous,Result) --> digit(D), { Result is 10 * Previous + D }.
digits(Previous, Result) --> digit(D), { Temp is 10 * Previous + D }, digits(Temp, Result).
digit(Result) --> [ C ], { integer(C), C >= 48, C =< 57, Result is C - 48 }.  % ascii for 0..9

% classify whitespace as one or more whitespace characters
wspace --> space.
wspace --> space, wspace.
space --> " ".
space --> [ 13 ].
space --> [ 10 ].
space --> [ 9 ].

