Compare commits
No commits in common. "175309ac7d10ce345fe83ac2c201938c4fa09835" and "2d9d56be7655440a3d7ea3337784093c1cbfaf0d" have entirely different histories.
175309ac7d
...
2d9d56be76
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1,2 +0,0 @@
|
||||||
*.class
|
|
||||||
|
|
||||||
|
|
@ -1,6 +1,2 @@
|
||||||
# Cobalt
|
# CobaltScript
|
||||||
Cobalt is a small programming language designed for general purpose and systems level scripting. This project follows very closely to the curriculum from [Crafting Interpreters](http://craftinginterpreters.com/) by Robert Nystrom, and is intended to be a pet project for myself to learn programming language design and interpreter design concepts.
|
A small programming language designed for general purpose scripting and for learning interpreter design
|
||||||
|
|
||||||
Currently, Cobalt follows the same grammer and design structure as [Lox](https://github.com/munificent/craftinginterpreters), the language featured in Nystrom's book. However, I eventually plan to mutate Cobalt from these design patterns and create a unique syntax and structure once I have reached a high enough proficiency.
|
|
||||||
|
|
||||||
This project is a **work in progress**, so some things might be a bit broken. YMMV :)
|
|
||||||
|
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
package cobalt.lang;
|
|
||||||
|
|
||||||
// Creates an unambiguous, if ugly, string representation of AST nodes.
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,91 +0,0 @@
|
||||||
package cobalt.lang;
|
|
||||||
|
|
||||||
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 Cobalt {
|
|
||||||
|
|
||||||
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: cobalt [script]");
|
|
||||||
System.exit(64);
|
|
||||||
} else if (args.length == 1) {
|
|
||||||
runFile(args[0]);
|
|
||||||
} else {
|
|
||||||
System.out.println("Cobalt v0.1.0");
|
|
||||||
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);
|
|
||||||
|
|
||||||
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);
|
|
||||||
List<Stmt> statements = parser.parse();
|
|
||||||
|
|
||||||
if (hadError) return;
|
|
||||||
|
|
||||||
//System.out.println(new AstPrinter().print(expression));
|
|
||||||
interpreter.interpret(statements);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void runtimeError(RuntimeError error) {
|
|
||||||
System.err.println("[line " + error.token.line + "] " + error.getMessage());
|
|
||||||
hadRuntimeError = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
package cobalt.lang;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
class Environment {
|
|
||||||
final Environment enclosing;
|
|
||||||
private final Map<String, Object> values = new HashMap<>();
|
|
||||||
|
|
||||||
|
|
||||||
Environment() {
|
|
||||||
enclosing = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Environment(Environment enclosing) {
|
|
||||||
this.enclosing = enclosing;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Object get(Token name) {
|
|
||||||
if (values.containsKey(name.lexeme)) {
|
|
||||||
return values.get(name.lexeme);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (enclosing != null) return enclosing.get(name);
|
|
||||||
|
|
||||||
throw new RuntimeError(name,
|
|
||||||
"Undefined reference to variable: '" + name.lexeme + "'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
void define(Token name, Object value) {
|
|
||||||
if (!values.containsKey(name.lexeme)) {
|
|
||||||
values.put(name.lexeme, value);
|
|
||||||
} else {
|
|
||||||
throw new RuntimeError(name,
|
|
||||||
"Undefined reference to variable: '" + name.lexeme + "'.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void assign(Token name, Object value) {
|
|
||||||
if (values.containsKey(name.lexeme)) {
|
|
||||||
values.put(name.lexeme, value);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (enclosing != null) {
|
|
||||||
enclosing.assign(name, value);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new RuntimeError(name,
|
|
||||||
"Undefined variable '" + name.lexeme + "'.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,102 +0,0 @@
|
||||||
package cobalt.lang;
|
|
||||||
|
|
||||||
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);
|
|
||||||
R visitVarExpr(Variable expr);
|
|
||||||
R visitAssignExpr(Assign expr);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static class Binary extends Expr {
|
|
||||||
Binary(Expr left, Token operator, Expr right) {
|
|
||||||
this.left = left;
|
|
||||||
this.operator = operator;
|
|
||||||
this.right = right;
|
|
||||||
}
|
|
||||||
|
|
||||||
<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;
|
|
||||||
}
|
|
||||||
|
|
||||||
<R> R accept(Visitor<R> visitor) {
|
|
||||||
return visitor.visitGroupingExpr(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
final Expr expression;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static class Literal extends Expr {
|
|
||||||
Literal(Object value) {
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
<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;
|
|
||||||
}
|
|
||||||
|
|
||||||
<R> R accept(Visitor<R> visitor) {
|
|
||||||
return visitor.visitUnaryExpr(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
final Token operator;
|
|
||||||
final Expr right;
|
|
||||||
}
|
|
||||||
|
|
||||||
static class Variable extends Expr {
|
|
||||||
Variable(Token name, boolean nullable) {
|
|
||||||
this.name = name;
|
|
||||||
this.nullable = nullable;
|
|
||||||
}
|
|
||||||
|
|
||||||
<R> R accept(Visitor<R> visitor) {
|
|
||||||
return visitor.visitVarExpr(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
final Token name;
|
|
||||||
final boolean nullable;
|
|
||||||
}
|
|
||||||
|
|
||||||
static class Assign extends Expr {
|
|
||||||
Assign(Token name, Expr value) {
|
|
||||||
this.name = name;
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
<R> R accept(Visitor<R> visitor) {
|
|
||||||
return visitor.visitAssignExpr(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
final Token name;
|
|
||||||
final Expr value;
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract <R> R accept(Visitor<R> visitor);
|
|
||||||
}
|
|
||||||
|
|
@ -1,250 +0,0 @@
|
||||||
package cobalt.lang;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
class Interpreter implements Expr.Visitor<Object>, Stmt.Visitor<Void> {
|
|
||||||
|
|
||||||
private Environment environment = new Environment();
|
|
||||||
|
|
||||||
void interpret(List<Stmt> statements) {
|
|
||||||
try {
|
|
||||||
for (Stmt statement : statements) {
|
|
||||||
execute(statement);
|
|
||||||
}
|
|
||||||
} catch(RuntimeError error) {
|
|
||||||
Cobalt.runtimeError(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object visitLiteralExpr(Expr.Literal expr) {
|
|
||||||
return expr.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object visitUnaryExpr(Expr.Unary expr) {
|
|
||||||
Object right = evaluate(expr.right);
|
|
||||||
|
|
||||||
switch (expr.operator.type) {
|
|
||||||
case BANG:
|
|
||||||
return !isTruthy(right);
|
|
||||||
case MINUS:
|
|
||||||
return -(double) right;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object visitVarExpr(Expr.Variable expr) {
|
|
||||||
// TODO: Fix analogous variable issues
|
|
||||||
return environment.get(expr.name);
|
|
||||||
// Object var_value = environment.get(expr.name);
|
|
||||||
// if (var_value != null) {
|
|
||||||
// return environment.get(expr.name);
|
|
||||||
// } else {
|
|
||||||
// throw new RuntimeError(expr.name,
|
|
||||||
// "Reference to undefined variable: '" + expr.name + "'.");
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isTruthy(Object object) {
|
|
||||||
if (object == null)
|
|
||||||
return false;
|
|
||||||
if (object instanceof Boolean)
|
|
||||||
return (boolean) object;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object visitGroupingExpr(Expr.Grouping expr) {
|
|
||||||
return evaluate(expr.expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Object evaluate(Expr expr) {
|
|
||||||
return expr.accept(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void execute(Stmt stmt) {
|
|
||||||
stmt.accept(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void executeBlock(List<Stmt> statements,
|
|
||||||
Environment environment) {
|
|
||||||
Environment previous = this.environment;
|
|
||||||
try {
|
|
||||||
this.environment = environment;
|
|
||||||
|
|
||||||
for (Stmt statement : statements) {
|
|
||||||
execute(statement);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
this.environment = previous;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Void visitBlockStmt(Stmt.Block stmt) {
|
|
||||||
executeBlock(stmt.statements, new Environment(environment));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Void visitExpressionStmt(Stmt.Expression stmt) {
|
|
||||||
evaluate(stmt.expression);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Void visitPrintStmt(Stmt.Print stmt) {
|
|
||||||
Object value = evaluate(stmt.expression);
|
|
||||||
System.out.println(stringify(value));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Void visitVarStmt(Stmt.Var stmt) {
|
|
||||||
Object value = null;
|
|
||||||
if (stmt.initializer != null) {
|
|
||||||
value = evaluate(stmt.initializer);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!stmt.nullable && value == null) {
|
|
||||||
throw new RuntimeError(stmt.name, "Assignment of 'nil' to non-nullable type");
|
|
||||||
} else {
|
|
||||||
environment.define(stmt.name, value);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object visitAssignExpr(Expr.Assign expr) {
|
|
||||||
Object value = evaluate(expr.value);
|
|
||||||
environment.assign(expr.name, value);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object visitBinaryExpr(Expr.Binary expr) {
|
|
||||||
Object left = evaluate(expr.left);
|
|
||||||
Object right = evaluate(expr.right);
|
|
||||||
|
|
||||||
switch (expr.operator.type) {
|
|
||||||
case GREATER:
|
|
||||||
checkNumberOperands(expr.operator, left, right);
|
|
||||||
return (double) left > (double) right;
|
|
||||||
case LESS:
|
|
||||||
checkNumberOperands(expr.operator, left, right);
|
|
||||||
return (double) left < (double) right;
|
|
||||||
case GREATER_EQUAL:
|
|
||||||
checkNumberOperands(expr.operator, left, right);
|
|
||||||
return (double) left >= (double) right;
|
|
||||||
case LESS_EQUAL:
|
|
||||||
checkNumberOperands(expr.operator, left, right);
|
|
||||||
return (double) left <= (double) right;
|
|
||||||
case BANG_EQUAL:
|
|
||||||
checkNumberOperands(expr.operator, left, right);
|
|
||||||
return !isEqual(left, right);
|
|
||||||
case EQUAL_EQUAL:
|
|
||||||
checkNumberOperands(expr.operator, left, right);
|
|
||||||
return isEqual(left, right);
|
|
||||||
case MINUS:
|
|
||||||
checkNumberOperand(expr.operator, right);
|
|
||||||
return (double) left - (double) right;
|
|
||||||
case PLUS:
|
|
||||||
if (left instanceof Double && right instanceof Double) {
|
|
||||||
return (double) left + (double) right;
|
|
||||||
} else if (left instanceof String && right instanceof String) {
|
|
||||||
return (String) left + (String) right;
|
|
||||||
} else if (left instanceof String && right instanceof Double) {
|
|
||||||
return (String) left + (Double) right;
|
|
||||||
} else if (left instanceof Double && right instanceof String) {
|
|
||||||
return (Double) left + (String) right;
|
|
||||||
} else if ((left == null) && right instanceof String) {
|
|
||||||
return "nil" + (String) right;
|
|
||||||
} else if (left instanceof String && (right == null)) {
|
|
||||||
return (String) left + "nil";
|
|
||||||
}
|
|
||||||
throw new RuntimeError(expr.operator, "Invalid operands: '" + left + "' and '" + right + "'");
|
|
||||||
case SLASH:
|
|
||||||
checkNumberOperands(expr.operator, left, right);
|
|
||||||
checkNumberOperand(expr.operator, right);
|
|
||||||
return (double) left / (double) right;
|
|
||||||
case STAR:
|
|
||||||
checkNumberOperands(expr.operator, left, right);
|
|
||||||
boolean usingStrings = false;
|
|
||||||
String val = "";
|
|
||||||
double limit = 0;
|
|
||||||
|
|
||||||
if (left instanceof String) {
|
|
||||||
usingStrings = true;
|
|
||||||
val = (String)left;
|
|
||||||
limit = (Double)right;
|
|
||||||
} else if (right instanceof String) {
|
|
||||||
usingStrings = true;
|
|
||||||
val = (String)right;
|
|
||||||
limit = (Double)left;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (usingStrings) {
|
|
||||||
String output = "";
|
|
||||||
for (int i = 0; i < limit; i++) {
|
|
||||||
output += val;
|
|
||||||
}
|
|
||||||
return output;
|
|
||||||
} else {
|
|
||||||
return (double) left * (double) right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unreachable
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isEqual(Object a, Object b) {
|
|
||||||
if (a == null && b == null)
|
|
||||||
return true;
|
|
||||||
if (a == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return a.equals(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 void checkNumberOperand(Token operator, Object operand) {
|
|
||||||
if (operand instanceof Double) {
|
|
||||||
if (operator.type == TokenType.SLASH && (double)operand == 0) {
|
|
||||||
throw new RuntimeError(operator, "Division by zero is not allowed");
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
if (left instanceof String && right instanceof Double) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (left instanceof Double && right instanceof String) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new RuntimeError(operator, "Operands must be numbers");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,315 +0,0 @@
|
||||||
package cobalt.lang;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import cobalt.lang.Expr.Variable;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import static cobalt.lang.TokenType.*;
|
|
||||||
|
|
||||||
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<Stmt> parse() {
|
|
||||||
List<Stmt> statements = new ArrayList<>();
|
|
||||||
while (!isAtEnd()) {
|
|
||||||
statements.add(declaration());
|
|
||||||
}
|
|
||||||
|
|
||||||
return statements;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private Expr expression() {
|
|
||||||
return assignment();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private Stmt declaration() {
|
|
||||||
try {
|
|
||||||
if (match(VAR)) return varDeclaration();
|
|
||||||
return statement();
|
|
||||||
} catch (ParseError error) {
|
|
||||||
synchronize();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private Stmt statement() {
|
|
||||||
if (match(PRINT)) return printStatement();
|
|
||||||
if (match(LEFT_BRACE)) return new Stmt.Block(block());
|
|
||||||
|
|
||||||
return expressionStatement();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private Stmt printStatement() {
|
|
||||||
Expr value = expression();
|
|
||||||
consume(SEMICOLON, "Expected ';' after value.");
|
|
||||||
return new Stmt.Print(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private Stmt varDeclaration() {
|
|
||||||
Token name = consume(IDENTIFIER, "Expected variable name.");
|
|
||||||
|
|
||||||
Expr initializer = null;
|
|
||||||
boolean nullable = false;
|
|
||||||
|
|
||||||
if (match(NULLABLE)) {
|
|
||||||
nullable = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (match(EQUAL)) {
|
|
||||||
initializer = expression();
|
|
||||||
}
|
|
||||||
|
|
||||||
consume(SEMICOLON, "Expected ';' after variable declaration.");
|
|
||||||
|
|
||||||
return new Stmt.Var(name, nullable, initializer);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private Stmt expressionStatement() {
|
|
||||||
Expr expr = expression();
|
|
||||||
consume(SEMICOLON, "Expected ';' after value.");
|
|
||||||
return new Stmt.Expression(expr);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private List<Stmt> block() {
|
|
||||||
List<Stmt> statements = new ArrayList<>();
|
|
||||||
|
|
||||||
while (!check(RIGHT_BRACE) && !isAtEnd()) {
|
|
||||||
statements.add(declaration());
|
|
||||||
}
|
|
||||||
|
|
||||||
consume(RIGHT_BRACE, "Expected '}' after block.");
|
|
||||||
return statements;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private Expr assignment() {
|
|
||||||
Expr expr = equality();
|
|
||||||
|
|
||||||
if (match(EQUAL)) {
|
|
||||||
Token equals = previous();
|
|
||||||
Expr value = assignment();
|
|
||||||
|
|
||||||
if (expr instanceof Expr.Variable) {
|
|
||||||
Variable var = (Expr.Variable) expr;
|
|
||||||
Token name = var.name;
|
|
||||||
if (!var.nullable && value == null) {
|
|
||||||
error(equals, "Invalid assignment of 'nil' on non-nullable type");
|
|
||||||
} else {
|
|
||||||
return new Expr.Assign(name, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
error(equals, "Invalid assignment on non-variable type");
|
|
||||||
}
|
|
||||||
|
|
||||||
return expr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parser definition for handling equalities
|
|
||||||
//
|
|
||||||
// equality -> comparison ( ( "!=" | "==" ) comparison )* ;
|
|
||||||
//
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Parser definition for handling comparisons
|
|
||||||
//
|
|
||||||
// comparison -> term ( ( ">" | ">=" | "<" | "<=" ) term )* ;
|
|
||||||
//
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Parser definition for handling unary statements
|
|
||||||
//
|
|
||||||
// unary -> ( "!" | "-" ) unary
|
|
||||||
// | primary ;
|
|
||||||
//
|
|
||||||
private Expr unary() {
|
|
||||||
if (match(BANG, MINUS)) {
|
|
||||||
Token operator = previous();
|
|
||||||
Expr right = unary();
|
|
||||||
return new Expr.Unary(operator, right);
|
|
||||||
}
|
|
||||||
|
|
||||||
return primary();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Parser definition for handling primary statements
|
|
||||||
//
|
|
||||||
// primary -> NUMBER | STRING | "true" | "false" | "nil"
|
|
||||||
// | "(" expression ")" ;
|
|
||||||
//
|
|
||||||
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(IDENTIFIER)) {
|
|
||||||
if (match(NULLABLE)) {
|
|
||||||
return new Expr.Variable(previous(), true);
|
|
||||||
} else {
|
|
||||||
return new Expr.Variable(previous(), false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (match(LEFT_PAREN)) {
|
|
||||||
Expr expr = expression();
|
|
||||||
consume(RIGHT_PAREN, "Expect ')' after expression.");
|
|
||||||
return new Expr.Grouping(expr);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw error(peek(), "Expected expression.");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Checks to see if the current token has any of the given types. If so,
|
|
||||||
// it consumes the token and returns true. Otherwise, it returns false and leaves
|
|
||||||
// the current token alone.
|
|
||||||
private boolean match(TokenType... types) {
|
|
||||||
for (TokenType type : types) {
|
|
||||||
if (check(type)) {
|
|
||||||
advance();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Consume function
|
|
||||||
private Token consume(TokenType type, String message) {
|
|
||||||
if (check(type)) return advance();
|
|
||||||
|
|
||||||
throw error(peek(), message);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Returns true if the current token is of the given type, false if otherwise
|
|
||||||
private boolean check(TokenType type) {
|
|
||||||
if (isAtEnd()) return false;
|
|
||||||
return peek().type == type;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Consume the current token and return it
|
|
||||||
private Token advance() {
|
|
||||||
if (!isAtEnd()) current++;
|
|
||||||
return previous();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Check to see if we've run out of tokens to parse
|
|
||||||
private boolean isAtEnd() {
|
|
||||||
return peek().type == EOF;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Return the current token we have yet to consume
|
|
||||||
private Token peek() {
|
|
||||||
return tokens.get(current);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Return the previous token we just consumed
|
|
||||||
private Token previous() {
|
|
||||||
return tokens.get(current - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Throw a new parse error based on the incorrect token
|
|
||||||
private ParseError error(Token token, String message) {
|
|
||||||
Cobalt.error(token, message);
|
|
||||||
return new ParseError();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Once finding a parse error, discard the rest of the tokens in
|
|
||||||
// a statement in order to prevent cascading errors.
|
|
||||||
private void synchronize() {
|
|
||||||
advance();
|
|
||||||
while (!isAtEnd()) {
|
|
||||||
if (previous().type == SEMICOLON) return;
|
|
||||||
|
|
||||||
switch (peek().type) {
|
|
||||||
case CLASS:
|
|
||||||
case FOR:
|
|
||||||
case FUNC:
|
|
||||||
case IF:
|
|
||||||
case PRINT:
|
|
||||||
case RETURN:
|
|
||||||
case VAR:
|
|
||||||
case WHILE:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
advance();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
package cobalt.lang;
|
|
||||||
|
|
||||||
class RuntimeError extends RuntimeException {
|
|
||||||
final Token token;
|
|
||||||
|
|
||||||
RuntimeError(Token token, String message) {
|
|
||||||
super(message);
|
|
||||||
this.token = token;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,318 +0,0 @@
|
||||||
package cobalt.lang;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import static cobalt.lang.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 = 0;
|
|
||||||
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("func", FUNC);
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Constructor
|
|
||||||
// Set our source string to be the incoming character data from the
|
|
||||||
// input script
|
|
||||||
Scanner(String source) {
|
|
||||||
this.source = source;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scan the input file for all available tokens, return a Token list with all
|
|
||||||
// of our valid tokens
|
|
||||||
List<Token> scanTokens() {
|
|
||||||
while (!isAtEnd()) {
|
|
||||||
start = current;
|
|
||||||
scanToken();
|
|
||||||
}
|
|
||||||
|
|
||||||
tokens.add(new Token(EOF, "", null, line));
|
|
||||||
return tokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check to see if we have reached the end of the script
|
|
||||||
private boolean isAtEnd() {
|
|
||||||
return current >= source.length();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the current token from the scanner to see if its a valid
|
|
||||||
// lexeme. Report an error otherwise
|
|
||||||
private void scanToken() {
|
|
||||||
char c = advance();
|
|
||||||
switch (c) {
|
|
||||||
// Structural and Accessors
|
|
||||||
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(SEMICOLON); break;
|
|
||||||
|
|
||||||
// Operators
|
|
||||||
case '*': addToken(STAR); break;
|
|
||||||
case '-': addToken(MINUS); break;
|
|
||||||
case '+': addToken(PLUS); 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 '/': addToken(SLASH); break;
|
|
||||||
case '?': addToken(NULLABLE); break;
|
|
||||||
|
|
||||||
// Comments
|
|
||||||
case '#':
|
|
||||||
// A comment goes until the end of the line
|
|
||||||
while (peek() != '\n' && !isAtEnd()) advance();
|
|
||||||
break;
|
|
||||||
|
|
||||||
|
|
||||||
// Whitespace and new lines
|
|
||||||
case ' ':
|
|
||||||
case '\r':
|
|
||||||
case '\t':
|
|
||||||
//ignore whitespace characters
|
|
||||||
break;
|
|
||||||
case '\n':
|
|
||||||
line++;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '"':
|
|
||||||
string();
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
if (isDigit(c)) {
|
|
||||||
// Check to see if our incoming value is part of a number
|
|
||||||
// Switch on base prefix (hex, binary, base10...)
|
|
||||||
switch (peek()) {
|
|
||||||
case 'x':
|
|
||||||
advance(); // Advance to disregard the '0x' prefix
|
|
||||||
hex();
|
|
||||||
break;
|
|
||||||
case 'b':
|
|
||||||
advance(); // Advance to disregard the '0b' prefix
|
|
||||||
binary();
|
|
||||||
break;
|
|
||||||
case 'o':
|
|
||||||
advance(); // Advance to disregard the '0o' prefix
|
|
||||||
octal();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
number();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else if (isAlpha(c)) {
|
|
||||||
// Check to see if our incoming value is part of
|
|
||||||
// a reserved word or identifier
|
|
||||||
identifier();
|
|
||||||
} else {
|
|
||||||
Cobalt.error(line, "Unexpected character.");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Determine if the char is a base 10 digit
|
|
||||||
private boolean isDigit(char c) {
|
|
||||||
return c >= '0' && c <= '9';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Determine if the character is a valid base 16 digit
|
|
||||||
private boolean isHex(char c) {
|
|
||||||
return (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Determine if the character is a valid base 16 digit
|
|
||||||
private boolean isBinary(char c) {
|
|
||||||
return (c >= '0' && c <= '1');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Determine if the character is a valid base 8 digit
|
|
||||||
private boolean isOctal(char c) {
|
|
||||||
return (c >= '0' && c <= '7');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Parse and tokenize the value as a base 10 number
|
|
||||||
private void number() {
|
|
||||||
while (isDigit(peek())) advance();
|
|
||||||
|
|
||||||
// Look for a decimal place.
|
|
||||||
if (peek() == '.' && isDigit(peekNext())) {
|
|
||||||
// Consume the .
|
|
||||||
advance();
|
|
||||||
|
|
||||||
while (isDigit(peek())) advance();
|
|
||||||
}
|
|
||||||
|
|
||||||
addToken(NUMBER, Double.parseDouble(source.substring(start, current)));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Parse and tokenize the value as a hexadecimal number, store in base 10
|
|
||||||
private void hex() {
|
|
||||||
while (isDigit(peek()) || isHex(peek())) advance();
|
|
||||||
|
|
||||||
addToken(NUMBER, Long.parseLong(source.substring(start + 2, current), 16));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Parse and tokenize the value as a binary number, store in base 10
|
|
||||||
private void binary() {
|
|
||||||
while (isBinary(peek())) advance();
|
|
||||||
|
|
||||||
addToken(NUMBER, Long.parseLong(source.substring(start + 2, current), 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Parse and tokenize the value as an octal number, store in base 10
|
|
||||||
private void octal() {
|
|
||||||
while (isOctal(peek())) advance();
|
|
||||||
|
|
||||||
addToken(NUMBER, Long.parseLong(source.substring(start + 2, current), 8));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// TODO: Lox spec supports multiline strings, we'll need to
|
|
||||||
// probably remove that support since I don't intend Cobalt's
|
|
||||||
// grammar to support that (maybe) :/
|
|
||||||
|
|
||||||
// TODO: Escape sequences are not supported atm, for the
|
|
||||||
// love of god please implement this functionality. Probably
|
|
||||||
// should make an enum for the valid escape sequences, parse them
|
|
||||||
// out like we do with operators, and inject the actual escape
|
|
||||||
// sequence in the object thats returned to the interpreter
|
|
||||||
|
|
||||||
// Process the input line if quotation marks are found
|
|
||||||
// and we have a string literal
|
|
||||||
private void string() {
|
|
||||||
while (peek() != '"' && !isAtEnd()) {
|
|
||||||
if (peek() == '\n') line++;
|
|
||||||
advance();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isAtEnd()) {
|
|
||||||
Cobalt.error(line, "Unterminated string.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get closing quotes
|
|
||||||
advance();
|
|
||||||
|
|
||||||
// Trim the
|
|
||||||
String value = source.substring(start + 1, current - 1);
|
|
||||||
addToken(STRING, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Determine if the infoming token is alphanumeric, and
|
|
||||||
// add it to the Token list if it is valid
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Checkout the next character in our input, but dont consume it
|
|
||||||
// This is mainly to process things like comments that take an entire line
|
|
||||||
private char peek() {
|
|
||||||
if (isAtEnd()) return '\0';
|
|
||||||
return source.charAt(current);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Checkout the next+1 character in our input, but dont consume it
|
|
||||||
// This is mainly to process things like comments that take an entire line
|
|
||||||
private char peekNext() {
|
|
||||||
if (current + 1 >= source.length()) return '\0';
|
|
||||||
return source.charAt(current + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Check to see if the character passed is within
|
|
||||||
// [a-z][A-Z]
|
|
||||||
private boolean isAlpha(char c) {
|
|
||||||
return (c >= 'a' && c <= 'z') ||
|
|
||||||
(c >= 'A' && c <= 'Z') ||
|
|
||||||
c == '_';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Check to see if the character passed is within
|
|
||||||
// [a-z][A-Z][0-9]
|
|
||||||
private boolean isAlphaNumeric(char c) {
|
|
||||||
return isAlpha(c) || isDigit(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Return a boolean based on if a char is found at the current cursor,
|
|
||||||
// then increment
|
|
||||||
private boolean match(char expected) {
|
|
||||||
if (isAtEnd()) return false;
|
|
||||||
if (source.charAt(current) != expected) return false;
|
|
||||||
|
|
||||||
current++;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Advance the char pointer in the line scanner
|
|
||||||
private char advance() {
|
|
||||||
return source.charAt(current++);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Add a token to the token List that does not have an object literal
|
|
||||||
// associated with it.
|
|
||||||
private void addToken(TokenType type) {
|
|
||||||
addToken(type, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Add a token to the token List that has an object associated with it
|
|
||||||
private void addToken(TokenType type, Object literal) {
|
|
||||||
String text = source.substring(start, current);
|
|
||||||
tokens.add(new Token(type, text, literal, line));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
package cobalt.lang;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
abstract class Stmt {
|
|
||||||
interface Visitor<R> {
|
|
||||||
R visitBlockStmt(Block stmt);
|
|
||||||
R visitExpressionStmt(Expression stmt);
|
|
||||||
R visitPrintStmt(Print stmt);
|
|
||||||
R visitVarStmt(Var stmt);
|
|
||||||
}
|
|
||||||
|
|
||||||
static class Block extends Stmt {
|
|
||||||
Block(List<Stmt> statements) {
|
|
||||||
this.statements = statements;
|
|
||||||
}
|
|
||||||
|
|
||||||
<R> R accept(Visitor<R> visitor) {
|
|
||||||
return visitor.visitBlockStmt(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<Stmt> statements;
|
|
||||||
}
|
|
||||||
|
|
||||||
static class Expression extends Stmt {
|
|
||||||
Expression(Expr expression) {
|
|
||||||
this.expression = expression;
|
|
||||||
}
|
|
||||||
|
|
||||||
<R> R accept(Visitor<R> visitor) {
|
|
||||||
return visitor.visitExpressionStmt(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
final Expr expression;
|
|
||||||
}
|
|
||||||
static class Print extends Stmt {
|
|
||||||
Print(Expr expression) {
|
|
||||||
this.expression = expression;
|
|
||||||
}
|
|
||||||
|
|
||||||
<R> R accept(Visitor<R> visitor) {
|
|
||||||
return visitor.visitPrintStmt(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
final Expr expression;
|
|
||||||
}
|
|
||||||
|
|
||||||
static class Var extends Stmt {
|
|
||||||
Var(Token name, boolean nullable, Expr initializer) {
|
|
||||||
this.name = name;
|
|
||||||
this.nullable = nullable;
|
|
||||||
this.initializer = initializer;
|
|
||||||
}
|
|
||||||
|
|
||||||
<R> R accept(Visitor<R> visitor) {
|
|
||||||
return visitor.visitVarStmt(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
final Token name;
|
|
||||||
final boolean nullable;
|
|
||||||
final Expr initializer;
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract <R> R accept(Visitor<R> visitor);
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
package cobalt.lang;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
package cobalt.lang;
|
|
||||||
|
|
||||||
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, NULLABLE,
|
|
||||||
|
|
||||||
// Literals
|
|
||||||
IDENTIFIER, STRING, NUMBER,
|
|
||||||
|
|
||||||
// Keywords
|
|
||||||
AND, CLASS, ELSE, FALSE, FUNC, FOR, IF, NIL, OR,
|
|
||||||
PRINT, RETURN, SUPER, THIS, TRUE, VAR, WHILE,
|
|
||||||
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
@ -1,101 +0,0 @@
|
||||||
package cobalt.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(1);
|
|
||||||
}
|
|
||||||
String outputDir = args[0];
|
|
||||||
|
|
||||||
defineAst(outputDir, "Expr", Arrays.asList(
|
|
||||||
"Assign : Token name, Expr value",
|
|
||||||
"Binary : Expr left, Token operator, Expr right",
|
|
||||||
"Grouping : Expr expression",
|
|
||||||
"Literal : Object value",
|
|
||||||
"Variable : Token Name",
|
|
||||||
"Unary : Token operator, Expr right",
|
|
||||||
"Variable : Token name"
|
|
||||||
));
|
|
||||||
|
|
||||||
defineAst(outputDir, "Stmt", Arrays.asList(
|
|
||||||
"Block : List<Stmt> statements",
|
|
||||||
"Expression : Expr expression",
|
|
||||||
"Print : Expr expression",
|
|
||||||
"Var : Token name, Expr initializer"
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
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 com.craftinginterpreters.lox;");
|
|
||||||
writer.println("");
|
|
||||||
writer.println("import java.util.List;");
|
|
||||||
writer.println("");
|
|
||||||
writer.println("abstract class " + baseName + " {");
|
|
||||||
|
|
||||||
defineVisitor(writer, baseName, types);
|
|
||||||
|
|
||||||
// The AST classes.
|
|
||||||
for (String type : types) {
|
|
||||||
String className = type.split(":")[0].trim();
|
|
||||||
String fields = type.split(":")[1].trim();
|
|
||||||
defineType(writer, baseName, className, fields);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The base accept() method.
|
|
||||||
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 + " {");
|
|
||||||
|
|
||||||
// Constructor.
|
|
||||||
writer.println(" " + className + "(" + fieldList + ") {");
|
|
||||||
|
|
||||||
// Store parameters in fields.
|
|
||||||
String[] fields = fieldList.split(", ");
|
|
||||||
for (String field : fields) {
|
|
||||||
String name = field.split(" ")[1];
|
|
||||||
writer.println(" this." + name + " = " + name + ";");
|
|
||||||
}
|
|
||||||
|
|
||||||
writer.println(" }");
|
|
||||||
|
|
||||||
// Visitor pattern.
|
|
||||||
writer.println();
|
|
||||||
writer.println(" <R> R accept(Visitor<R> visitor) {");
|
|
||||||
writer.println(" return visitor.visit" + className + baseName + "(this);");
|
|
||||||
writer.println(" }");
|
|
||||||
|
|
||||||
// Fields.
|
|
||||||
writer.println();
|
|
||||||
for (String field : fields) {
|
|
||||||
writer.println(" final " + field + ";");
|
|
||||||
}
|
|
||||||
|
|
||||||
writer.println(" }");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
3
compile
Executable file
3
compile
Executable file
|
|
@ -0,0 +1,3 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
g++ parser.cpp lexer.cpp main.cpp -o main
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
# Statement tests
|
|
||||||
print "These are statement tests for Cobalt!";
|
|
||||||
print 10 + 12;
|
|
||||||
print 13 + (14 - 7) * 3;
|
|
||||||
print "Hello, " + "World!";
|
|
||||||
print "String " + "Multiplication " * 3;
|
|
||||||
|
|
||||||
# Variable declaration
|
|
||||||
var a = 10;
|
|
||||||
print ("a is " + a);
|
|
||||||
|
|
||||||
# Nullable variable declaration
|
|
||||||
var b? = nil;
|
|
||||||
print ("b is " + b);
|
|
||||||
|
|
||||||
# Examples of structured block statements with scoping
|
|
||||||
{
|
|
||||||
print "we are now in a nested scope!";
|
|
||||||
|
|
||||||
# Don't really like this behaviour, as I think it should throw a runtime error
|
|
||||||
# Instead it pulls a's state of 10 in the upper environment block
|
|
||||||
# Still trying to resolve this...
|
|
||||||
var a = a + 5;
|
|
||||||
print ("a is " + a);
|
|
||||||
}
|
|
||||||
14
include/lexer.h
Normal file
14
include/lexer.h
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#ifndef LEXER_H
|
||||||
|
#define LEXER_H
|
||||||
|
|
||||||
|
class Lexer
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
std::string extension = ".prog";
|
||||||
|
public:
|
||||||
|
Lexer(std::string &file_name);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
14
include/parser.h
Normal file
14
include/parser.h
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#ifndef PARSER_H
|
||||||
|
#define PARSER_H
|
||||||
|
|
||||||
|
class Parser
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
|
||||||
|
public:
|
||||||
|
Parser(std::string input_file);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
0
include/token.h
Normal file
0
include/token.h
Normal file
|
|
@ -1,75 +0,0 @@
|
||||||
using cobalt.std;
|
|
||||||
using cobalt.math;
|
|
||||||
|
|
||||||
// Comments
|
|
||||||
|
|
||||||
/*
|
|
||||||
Block Comments
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class MyProgram {
|
|
||||||
|
|
||||||
// *** Instance Variables ***
|
|
||||||
|
|
||||||
// (Option 1)
|
|
||||||
// Since mutable variables are frequently used, you should have to specify if the variable is intended
|
|
||||||
// to be immutable/constant
|
|
||||||
|
|
||||||
let x: int = 0; // Mutable type
|
|
||||||
let y: const int = 0; // Immutable type
|
|
||||||
|
|
||||||
|
|
||||||
// (Option 2)
|
|
||||||
// Assume all variables are immutable, and only allow them to be mutable if specified. Helpful in compiled, memory
|
|
||||||
// safe languages, but probably not for an interpreted language that sits in memory?
|
|
||||||
|
|
||||||
let mut x: int = 0; // Mutable type
|
|
||||||
let y: int = 0; // Immutable type
|
|
||||||
|
|
||||||
|
|
||||||
// *** Main and Declaring functions ***
|
|
||||||
|
|
||||||
// I think scripts should work similar to python, but not be as
|
|
||||||
// funky/verbose with function names
|
|
||||||
|
|
||||||
// If there is no main function defined within the script, the interpreter should
|
|
||||||
// process the file sequentially like Python,
|
|
||||||
// Otherwise, the main function is ran and operates like any normal program
|
|
||||||
|
|
||||||
// Best practice would have main return an integer, however it could be
|
|
||||||
// void or return any other type
|
|
||||||
|
|
||||||
|
|
||||||
// (Option 1, C/C++/Java style)
|
|
||||||
private int main1() {
|
|
||||||
// Some code
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// (Option 2, Swift/Rust style)
|
|
||||||
private func main() => int {
|
|
||||||
// Some code
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// (Option 3, Ada style)
|
|
||||||
private func main() returns int {
|
|
||||||
// Some code
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// *** Handling Multiple Main Methods ***
|
|
||||||
|
|
||||||
// I feel that classes should be able to have their own main methods, and it could be
|
|
||||||
// determined which one is the entry point by requiring the user to provide an entry point
|
|
||||||
//
|
|
||||||
// (Example)
|
|
||||||
// -Multiple classes in one file, each with their own main method
|
|
||||||
// -Specify the script along with the class when opening with Cobalt, and it will run that specific class
|
|
||||||
// -Attempt to run the class's main method. Error if it doesn't exist
|
|
||||||
// -If there are multiple mains and one isn't specific, just error
|
|
||||||
//
|
|
||||||
// Ex: cobalt script.cblt --main MyClass
|
|
||||||
//
|
|
||||||
// This allows for multiple "sub programs" within a single Cobalt script
|
|
||||||
}
|
|
||||||
20
makefile
Normal file
20
makefile
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
|
||||||
|
#OBJS specifies which files to compile as part of the project
|
||||||
|
OBJS = $(wildcard src/*.cpp)
|
||||||
|
|
||||||
|
#CC specifies which compiler we're using
|
||||||
|
CC = g++
|
||||||
|
|
||||||
|
#COMPILER_FLAGS specifies the additional compilation options we're using
|
||||||
|
# -w suppresses all warnings
|
||||||
|
COMPILER_FLAGS = -w
|
||||||
|
|
||||||
|
#LINKER_FLAGS specifies the libraries we're linking against
|
||||||
|
LINKER_FLAGS =
|
||||||
|
|
||||||
|
#OBJ_NAME specifies the name of our exectuable
|
||||||
|
OBJ_NAME = cobalt
|
||||||
|
|
||||||
|
#This is the target that compiles our executable
|
||||||
|
all : $(OBJS)
|
||||||
|
$(CC) $(OBJS) $(COMPILER_FLAGS) $(LINKER_FLAGS) -o $(OBJ_NAME)
|
||||||
3
script.prog
Normal file
3
script.prog
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
hello world, this is a lexer test
|
||||||
|
hello world
|
||||||
|
Kanye's new name is Ye
|
||||||
30
src/lexer.cpp
Normal file
30
src/lexer.cpp
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include "../include/lexer.h"
|
||||||
|
|
||||||
|
char c;
|
||||||
|
FILE* input_file;
|
||||||
|
|
||||||
|
Lexer::Lexer(std::string &file_name) {
|
||||||
|
input_file = fopen(file_name.c_str(),"r");
|
||||||
|
if(input_file == nullptr) {
|
||||||
|
std::cout << "[Error] Script " << input_file << " could not be read\n";
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
c = getc(input_file);
|
||||||
|
if (c == EOF) {
|
||||||
|
//do something related to eof here?
|
||||||
|
fclose(input_file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char nextChar() {
|
||||||
|
if (c != EOF) {
|
||||||
|
c = getc(input_file);
|
||||||
|
} else {
|
||||||
|
c = EOF;
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
15
src/main.cpp
Normal file
15
src/main.cpp
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "../include/lexer.h"
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
if (argc > 1) {
|
||||||
|
std::string file_name(argv[1]);
|
||||||
|
Lexer lex(file_name);
|
||||||
|
} else {
|
||||||
|
std::cout << "[Error] No input file provided\n";
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
8
src/parser.cpp
Normal file
8
src/parser.cpp
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "../include/parser.h"
|
||||||
|
|
||||||
|
Parser::Parser(std::string input_file) {
|
||||||
|
|
||||||
|
}
|
||||||
0
src/token.cpp
Normal file
0
src/token.cpp
Normal file
Loading…
Reference in New Issue
Block a user