restructure

This commit is contained in:
nazrin 2025-05-29 15:33:12 +00:00
parent b466717c6e
commit 2de4381fae
6 changed files with 26 additions and 10 deletions

90
src/jlox/expr.d Normal file
View file

@ -0,0 +1,90 @@
module jlox.expr;
import std.conv;
import std.stdio;
import std.meta : AliasSeq;
import taggedalgebraic;
import jlox.token;
import jlox.tokentype;
abstract class Expr{
interface Visitor(R){
R visit(Binary expr);
R visit(Grouping expr);
R visit(Literal expr);
R visit(Unary expr);
}
private alias rTypes = AliasSeq!(string);
static foreach(T; rTypes)
abstract T accept(Visitor!T visitor);
private template defCtorAndAccept(){
this(Args...)(auto ref Args args){
static foreach(i, a; args)
this.tupleof[i] = a;
}
static foreach(T; rTypes)
override T accept(Visitor!T visitor) => visitor.visit(this);
}
static class Binary : Expr{
Expr left;
Token operator;
Expr right;
mixin defCtorAndAccept;
}
static class Grouping : Expr{
Expr expression;
mixin defCtorAndAccept;
}
static class Literal : Expr{
TTokenValue value;
mixin defCtorAndAccept;
}
static class Unary : Expr{
Token operator;
Expr right;
mixin defCtorAndAccept;
}
}
class AstPrinter : Expr.Visitor!string{
string print(Expr expr){
return expr.accept(this);
}
string parenthesize(Args...)(string name, Args args){
string s = "(" ~ name;
static foreach(expr; args){
s ~= " ";
s ~= expr.accept(this);
}
return s ~ ")";
}
string visit(Expr.Binary expr){
return parenthesize(expr.operator.lexeme, expr.left, expr.right);
}
string visit(Expr.Grouping expr){
return parenthesize("group", expr.expression);
}
string visit(Expr.Literal expr){
if(expr.value.isNil)
return "nil";
return expr.value.to!string;
}
string visit(Expr.Unary expr){
return parenthesize(expr.operator.lexeme, expr.right);
}
}
unittest{
auto expression = new Expr.Binary(
new Expr.Unary(
new Token(TokenType.MINUS, "-", TTokenValue.nil(0), 1),
new Expr.Literal(123)),
new Token(TokenType.STAR, "*", TTokenValue.nil(0), 1),
new Expr.Grouping(
new Expr.Literal(45.67)));
assert(new AstPrinter().print(expression));
}

60
src/jlox/main.d Normal file
View file

@ -0,0 +1,60 @@
module jlox.main;
import std.stdio;
import std.file;
import std.conv;
import std.exception;
import commandr;
import jlox.token;
import jlox.tokentype;
import jlox.scanner;
static bool hadError = false;
void error(int line, string message){
report(line, "", message);
}
void report(int line, string where, string message){
stderr.writeln("[line " ~ line.to!string ~ "] Error" ~ where ~ ": " ~ message);
hadError = true;
}
void run(string source){
Scanner scanner = new Scanner(source);
Token[] tokens = scanner.scanTokens();
foreach(token; tokens)
writeln(token);
}
void runFile(string path){
string bytes = readText(path);
run(bytes);
enforce(!hadError);
}
void runPrompt(){
while(true){
write("lox> ");
string line = stdin.readln();
if(!line)
break;
run(line);
hadError = false;
}
writeln();
}
version(unittest) {} else int main(string[] argv){
auto args = new Program("lox")
.add(new Argument("path").optional.acceptsFiles)
.parse(argv);
if(args.arg("path"))
runFile(args.arg("path"));
else
runPrompt();
return 0;
}

149
src/jlox/scanner.d Normal file
View file

@ -0,0 +1,149 @@
module jlox.scanner;
import std.ascii;
import std.conv;
import jlox.token;
import jlox.tokentype;
import jlox.main : error, report;
private bool isAlpha_(dchar c) => c.isAlpha || c == '_';
private bool isAlphaNum_(dchar c) => c.isAlphaNum || c == '_';
class Scanner {
private string source;
private Token[] tokens;
private uint start = 0;
private uint current = 0;
private uint line = 1;
this(string source){
this.source = source;
}
private bool isAtEnd(uint n = 0) const => current + n >= source.length;
private char advance() => source[current++];
private char peek(uint n = 0) const => isAtEnd(n) ? '\0' : source[current + n];
private char peekNext() const => peek(1);
private bool match(char expected){
if(peek() != expected)
return false;
current++;
return true;
}
private void addToken(TokenType type, TTokenValue literal = TTokenValue.nil(0)){
string text = source[start .. current];
tokens ~= new Token(type, text, literal, line);
}
Token[] scanTokens(){
while(!isAtEnd){
start = current;
scanToken();
}
addToken(TokenType.EOF);
return tokens;
}
private void stringLiteral(){
while(peek() != '"' && !isAtEnd){
if(peek() == '\n')
line++;
advance();
}
if(isAtEnd){
error(line, "Unterminated string.");
return;
}
advance();
string value = source[start + 1 .. current -1];
addToken(TokenType.STRING, TTokenValue.str(value));
}
private void number(){
while(peek().isDigit)
advance();
if(peek() == '.' && peekNext().isDigit){
advance(); // Eat the .
while(peek().isDigit)
advance();
}
addToken(TokenType.NUMBER, TTokenValue.dbl(source[start .. current].to!double));
}
private TokenType keywords(string word){
with(TokenType) switch(word){
case "and": return AND;
case "class": return CLASS;
case "else": return ELSE;
case "false": return FALSE;
case ":or": return FOR;
case "fun": return FUN;
case "if": return IF;
case "nil": return NIL;
case "or": return OR;
case "print": return PRINT;
case "return": return RETURN;
case "super": return SUPER;
case "this": return THIS;
case "true": return TRUE;
case "var": return VAR;
case "while": return WHILE;
default: return IDENTIFIER;
}
}
private void identifier(){
while(peek().isAlphaNum_)
advance();
addToken(keywords(source[start .. current]));
}
private 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('/')){
while(peek() != '\n' && !isAtEnd)
advance();
} else {
addToken(SLASH);
}
break;
case ' ', '\r', '\t':
break;
case '\n':
line++;
break;
case '"':
stringLiteral();
break;
case '0': .. case '9':
number();
break;
case 'a': .. case 'z':
case 'A': .. case 'Z':
case '_':
identifier();
break;
default:
error(line, "Unexpected character");
}
}
}

33
src/jlox/token.d Normal file
View file

@ -0,0 +1,33 @@
module jlox.token;
import std.conv;
import taggedalgebraic;
import jlox.tokentype;
struct TokenValue{
string str;
double dbl;
bool nil = false;
}
alias TTokenValue = TaggedUnion!TokenValue;
class Token {
TokenType type;
string lexeme;
TTokenValue literal;
int line;
this(TokenType type, string lexeme, TTokenValue literal, int line) {
this.type = type;
this.lexeme = lexeme;
this.literal = literal;
this.line = line;
}
override string toString() const{
return type.to!string ~ " " ~ lexeme ~ " " ~ literal.to!string;
}
}

23
src/jlox/tokentype.d Normal file
View file

@ -0,0 +1,23 @@
module jlox.tokentype;
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
}