Compare commits
10 Commits
2d9d56be76
...
175309ac7d
| Author | SHA1 | Date | |
|---|---|---|---|
| 175309ac7d | |||
| a5ee63fffc | |||
| c8b7214e6f | |||
| 5cd5dda34d | |||
| da88ff4071 | |||
| bc84ec43ce | |||
| 535c557cb4 | |||
| 6d2f1ee907 | |||
| 449ce575a7 | |||
| 8c05ed5835 |
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
*.class
|
||||
|
||||
|
|
@ -1,2 +1,6 @@
|
|||
# CobaltScript
|
||||
A small programming language designed for general purpose scripting and for learning interpreter design
|
||||
# Cobalt
|
||||
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.
|
||||
|
||||
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 :)
|
||||
|
|
|
|||
54
cobalt/lang/AstPrinter.java
Normal file
54
cobalt/lang/AstPrinter.java
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
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));
|
||||
}
|
||||
}
|
||||
91
cobalt/lang/Cobalt.java
Normal file
91
cobalt/lang/Cobalt.java
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
54
cobalt/lang/Environment.java
Normal file
54
cobalt/lang/Environment.java
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
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 + "'.");
|
||||
}
|
||||
}
|
||||
102
cobalt/lang/Expr.java
Normal file
102
cobalt/lang/Expr.java
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
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);
|
||||
}
|
||||
250
cobalt/lang/Interpreter.java
Normal file
250
cobalt/lang/Interpreter.java
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
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");
|
||||
}
|
||||
}
|
||||
315
cobalt/lang/Parser.java
Normal file
315
cobalt/lang/Parser.java
Normal file
|
|
@ -0,0 +1,315 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
10
cobalt/lang/RuntimeError.java
Normal file
10
cobalt/lang/RuntimeError.java
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
package cobalt.lang;
|
||||
|
||||
class RuntimeError extends RuntimeException {
|
||||
final Token token;
|
||||
|
||||
RuntimeError(Token token, String message) {
|
||||
super(message);
|
||||
this.token = token;
|
||||
}
|
||||
}
|
||||
318
cobalt/lang/Scanner.java
Normal file
318
cobalt/lang/Scanner.java
Normal file
|
|
@ -0,0 +1,318 @@
|
|||
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));
|
||||
}
|
||||
}
|
||||
65
cobalt/lang/Stmt.java
Normal file
65
cobalt/lang/Stmt.java
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
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);
|
||||
}
|
||||
19
cobalt/lang/Token.java
Normal file
19
cobalt/lang/Token.java
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
20
cobalt/lang/TokenType.java
Normal file
20
cobalt/lang/TokenType.java
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
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
|
||||
}
|
||||
101
cobalt/tool/GenerateAst.java
Normal file
101
cobalt/tool/GenerateAst.java
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
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(" }");
|
||||
}
|
||||
}
|
||||
25
examples/test.cobalt
Normal file
25
examples/test.cobalt
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# 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);
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
#include <iostream>
|
||||
|
||||
#ifndef LEXER_H
|
||||
#define LEXER_H
|
||||
|
||||
class Lexer
|
||||
{
|
||||
private:
|
||||
std::string extension = ".prog";
|
||||
public:
|
||||
Lexer(std::string &file_name);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
#include <iostream>
|
||||
|
||||
#ifndef PARSER_H
|
||||
#define PARSER_H
|
||||
|
||||
class Parser
|
||||
{
|
||||
private:
|
||||
|
||||
public:
|
||||
Parser(std::string input_file);
|
||||
};
|
||||
|
||||
#endif
|
||||
75
lang_spec.txt
Normal file
75
lang_spec.txt
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
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
20
makefile
|
|
@ -1,20 +0,0 @@
|
|||
|
||||
#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)
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
hello world, this is a lexer test
|
||||
hello world
|
||||
Kanye's new name is Ye
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
#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
15
src/main.cpp
|
|
@ -1,15 +0,0 @@
|
|||
#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;
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
#include <stdio.h>
|
||||
#include <iostream>
|
||||
|
||||
#include "../include/parser.h"
|
||||
|
||||
Parser::Parser(std::string input_file) {
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user