I am writing an interpreter for a simple mathematical programming language.
Below is a simple program written in it that prints 41.22
:
let $x = 35; # This is a comment
$x + 6.22;
Below is the code for the lexer. As this is my first attempt at writing an interpreter, I would appreciate any general suggestions for improvement. I am also new to the D programming language, so I would like to know if it could be made more idiomatic.
import std.stdio;
import std.format;
import std.ascii : isAlpha, isDigit;
void main() {
Token token;
Lexer lexer;
lexer.fileName = "test";
lexer.input("let $x = 35;\n$x + 6.22;");
while (token.type != TokenType.eof) {
try token = lexer.lex();
catch (Panic) break;
writeln(token);
}
}
enum State {
none,
number,
keyword,
variable,
comment
}
enum TokenType {
number,
keyword,
operator,
variable,
semicolon,
eof
}
struct Token {
TokenType type;
string value;
}
class Panic : Exception {
// Thrown when the 'panic' method of 'Lexer' is called
this(string msg) {
super(msg);
}
}
bool isWhitespace(int c) {
return (c == ' ' || c == '\t');
}
bool isOperator(int c) {
return (c == '=' || c == '+' ||
c == '-' || c == '*' ||
c == '/' || c == '%');
}
bool isDelimiter(int c) {
return (c == '\n' || c == EOF || c == ';' ||
isWhitespace(c) || isOperator(c));
}
struct Lexer {
string code;
string fileName;
uint lineNum = 1;
uint colNum = 1;
int c;
State state = State.none;
void panic(string message) {
writefln("Syntax ERROR in '%s' at line %d, column %d:",
fileName, lineNum, colNum);
writeln(message);
throw new Panic("Syntax error");
}
void input(string code) {
this.code = code;
if (code == "") c = EOF;
else c = code[0];
}
void nextChar() {
colNum++;
code = code[1..$];
if (code == "") {
c = EOF;
} else {
c = code[0];
}
}
Token lex() {
// Returns the next token
Token token;
while (1) {
switch (state) {
case State.none:
if (c == EOF) {
token.type = TokenType.eof;
return token;
} else if (isWhitespace(c)) {
nextChar();
} else if (c == '\n') {
lineNum++;
nextChar();
colNum = 1;
} else if (c == '#') {
nextChar();
state = State.comment;
} else if (c == ';') {
token.type = TokenType.semicolon;
nextChar();
return token;
} else if (c == '$') {
nextChar();
state = State.variable;
} else if (isAlpha(c)) {
state = State.keyword;
} else if (isOperator(c)) {
token.value ~= c;
token.type = TokenType.operator;
nextChar();
return token;
} else if (isDigit(c)) {
state = State.number;
} else {
panic(format("Unexpected character `%c`",
cast(char)(c)));
}
break;
case State.comment:
while (c != '\n' && c != EOF) nextChar();
state = State.none;
break;
case State.variable:
while(!isDelimiter(c)) {
token.value ~= c;
nextChar();
}
state = State.none;
token.type = TokenType.variable;
return token;
case State.keyword:
while (!isDelimiter(c)) {
token.value ~= c;
nextChar();
}
state = State.none;
token.type = TokenType.keyword;
return token;
case State.number:
while (isDigit(c) || c == '.') {
token.value ~= c;
nextChar();
}
state = State.none;
token.type = TokenType.number;
return token;
default:
assert(0);
}
}
}
}