-
Notifications
You must be signed in to change notification settings - Fork 54
/
calculator.m
150 lines (119 loc) · 3.93 KB
/
calculator.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
%-----------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%-----------------------------------------------------------------------------%
%
% A simpler calculator - parses and evaluates integer expressions.
%
% For an example of a parser with better error handling, see
% mercury_term_parser.m in the Mercury library source code.
%
% Author: fjh.
%
% This source file is hereby placed in the public domain. -fjh.
%
%-----------------------------------------------------------------------------%
:- module calculator.
:- interface.
:- import_module io.
:- pred main(io::di, io::uo) is det.
%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%
:- implementation.
:- import_module char.
:- import_module int.
:- import_module list.
:- import_module string.
%-----------------------------------------------------------------------------%
:- type expr
---> number(int)
; plus(expr, expr)
; minus(expr, expr)
; times(expr, expr)
; div(expr, expr).
main(!IO) :-
io.write_string("calculator> ", !IO),
io.flush_output(!IO),
io.read_line(Res, !IO),
(
Res = error(_),
io.write_string("Error reading from stdin\n", !IO)
;
Res = eof,
io.write_string("EOF\n", !IO)
;
Res = ok(Line0),
list.delete_all(Line0, ' ', Line),
( if fullexpr(X, Line, []) then
Num = evalexpr(X),
io.write_int(Num, !IO),
io.write_string("\n", !IO)
else
io.write_string("Syntax error\n", !IO)
),
main(!IO) % recursively call ourself for the next line(s)
).
:- func evalexpr(expr) = int.
evalexpr(number(Num)) = Num.
evalexpr(plus(X, Y)) = evalexpr(X) + evalexpr(Y).
evalexpr(minus(X, Y)) = evalexpr(X) - evalexpr(Y).
evalexpr(times(X, Y)) = evalexpr(X) * evalexpr(Y).
evalexpr(div(X, Y)) = evalexpr(X) // evalexpr(Y).
% Simple recursive-descent parser.
:- pred fullexpr(expr::out, list(char)::in, list(char)::out) is semidet.
fullexpr(X) -->
expr(X),
newline.
:- pred expr(expr::out, list(char)::in, list(char)::out) is semidet.
expr(Expr) -->
factor(Factor),
expr2(Factor, Expr).
:- pred expr2(expr::in, expr::out, list(char)::in, list(char)::out) is semidet.
expr2(Factor, Expr) -->
( if ['+'] then
factor(Factor2), expr2(plus( Factor, Factor2), Expr)
else if ['-'] then
factor(Factor2), expr2(minus(Factor, Factor2), Expr)
else
{ Expr = Factor }
).
:- pred factor(expr::out, list(char)::in, list(char)::out) is semidet.
factor(Factor) -->
term(Term),
factor2(Term, Factor).
:- pred factor2(expr::in, expr::out, list(char)::in, list(char)::out)
is semidet.
factor2(Term, Factor) -->
( if ['*'] then
term(Term2), factor2(times(Term, Term2), Factor)
else if ['/'] then
term(Term2), factor2(div( Term, Term2), Factor)
else
{ Factor = Term }
).
:- pred term(expr::out, list(char)::in, list(char)::out) is semidet.
term(Term) -->
( if const(Const) then
{ string.from_char_list(Const, ConstString) },
{ string.to_int(ConstString, Num) },
{ Term = number(Num) }
else
['('], expr(Term), [')']
).
:- pred const(list(char)::out, list(char)::in, list(char)::out) is semidet.
const([Digit|Rest]) -->
digit(Digit),
( if const(Const) then
{ Rest = Const }
else
{ Rest = [] }
).
:- pred digit(char::out, list(char)::in, list(char)::out) is semidet.
digit(Char) -->
[Char],
{ char.is_digit(Char) }.
:- pred newline(list(char)::in, list(char)::out) is semidet.
newline --> ['\n'].
newline --> ['\r'], ['\n'].
%-----------------------------------------------------------------------------%
:- end_module calculator.
%-----------------------------------------------------------------------------%