Init
This commit is contained in:
commit
5dac9efd38
2
.clang-format
Normal file
2
.clang-format
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
---
|
||||
BasedOnStyle: GNU
|
||||
57
.classpath
Normal file
57
.classpath
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="optional" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="test" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="test" value="true"/>
|
||||
<attribute name="optional" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" path="target/generated-sources/annotations">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="ignore_optional_problems" value="true"/>
|
||||
<attribute name="m2e-apt" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/test-classes" path="target/generated-test-sources/test-annotations">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="ignore_optional_problems" value="true"/>
|
||||
<attribute name="m2e-apt" value="true"/>
|
||||
<attribute name="test" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
build/
|
||||
target/
|
||||
0
.mvn/jvm.config
Normal file
0
.mvn/jvm.config
Normal file
0
.mvn/maven.config
Normal file
0
.mvn/maven.config
Normal file
34
.project
Normal file
34
.project
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>jlox</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
<filteredResources>
|
||||
<filter>
|
||||
<id>1761899067174</id>
|
||||
<name></name>
|
||||
<type>30</type>
|
||||
<matcher>
|
||||
<id>org.eclipse.core.resources.regexFilterMatcher</id>
|
||||
<arguments>node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
|
||||
</matcher>
|
||||
</filter>
|
||||
</filteredResources>
|
||||
</projectDescription>
|
||||
4
.settings/org.eclipse.core.resources.prefs
Normal file
4
.settings/org.eclipse.core.resources.prefs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
eclipse.preferences.version=1
|
||||
encoding//src/main/java=UTF-8
|
||||
encoding//src/test/java=UTF-8
|
||||
encoding/<project>=UTF-8
|
||||
2
.settings/org.eclipse.jdt.apt.core.prefs
Normal file
2
.settings/org.eclipse.jdt.apt.core.prefs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.apt.aptEnabled=false
|
||||
9
.settings/org.eclipse.jdt.core.prefs
Normal file
9
.settings/org.eclipse.jdt.core.prefs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=17
|
||||
org.eclipse.jdt.core.compiler.compliance=17
|
||||
org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
|
||||
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
||||
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore
|
||||
org.eclipse.jdt.core.compiler.processAnnotations=disabled
|
||||
org.eclipse.jdt.core.compiler.release=enabled
|
||||
org.eclipse.jdt.core.compiler.source=17
|
||||
4
.settings/org.eclipse.m2e.core.prefs
Normal file
4
.settings/org.eclipse.m2e.core.prefs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
activeProfiles=
|
||||
eclipse.preferences.version=1
|
||||
resolveWorkspaceProjects=true
|
||||
version=1
|
||||
16
LICENSE
Normal file
16
LICENSE
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
ISC License
|
||||
|
||||
Copyright (c) 2025 Ashley Rose <ashleyrose@incel.email>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
15
Makefile
Normal file
15
Makefile
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
VERSION=1.0-SNAPSHOT
|
||||
|
||||
jar:
|
||||
mvn package
|
||||
|
||||
linux-build: jar
|
||||
mkdir -p build
|
||||
cat ./stub.sh ./target/jlox-$(VERSION).jar > ./build/jlox
|
||||
chmod +x ./build/jlox
|
||||
|
||||
install: linux-build
|
||||
mv ./build/jlox /usr/local/bin/jlox
|
||||
|
||||
clean:
|
||||
rm -r build target
|
||||
17
README.md
Normal file
17
README.md
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# JLox
|
||||
|
||||
Lox interpreter written in Java.
|
||||
|
||||
## Linux Install
|
||||
Requirements:
|
||||
- JDK 17+
|
||||
- Apache Maven
|
||||
- GNU Make
|
||||
|
||||
```sh
|
||||
\# make install
|
||||
```
|
||||
|
||||
---
|
||||
|
||||

