restructure
This commit is contained in:
parent
b466717c6e
commit
2de4381fae
6 changed files with 26 additions and 10 deletions
90
src/jlox/expr.d
Normal file
90
src/jlox/expr.d
Normal 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
60
src/jlox/main.d
Normal 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
149
src/jlox/scanner.d
Normal 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
33
src/jlox/token.d
Normal 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
23
src/jlox/tokentype.d
Normal 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
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue