first commit

This commit is contained in:
2025-07-13 16:43:07 +02:00
commit e08bfb2408
17 changed files with 1096 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
out

8
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/

6
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/jlox.iml" filepath="$PROJECT_DIR$/jlox.iml" />
</modules>
</component>
</project>

124
.idea/uiDesigner.xml generated Normal file
View File

@@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Palette2">
<group name="Swing">
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
</item>
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
</item>
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false" auto-create-binding="false" can-attach-label="true">
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
</item>
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
<initial-values>
<property name="text" value="Button" />
</initial-values>
</item>
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="RadioButton" />
</initial-values>
</item>
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="CheckBox" />
</initial-values>
</item>
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
<initial-values>
<property name="text" value="Label" />
</initial-values>
</item>
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
</item>
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
</item>
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
<preferred-size width="-1" height="20" />
</default-constraints>
</item>
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
</item>
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
</item>
</group>
</component>
</project>

11
jlox.iml Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@@ -0,0 +1,55 @@
package de.djledda.lox;
class AstPrinter implements Expression.Visitor<String> {
public static void main(String[] args) {
Expression expression = new Expression.Binary(
new Expression.Unary(
new Token(TokenType.MINUS, "-", null, 1),
new Expression.Literal(123)),
new Token(TokenType.STAR, "*", null, 1),
new Expression.Grouping(
new Expression.Literal(45.67)));
System.out.println(new AstPrinter().print(expression));
}
String print(Expression expression) {
return expression.accept(this);
}
private String parenthesise(String name, Expression... expressions) {
StringBuilder builder = new StringBuilder();
builder.append("(").append(name);
for (Expression expression : expressions) {
builder.append(" ");
builder.append(expression.accept(this));
}
builder.append(")");
return builder.toString();
}
@Override
public String visitBinaryExpression(Expression.Binary expression) {
return parenthesise(expression.operator.lexeme, expression.left, expression.right);
}
@Override
public String visitGroupingExpression(Expression.Grouping expression) {
return parenthesise("group", expression.expression);
}
@Override
public String visitLiteralExpression(Expression.Literal expression) {
if (expression.value == null) {
return "nil";
}
return expression.value.toString();
}
@Override
public String visitUnaryExpression(Expression.Unary expression) {
return parenthesise(expression.operator.lexeme, expression.right);
}
}

View File

@@ -0,0 +1,71 @@
package de.djledda.lox;
abstract class Expression {
interface Visitor<R> {
R visitBinaryExpression(Binary expression);
R visitGroupingExpression(Grouping expression);
R visitLiteralExpression(Literal expression);
R visitUnaryExpression(Unary expression);
}
static class Binary extends Expression {
final Expression left;
final Token operator;
final Expression right;
Binary(Expression left, Token operator, Expression right) {
this.left = left;
this.operator = operator;
this.right = right;
}
@Override
<R> R accept(Visitor<R> visitor) {
return visitor.visitBinaryExpression(this);
}
}
static class Grouping extends Expression {
final Expression expression;
Grouping(Expression expression) {
this.expression = expression;
}
@Override
<R> R accept(Visitor<R> visitor) {
return visitor.visitGroupingExpression(this);
}
}
static class Literal extends Expression {
final Object value;
Literal(Object value) {
this.value = value;
}
@Override
<R> R accept(Visitor<R> visitor) {
return visitor.visitLiteralExpression(this);
}
}
static class Unary extends Expression {
final Token operator;
final Expression right;
Unary(Token operator, Expression right) {
this.operator = operator;
this.right = right;
}
@Override
<R> R accept(Visitor<R> visitor) {
return visitor.visitUnaryExpression(this);
}
}
abstract <R> R accept(Visitor<R> visitor);
}

View File

@@ -0,0 +1,134 @@
package de.djledda.lox;
class Interpreter implements Expression.Visitor<Object>, Statement.Visitor<Void> {
void interpret(Expression expression) {
try {
Object value = evaluate(expression);
System.out.println(stringify(value));
} catch (RuntimeError error) {
Lox.runtimeError(error);
}
}
private String stringify(Object object) {
if (object == null) {
return "nil";
}
if (object instanceof Double) {
String text = object.toString();
if (text.endsWith(".0")) {
text = text.substring(0, text.length() - 2);
}
return text;
}
return object.toString();
}
private Object evaluate(Expression expression) {
return expression.accept(this);
}
private Boolean isTruthy(Object object) {
if (object == null) {
return false;
}
if (object instanceof Boolean) {
return (boolean)object;
}
return true;
}
private Boolean isEqual(Object left, Object right) {
if (left == null && right == null) {
return true;
}
if (left == null) {
return false;
}
return left.equals(right);
}
private void checkNumberOperand(Token operator, Object operand) {
if (operand instanceof Double) {
return;
}
throw new RuntimeError(operator, "Operand must be a number.");
}
private void checkNumberOperands(Token operator, Object left, Object right) {
if (left instanceof Double && right instanceof Double) {
return;
}
throw new RuntimeError(operator, "Operands must both be numbers.");
}
@Override
public Object visitGroupingExpression(Expression.Grouping expression) {
return evaluate(expression.expression);
}
@Override
public Object visitLiteralExpression(Expression.Literal expression) {
return expression.value;
}
@Override
public Object visitUnaryExpression(Expression.Unary expression) {
Object right = evaluate(expression.right);
switch (expression.operator.type) {
case MINUS:
checkNumberOperand(expression.operator, right);
return -(double) right;
case BANG:
return !isTruthy(right);
}
return null;
}
@Override
public Object visitBinaryExpression(Expression.Binary expression) {
Object left = evaluate(expression.left);
Object right = evaluate(expression.right);
switch (expression.operator.type) {
case GREATER:
checkNumberOperands(expression.operator, left, right);
return (double)left > (double)right;
case GREATER_EQUAL:
checkNumberOperands(expression.operator, left, right);
return (double)left >= (double)right;
case LESS:
checkNumberOperands(expression.operator, left, right);
return (double)left < (double)right;
case LESS_EQUAL:
checkNumberOperands(expression.operator, left, right);
return (double)left <= (double)right;
case BANG_EQUAL:
return !isEqual(left, right);
case EQUAL_EQUAL:
return isEqual(left, right);
case MINUS:
checkNumberOperands(expression.operator, left, right);
return (double)left - (double)right;
case PLUS:
checkNumberOperands(expression.operator, left, right);
if (left instanceof Double && right instanceof Double) {
return left + right.toString();
}
if (left instanceof String && right instanceof String) {
return left + right.toString();
}
break;
case SLASH:
checkNumberOperands(expression.operator, left, right);
if ((double)right == 0) {
throw new RuntimeError(expression.operator, "Divide by zero error.");
}
return (double)left / (double)right;
case STAR:
checkNumberOperands(expression.operator, left, right);
return (double)left * (double)right;
}
throw new RuntimeError(expression.operator, "Operands must be two numbers or two strings.");
}
}

View File

@@ -0,0 +1,82 @@
package de.djledda.lox;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
public class Lox {
private static final Interpreter interpreter = new Interpreter();
static boolean hadError = false;
static boolean hadRuntimeError = false;
public static void main(String[] args) throws IOException {
if (args.length > 1) {
System.out.println("Usage: jlox [script]");
System.exit(64);
} else if (args.length == 1) {
runFile(args[0]);
} else {
runPrompt();
}
}
private static void runFile(String path) throws IOException {
byte[] bytes = Files.readAllBytes(Paths.get(path));
run(new String(bytes, Charset.defaultCharset()));
if (hadError) {
System.exit(65);
}
if (hadRuntimeError) {
System.exit(70);
}
}
private static void runPrompt() throws IOException {
InputStreamReader input = new InputStreamReader(System.in);
BufferedReader reader = new BufferedReader(input);
while (true) {
System.out.print("> ");
String line = reader.readLine();
if (line == null) {
break;
}
run(line);
hadError = false;
}
}
private static void run(String source) {
Scanner scanner = new Scanner(source);
List<Token> tokens = scanner.scanTokens();
Parser parser = new Parser(tokens);
if (hadError) {
return;
}
}
static void error(int line, String message) {
report(line, "", message);
}
static void error(Token token, String message) {
if (token.type == TokenType.EOF) {
report(token.line, " at end", message);
} else {
report(token.line, " at '" + token.lexeme + "'", message);
}
}
static void runtimeError(RuntimeError error) {
System.err.println(error.getMessage() + "\n[line " + error.token.line + "]");
hadRuntimeError = true;
}
private static void report(int line, String where, String message) {
System.err.println("[line " + line + "] Error" + where + ": " + message);
hadError = true;
}
}

View File

@@ -0,0 +1,186 @@
package de.djledda.lox;
import java.util.ArrayList;
import java.util.List;
class Parser {
private static class ParseError extends RuntimeException {}
private final List<Token> tokens;
private int current = 0;
Parser(List<Token> tokens) {
this.tokens = tokens;
}
List<Statement> parse() {
List<Statement> statements = new ArrayList<>();
while (!isAtEnd()) {
statements.add(statement());
}
return statements;
}
private Statement statement() {
if (match(TokenType.PRINT)) {
return printStatement();
}
return expressionStatement();
}
private Statement printStatement() {
Expression value = expression();
consume(TokenType.SEMICOLON, "';' after value expected.");
return new Statement.Print(value);
}
private Statement expressionStatement() {
Expression expression = expression();
consume(TokenType.SEMICOLON, "';' after expression expected.");
return new Statement.Expression(expression);
}
private Expression expression() {
return equality();
}
private Expression equality() {
Expression expression = comparison();
while (match(TokenType.BANG_EQUAL, TokenType.EQUAL_EQUAL)) {
Token operator = previous();
Expression right = comparison();
expression = new Expression.Binary(expression, operator, right);
}
return expression;
}
private Expression comparison() {
Expression expression = term();
while (match(TokenType.GREATER, TokenType.GREATER_EQUAL, TokenType.LESS, TokenType.LESS_EQUAL)) {
Token operator = previous();
Expression right = term();
expression = new Expression.Binary(expression, operator, right);
}
return expression;
}
private Expression term() {
Expression expression = factor();
while (match(TokenType.MINUS, TokenType.PLUS)) {
Token operator = previous();
Expression right = factor();
expression = new Expression.Binary(expression, operator, right);
}
return expression;
}
private Expression factor() {
Expression expression = unary();
while (match(TokenType.SLASH, TokenType.STAR)) {
Token operator = previous();
Expression right = unary();
expression = new Expression.Binary(expression, operator, right);
}
return expression;
}
private Expression unary() {
if (match(TokenType.BANG, TokenType.MINUS)) {
Token operator = previous();
Expression right = unary();
return new Expression.Unary(operator, right);
}
return primary();
}
private Expression primary() {
if (match(TokenType.FALSE)) {
return new Expression.Literal(false);
}
if (match(TokenType.TRUE)) {
return new Expression.Literal(true);
}
if (match(TokenType.NIL)) {
return new Expression.Literal(null);
}
if (match(TokenType.NUMBER, TokenType.STRING)) {
return new Expression.Literal(previous().literal);
}
if (match(TokenType.LEFT_PAREN)) {
Expression expression = expression();
consume(TokenType.RIGHT_PAREN, "')' after expression expected.");
return new Expression.Grouping(expression);
}
throw error(peek(), "Expression expected.");
}
private boolean match(TokenType... types) {
for (TokenType type : types) {
if (check(type)) {
advance();
return true;
}
}
return false;
}
private boolean check(TokenType type) {
if (isAtEnd()) {
return false;
}
return peek().type == type;
}
private Token advance() {
if (!isAtEnd()) {
current++;
}
return previous();
}
private boolean isAtEnd() {
return peek().type == TokenType.EOF;
}
private Token peek() {
return tokens.get(current);
}
private Token previous() {
return tokens.get(current - 1);
}
private Token consume(TokenType type, String message) {
if (check(type)) {
return advance();
}
throw error(peek(), message);
}
private ParseError error(Token token, String message) {
Lox.error(token, message);
return new ParseError();
}
private void synchronise() {
advance();
while (!isAtEnd()) {
if (previous().type == TokenType.SEMICOLON) {
return;
}
switch (peek().type) {
case CLASS:
case FUN:
case VAR:
case FOR:
case IF:
case WHILE:
case PRINT:
case RETURN:
return;
}
advance();
}
}
}

View File