|
||||
BIN
images/isc-logo.png
Normal file
BIN
images/isc-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.8 KiB |
98
pom.xml
Normal file
98
pom.xml
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>me.rose.lox</groupId>
|
||||
<artifactId>jlox</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
|
||||
<name>jlox</name>
|
||||
<!-- FIXME change it to the project's website -->
|
||||
<url>http://www.example.com</url>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.compiler.release>17</maven.compiler.release>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.junit</groupId>
|
||||
<artifactId>junit-bom</artifactId>
|
||||
<version>5.11.0</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- Optionally: parameterized tests support -->
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-params</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
|
||||
<plugins>
|
||||
<!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
|
||||
<plugin>
|
||||
<artifactId>maven-clean-plugin</artifactId>
|
||||
<version>3.4.0</version>
|
||||
</plugin>
|
||||
<!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
|
||||
<plugin>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<version>3.3.1</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.13.0</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.3.0</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.4.2</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<addClasspath>true</addClasspath>
|
||||
<mainClass>me.rose.lox.Lox</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-install-plugin</artifactId>
|
||||
<version>3.1.2</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
<version>3.1.2</version>
|
||||
</plugin>
|
||||
<!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
|
||||
<plugin>
|
||||
<artifactId>maven-site-plugin</artifactId>
|
||||
<version>3.12.1</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-project-info-reports-plugin</artifactId>
|
||||
<version>3.6.1</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
</project>
|
||||
68
src/main/java/me/rose/lox/AstPrinter.java
Normal file
68
src/main/java/me/rose/lox/AstPrinter.java
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
package me.rose.lox;
|
||||
|
||||
class AstPrinter implements Expr.Visitor<String>
|
||||
{
|
||||
String
|
||||
print (Expr expr)
|
||||
{
|
||||
return expr.accept (this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String
|
||||
visitBinaryExpr (Expr.Binary expr)
|
||||
{
|
||||
return parenthesize (expr.operator.lexeme, expr.left, expr.right);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String
|
||||
visitGroupingExpr (Expr.Grouping expr)
|
||||
{
|
||||
return parenthesize ("group", expr.expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String
|
||||
visitLiteralExpr (Expr.Literal expr)
|
||||
{
|
||||
if (expr.value == null)
|
||||
return "nil";
|
||||
return expr.value.toString ();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String
|
||||
visitUnaryExpr (Expr.Unary expr)
|
||||
{
|
||||
return parenthesize (expr.operator.lexeme, expr.right);
|
||||
}
|
||||
|
||||
private String
|
||||
parenthesize (String name, Expr... exprs)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder ();
|
||||
|
||||
builder.append ("(").append (name);
|
||||
for (Expr expr : exprs)
|
||||
{
|
||||
builder.append (" ");
|
||||
builder.append (expr.accept (this));
|
||||
}
|
||||
builder.append (")");
|
||||
|
||||
return builder.toString ();
|
||||
}
|
||||
|
||||
public static void
|
||||
main (String[] args)
|
||||
{
|
||||
Expr expression = new Expr.Binary (
|
||||
new Expr.Unary (new Token (TokenType.MINUS, "-", null, 1),
|
||||
new Expr.Literal (123)),
|
||||
new Token (TokenType.STAR, "*", null, 1),
|
||||
new Expr.Grouping (new Expr.Literal (45.67)));
|
||||
|
||||
System.out.println (new AstPrinter ().print (expression));
|
||||
}
|
||||
}
|
||||
68
src/main/java/me/rose/lox/Expr.java
Normal file
68
src/main/java/me/rose/lox/Expr.java
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
package me.rose.lox;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
abstract class Expr {
|
||||
interface Visitor<R> {
|
||||
R visitBinaryExpr(Binary expr);
|
||||
R visitGroupingExpr(Grouping expr);
|
||||
R visitLiteralExpr(Literal expr);
|
||||
R visitUnaryExpr(Unary expr);
|
||||
}
|
||||
static class Binary extends Expr {
|
||||
Binary(Expr left, Token operator, Expr right) {
|
||||
this.left = left;
|
||||
this.operator = operator;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
@Override
|
||||
<R> R accept(Visitor<R> visitor) {
|
||||
return visitor.visitBinaryExpr(this);
|
||||
}
|
||||
|
||||
final Expr left;
|
||||
final Token operator;
|
||||
final Expr right;
|
||||
}
|
||||
static class Grouping extends Expr {
|
||||
Grouping(Expr expression) {
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
@Override
|
||||
<R> R accept(Visitor<R> visitor) {
|
||||
return visitor.visitGroupingExpr(this);
|
||||
}
|
||||
|
||||
final Expr expression;
|
||||
}
|
||||
static class Literal extends Expr {
|
||||
Literal(Object value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
<R> R accept(Visitor<R> visitor) {
|
||||
return visitor.visitLiteralExpr(this);
|
||||
}
|
||||
|
||||
final Object value;
|
||||
}
|
||||
static class Unary extends Expr {
|
||||
Unary(Token operator, Expr right) {
|
||||
this.operator = operator;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
@Override
|
||||
<R> R accept(Visitor<R> visitor) {
|
||||
return visitor.visitUnaryExpr(this);
|
||||
}
|
||||
|
||||
final Token operator;
|
||||
final Expr right;
|
||||
}
|
||||
|
||||
abstract <R> R accept(Visitor<R> visitor);
|
||||
}
|
||||
97
src/main/java/me/rose/lox/Lox.java
Normal file
97
src/main/java/me/rose/lox/Lox.java
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
package me.rose.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 ()));
|
||||
|
||||
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 ();
|
||||
Parser parser = new Parser (tokens);
|
||||
Expr expression = parser.parse ();
|
||||
|
||||
if (hadError)
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
226
src/main/java/me/rose/lox/Parser.java
Normal file
226
src/main/java/me/rose/lox/Parser.java
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
package me.rose.lox;
|
||||
|
||||
import static me.rose.lox.TokenType.*;
|
||||
|
||||
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; }
|
||||
|
||||
Expr
|
||||
parse ()
|
||||
{
|
||||
try
|
||||
{
|
||||
return expression ();
|
||||
}
|
||||
catch (ParseError error)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Expr
|
||||
expression ()
|
||||
{
|
||||
return equality ();
|
||||
}
|
||||
|
||||
private Expr
|
||||
equality ()
|
||||
{
|
||||
Expr expr = comparison ();
|
||||
|
||||
while (match (BANG_EQUAL, EQUAL_EQUAL))
|
||||
{
|
||||
Token operator = previous ();
|
||||
Expr right = comparison ();
|
||||
expr = new Expr.Binary (expr, operator, right);
|
||||
}
|
||||
|
||||
return expr;
|
||||
}
|
||||
|
||||
private Expr
|
||||
comparison ()
|
||||
{
|
||||
Expr expr = term ();
|
||||
|
||||
while (match (GREATER, GREATER_EQUAL, LESS, LESS_EQUAL))
|
||||
{
|
||||
Token operator = previous ();
|
||||
Expr right = term ();
|
||||
expr = new Expr.Binary (expr, operator, right);
|
||||
}
|
||||
|
||||
return expr;
|
||||
}
|
||||
|
||||
private Expr
|
||||
term ()
|
||||
{
|
||||
Expr expr = factor ();
|
||||
|
||||
while (match (MINUS, PLUS))
|
||||
{
|
||||
Token operator = previous ();
|
||||
Expr right = factor ();
|
||||
expr = new Expr.Binary (expr, operator, right);
|
||||
}
|
||||
|
||||
return expr;
|
||||
}
|
||||
|
||||
private Expr
|
||||
factor ()
|
||||
{
|
||||
Expr expr = unary ();
|
||||
|
||||
while (match (SLASH, STAR))
|
||||
{
|
||||
Token operator = previous ();
|
||||
Expr right = unary ();
|
||||
expr = new Expr.Binary (expr, operator, right);
|
||||
}
|
||||
|
||||
return expr;
|
||||
}
|
||||
|
||||
private Expr
|
||||
unary ()
|
||||
{
|
||||
if (match (BANG, MINUS))
|
||||
{
|
||||
Token operator = previous ();
|
||||
Expr right = unary ();
|
||||
return new Expr.Unary (operator, right);
|
||||
}
|
||||
|
||||
return primary ();
|
||||
}
|
||||
|
||||
private Expr
|
||||
primary ()
|
||||
{
|
||||
if (match (FALSE))
|
||||
return new Expr.Literal (false);
|
||||
if (match (TRUE))
|
||||
return new Expr.Literal (true);
|
||||
if (match (NIL))
|
||||
return new Expr.Literal (null);
|
||||
|
||||
if (match (NUMBER, STRING))
|
||||
{
|
||||
return new Expr.Literal (previous ().literal);
|
||||
}
|
||||
|
||||
if (match (LEFT_PAREN))
|
||||
{
|
||||
Expr expr = expression ();
|
||||
consume (RIGHT_PAREN, "Expect ')' after expression.");
|
||||
return new Expr.Grouping (expr);
|
||||
}
|
||||
|
||||
throw error (peek (), "Expect expression.");
|
||||
}
|
||||
|
||||
private boolean
|
||||
match (TokenType... types)
|
||||
{
|
||||
for (TokenType type : types)
|
||||
{
|
||||
if (check (type))
|
||||
{
|
||||
advance ();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private Token
|
||||
consume (TokenType type, String message)
|
||||
{
|
||||
if (check (type))
|
||||
return advance ();
|
||||
|
||||
throw error (peek (), message);
|
||||
}
|
||||
|
||||
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 == EOF;
|
||||
}
|
||||
|
||||
private Token
|
||||
peek ()
|
||||
{
|
||||
return tokens.get (current);
|
||||
}
|
||||
|
||||
private Token
|
||||
previous ()
|
||||
{
|
||||
return tokens.get (current - 1);
|
||||
}
|
||||
|
||||
private ParseError
|
||||
error (Token token, String message)
|
||||
{
|
||||
Lox.error (token, message);
|
||||
return new ParseError ();
|
||||
}
|
||||
|
||||
private void
|
||||
synchronize ()
|
||||
{
|
||||
advance ();
|
||||
|
||||
while (!isAtEnd ())
|
||||
{
|
||||
if (previous ().type == SEMICOLON)
|
||||
return;
|
||||
|
||||
switch (peek ().type)
|
||||
{
|
||||
case CLASS:
|
||||
case FUN:
|
||||
case VAR:
|
||||
case FOR:
|
||||
case IF:
|
||||
case WHILE:
|
||||
case PRINT:
|
||||
case RETURN:
|
||||
return;
|
||||
}
|
||||
|
||||
advance ();
|
||||
}
|
||||
}
|
||||
}
|
||||
268
src/main/java/me/rose/lox/Scanner.java
Normal file
268
src/main/java/me/rose/lox/Scanner.java
Normal file
|
|
@ -0,0 +1,268 @@
|
|||
package me.rose.lox;
|
||||
|
||||
import static me.rose.lox.TokenType.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
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 (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 ('/'))
|
||||
{
|
||||
while (peek () != '\n' && !isAtEnd ())
|
||||
advance ();
|
||||
}
|
||||
else
|
||||
{
|
||||
addToken (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 = 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 ())
|
||||
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 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));
|
||||
}
|
||||
}
|
||||
23
src/main/java/me/rose/lox/Token.java
Normal file
23
src/main/java/me/rose/lox/Token.java
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
package me.rose.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;
|
||||
}
|
||||
}
|
||||
48
src/main/java/me/rose/lox/TokenType.java
Normal file
48
src/main/java/me/rose/lox/TokenType.java
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
package me.rose.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
|
||||
}
|
||||
103
src/main/java/me/rose/tool/GenerateAst.java
Normal file
103
src/main/java/me/rose/tool/GenerateAst.java
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
package me.rose.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, "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, "UTF-8");
|
||||
|
||||
writer.println ("package me.rose.lox;");
|
||||
writer.println ();
|
||||
writer.println ("import java.util.List;");
|
||||
writer.println ();
|
||||
writer.println ("abstract class " + baseName + " {");
|
||||
|
||||
defineVisitor (writer, baseName, types);
|
||||
|
||||
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 (" abstract <R> R accept(Visitor<R> visitor);");
|
||||
|
||||
writer.println ("}");
|
||||
writer.close ();
|
||||
}
|
||||
|
||||
private static void
|
||||
defineVisitor (PrintWriter writer, String baseName, List<String> types)
|
||||
{
|
||||
writer.println (" interface Visitor<R> {");
|
||||
|
||||
for (String type : types)
|
||||
{
|
||||
String typeName = type.split (":")[0].trim ();
|
||||
writer.println (" R visit" + typeName + baseName + "(" + typeName
|
||||
+ " " + baseName.toLowerCase () + ");");
|
||||
}
|
||||
|
||||
writer.println (" }");
|
||||
}
|
||||
|
||||
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 ();
|
||||
writer.println (" @Override");
|
||||
writer.println (" <R> R accept(Visitor<R> visitor) {");
|
||||
writer.println (" return visitor.visit" + className + baseName
|
||||
+ "(this);");
|
||||
writer.println (" }");
|
||||
|
||||
writer.println ();
|
||||
for (String field : fields)
|
||||
{
|
||||
writer.println (" final " + field + ";");
|
||||
}
|
||||
|
||||
writer.println (" }");
|
||||
}
|
||||
}
|
||||
19
src/test/java/me/rose/lox/AppTest.java
Normal file
19
src/test/java/me/rose/lox/AppTest.java
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
package me.rose.lox;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Unit test for simple App.
|
||||
*/
|
||||
public class AppTest {
|
||||
|
||||
/**
|
||||
* Rigorous Test :-)
|
||||
*/
|
||||
@Test
|
||||
public void shouldAnswerWithTrue() {
|
||||
assertTrue(true);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue