first commit
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
out
|
||||
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal 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
6
.idea/misc.xml
generated
Normal 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
8
.idea/modules.xml
generated
Normal 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
124
.idea/uiDesigner.xml
generated
Normal 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
11
jlox.iml
Normal 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>
|
||||
55
src/de/djledda/lox/AstPrinter.java
Normal file
55
src/de/djledda/lox/AstPrinter.java
Normal 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);
|
||||
}
|
||||
}
|
||||
71
src/de/djledda/lox/Expression.java
Normal file
71
src/de/djledda/lox/Expression.java
Normal 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);
|
||||
}
|
||||
134
src/de/djledda/lox/Interpreter.java
Normal file
134
src/de/djledda/lox/Interpreter.java
Normal 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.");
|
||||
}
|
||||
}
|
||||
82
src/de/djledda/lox/Lox.java
Normal file
82
src/de/djledda/lox/Lox.java
Normal 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;
|
||||
}
|
||||
}
|
||||
186
src/de/djledda/lox/Parser.java
Normal file
186
src/de/djledda/lox/Parser.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
10
src/de/djledda/lox/RuntimeError.java
Normal file
10
src/de/djledda/lox/RuntimeError.java
Normal 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;
|
||||
}
|
||||
}
|
||||
203
src/de/djledda/lox/Scanner.java
Normal file
203
src/de/djledda/lox/Scanner.java
Normal 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));
|
||||
}
|
||||
}
|
||||
37
src/de/djledda/lox/Statement.java
Normal file
37
src/de/djledda/lox/Statement.java
Normal 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);
|
||||
}
|
||||
21
src/de/djledda/lox/Token.java
Normal file
21
src/de/djledda/lox/Token.java
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
52
src/de/djledda/lox/TokenType.java
Normal file
52
src/de/djledda/lox/TokenType.java
Normal 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
|
||||
}
|
||||
87
src/de/djledda/tool/GenerateAst.java
Normal file
87
src/de/djledda/tool/GenerateAst.java
Normal 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}");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user