DzLox

:)
git clone https://git.sr.ht/~ashymad/DzLox
Log | Files | Refs | Submodules | LICENSE

commit 413070b52ff8ec387b559d48d4e3b1d2b2c4f0c4
Author: Szymon Mikulicz <szymon.mikulicz@posteo.net>
Date:   Fri,  9 Sep 2022 16:20:44 +0200

Inital commit

Diffstat:
A.gitignore | 16++++++++++++++++
Adlox | 0
Adub.json | 13+++++++++++++
Adub.selections.json | 6++++++
Asource/app.d | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/keywords.d | 26++++++++++++++++++++++++++
Asource/scaner.d | 171+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/token.d | 25+++++++++++++++++++++++++
Asource/tokentype.d | 20++++++++++++++++++++
9 files changed, 357 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,16 @@ +.dub +docs.json +__dummy.html +docs/ +/lox +lox.so +lox.dylib +lox.dll +lox.a +lox.lib +lox-test-* +*.exe +*.o +*.obj +*.lst +*.dlox_history diff --git a/dlox b/dlox Binary files differ. diff --git a/dub.json b/dub.json @@ -0,0 +1,12 @@ +{ + "authors": [ + "Ashymad" + ], + "copyright": "Copyright © 2022, Ashymad", + "dependencies": { + "linenoise": "~>1.1.0" + }, + "description": "lox interpreter in D", + "license": "MIT", + "name": "dlox" +} +\ No newline at end of file diff --git a/dub.selections.json b/dub.selections.json @@ -0,0 +1,6 @@ +{ + "fileVersion": 1, + "versions": { + "linenoise": "1.1.0+1.0.0" + } +} diff --git a/source/app.d b/source/app.d @@ -0,0 +1,80 @@ +import std.stdio; +import std.file; +import std.container; +import token; +import scaner; +import deimos.linenoise; +import std.string; +import std.conv; +import keywords; + +int main(string[] args) { + Lox lox = new Lox; + if (args.length > 2) { + writeln("Usage: dlox [script]"); + return 64; + } else if (args.length == 2) { + lox.runFile(args[1]); + } else { + lox.runPrompt(); + } + if (lox.hadError) return 65; + return 0; +} + +class Lox { + static bool hadError = false; + + extern(C) static void completion(const char *buf, linenoiseCompletions *lc) { + auto bufs = fromStringz(buf); + auto lastsp = lastIndexOfAny(bufs, [' ', '\t']) + 1; + auto klen = bufs.length - lastsp; + if (bufs.length <= 0) return; + + foreach (key; keywords.keywords.keys) { + if (klen <= key.length && bufs[lastsp..bufs.length] == key[0..klen]) { + linenoiseAddCompletion(lc, toStringz(bufs[0..lastsp] ~ key)); + } + } + } + + void runFile(string file) { + run(to!(char[])(readText(file))); + } + + void runPrompt() { + char* line; + const char* history = ".dlox_history"; + + linenoiseSetCompletionCallback(&completion); + linenoiseHistoryLoad(history); + + while((line = linenoise("lox> ")) !is null) { + if (line[0] != '\0' && line[0] != '/') { + linenoiseHistoryAdd(line); + linenoiseHistorySave(history); + } + run(fromStringz(line)); + hadError = false; + } + } + + void run(char[] source) { + Scanner scanner = new Scanner(source); + + auto tokens = scanner.scanTokens(); + foreach (token; tokens) { + writeln(token); + } + } + + static void error(int line, string msg) { + report(line, "", msg); + } + + static void report(int line, string where, string msg) { + writefln("[line %s] Error%s: %s", line, where, msg); + hadError = true; + } +} + diff --git a/source/keywords.d b/source/keywords.d @@ -0,0 +1,26 @@ +import tokentype; + +static immutable TokenType[const(char)[]] keywords; + +shared static this() { + with (TokenType) { + keywords = [ + "and": AND, + "class": CLASS, + "else": ELSE, + "false": FALSE, + "for": FOR, + "fun": FUN, + "if": IF, + "nil": NIL, + "or": OR, + "print": PRINT, + "return": RETURN, + "super": SUPER, + "this": THIS, + "true": TRUE, + "var": VAR, + "while": WHILE, + ]; + } +} diff --git a/source/scaner.d b/source/scaner.d @@ -0,0 +1,171 @@ +import token; +import tokentype; +import std.container; +import std.format; +import std.conv; +import app; + +class Scanner { + private string source; + private int start = 0; + private int current = 0; + private int line = 0; + + private Array!Token tokens; + + this(string source) { + this.source = source; + this.tokens = Array!Token(); + } + + this(char[] source) { + this.source = to!string(source); + this.tokens = Array!Token(); + } + + Array!Token scanTokens() { + while (!isAtEnd()) { + start = current; + scanToken(); + } + tokens.insert(new Token(TokenType.EOF, "", null, line)); + return tokens; + } + + void scanToken() { + char c = advance(); + with (TokenType) { + switch (c) { + case '(': addToken(LEFT_PAREN); break; + case ')': addToken(RIGHT_PAREN); break; + case '{': addToken(LEFT_BRACE); break; + case '}': addToken(RIGHT_BRACE); break; + case ',': addToken(COMMA); break; + case '.': addToken(DOT); break; + case '-': addToken(MINUS); break; + case '+': addToken(PLUS); break; + case ';': addToken(SEMICOLON); break; + case '*': addToken(STAR); break; + case '!': + addToken(match('=') ? BANG_EQUAL : BANG); + break; + case '=': + addToken(match('=') ? EQUAL_EQUAL : EQUAL); + break; + case '<': + addToken(match('=') ? LESS_EQUAL : LESS); + break; + case '>': + addToken(match('=') ? GREATER_EQUAL : GREATER); + break; + case '/': + if (match('/')) { + // A comment goes until the end of the line. + while (peek() != '\n' && !isAtEnd()) advance(); + } else if (match('*')) { + int nest = 1; + while (nest > 0) { + while ((peek() != '*' || peekNext() != '/') && !isAtEnd()) { + if (peek() == '\n') line++; + if (peek() == '/' && peekNext() == '*') nest++; + advance(); + } + advance(); + advance(); + nest--; + } + } else { + addToken(SLASH); + } + break; + case '"': stringToken(); break; + case ' ': + case '\r': + case '\t': + break; + case '\n': + line++; + break; + default: + if (isDigit(c)) { + numberToken(); + } else { + Lox.error(line, format("Unexpected character: %c", c)); + } + break; + } + } + } + + private char peekNext() { + if (current + 1 >= source.length) return '\0'; + return source[current + 1]; + } + + private void numberToken() { + while (isDigit(peek())) advance(); + + // Look for a fractional part. + if (peek() == '.' && isDigit(peekNext())) { + // Consume the "." + advance(); + + while (isDigit(peek())) advance(); + } + + addToken(TokenType.NUMBER, + to!double(source[start..current])); + } + + private bool isDigit(char c) { + return c >= '0' && c <= '9'; + } + + private void stringToken() { + while (peek() != '"' && !isAtEnd()) { + if (peek() == '\n') line++; + advance(); + } + + if (isAtEnd()) { + Lox.error(line, "Unterminated string."); + return; + } + + // The closing ". + advance(); + + // Trim the surrounding quotes. + string value = source[start + 1..current - 1]; + addToken(TokenType.STRING, value); + } + + private char peek() { + if (isAtEnd()) return '\0'; + return source[current]; + } + + private bool match(char expected) { + if (isAtEnd()) return false; + if (source[current] != expected) return false; + + current++; + return true; + } + + private char advance() { + return source[current++]; + } + + private void addToken(TokenType type) { + addToken(type, null); + } + + private void addToken(T)(TokenType type, T literal) { + string text = source[start..current]; + tokens.insert(new Token(type, text, literal, line)); + } + private bool isAtEnd() { + return current >= source.length; + } +} diff --git a/source/token.d b/source/token.d @@ -0,0 +1,25 @@ +import tokentype; +import std.conv; +import std.variant; + +class Token { + immutable TokenType type; + immutable string lexeme; + const Variant literal; + immutable int line; + + this(T)(TokenType type, string lexeme, T literal, int line) { + this.type = type; + this.lexeme = lexeme; + this.literal = literal; + this.line = line; + } + + void toString(scope void delegate(const(char)[]) sink) const { + sink(text(type)); + sink(" "); + sink(lexeme); + sink(" "); + sink(text(line)); + } +} diff --git a/source/tokentype.d b/source/tokentype.d @@ -0,0 +1,20 @@ +enum TokenType { + // Single-character tokens. + LEFT_PAREN, RIGHT_PAREN, LEFT_BRACE, RIGHT_BRACE, + COMMA, DOT, MINUS, PLUS, SEMICOLON, SLASH, STAR, + + // One or two character tokens. + BANG, BANG_EQUAL, + EQUAL, EQUAL_EQUAL, + GREATER, GREATER_EQUAL, + LESS, LESS_EQUAL, + + // Literals. + IDENTIFIER, STRING, NUMBER, + + // Keywords. + AND, CLASS, ELSE, FALSE, FUN, FOR, IF, NIL, OR, + PRINT, RETURN, SUPER, THIS, TRUE, VAR, WHILE, + + EOF +}