1/**
2 * Example Whiley program, taken from the Whiley benchmark suite.
3 * https://github.com/Whiley/WyBench/blob/master/src/101_interpreter/Main.whiley
4 */
5
6import whiley.lang.System
7import whiley.lang.Int
8import whiley.io.File
9import string from whiley.lang.ASCII
10import char from whiley.lang.ASCII
11
12// ====================================================
13// A simple calculator for expressions
14// ====================================================
15
16constant ADD is 0
17constant SUB is 1
18constant MUL is 2
19constant DIV is 3
20
21// binary operation
22type BOp is (int x) where ADD <= x && x <= DIV
23type BinOp is { BOp op, Expr lhs, Expr rhs }
24
25// variables
26type Var is { string id }
27
28// list access
29type ListAccess is {
30    Expr src,
31    Expr index
32}
33
34// expression tree
35type Expr is int |  // constant
36    Var |              // variable
37    BinOp |            // binary operator
38    Expr[] |           // array constructor
39    ListAccess         // list access
40
41// values
42type Value is int | Value[]
43
44// stmts
45type Print is { Expr rhs }
46type Set is { string lhs, Expr rhs }
47type Stmt is Print | Set
48
49// ====================================================
50// Expression Evaluator
51// ====================================================
52
53type RuntimeError is { string msg }
54type Environment is [{string k, Value v}]
55
56// Evaluate an expression in a given environment reducing either to a
57// value, or a runtime error.  The latter occurs if evaluation gets
58// "stuck" (e.g. expression is // not well-formed)
59function evaluate(Expr e, Environment env) -> Value | RuntimeError:
60    //
61    if e is int:
62        return e
63    else if e is Var:
64        return env[e.id]
65    else if e is BinOp:
66        Value|RuntimeError lhs = evaluate(e.lhs, env)
67        Value|RuntimeError rhs = evaluate(e.rhs, env)
68        // check if stuck
69        if !(lhs is int && rhs is int):
70            return {msg: "arithmetic attempted on non-numeric value"}
71        // switch statement would be good
72        if e.op == ADD:
73            return lhs + rhs
74        else if e.op == SUB:
75            return lhs - rhs
76        else if e.op == MUL:
77            return lhs * rhs
78        else if rhs != 0:
79            return lhs / rhs
80        return {msg: "divide-by-zero"}
81    else if e is Expr[]:
82        [Value] r = []
83        for i in e:
84            Value|RuntimeError v = evaluate(i, env)
85            if v is RuntimeError:
86                return v
87            else:
88                r = r ++ [v]
89        return r
90    else if e is ListAccess:
91        Value|RuntimeError src = evaluate(e.src, env)
92        Value|RuntimeError index = evaluate(e.index, env)
93        // santity checks
94        if src is [Value] && index is int && index >= 0 && index < |src|:
95            return src[index]
96        else:
97            return {msg: "invalid list access"}
98    else:
99        return 0 // dead-code
100
101// ====================================================
102// Expression Parser
103// ====================================================
104
105type State is { string input, int pos }
106type SyntaxError is { string msg, int start, int end }
107
108function SyntaxError(string msg, int start, int end) -> SyntaxError:
109    return { msg: msg, start: start, end: end }
110
111// Top-level parse method
112function parse(State st) -> (Stmt,State)|SyntaxError:
113    //
114    Var keyword, Var v
115    Expr e
116    int start = st.pos
117    //
118    keyword,st = parseIdentifier(st)
119    switch keyword.id:
120        case "print":
121            any r = parseAddSubExpr(st)
122            if !(r is SyntaxError):
123                e,st = r
124                return {rhs: e},st
125            else:
126                return r // error case
127        case "set":
128            st = parseWhiteSpace(st)
129            v,st = parseIdentifier(st)
130            any r = parseAddSubExpr(st)
131            if !(r is SyntaxError):
132                e,st = r
133                return {lhs: v.id, rhs: e},st
134            else:
135                return r // error case
136        default:
137            return SyntaxError("unknown statement",start,st.pos-1)
138
139function parseAddSubExpr(State st) -> (Expr, State)|SyntaxError:
140    //
141    Expr lhs, Expr rhs
142    // First, pass left-hand side
143    any r  = parseMulDivExpr(st)
144    //
145    if r is SyntaxError:
146        return r
147    //
148    lhs,st = r
149    st = parseWhiteSpace(st)
150    // Second, see if there is a right-hand side
151    if st.pos < |st.input| && st.input[st.pos] == '+':
152        // add expression
153        st.pos = st.pos + 1
154        r = parseAddSubExpr(st)
155        if !(r is SyntaxError):
156            rhs,st = r
157            return {op: ADD, lhs: lhs, rhs: rhs},st
158        else:
159            return r
160    else if st.pos < |st.input| && st.input[st.pos] == '-':
161        // subtract expression
162        st.pos = st.pos + 1
163        r = parseAddSubExpr(st)
164        if !(r is SyntaxError):
165            rhs,st = r
166            return {op: SUB, lhs: lhs, rhs: rhs},st
167        else:
168            return r
169    // No right-hand side
170    return (lhs,st)
171
172function parseMulDivExpr(State st) -> (Expr, State)|SyntaxError:
173    // First, parse left-hand side
174    Expr lhs, Expr rhs
175    any r  = parseTerm(st)
176    if r is SyntaxError:
177        return r
178    //
179    lhs,st = r
180    st = parseWhiteSpace(st)
181    // Second, see if there is a right-hand side
182    if st.pos < |st.input| && st.input[st.pos] == '*':
183        // add expression
184        st.pos = st.pos + 1
185        r = parseMulDivExpr(st)
186        if !(r is SyntaxError):
187            rhs,st = r
188            return {op: MUL, lhs: lhs, rhs: rhs}, st
189        else:
190            return r
191    else if st.pos < |st.input| && st.input[st.pos] == '/':
192        // subtract expression
193        st.pos = st.pos + 1
194        r = parseMulDivExpr(st)
195        if !(r is SyntaxError):
196            rhs,st = r
197            return {op: DIV, lhs: lhs, rhs: rhs}, st
198        else:
199            return r
200    // No right-hand side
201    return (lhs,st)
202
203function parseTerm(State st) -> (Expr, State)|SyntaxError:
204    //
205    st = parseWhiteSpace(st)
206    if st.pos < |st.input|:
207        if ASCII.isLetter(st.input[st.pos]):
208            return parseIdentifier(st)
209        else if ASCII.isDigit(st.input[st.pos]):
210            return parseNumber(st)
211        else if st.input[st.pos] == '[':
212            return parseList(st)
213    //
214    return SyntaxError("expecting number or variable",st.pos,st.pos)
215
216function parseIdentifier(State st) -> (Var, State):
217    //
218    string txt = ""
219    // inch forward until end of identifier reached
220    while st.pos < |st.input| && ASCII.isLetter(st.input[st.pos]):
221        txt = txt ++ [st.input[st.pos]]
222        st.pos = st.pos + 1
223    return ({id:txt}, st)
224
225function parseNumber(State st) -> (Expr, State)|SyntaxError:
226    // inch forward until end of identifier reached
227    int start = st.pos
228    while st.pos < |st.input| && ASCII.isDigit(st.input[st.pos]):
229        st.pos = st.pos + 1
230    //
231    int|null iv = Int.parse(st.input[start..st.pos])
232    if iv == null:
233        return SyntaxError("Error parsing number",start,st.pos)
234    else:
235        return iv, st
236
237function parseList(State st) -> (Expr, State)|SyntaxError:
238    //
239    st.pos = st.pos + 1 // skip '['
240    st = parseWhiteSpace(st)
241    [Expr] l = [] // initial list
242    bool firstTime = true
243    while st.pos < |st.input| && st.input[st.pos] != ']':
244        if !firstTime && st.input[st.pos] != ',':
245            return SyntaxError("expecting comma",st.pos,st.pos)
246        else if !firstTime:
247            st.pos = st.pos + 1 // skip ','
248        firstTime = false
249        any r = parseAddSubExpr(st)
250        if r is SyntaxError:
251            return r
252        else:
253            Expr e
254            e,st = r
255            // perform annoying error check
256            l = l ++ [e]
257            st = parseWhiteSpace(st)
258    st.pos = st.pos + 1
259    return l,st
260
261// Parse all whitespace upto end-of-file
262function parseWhiteSpace(State st) -> State:
263    while st.pos < |st.input| && ASCII.isWhiteSpace(st.input[st.pos]):
264        st.pos = st.pos + 1
265    return st
266
267// ====================================================
268// Main Method
269// ====================================================
270
271public method main(System.Console sys):
272    if(|sys.args| == 0):
273        sys.out.println("no parameter provided!")
274    else:
275        File.Reader file = File.Reader(sys.args[0])
276        string input = ASCII.fromBytes(file.readAll())
277
278        Environment env = Environment()
279        State st = {pos: 0, input: input}
280        while st.pos < |st.input|:
281            Stmt s
282            any r = parse(st)
283            if r is SyntaxError:
284                sys.out.println("syntax error: " ++ r.msg)
285                return
286            s,st = r
287            Value|RuntimeError v = evaluate(s.rhs,env)
288            if v is RuntimeError:
289                sys.out.println("runtime error: " ++ v.msg)
290                return
291            if s is Set:
292                env[s.lhs] = v
293            else:
294                sys.out.println(r)
295            st = parseWhiteSpace(st)
296
297