import std.stdio; import std.string; import std.algorithm : canFind; import std.conv : to; import chunk; import dbg; enum FormType { ATOM, LITERALSTRING, CONS, NIL, SYMBOL, FUNC, LAMBDA, DEF, BLOCK, IF, AND, OR, EOF, PARSE_ERROR } abstract class Form { FormType type; bool evaluate; int line; ValueType returnType = ValueType.ANY; void compile(Chunk chunk) { writeln("writing default op"); chunk.writeOp(OpCode.OP_NIL, line); } void compile(Function func) { writeln("writing default op"); func.chunk.writeOp(OpCode.OP_NIL, line); } } class Symbol : Form { string name; this(string name, int line) { this(name, false, line); } this(string name, bool quoted, int line) { this.name = name; this.line = line; this.evaluate = !quoted; this.type = FormType.SYMBOL; } } class Eof : Form { this(int line) { this.line = line; this.type = FormType.EOF; } } class ParseError : Form { string message; this(string message, int line) { this.message = message; this.line = line; this.type = FormType.PARSE_ERROR; } } class Block : Form { Form[] blockBody; this(int line) { this.line = line; this.type = FormType.BLOCK; } void addToBody(Form f) { this.blockBody ~= f; } } class Cons : Form { Form head; Form[] tail; int argCount; override void compile(Chunk chunk) { if (type == FormType.NIL) { writeln("writing nil on purpose"); chunk.writeOp(OpCode.OP_NIL, line); return; } // TODO writing head first is almost certainly wrong // it will depend on what head is head.compile(chunk); foreach (Form f ; tail) { f.compile(chunk); } } this(int line) { this.line = line; this.type = FormType.NIL; this.argCount = 0; this.evaluate = false; } void append(Form form) { if (argCount == 0) { head = form; type = FormType.CONS; } else { tail ~= form; } argCount++; } } class If : Form { Form cond; Form thenForm; Form elseForm; bool hasElse; this(int line, Form cond, Form then) { this.line = line; this.type = FormType.IF; this.cond = cond; this.thenForm = then; this.hasElse = false; } this(int line, Form cond, Form then, Form else_) { this.line = line; this.type = FormType.IF; this.cond = cond; this.thenForm = then; this.elseForm = else_; this.hasElse = true; } } class And : Form { Form[] clauses; this(int line) { this.line = line; this.type = FormType.AND; this.returnType = ValueType.BOOLEAN; } void addClause(Form clause) { clauses ~= clause; } } class Or : Form { Form[] clauses; this(int line) { this.line = line; this.type = FormType.OR; this.returnType = ValueType.BOOLEAN; } void addClause(Form clause) { clauses ~= clause; } } class Func : Form { Symbol name; Cons args; Form[] funcBody; this(int line) { this.line = line; this.type = FormType.FUNC; } void addToBody(Form f) { this.funcBody ~= f; } } class Lambda : Form { Cons args; Form[] lambdaBody; this (int line) { this.line = line; this.type = FormType.LAMBDA; } void addToBody(Form f) { this.lambdaBody ~= f; } } class Def : Form { Symbol name; Form val; this(Symbol name, Form val, int line) { this.name = name; this.val = val; this.line = line; this.type = FormType.DEF; } } class LiteralString : Form { Value value; this(string value, int line) { this.value = makeStringValue(value); this.line = line; this.type = FormType.LITERALSTRING; this.evaluate = false; this.returnType = this.value.type; } } class Atom : Form { Value value; override void compile(Chunk chunk) { chunk.writeOp(OpCode.OP_CONSTANT, line); int idx = chunk.addConstant(value); chunk.writeOp(to!ubyte(idx), line); } override void compile(Function func) { func.chunk.writeOp(OpCode.OP_CONSTANT, line); int idx = func.chunk.addConstant(value); func.chunk.writeOp(to!ubyte(idx), line); } this(string value, int line) { this.value = makeStringValue(value); this.line = line; this.type = FormType.ATOM; this.evaluate = false; this.returnType = this.value.type; } this(double value, int line) { this.value = makeNumberValue(value); this.line = line; this.type = FormType.ATOM; this.evaluate = false; this.returnType = this.value.type; } this(bool value, int line) { this.value = makeBooleanValue(value); this.line = line; this.type = FormType.ATOM; this.evaluate = false; this.returnType = this.value.type; } } enum ValueType { STRING, NUMBER, BOOLEAN, TYPE, SEQ, OBJ, ANY, NIL, } union As { bool boolean; double number; string str; string type; Obj obj; Seq seq; } struct Value { ValueType type; As as; } bool areValuesEqual(Value left, Value right) { if (left.type != right.type) { return false; } switch (left.type) { case ValueType.NUMBER: return left.as.number == right.as.number; case ValueType.BOOLEAN: return left.as.boolean == right.as.boolean; case ValueType.SEQ: Seq leftSeq = cast(Seq)left.as.seq; Seq rightSeq = cast(Seq)right.as.seq; if (leftSeq.type != rightSeq.type) { return false; } else if (leftSeq.type == SeqType.STRING) { return (cast(String)leftSeq).str == (cast(String)rightSeq).str; } else { // NOTE if eq? should work for lists, that logic goes here return false; } default: writeln("!!! THIS VALUE TYPE IS NOT SUPPORTED BY eq?"); return false; } } Value makeStringValue(string str) { As as = { str: str }; Value val = { ValueType.STRING, as }; return val; } Value makeNumberValue(double number) { As as = { number: number }; Value val = { ValueType.NUMBER, as }; return val; } Value makeBooleanValue(bool boolean) { As as = { boolean: boolean }; Value val = { ValueType.BOOLEAN, as }; return val; } Value makeObjValue(Obj obj) { As as = { obj: obj }; Value val = { ValueType.OBJ, as }; return val; } Value makeSeqValue(Seq seq) { As as = { seq: seq }; Value val = { ValueType.SEQ, as }; return val; } Value makeTypeValue(string name) { As as = { type: name }; Value val = { ValueType.TYPE, as }; return val; } class Parser { string source; int pos; int line; bool peekable() { return (pos < source.length); } char peek() { if (pos < source.length) { return source[pos]; } else { return '\0'; } } char advance() { if (!peekable()) { return '\0'; } else { char ret = peek(); pos++; return ret; } } void skipWhitespace() { while (peekable() && canFind([' ', '\t', '\r', '\n'], peek())) { if (peek() == '\n') { line++; } advance(); } } Form parseSymbol() { char[] acc; char next; while (peekable()) { next = peek(); if (isBoundary(next)) { break; } acc ~= next; advance(); } return new Symbol(to!string(acc), line); } Form parseString() { char[] acc; char next; while (peekable()) { next = peek(); if (next == '"') { break; } else if (next == '\n') { line++; } acc ~= advance(); } if (!peekable() && next != '"') { return new ParseError("unterminated string!", line); } advance(); // go past last quote return new LiteralString(to!string(acc), line); } bool isBoundary(char ch) { return canFind([' ', '\r', '\t', '\n', ')'], ch); } bool isDigit(char ch) { return '0' <= ch && '9' >= ch; } Form parseNumber() { char[] acc; char next; while (peekable()) { next = peek(); if (isBoundary(next)) { break; } else if (isDigit(next)) { acc ~= next; } else { return new ParseError("unparseable number", line); } advance(); } return new Atom(to!double(to!string(acc)), line); } Form parseDef() { // we've parsed `def`, but not the symbol yet Form sym = parseForm(); if (sym.type != FormType.SYMBOL) { return new ParseError("func definitions expect a symbol name", line); } // get the value Form val = parseForm(); Def def = new Def(cast(Symbol)sym, val, line); char ch = advance(); // closing paren writefln("got this char: '%c'", ch); return def; } Form parseLambda() { // we've parsed `lambda` already Form args = parseForm(); if (args.type != FormType.CONS && args.type != FormType.NIL) { return new ParseError("func definitions expect a list of arguments", line); } Lambda lamb = new Lambda(line); lamb.args = cast(Cons)args; char next; while(peekable()) { next = peek(); if (next == ')') { break; } lamb.addToBody(this.parseForm()); } if (!peekable()) { return new ParseError("unterminated lambda", line); } advance(); // consume closing paren return lamb; } Form parseFunc() { // we've parsed `func`, but not the symbol yet Form sym = parseForm(); if (sym.type != FormType.SYMBOL) { return new ParseError("func definitions expect a symbol name", line); } // args should be a cons Form args = parseForm(); if (args.type != FormType.CONS && args.type != FormType.NIL) { return new ParseError("func definitions expect a list of arguments", line); } Func func = new Func(line); func.name = cast(Symbol)sym; func.args = cast(Cons)args; char next; while(peekable()) { next = peek(); if (next == ')') { break; } func.addToBody(this.parseForm()); } if (!peekable()) { return new ParseError("unterminated cons", line); } advance(); // consume closing paren return func; } Form parseIf() { If if_; Form cond = parseForm(); Form then = parseForm(); if(peekable() && peek() == ')') { if_ = new If(cond.line, cond, then); } else { Form else_ = parseForm(); if_ = new If(cond.line, cond, then, else_); } advance(); // consume closing paren return if_; } Form parseAnd() { And and_ = new And(line); char next; while(peekable()) { next = peek(); if (next == ')') { break; } and_.addClause(parseForm()); } if (!peekable()) { return new ParseError("unterminated and", line); } advance(); // consume closing paren return and_; } Form parseOr() { Or or_ = new Or(line); char next; while(peekable()) { next = peek(); if (next == ')') { break; } or_.addClause(parseForm()); } if (!peekable()) { return new ParseError("unterminated or", line); } advance(); // consume closing paren return or_; } Form parseBlock() { Block block = new Block(line); char next; while(peekable()) { next = peek(); if (next == ')') { break; } block.addToBody(parseForm()); } if (!peekable()) { return new ParseError("unterminated block", line); } advance(); // consume closing paren return block; } Form parseCons() { skipWhitespace(); char next; Cons cons = new Cons(line); if (peekable() && peek() == ')') { advance(); return cons; } Form f = parseForm(); if (f.type == FormType.SYMBOL) { Symbol s = cast(Symbol)f; switch (s.name) { case "func": return parseFunc(); case "lambda": return parseLambda(); case "def": return parseDef(); case "block": return parseBlock(); case "if": return parseIf(); case "and": return parseAnd(); case "or": return parseOr(); default: break; } } // if it's not a special symbol, add it to the cons and keep parsing cons.append(f); while(peekable()) { next = peek(); if (next == ')') { break; } cons.append(parseForm()); } if (!peekable()) { return new ParseError("unterminated cons", line); } advance(); // go past closing paren return cons; } Form parseForm() { skipWhitespace(); if (!peekable()) { return new Eof(line); } char next = peek(); if (isDigit(next)) { return parseNumber(); } switch (next) { case '"': advance(); // go past first quote return parseString(); case '(': advance(); // go past the open paren return parseCons(); default: return parseSymbol(); } advance(); return new Atom(0, -1); } this(string source) { this.source = source; this.pos = 0; this.line = 1; } }