diff --git a/src/clox/compiler.d b/src/clox/compiler.d new file mode 100644 index 0000000..c1df16b --- /dev/null +++ b/src/clox/compiler.d @@ -0,0 +1,9 @@ +module clox.compiler; + +import std.stdio; + +import clox.scanner; + +void compile(string source) @nogc nothrow { +} + diff --git a/src/clox/main.d b/src/clox/main.d index f5dd3be..44ec26d 100644 --- a/src/clox/main.d +++ b/src/clox/main.d @@ -1,38 +1,46 @@ module clox.main; import std.stdio; +import std.file; import clox.chunk; import clox.dbg; import clox.vm; -int main(string[] argv){ - Chunk chunk; +extern(C) int isatty(int); - ubyte constant = chunk.addConstant(1.2); - chunk.write(OpCode.Constant); - chunk.write(constant); - - constant = chunk.addConstant(3.4); - chunk.write(OpCode.Constant); - chunk.write(constant); - - chunk.write(OpCode.Add); - - constant = chunk.addConstant(5.6); - chunk.write(OpCode.Constant); - chunk.write(constant); - - chunk.write(OpCode.Divide); - - chunk.write(OpCode.Negate); - - chunk.write(OpCode.Return); - - VM vm = VM(0); - vm.interpret(&chunk); - - - return 0; +struct Lox{ + VM vm; + this(int _){ + vm = VM(0); + } + int runFile(string path){ + string source = path.readText(); + VM.InterpretResult result = vm.interpret(source); + final switch(result){ + case VM.InterpretResult.CompileError: return 65; + case VM.InterpretResult.RuntimeError: return 70; + case VM.InterpretResult.Ok: return 0; + } + } + int runPrompt(){ + while(true){ + write("> "); + string line = stdin.readln(); + if(!line){ + writeln(); + return 0; + } + vm.interpret(line); + } + } +} + +int main(string[] argv){ + Lox lox = Lox(0); + if(isatty(stdin.fileno)) + return lox.runPrompt(); + else + return lox.runFile("/dev/stdin"); } diff --git a/src/clox/scanner.d b/src/clox/scanner.d new file mode 100644 index 0000000..1d95000 --- /dev/null +++ b/src/clox/scanner.d @@ -0,0 +1,153 @@ +module clox.scanner; + +import std.stdio; +import std.ascii; + +import common.util; + +struct Token{ + enum Type : ubyte { + LeftParen, RightParen, // Single-character tokens. + LeftBrace, RightBrace, + Comma, Dot, Minus, Plus, + Semicolon, Slash, Star, + Bang, BangEqual, // One or two character tokens. + Equal, EqualEqual, + Greater, GreaterEqual, + Less, LessEqual, + Identifier, String, Number, // Literals. + And, Class, Else, False, // Keywords. + For, Fun, If, Nil, Or, + Print, Return, Super, This, + True, Var, While, + Error, EOF // Special + } + Type type; + int line; + string lexeme; + static Token error(string msg) => Token(Token.Type.Error, 0, msg); +} + +struct Scanner{ + string start; + string current; + int line = 1; + this(string source){ + start = current = source; + } + bool isAtEnd() const => current.length == 0; + private char peek() const => current[0]; + private char peekNext() const => current.length >= 2 ? current[1] : '\0'; + private Token makeToken(Token.Type type) const{ + Token token; + token.type = type; + token.lexeme = start[0 .. current.ptr - start.ptr]; + return token; + } + private char advance(){ + char c = current[0]; + current = current[1 .. $]; + return c; + } + private bool match(char needle){ + if(isAtEnd || current[0] != needle) + return false; + current = current[1 .. $]; + return true; + } + private void skipWhitespace(){ + while(!isAtEnd){ + char c = peek(); + if(!c) + return; + if(c == '/' && peekNext() == '/'){ + while(!isAtEnd && peek() != '\n') + advance(); + continue; + } + if(!c.isWhite) + return; + if(c == '\n') + line++; + current = current[1 .. $]; + } + } + private Token parseString(){ + while(peek() != '"' && !isAtEnd){ + if(peek() == '\n') + line++; + advance(); + } + if(isAtEnd) + return Token.error("Unterminated string."); + advance(); + return makeToken(Token.Type.String); + } + private Token parseNumber(){ + while(peek().isDigit) + advance(); + if(peek() == '.' && peekNext().isDigit){ + advance(); + while(peek().isDigit) + advance(); + } + return makeToken(Token.Type.Number); + } + private Token parseIdentifier(){ + while(peek().isAlphaNum_) + advance(); + Token token = makeToken(Token.Type.Identifier); + switch(token.lexeme){ + case "and": token.type = Token.Type.And; break; + case "class": token.type = Token.Type.Class; break; + case "else": token.type = Token.Type.Else; break; + case "if": token.type = Token.Type.If; break; + case "nil": token.type = Token.Type.Nil; break; + case "or": token.type = Token.Type.Or; break; + case "print": token.type = Token.Type.Print; break; + case "return": token.type = Token.Type.Return; break; + case "super": token.type = Token.Type.Super; break; + case "var": token.type = Token.Type.Var; break; + case "while": token.type = Token.Type.While; break; + case "false": token.type = Token.Type.False; break; + case "for": token.type = Token.Type.For; break; + case "fun": token.type = Token.Type.Fun; break; + case "this": token.type = Token.Type.This; break; + case "true": token.type = Token.Type.True; break; + default: break; + } + return token; + } + Token scan(){ + skipWhitespace(); + start = current; + if(isAtEnd) + return Token(Token.Type.EOF); + char c = advance(); + if(c.isAlpha_) + return parseIdentifier(); + switch(c){ + case '(': return makeToken(Token.Type.LeftParen); + case ')': return makeToken(Token.Type.RightParen); + case '{': return makeToken(Token.Type.LeftBrace); + case '}': return makeToken(Token.Type.RightBrace); + case ';': return makeToken(Token.Type.Semicolon); + case ',': return makeToken(Token.Type.Comma); + case '.': return makeToken(Token.Type.Dot); + case '-': return makeToken(Token.Type.Minus); + case '+': return makeToken(Token.Type.Plus); + case '/': return makeToken(Token.Type.Slash); + case '*': return makeToken(Token.Type.Star); + case '!': return makeToken(match('=') ? Token.Type.BangEqual : Token.Type.Bang); + case '=': return makeToken(match('=') ? Token.Type.EqualEqual : Token.Type.Equal); + case '<': return makeToken(match('=') ? Token.Type.LessEqual : Token.Type.Less); + case '>': return makeToken(match('=') ? Token.Type.GreaterEqual : Token.Type.Greater); + case '"': return parseString(); + default: break; + } + if(c.isDigit) + return parseNumber(); + return Token.error("Unexpected character '" ~ c ~ "'."); + } +} + diff --git a/src/clox/vm.d b/src/clox/vm.d index b9c9cfe..151ec6d 100644 --- a/src/clox/vm.d +++ b/src/clox/vm.d @@ -6,6 +6,7 @@ import clox.chunk; import clox.value; import clox.dbg; import clox.util; +import clox.compiler; enum stackMax = 256; @@ -13,10 +14,14 @@ struct VM{ const(ubyte)* ip; Stack!(Value, stackMax) stack; Chunk* chunk; - enum InterpretResult{ Ok, CompileError, RunetimeError } + enum InterpretResult{ Ok, CompileError, RuntimeError } this(int _) @nogc nothrow { stack = typeof(stack)(0); } + InterpretResult interpret(string source) @nogc nothrow { + compile(source); + return InterpretResult.Ok; + } InterpretResult interpret(Chunk* chunk) @nogc nothrow { this.chunk = chunk; ip = &chunk.code[0]; diff --git a/src/common/util.d b/src/common/util.d index f8dc60d..0efa053 100644 --- a/src/common/util.d +++ b/src/common/util.d @@ -7,3 +7,7 @@ template defaultCtor(){ } } +import std.ascii : isAlpha, isAlphaNum; +bool isAlpha_(dchar c) => c.isAlpha || c == '_'; +bool isAlphaNum_(dchar c) => c.isAlphaNum || c == '_'; + diff --git a/src/jlox/scanner.d b/src/jlox/scanner.d index 8e78a08..8da932f 100644 --- a/src/jlox/scanner.d +++ b/src/jlox/scanner.d @@ -6,11 +6,9 @@ import std.conv; import jlox.token; import jlox.tokentype; import jlox.main; +import common.util; -private bool isAlpha_(dchar c) => c.isAlpha || c == '_'; -private bool isAlphaNum_(dchar c) => c.isAlphaNum || c == '_'; - -class Scanner { +class Scanner{ private string source; private Token[] tokens; private uint start = 0;