@@ -0,0 +1,10 @@
package de.djledda.lox;
class RuntimeError extends RuntimeException {
final Token token;
RuntimeError(Token token, String message) {
super(message);
this.token = token;
}
}

View File

@@ -0,0 +1,203 @@
package de.djledda.lox;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Scanner {
private final String source;
private final List<Token> tokens = new ArrayList<>();
private int start = 0;
private int current = 0;
private int line = 1;
private static final Map<String, TokenType> keywords;
static {
keywords = new HashMap<>();
keywords.put("and", TokenType.AND);
keywords.put("class", TokenType.CLASS);
keywords.put("else", TokenType.ELSE);
keywords.put("false", TokenType.FALSE);
keywords.put("for", TokenType.FOR);
keywords.put("fun", TokenType.FUN);
keywords.put("if", TokenType.IF);
keywords.put("nil", TokenType.NIL);
keywords.put("or", TokenType.OR);
keywords.put("print", TokenType.PRINT);
keywords.put("return", TokenType.RETURN);
keywords.put("super", TokenType.SUPER);
keywords.put("this", TokenType.THIS);
keywords.put("true", TokenType.TRUE);
keywords.put("var", TokenType.VAR);
keywords.put("while", TokenType.WHILE);
}
Scanner(String source) {
this.source = source;
}
List<Token> scanTokens() {
while (!isAtEnd()) {
start = current;
scanToken();
}
tokens.add(new Token(TokenType.EOF, "", null, line));
return tokens;
}
private boolean isAtEnd() {
return current >= source.length();
}
private void scanToken() {
char c = advance();
switch(c) {
case '(': addToken(TokenType.LEFT_PAREN); break;
case ')': addToken(TokenType.RIGHT_PAREN); break;
case '{': addToken(TokenType.LEFT_BRACE); break;
case '}': addToken(TokenType.RIGHT_BRACE); break;
case ',': addToken(TokenType.COMMA); break;
case '.': addToken(TokenType.DOT); break;
case '-': addToken(TokenType.MINUS); break;
case '+': addToken(TokenType.PLUS); break;
case ';': addToken(TokenType.SEMICOLON); break;
case '*': addToken(TokenType.STAR); break;
case '!':
addToken(match('=') ? TokenType.BANG_EQUAL : TokenType.BANG);
break;
case '=':
addToken(match('=') ? TokenType.EQUAL_EQUAL : TokenType.EQUAL);
break;
case '<':
addToken(match('=') ? TokenType.LESS_EQUAL : TokenType.LESS);
break;
case '>':
addToken(match('=') ? TokenType.GREATER_EQUAL : TokenType.GREATER);
case '/':
if (match('/')) {
while (peek() != '\n' && !isAtEnd()) {
advance();
}
} else {
addToken(TokenType.SLASH);
}
break;
case ' ':
case '\r':
case '\t':
break;
case '\n':
line++;
break;
case '"':
string();
break;
default:
if (isDigit(c)) {
number();
} else if (isAlpha(c)) {
identifier();
} else {
Lox.error(line, "Unexpected character.");
}
break;
}
}
private void identifier() {
while (isAlphaNumeric(peek())) {
advance();
}
String text = source.substring(start, current);
TokenType type = keywords.get(text);
if (type == null) {
type = TokenType.IDENTIFIER;
}
addToken(type);
}
private void number() {
while (isDigit(peek())) {
advance();
}
if (peek() == '.' && isDigit(peekNext())) {
advance();
while (isDigit(peek())) {
advance();
}
}
addToken(TokenType.NUMBER, Double.parseDouble(source.substring(start, current)));
}
private void string() {
while (peek() != '"') {
if (isAtEnd()) {
Lox.error(line, "Unterminated string.");
return;
}
if (peek() == '\n') {
line++;
}
advance();
}
advance();
String value = source.substring(start + 1, current - 1);
addToken(TokenType.STRING, value);
}
private boolean match(char expected) {
if (isAtEnd()) {
return false;
}
if (source.charAt(current) != expected) {
return false;
}
current++;
return true;
}
private char peek() {
if (isAtEnd()) {
return '\0';
}
return source.charAt(current);
}
private char peekNext() {
if (current + 1 >= source.length()) {
return '\0';
}
return source.charAt(current + 1);
}
private boolean isAlpha(char c) {
return (c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
c == '_';
}
private boolean isAlphaNumeric(char c) {
return isAlpha(c) || isDigit(c);
}
private boolean isDigit(char c) {
return c >= '0' && c <= '9';
}
private char advance() {
current++;
return source.charAt(current - 1);
}
private void addToken(TokenType type) {
addToken(type, null);
}
private void addToken(TokenType type, Object literal) {
String text = source.substring(start, current);
tokens.add(new Token(type, text, literal, line));
}
}

View File

@@ -0,0 +1,37 @@
package de.djledda.lox;
abstract class Statement {
interface Visitor<R> {
R visitExpressionStatement(Expression statement);
R visitPrintStatement(Print statement);
}
static class Expression extends Statement {
final Expression expression;
Expression(Expression expression) {
this.expression = expression;
}
@Override
<R> R accept(Visitor<R> visitor) {
return visitor.visitExpressionStatement(this);
}
}
static class Print extends Statement {
final Expression expression;
Print(Expression expression) {
this.expression = expression;
}
@Override
<R> R accept(Visitor<R> visitor) {
return visitor.visitPrintStatement(this);
}
}
abstract <R> R accept(Visitor<R> visitor);
}

View File

@@ -0,0 +1,21 @@
package de.djledda.lox;
public class Token {
final TokenType type;
final String lexeme;
final Object literal;
final int line;
Token(TokenType type, String lexeme, Object literal, int line) {
this.type = type;
this.lexeme = lexeme;
this.literal = literal;
this.line = line;
}
public String toString() {
return type + " " + lexeme + " " + literal;
}
}

View File

@@ -0,0 +1,52 @@
package de.djledda.lox;
public 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
EOF
}

