import std.stdio; import std.string; import std.file; import std.algorithm : canFind, map; import std.conv; interface Function : Expr { Expr run(Expr[] args, Expr[string] std); } class UserFunction : Function { string name; string[] argNames; Expr[] bodyExprs; this(string name, Cons argNames, Expr[] bodyExprs) { this.name = name; foreach (Expr arg; argNames.inner) { Symbol sym = cast(Symbol) arg; this.argNames ~= sym.value; } this.bodyExprs = bodyExprs; } Expr run(Expr[] args, Expr[string] std) { Interpreter intrp = new Interpreter(); if (args.length != this.argNames.length) { throw new Exception("wrong number of arguments!"); } // resolve variables Expr[] evaluated; Expr[string] env = std.dup; for (int i = 0; i < args.length; i++) { env[this.argNames[i]] = intrp.evaluate(args[i], std); } // call the body return intrp.interpret(this.bodyExprs, env); } } interface Builtin : Function { } class If : Builtin { this() {} Expr run(Expr[] args, Expr[string] std) { if (args.length != 2 && args.length != 3) { throw new Exception("expected 2 or three arguments"); } Interpreter intrp = new Interpreter(); Expr e1 = intrp.evaluate(args[0], std); if (!cast(Bool) e1) { throw new Exception("expected a boolean condition"); } Bool cond = cast(Bool) e1; if (cond.value) { return intrp.evaluate(args[1], std); } else if (args.length == 3) { return intrp.evaluate(args[2], std); } else { return null; } } } class Addition : Builtin { this() {} Expr run(Expr[] args, Expr[string] std) { Interpreter intrp = new Interpreter(); Number[] evaluated; foreach (Expr e; args) { Expr ret = intrp.evaluate(e, std); if (!cast(Number) ret) { throw new Exception("expected a number, got " ~ to!string(ret)); } evaluated ~= cast(Number) ret; } float fsum = 0; int isum = 0; foreach (Number num; evaluated) { if (cast(Int) num) { Int tmp = cast(Int) num; isum += tmp.value; } else { Float tmp = cast(Float) num; fsum += tmp.value; } } if (fsum != 0) { float total = fsum + to!float(isum); return new Float(total); } else { return new Int(isum); } } } class Subtraction : Builtin { this() { } Expr run(Expr[] args, Expr[string] std) { Interpreter intrp = new Interpreter(); Number[] evaluated; foreach (Expr e; args) { Expr ret = intrp.evaluate(e, std); if (!cast(Number) ret) { throw new Exception("expected a number, got " ~ to!string(ret)); } evaluated ~= cast(Number) ret; } float total = 0; bool first = true; bool swapped = false; foreach (Number num; evaluated) { if (cast(Int) num) { Int i = cast(Int) num; if (first) { total -= i.value; first = false; } else if (!swapped) { total = (-1 * total) - i.value; swapped = true; } else { total -= i.value; } } else { Float f = cast(Float) num; if (first) { total -= f.value; first = false; } else if (!swapped) { total = (-1 * total) - f.value; swapped = true; } else { total -= f.value; } } } if (to!int(total) == total) { return new Int(to!int(total)); } else { return new Float(total); } } } class LessThan : Builtin { this() {} Expr run(Expr[] args, Expr[string] std) { if (args.length != 2) { throw new Exception("requires 2 arguments"); } Interpreter intrp = new Interpreter(); Number[] evaluated; foreach (Expr e; args) { Expr ret = intrp.evaluate(e, std); if (!cast(Number) ret) { throw new Exception("expected a number, got " ~ to!string(ret)); } evaluated ~= cast(Number) ret; } float left, right; Number first = evaluated[0]; if (cast(Int) first) { Int i = cast(Int) first; left = to!float(i.value); } else { Float f = cast(Float) first; left = f.value; } Number second = evaluated[1]; if (cast(Int) second) { Int i = cast(Int) second; right = to!float(i.value); } else { Float f = cast(Float) second; right = f.value; } return new Bool(left < right); } } class Interpreter { this() { } Expr interpret(Expr[] exprs, Expr[string] std) { Expr ret; foreach (Expr expr; exprs) { ret = evaluate(expr, std); } return ret; } Number evaluateNumber(Expr expr, Expr[string] std) { Expr ret = evaluate(expr, std); if (!cast(Number) ret) { throw new Exception("expected a number, got " ~ to!string(ret)); } return cast(Number) ret; } Expr evaluate(Expr expr, Expr[string] std) { if (cast(Cons) expr) { Cons cons = cast(Cons) expr; // an empty cons should be returned as-is if (cons.inner.length == 0) { return cons; } // look up the symbol, which is the first element if (!cast(Symbol) cons.inner[0]) { throw new Exception("cons must begin with a symbol"); } Symbol sym = cast(Symbol) cons.inner[0]; if (sym.value == "func") { //std Symbol nameSym = cast(Symbol) cons.inner[1]; Cons funcArgs = cast(Cons) cons.inner[2]; UserFunction func = new UserFunction(nameSym.value, funcArgs, cons.inner[3..$]); std[nameSym.value] = func; return func; } Expr* ex = (sym.value in std); if (ex is null) { writeln("unknown symbol ", sym); return null; } if (!cast(Function) ex) { return *ex; } Function* func = cast(Function*) ex; /* Expr[] args; foreach (Expr e; cons.inner[1..$]) { args ~= this.evaluateNumber(e, std); } */ Expr[] args = cons.inner[1..$]; //return func.run(cons.inner[1..$]); return func.run(args, std); } else if (cast(Symbol) expr) { Symbol sym = cast(Symbol) expr; return std[sym.value]; } else if (cast(Type) expr) { return expr; } else if (cast(Value) expr) { return cast(typeof(expr)) expr; } else { writeln("not sure what this is ", expr); return null; } } } //Number addition( class Parser { int idx; Token[] tokens; this(Token[] tokens) { this.tokens = tokens; } Type parseType() { assert(this.tokens[this.idx].type == TokenType.COLON); this.idx++; Token token = this.tokens[this.idx]; Type type; switch (token.type) { case TokenType.SYMBOL: type = new Type(token, null); break; case TokenType.OPEN_BRACKET: this.idx++; // opening bracket Type inner = parseType(); this.idx++; // closing bracket if (this.tokens[this.idx].type != TokenType.CLOSE_BRACKET) { throw new Exception("inner type not properly closed"); } type = new Type(new Symbol(":[]"), inner); break; default: throw new Exception("types must be a valid symbol"); } return type; } Expr parseExpr() { this.idx++; // open paren Token token; Expr[] inner; while(this.idx < this.tokens.length) { token = this.tokens[this.idx]; if (token.type == TokenType.CLOSE_PAREN) { break; } switch (token.type) { case TokenType.OPEN_PAREN: Expr expr = parseExpr(); inner ~= expr; //this.idx--; // to account for the extra closing? break; case TokenType.COLON: Type type = this.parseType(); inner ~= type; break; default: inner ~= token.value; break; } this.idx++; } if (token.type == TokenType.EOF) { throw new Exception("not enough closing parens!"); } assert(token.type == TokenType.CLOSE_PAREN); return new Cons(inner); } Expr[] parse() { this.idx = 0; Expr[] exprs; Token token; while (this.idx < this.tokens.length) { token = this.tokens[this.idx]; if (token.type == TokenType.EOF) { break; } switch (token.type) { case TokenType.OPEN_PAREN: Expr expr = this.parseExpr(); exprs ~= expr; break; case TokenType.COLON: Type type = this.parseType(); exprs ~= type; break; default: exprs ~= token.value; break; } this.idx++; } return exprs; } } class Lexer { int line, idx; char[] data; char[3] whitespace = [' ', '\t', '\n']; this(char[] data) { this.line = 1; this.idx = 0; this.data = data; } char peek() { return data[this.idx + 1]; } Token lexSymbol() { int startLine = this.line; char[] acc; while (this.idx < this.data.length && !canFind([' ', '\t', '\n', ')', ']', '&', ','], this.data[idx])) { acc ~= this.data[idx]; this.idx++; } this.idx--; Value val = new Symbol(to!string(acc)); return Token(startLine, TokenType.SYMBOL, val); } Token lexString() { assert(this.data[this.idx] == '"'); this.idx++; int startLine = this.line; // strings can be multi-lined char[] acc; bool esc = false; while (this.idx < this.data.length) { char ch = this.data[idx]; // end of the string if (!esc && ch == '"') { break; } // in an escape, always add the next character else if (esc) { acc ~= ch; esc = false; } // escape character doesn't get added else if (ch == '\\') { esc = true; } // everything else is valid else { acc ~= ch; if (ch == '\n') { this.line++; } } this.idx++; } if (this.idx == this.data.length) { throw new Exception("EOF reached -- unterminated string!"); } Value val = new String(to!string(acc)); return Token(startLine, TokenType.STRING, val); } Token lexNumber() { int startLine = this.line; char[] acc; while (this.idx < this.data.length && !canFind([' ', '\t', '\n', ')', ']'], this.data[idx])) { acc ~= this.data[idx]; this.idx++; } // if we got a newline, register it if (this.data[idx] == '\n') { this.line++; } Token tok; Value val; if (canFind(acc, '.')) { float f; try { f = to!float(acc); } catch (ConvException e) { throw new Exception("invalid float!"); } val = new Float(f); tok = Token(startLine, TokenType.FLOAT, val); } else { int i; try { i = to!int(acc); } catch (ConvException e) { throw new Exception("invalid int!"); } val = new Int(i); tok = Token(startLine, TokenType.INT, val); } this.idx--; return tok; } Token lexBool() { assert(this.data[this.idx] == '#'); char[] acc = ['#']; while (this.idx < this.data.length && !canFind([' ', '\t', '\n', ')', ']'], this.peek())) { acc ~= this.peek(); this.idx++; } Value val; switch (acc) { case "#true": val = new Bool(true); break; case "#false": val = new Bool(false); break; default: throw new Exception("bad bool"); break; } return Token(this.line, TokenType.BOOL, val); } Token[] lex() { Token[] tokens; while (idx < this.data.length) { char ch = this.data[idx]; switch (ch) { case ' ': break; case '\n': this.line++; break; case '(': tokens ~= Token(this.line, TokenType.OPEN_PAREN, null); break; case ')': tokens ~= Token(this.line, TokenType.CLOSE_PAREN, null); break; case ':': tokens ~= Token(this.line, TokenType.COLON, null); break; case '[': tokens ~= Token(this.line, TokenType.OPEN_BRACKET, null); break; case ']': tokens ~= Token(this.line, TokenType.CLOSE_BRACKET, null); break; // comments case ';': while (idx < this.data.length && this.peek() != '\n') { idx++; } break; // bool case '#': Token token = this.lexBool(); tokens ~= token; break; // int and float case '0':.. case '9': Token token = this.lexNumber(); tokens ~= token; break; // string case '"': Token token = this.lexString(); tokens ~= token; break; default: Token token = this.lexSymbol(); tokens ~= token; break; } this.idx++; } tokens ~= Token(this.line, TokenType.EOF); return tokens; } } char[] readFile(string fname) { File f = File(fname, "r"); char[] ret = []; while (!f.eof()) { ret = ret ~ f.readln(); } f.close(); return ret; } interface Expr { } class Cons : Expr { string name; Expr[] inner; this(Expr[] inner) { this.name = "cons"; this.inner = inner; } this(Expr wrap) { this.inner ~= wrap; } override string toString() { string ret = "("; foreach (Expr e; this.inner) { ret ~= to!string(e) ~ " "; } ret ~= ")"; return ret; } } class Type : Expr { string name; Symbol sym; int line; Type inner; this(Token tok) { this.name = "type"; this.sym = to!Symbol(tok.value); this.line = tok.line; this.inner = null; } this(Token tok, Type inner) { this.name = "type"; this.sym = to!Symbol(tok.value); this.line = tok.line; this.inner = inner; } this(Symbol sym) { this.name = "type"; this.sym = sym; this.inner = null; } this(Symbol sym, Type inner) { this.name = "type"; this.sym = sym; this.inner = inner; } override string toString() { if (this.sym.value == ":[]") { return ":[" ~ to!string(this.inner) ~ "]"; } else { return ":" ~ to!string(this.sym.value); } } } class Value : Expr { string name; this(string name) { this.name = name; } } class Number : Value { float value; this(string name) { super(name); } } class Int : Number { int value; this(int value) { super("int"); this.value = value; } override string toString() { return to!string(this.value); } } class Float : Number { float value; this(float value) { super("float"); this.value = value; } override string toString() { return to!string(this.value); } } class Bool : Value { bool value; this(bool value) { super("bool"); this.value = value; } override string toString() { return '#' ~ to!string(this.value); } } class String : Value { string value; this(string value) { super("string"); this.value = value; } override string toString() { return this.value; } } class Symbol : Value { string value; this(string value) { super("symbol"); this.value = value; } override string toString() { return '\'' ~ this.value; } } struct NebInt { int value; string name = "int"; } struct NebFloat { float value; string name = "float"; } struct NebBool { bool value; string name = "bool"; } //alias NebValue = SumType!(NebInt, NebFloat, NebBool); struct Token { int line; TokenType type; Value value; } enum TokenType { OPEN_PAREN, CLOSE_PAREN, OPEN_BRACKET, CLOSE_BRACKET, COLON, EOF, BOOL, INT, FLOAT, STRING, SYMBOL }; int main(string[] args) { if (args.length <= 1) { writeln("no input file!"); return 1; } string fname = args[1]; if (!exists(fname)) { writeln("file doesn't exist: ", fname); return 1; } char[] data = readFile(fname); Lexer lexer = new Lexer(data); Token[] tokens = lexer.lex(); Parser parser = new Parser(tokens); Expr[] exprs = parser.parse(); Expr[string] stdi; stdi["+"] = new Addition(); stdi["-"] = new Subtraction(); stdi["<"] = new LessThan(); stdi["if"] = new If(); Interpreter interpreter = new Interpreter(); Expr result = interpreter.interpret(exprs, stdi); writeln("=> ", result); return 0; }