Hello everybody,
There seems to be a new trend of making calculators.
So, here is my take on a simple stack based RPN calculator with the following features:
- simple line based input
- push a number on the stack
- pop a number from the stack
- apply an arithmetic operation and push the result on the stack
- repeat the last operation
- quit ^^
Interaction example:
?- calc.
prog. noop % the stack starts empty with a noop at the top of the stack
|: 1 % |: is the prompt for user input and ends when a carriage return is entered
1. 1 % stack grew by one
prog. push(1)
|: % empty user input means repeat the last command
1. 1
2. 1
prog. push(1)
|: pi % pi is actually a zero arity arithmetic function
1. 1
2. 1
3. 3.141592653589793
prog. op(pi)
|: * % 1 * pi = pi
1. 1
2. 3.141592653589793
prog. op(*)
|: random_float % another 0 arity arithmetic function
1. 1
2. 3.141592653589793
3. 0.1300549386238541
prog. op(random_float)
|: *
1. 1
2. 0.40857963974377143
prog. op(*)
|:
1. 0.40857963974377143
prog. op(*)
|: q
true.
Here is the complete program for the curious:
source code
%! calc is det.
%
% Reverse Polish Notation calculator.
% This calculator uses a simple stack where the top of the stack stores the current
% operation.
% The calculator supports 4 operators:
%
% - push(Number): push `Number` on the top of the stack
% - pop: pop the top of the stack
% - op(Op): apply `Op` from the top of the stack. For example, if `Op = +` and the
% stack is `[1, 2 | Rest]`, the stack after applying the operation will be
% `[3 | Rest]`.
% - repeat: apply the current operator.
%
% The interpreter is line based, so you will always need to end your input with a
% carriage return.
% The possible inputs are:
%
% - a number
% - `p` for pop
% - an operator, which can be any swi-prolog arithmetic function
% - en empty line for repeat
% - `q` for quit
calc :-
phrase(loop, [noop], _).
loop -->
print,
( { stream_property(Stream, alias(user_input)),
read_line_to_codes(Stream, Line, []),
phrase(parse(NewProg), Line)
}
-> []
; { print_message(warning, "bad input.~n") },
loop
),
quit(NewProg).
quit(quit) --> !.
quit(Prog) -->
repeat(Prog, NewProg),
( do(NewProg)
-> []
; {print_message(warning, "bad prog.~n")}
),
push(NewProg), loop.
print, [Prog] -->
[Prog], print_state(_),
{ format("prog. ~p~n", [Prog]) }.
print_state(C), [Entry] -->
[Entry], !,
print_state(C1),
{ C is C1 + 1 },
{ format("~d. ~p~n", [C, Entry]) }.
print_state(0) --> eos.
parse(pop) --> "p", eol.
parse(quit) --> "q", eol.
parse(repeat) --> eol.
parse(push(Number)) --> number(Number), eol.
parse(op(Op)) -->
{ arithmetic(_, Op, _, _) },
csym(Op), eol.
repeat(repeat, Prog) --> !, [Prog].
repeat(Prog, Prog) --> [_].
do(pop) --> [_].
do(noop) --> [].
do(push(N)), [N] --> [].
do(op(Op)), [R] -->
{ arithmetic(Head, Op, Args, _) }, Args, { R is Head }.
:- table arithmetic/4.
arithmetic(Head, Op, Args, Arity) :-
current_arithmetic_function(Head),
\+ illegal_op(Head),
Head =.. [Op | Args],
length(Args, Arity).
illegal_op(+(_)).
illegal_op(-(_)).
illegal_op(atan(_, _)).
push(X), [X] --> [].
What do you think ?
If anybody has suggestions to improve it, let me know !
Should we create a pack with small utilities like this ?