296 lines
8.8 KiB
Whiley
296 lines
8.8 KiB
Whiley
/**
|
|
* Example Whiley program, taken from the Whiley benchmark suite.
|
|
* https://github.com/Whiley/WyBench/blob/master/src/101_interpreter/Main.whiley
|
|
*/
|
|
|
|
import whiley.lang.System
|
|
import whiley.lang.Int
|
|
import whiley.io.File
|
|
import string from whiley.lang.ASCII
|
|
import char from whiley.lang.ASCII
|
|
|
|
// ====================================================
|
|
// A simple calculator for expressions
|
|
// ====================================================
|
|
|
|
constant ADD is 0
|
|
constant SUB is 1
|
|
constant MUL is 2
|
|
constant DIV is 3
|
|
|
|
// binary operation
|
|
type BOp is (int x) where ADD <= x && x <= DIV
|
|
type BinOp is { BOp op, Expr lhs, Expr rhs }
|
|
|
|
// variables
|
|
type Var is { string id }
|
|
|
|
// list access
|
|
type ListAccess is {
|
|
Expr src,
|
|
Expr index
|
|
}
|
|
|
|
// expression tree
|
|
type Expr is int | // constant
|
|
Var | // variable
|
|
BinOp | // binary operator
|
|
Expr[] | // array constructor
|
|
ListAccess // list access
|
|
|
|
// values
|
|
type Value is int | Value[]
|
|
|
|
// stmts
|
|
type Print is { Expr rhs }
|
|
type Set is { string lhs, Expr rhs }
|
|
type Stmt is Print | Set
|
|
|
|
// ====================================================
|
|
// Expression Evaluator
|
|
// ====================================================
|
|
|
|
type RuntimeError is { string msg }
|
|
type Environment is [{string k, Value v}]
|
|
|
|
// Evaluate an expression in a given environment reducing either to a
|
|
// value, or a runtime error. The latter occurs if evaluation gets
|
|
// "stuck" (e.g. expression is // not well-formed)
|
|
function evaluate(Expr e, Environment env) -> Value | RuntimeError:
|
|
//
|
|
if e is int:
|
|
return e
|
|
else if e is Var:
|
|
return env[e.id]
|
|
else if e is BinOp:
|
|
Value|RuntimeError lhs = evaluate(e.lhs, env)
|
|
Value|RuntimeError rhs = evaluate(e.rhs, env)
|
|
// check if stuck
|
|
if !(lhs is int && rhs is int):
|
|
return {msg: "arithmetic attempted on non-numeric value"}
|
|
// switch statement would be good
|
|
if e.op == ADD:
|
|
return lhs + rhs
|
|
else if e.op == SUB:
|
|
return lhs - rhs
|
|
else if e.op == MUL:
|
|
return lhs * rhs
|
|
else if rhs != 0:
|
|
return lhs / rhs
|
|
return {msg: "divide-by-zero"}
|
|
else if e is Expr[]:
|
|
[Value] r = []
|
|
for i in e:
|
|
Value|RuntimeError v = evaluate(i, env)
|
|
if v is RuntimeError:
|
|
return v
|
|
else:
|
|
r = r ++ [v]
|
|
return r
|
|
else if e is ListAccess:
|
|
Value|RuntimeError src = evaluate(e.src, env)
|
|
Value|RuntimeError index = evaluate(e.index, env)
|
|
// santity checks
|
|
if src is [Value] && index is int && index >= 0 && index < |src|:
|
|
return src[index]
|
|
else:
|
|
return {msg: "invalid list access"}
|
|
else:
|
|
return 0 // dead-code
|
|
|
|
// ====================================================
|
|
// Expression Parser
|
|
// ====================================================
|
|
|
|
type State is { string input, int pos }
|
|
type SyntaxError is { string msg, int start, int end }
|
|
|
|
function SyntaxError(string msg, int start, int end) -> SyntaxError:
|
|
return { msg: msg, start: start, end: end }
|
|
|
|
// Top-level parse method
|
|
function parse(State st) -> (Stmt,State)|SyntaxError:
|
|
//
|
|
Var keyword, Var v
|
|
Expr e
|
|
int start = st.pos
|
|
//
|
|
keyword,st = parseIdentifier(st)
|
|
switch keyword.id:
|
|
case "print":
|
|
any r = parseAddSubExpr(st)
|
|
if !(r is SyntaxError):
|
|
e,st = r
|
|
return {rhs: e},st
|
|
else:
|
|
return r // error case
|
|
case "set":
|
|
st = parseWhiteSpace(st)
|
|
v,st = parseIdentifier(st)
|
|
any r = parseAddSubExpr(st)
|
|
if !(r is SyntaxError):
|
|
e,st = r
|
|
return {lhs: v.id, rhs: e},st
|
|
else:
|
|
return r // error case
|
|
default:
|
|
return SyntaxError("unknown statement",start,st.pos-1)
|
|
|
|
function parseAddSubExpr(State st) -> (Expr, State)|SyntaxError:
|
|
//
|
|
Expr lhs, Expr rhs
|
|
// First, pass left-hand side
|
|
any r = parseMulDivExpr(st)
|
|
//
|
|
if r is SyntaxError:
|
|
return r
|
|
//
|
|
lhs,st = r
|
|
st = parseWhiteSpace(st)
|
|
// Second, see if there is a right-hand side
|
|
if st.pos < |st.input| && st.input[st.pos] == '+':
|
|
// add expression
|
|
st.pos = st.pos + 1
|
|
r = parseAddSubExpr(st)
|
|
if !(r is SyntaxError):
|
|
rhs,st = r
|
|
return {op: ADD, lhs: lhs, rhs: rhs},st
|
|
else:
|
|
return r
|
|
else if st.pos < |st.input| && st.input[st.pos] == '-':
|
|
// subtract expression
|
|
st.pos = st.pos + 1
|
|
r = parseAddSubExpr(st)
|
|
if !(r is SyntaxError):
|
|
rhs,st = r
|
|
return {op: SUB, lhs: lhs, rhs: rhs},st
|
|
else:
|
|
return r
|
|
// No right-hand side
|
|
return (lhs,st)
|
|
|
|
function parseMulDivExpr(State st) -> (Expr, State)|SyntaxError:
|
|
// First, parse left-hand side
|
|
Expr lhs, Expr rhs
|
|
any r = parseTerm(st)
|
|
if r is SyntaxError:
|
|
return r
|
|
//
|
|
lhs,st = r
|
|
st = parseWhiteSpace(st)
|
|
// Second, see if there is a right-hand side
|
|
if st.pos < |st.input| && st.input[st.pos] == '*':
|
|
// add expression
|
|
st.pos = st.pos + 1
|
|
r = parseMulDivExpr(st)
|
|
if !(r is SyntaxError):
|
|
rhs,st = r
|
|
return {op: MUL, lhs: lhs, rhs: rhs}, st
|
|
else:
|
|
return r
|
|
else if st.pos < |st.input| && st.input[st.pos] == '/':
|
|
// subtract expression
|
|
st.pos = st.pos + 1
|
|
r = parseMulDivExpr(st)
|
|
if !(r is SyntaxError):
|
|
rhs,st = r
|
|
return {op: DIV, lhs: lhs, rhs: rhs}, st
|
|
else:
|
|
return r
|
|
// No right-hand side
|
|
return (lhs,st)
|
|
|
|
function parseTerm(State st) -> (Expr, State)|SyntaxError:
|
|
//
|
|
st = parseWhiteSpace(st)
|
|
if st.pos < |st.input|:
|
|
if ASCII.isLetter(st.input[st.pos]):
|
|
return parseIdentifier(st)
|
|
else if ASCII.isDigit(st.input[st.pos]):
|
|
return parseNumber(st)
|
|
else if st.input[st.pos] == '[':
|
|
return parseList(st)
|
|
//
|
|
return SyntaxError("expecting number or variable",st.pos,st.pos)
|
|
|
|
function parseIdentifier(State st) -> (Var, State):
|
|
//
|
|
string txt = ""
|
|
// inch forward until end of identifier reached
|
|
while st.pos < |st.input| && ASCII.isLetter(st.input[st.pos]):
|
|
txt = txt ++ [st.input[st.pos]]
|
|
st.pos = st.pos + 1
|
|
return ({id:txt}, st)
|
|
|
|
function parseNumber(State st) -> (Expr, State)|SyntaxError:
|
|
// inch forward until end of identifier reached
|
|
int start = st.pos
|
|
while st.pos < |st.input| && ASCII.isDigit(st.input[st.pos]):
|
|
st.pos = st.pos + 1
|
|
//
|
|
int|null iv = Int.parse(st.input[start..st.pos])
|
|
if iv == null:
|
|
return SyntaxError("Error parsing number",start,st.pos)
|
|
else:
|
|
return iv, st
|
|
|
|
function parseList(State st) -> (Expr, State)|SyntaxError:
|
|
//
|
|
st.pos = st.pos + 1 // skip '['
|
|
st = parseWhiteSpace(st)
|
|
[Expr] l = [] // initial list
|
|
bool firstTime = true
|
|
while st.pos < |st.input| && st.input[st.pos] != ']':
|
|
if !firstTime && st.input[st.pos] != ',':
|
|
return SyntaxError("expecting comma",st.pos,st.pos)
|
|
else if !firstTime:
|
|
st.pos = st.pos + 1 // skip ','
|
|
firstTime = false
|
|
any r = parseAddSubExpr(st)
|
|
if r is SyntaxError:
|
|
return r
|
|
else:
|
|
Expr e
|
|
e,st = r
|
|
// perform annoying error check
|
|
l = l ++ [e]
|
|
st = parseWhiteSpace(st)
|
|
st.pos = st.pos + 1
|
|
return l,st
|
|
|
|
// Parse all whitespace upto end-of-file
|
|
function parseWhiteSpace(State st) -> State:
|
|
while st.pos < |st.input| && ASCII.isWhiteSpace(st.input[st.pos]):
|
|
st.pos = st.pos + 1
|
|
return st
|
|
|
|
// ====================================================
|
|
// Main Method
|
|
// ====================================================
|
|
|
|
public method main(System.Console sys):
|
|
if(|sys.args| == 0):
|
|
sys.out.println("no parameter provided!")
|
|
else:
|
|
File.Reader file = File.Reader(sys.args[0])
|
|
string input = ASCII.fromBytes(file.readAll())
|
|
|
|
Environment env = Environment()
|
|
State st = {pos: 0, input: input}
|
|
while st.pos < |st.input|:
|
|
Stmt s
|
|
any r = parse(st)
|
|
if r is SyntaxError:
|
|
sys.out.println("syntax error: " ++ r.msg)
|
|
return
|
|
s,st = r
|
|
Value|RuntimeError v = evaluate(s.rhs,env)
|
|
if v is RuntimeError:
|
|
sys.out.println("runtime error: " ++ v.msg)
|
|
return
|
|
if s is Set:
|
|
env[s.lhs] = v
|
|
else:
|
|
sys.out.println(r)
|
|
st = parseWhiteSpace(st)
|
|
|