commit
9def28e2b1
7 changed files with 477 additions and 0 deletions
@ -0,0 +1,41 @@
|
||||
package lox; |
||||
|
||||
abstract class Expr { |
||||
static class Binary extends Expr { |
||||
Binary(Expr left, Token operator, Expr right) { |
||||
this.left = left; |
||||
this.operator = operator; |
||||
this.right = right; |
||||
} |
||||
|
||||
final Expr left; |
||||
final Token operator; |
||||
final Expr right; |
||||
} |
||||
|
||||
static class Grouping extends Expr { |
||||
Grouping(Expr expression) { |
||||
this.expression = expression; |
||||
} |
||||
|
||||
final Expr expression; |
||||
} |
||||
|
||||
static class Literal extends Expr { |
||||
Literal(Object value) { |
||||
this.value = value; |
||||
} |
||||
|
||||
final Object value; |
||||
} |
||||
|
||||
static class Unary extends Expr { |
||||
Unary(Token operator, Expr right) { |
||||
this.operator = operator; |
||||
this.right = right; |
||||
} |
||||
|
||||
final Token operator; |
||||
final Expr right; |
||||
} |
||||
} |
@ -0,0 +1,67 @@
|
||||
package 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 { |
||||
static boolean hadError = 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())); |
||||
|
||||
// whoops
|
||||
if (hadError) { |
||||
System.exit(65); |
||||
} |
||||
} |
||||
|
||||
private static void runPrompt() throws IOException { |
||||
InputStreamReader input = new InputStreamReader(System.in); |
||||
BufferedReader reader = new BufferedReader(input); |
||||
|
||||
for (; ; ) { |
||||
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(); |
||||
|
||||
for (Token token : tokens) { |
||||
System.out.println(token); |
||||
} |
||||
} |
||||
|
||||
static void error(int line, String message) { |
||||
report(line, "", message); |
||||
} |
||||
|
||||
private static void report(int line, String where, String message) { |
||||
System.err.println("[line " + line + "] Error" + where + ": " + message); |
||||
hadError = true; |
||||
} |
||||
} |
@ -0,0 +1,230 @@
|
||||
package lox; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.HashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
import static lox.TokenType.*; |
||||
|
||||
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", AND); |
||||
keywords.put("class", CLASS); |
||||
keywords.put("else", ELSE); |
||||
keywords.put("false", FALSE); |
||||
keywords.put("for", FOR); |
||||
keywords.put("fun", FUN); |
||||
keywords.put("if", IF); |
||||
keywords.put("nil", NIL); |
||||
keywords.put("or", OR); |
||||
keywords.put("print", PRINT); |
||||
keywords.put("return", RETURN); |
||||
keywords.put("super", SUPER); |
||||
keywords.put("this", THIS); |
||||
keywords.put("true", TRUE); |
||||
keywords.put("var", VAR); |
||||
keywords.put("while", 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 void scanToken() { |
||||
char c = advance(); |
||||
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('/')) { |
||||
// comments run to the end of the line
|
||||
while (peek() != '\n' && !isAtEnd()) { |
||||
advance(); |
||||
} |
||||
} else { |
||||
addToken(SLASH); |
||||
} |
||||
break; |
||||
case ' ': |
||||
case '\r': |
||||
case '\t': |
||||
break; // skip whitespace
|
||||
case '\n': |
||||
line++; |
||||
break; // EOL
|
||||
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.getOrDefault(text, IDENTIFIER); |
||||
addToken(type); |
||||
} |
||||
|
||||
private void number() { |
||||
while (isDigit(peek())) { |
||||
advance(); |
||||
} |
||||
|
||||
if (peek() == '.' && isDigit(peekNext())) { |
||||
advance(); |
||||
|
||||
while (isDigit(peek())) { |
||||
advance(); |
||||
} |
||||
} |
||||
|
||||
addToken(NUMBER, Double.parseDouble(source.substring(start, current))); |
||||
} |
||||
|
||||
private void string() { |
||||
while (peek() != '"' && !isAtEnd()) { |
||||
if (peek() == '\n') { |
||||
line++; |
||||
} |
||||
advance(); |
||||
} |
||||
|
||||
if (isAtEnd()) { |
||||
Lox.error(line, "Unterminated string."); |
||||
return; |
||||
} |
||||
|
||||
advance(); |
||||
|
||||
String value = source.substring(start + 1, current - 1); |
||||
addToken(STRING, value); |
||||
} |
||||
|
||||
private boolean match(char expected) { |
||||
if (isAtEnd()) { // ran off the end of the line
|
||||
return false; |
||||
} |
||||
if (source.charAt(current) != expected) { // next char is not our expected extra
|
||||
return false; |
||||
} |
||||
|
||||
// consume second char
|
||||
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 boolean isAtEnd() { |
||||
return current >= source.length(); |
||||
} |
||||
|
||||
private char advance() { |
||||
return source.charAt(current++); |
||||
} |
||||
|
||||
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)); |
||||
} |
||||
} |
@ -0,0 +1,19 @@
|
||||
package lox; |
||||
|
||||
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; |
||||
} |
||||
} |
@ -0,0 +1,47 @@
|
||||
package lox; |
||||
|
||||
enum TokenType { |
||||
LEFT_PAREN, |
||||
RIGHT_PAREN, |
||||
LEFT_BRACE, |
||||
RIGHT_BRACE, |
||||
COMMA, |
||||
DOT, |
||||
MINUS, |
||||
PLUS, |
||||
SEMICOLON, |
||||
SLASH, |
||||
STAR, |
||||
|
||||
BANG, |
||||
BANG_EQUAL, |
||||
EQUAL, |
||||
EQUAL_EQUAL, |
||||
GREATER, |
||||
GREATER_EQUAL, |
||||
LESS, |
||||
LESS_EQUAL, |
||||
|
||||
IDENTIFIER, |
||||
STRING, |
||||
NUMBER, |
||||
|
||||
AND, |
||||
CLASS, |
||||
ELSE, |
||||
FALSE, |
||||
FUN, |
||||
FOR, |
||||
IF, |
||||
NIL, |
||||
OR, |
||||
PRINT, |
||||
RETURN, |
||||
SUPER, |
||||
THIS, |
||||
TRUE, |
||||
VAR, |
||||
WHILE, |
||||
|
||||
EOF |
||||
} |
@ -0,0 +1,69 @@
|
||||
package tool; |
||||
|
||||
import java.io.IOException; |
||||
import java.io.PrintWriter; |
||||
import java.nio.charset.Charset; |
||||
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, |
||||
"Expr", |
||||
Arrays.asList( |
||||
"Binary : Expr left, Token operator, Expr right", |
||||
"Grouping : Expr expression", |
||||
"Literal : Object value", |
||||
"Unary : Token operator, Expr right")); |
||||
} |
||||
|
||||
private static void defineAST(String outputDir, String baseName, List<String> types) |
||||
throws IOException { |
||||
String path = outputDir + "/" + baseName + ".java"; |
||||
PrintWriter writer = new PrintWriter(path, Charset.defaultCharset()); |
||||
|
||||
writer.println("package lox;"); |
||||
writer.println(); |
||||
writer.println("import java.util.List;"); |
||||
writer.println(); |
||||
writer.println("abstract class " + baseName + " {"); |
||||
|
||||
for (String type : types) { |
||||
String className = type.split(":")[0].trim(); |
||||
String fields = type.split(":")[1].trim(); |
||||
defineType(writer, baseName, className, fields); |
||||
} |
||||
|
||||
writer.println("}"); |
||||
writer.close(); |
||||
} |
||||
|
||||
private static void defineType( |
||||
PrintWriter writer, String baseName, String className, String fieldList) { |
||||
writer.println(" static class " + className + " extends " + baseName + " {"); |
||||
|
||||
writer.println(" " + className + "(" + fieldList + ") { "); |
||||
|
||||
String[] fields = fieldList.split(", "); |
||||
for (String field : fields) { |
||||
String name = field.split(" ")[1]; |
||||
writer.println(" this." + name + " = " + name + ";"); |
||||
} |
||||
|
||||
writer.println(" }"); |
||||
writer.println(); |
||||
|
||||
for (String field : fields) { |
||||
writer.println(" final " + field + ";"); |
||||
} |
||||
|
||||
writer.println(" }"); |
||||
writer.println(); |
||||
} |
||||
} |
Loading…
Reference in new issue