View File

@@ -0,0 +1,87 @@
package de.djledda.tool;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.List;
public class GenerateAst {
public static void main(String[] args) throws IOException {
if (args.length != 1) {
System.err.println("Usage: generate_ast <output directory>");
System.exit(64);
}
String outputDir = args[0];
defineAst(outputDir, "Expression", Arrays.asList(
"Binary : Expression left, Token operator, Expression right",
"Grouping : Expression expression",
"Literal : Object value",
"Unary : Token operator, Expression right"
));
defineAst(outputDir, "Statement", Arrays.asList(
"Expression : Expression expression",
"Print : Expression expression"
));
}
private static void defineAst(String outputDir, String baseName, List<String> types) throws IOException {
String path = outputDir + "/" + baseName + ".java";
PrintWriter writer = new PrintWriter(path, "UTF-8");
writer.println("package de.djledda.lox;");
writer.println();
writer.println("import java.util.List;");
writer.println();
writer.println("abstract class " + baseName + " {");
defineVisitor(writer, baseName, types);
writer.println();
for (String type : types) {
String className = type.split(":")[0].trim();
String fields = type.split(":")[1].trim();
defineType(writer, baseName, className, fields);
}
writer.println();
writer.println("\tabstract <R> R accept(Visitor<R> visitor);");
writer.println("}");
writer.close();
}
private static void defineType(PrintWriter writer, String baseName, String className, String fieldList) {
writer.println("\tstatic class " + className + " extends " + baseName + " {");
String[] fields = fieldList.split(", ");
for (String field : fields) {
writer.println("\t\tfinal " + field + ";");
}
writer.println();
writer.println("\t\t" + className + "(" + fieldList + ") {");
for (String field : fields) {
String name = field.split(" ")[1];
writer.println("\t\t\tthis." + name + " = " + name + ";");
}
writer.println("\t\t}");
writer.println();
writer.println("\t\t@Override");
writer.println("\t\t<R> R accept(Visitor<R> visitor) {");
writer.println("\t\t\treturn visitor.visit" + className + baseName + "(this);");
writer.println("\t\t}");
writer.println("\t}");
writer.println();
}
private static void defineVisitor(PrintWriter writer, String baseName, List<String> types) {
writer.println("\tinterface Visitor<R> {");
for (String type : types) {
String typeName = type.split(":")[0].trim();
writer.println("\t\tR visit" + typeName + baseName + "(" + typeName + " " + baseName.toLowerCase() + ");");
}
writer.println("\t}");
}
}