From 9a2ab9bb803aa4da2a6007cc2e945d6bc1fe0b39 Mon Sep 17 00:00:00 2001 From: dibyendumajumdar Date: Fri, 25 Aug 2023 23:16:07 +0100 Subject: Reorganized the project as multi-maven module Updated README Added language module with grammar Split the code into common and chapter01 modules. Removed a number of utility classes that were imported from AA. Added github workflow file. --- chapter01/README.md | 0 chapter01/docs/01-grammar.md | 0 chapter01/pom.xml | 22 ++ .../java/com/seaofnodes/simple/parser/Ast.java | 132 +++++++++++ .../java/com/seaofnodes/simple/parser/Parser.java | 259 +++++++++++++++++++++ .../com/seaofnodes/simple/lexer/TestParser.java | 68 ++++++ 6 files changed, 481 insertions(+) create mode 100644 chapter01/README.md create mode 100644 chapter01/docs/01-grammar.md create mode 100644 chapter01/pom.xml create mode 100644 chapter01/src/main/java/com/seaofnodes/simple/parser/Ast.java create mode 100644 chapter01/src/main/java/com/seaofnodes/simple/parser/Parser.java create mode 100644 chapter01/src/test/java/com/seaofnodes/simple/lexer/TestParser.java (limited to 'chapter01') diff --git a/chapter01/README.md b/chapter01/README.md new file mode 100644 index 00000000..e69de29b diff --git a/chapter01/docs/01-grammar.md b/chapter01/docs/01-grammar.md new file mode 100644 index 00000000..e69de29b diff --git a/chapter01/pom.xml b/chapter01/pom.xml new file mode 100644 index 00000000..842e67e9 --- /dev/null +++ b/chapter01/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + com.seaofnodes + simple + 1.0 + + chapter01 + jar + Chapter 1 + + + + com.seaofnodes + common + 1.0 + + + \ No newline at end of file diff --git a/chapter01/src/main/java/com/seaofnodes/simple/parser/Ast.java b/chapter01/src/main/java/com/seaofnodes/simple/parser/Ast.java new file mode 100644 index 00000000..a93b230f --- /dev/null +++ b/chapter01/src/main/java/com/seaofnodes/simple/parser/Ast.java @@ -0,0 +1,132 @@ +package com.seaofnodes.simple.parser; + +import com.seaofnodes.simple.common.CompilerException; +import com.seaofnodes.simple.lexer.ecstasy.Token; + +import java.util.ArrayList; +import java.util.List; + +public abstract class Ast { + + public static class Node { + } + + public static class Expr extends Node { + public final Token op; + + public Expr(Token op) { + this.op = op; + } + } + + public static class Identifier extends Expr { + public final String name; + public Identifier(Token op) { + super(op); + name = op.getValueText(); + } + } + + public static class Symbol extends Expr { + public final String name; + public Symbol(Token op) { + super(op); + name = op.getValueText(); + } + } + + public static class Binary extends Expr { + public final Expr expr1, expr2; + + public Binary(Token op, Expr expr1, Expr expr2) { + super(op); + this.expr1 = expr1; + this.expr2 = expr2; + } + } + + public static class Unary extends Expr { + public final Expr expr; + + public Unary(Token op, Expr expr) { + super(op); + this.expr = expr; + } + } + + public static class Constant extends Expr { + public Constant(Token op) { + super(op); + } + } + + public static class Statement extends Node { + } + + public static class IfElse extends Statement { + public final Expr expr; + public final Statement ifStmt, elseStmt; + + public IfElse(Expr expr, Statement ifStmt, Statement elseStmt) { + this.expr = expr; + this.ifStmt = ifStmt; + this.elseStmt = elseStmt; + } + } + + public static class While extends Statement { + public Expr expr; + public Statement stmt; + + public While() { + } + public void init(Expr expr, Statement stmt) { + this.expr = expr; + this.stmt = stmt; + } + } + + public static class Break extends Statement { + public final Statement stmt; + + public Break(Statement stmt) { + if (stmt == null) + { + throw new CompilerException("uneclosed break"); + } + this.stmt = stmt; + } + } + + public static class Assign extends Statement { + public final Identifier identifier; + public final Expr expr; + + public Assign(Identifier identifier, Expr expr) { + this.identifier = identifier; + this.expr = expr; + } + } + + public static class Type extends Node { + public final Token.Id typeId; + + public Type(Token.Id typeId) { + this.typeId = typeId; + } + } + + public static class Declare extends Statement { + public final Type type; + public final Identifier identifier; + + public Declare(Type type, Identifier identifier) { + this.type = type; + this.identifier = identifier; + } + } + + public static class Block extends Statement { + public final List stmtList = new ArrayList<>(); + } +} diff --git a/chapter01/src/main/java/com/seaofnodes/simple/parser/Parser.java b/chapter01/src/main/java/com/seaofnodes/simple/parser/Parser.java new file mode 100644 index 00000000..b36942d4 --- /dev/null +++ b/chapter01/src/main/java/com/seaofnodes/simple/parser/Parser.java @@ -0,0 +1,259 @@ +/** + * DO NOT REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Contributor(s): + * + * The Initial Developer of the Original Software is Dibyendu Majumdar. + * + * This file is part of the Sea Of Nodes Simple Programming language + * implementation. See https://github.com/SeaOfNodes/Simple + * + * The contents of this file are subject to the terms of the + * Apache License Version 2 (the "APL"). You may not use this + * file except in compliance with the License. A copy of the + * APL may be obtained from: + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package com.seaofnodes.simple.parser; + +import com.seaofnodes.simple.common.CompilerException; +import com.seaofnodes.simple.lexer.ecstasy.Lexer; +import com.seaofnodes.simple.lexer.ecstasy.Token; +import com.seaofnodes.simple.common.Severity; + +public class Parser { + final Lexer lexer; + Token look; + Ast.Statement statementEnclosing; + + public Parser(Lexer lexer) { + this.lexer = lexer; + } + + Token nextOrEof() { + if (lexer.hasNext()) + return lexer.next(); + return new Token(look.getStartPosition(), look.getEndPosition(), Token.Id.EOF); + } + + void nextToken() { + look = nextOrEof(); + while (look.isComment()) { + look = nextOrEof(); + } + if (look.isContextSensitive() && + look.getId() == Token.Id.IDENTIFIER) { + look = look.convertToKeyword(); + } + } + + void error(Token t, String errorMessage) { + lexer.log(Severity.ERROR, errorMessage, t.getStartPosition(), t.getEndPosition()); + throw new CompilerException(errorMessage + ": " + t.toDebugString()); + } + + void match(Token.Id tag) { + if (look.getId() == tag) { + nextToken(); + } else { + error(look, "syntax error, expected " + tag); + } + } + + public Ast.Node parse() { + nextToken(); + return parseProgram(); + } + + Ast.Statement parseProgram() { + var block = new Ast.Block(); + parseDeclarations(block); + parseStatements(block); + return block; + } + + void parseDeclarations(Ast.Block block) { + while (look.getId() == Token.Id.VAR) { + nextToken(); + var tok = look; + match(Token.Id.IDENTIFIER); + match(Token.Id.COLON); + var type = parseType(); + match(Token.Id.SEMICOLON); + var identifier = new Ast.Identifier(tok); + block.stmtList.add(new Ast.Declare(type, identifier)); + } + } + + Ast.Type parseType() { + if (look.getId() == Token.Id.LIT_INTA) { + nextToken(); + return new Ast.Type(look.getId()); + } + error(look, "syntax error: unsupported type (at present only Int type is allowed)"); + return null; + } + + void parseStatements(Ast.Block block) { + while (look.getId() != Token.Id.EOF && + look.getId() != Token.Id.R_CURLY) { + block.stmtList.add(parseStatement()); + } + } + + Ast.Statement parseStatement() { + Ast.Expr x; + Ast.Statement s1; + Ast.Statement s2; + + switch (look.getId()) { + case IF -> { + match(Token.Id.IF); + match(Token.Id.L_PAREN); + x = parseBool(); + match(Token.Id.R_PAREN); + s1 = parseStatement(); + if (look.getId() != Token.Id.ELSE) { + return new Ast.IfElse(x, s1, null); + } + s2 = parseStatement(); + return new Ast.IfElse(x, s1, s2); + } + case WHILE -> { + var whilenode = new Ast.While(); + var savedStmt = statementEnclosing; + statementEnclosing = whilenode; + match(Token.Id.WHILE); + match(Token.Id.L_PAREN); + x = parseBool(); + match(Token.Id.R_PAREN); + s1 = parseStatement(); + whilenode.init(x, s1); + statementEnclosing = savedStmt; + return whilenode; + } + case BREAK -> { + match(Token.Id.BREAK); + match(Token.Id.SEMICOLON); + return new Ast.Break(statementEnclosing); + } + case L_CURLY -> { + return parseBlock(); + } + default -> { + return parseAssign(); + } + } + } + + Ast.Statement parseBlock() { + match(Token.Id.L_CURLY); + var block = new Ast.Block(); + parseDeclarations(block); + parseStatements(block); + match(Token.Id.R_CURLY); + return block; + } + + Ast.Statement parseAssign() { + match(Token.Id.IDENTIFIER); + Ast.Identifier identifier = new Ast.Identifier(look); + match(Token.Id.ASN); + var s = new Ast.Assign(identifier, parseBool()); + match(Token.Id.SEMICOLON); + return s; + } + + Ast.Expr parseBool() { + var x = parseAnd(); + while (look.getId() == Token.Id.COND_OR) { + var tok = look; + nextToken(); + x = new Ast.Binary(tok, x, parseAnd()); + } + return x; + } + + Ast.Expr parseAnd() { + var x = parseRelational(); + while (look.getId() == Token.Id.COND_AND) { + var tok = look; + nextToken(); + x = new Ast.Binary(tok, x, parseRelational()); + } + return x; + } + + Ast.Expr parseRelational() { + var x = parseAddition(); + while (look.getId() == Token.Id.COMP_EQ || + look.getId() == Token.Id.COMP_NEQ || + look.getId() == Token.Id.COMP_LT || + look.getId() == Token.Id.COMP_GT || + look.getId() == Token.Id.COMP_GTEQ || + look.getId() == Token.Id.COMP_LTEQ) { + var tok = look; + nextToken(); + x = new Ast.Binary(tok, x, parseAddition()); + } + return x; + } + + Ast.Expr parseAddition() { + var x = parseMultiplication(); + while (look.getId() == Token.Id.ADD || + look.getId() == Token.Id.SUB) { + var tok = look; + nextToken(); + x = new Ast.Binary(tok, x, parseMultiplication()); + } + return x; + } + + Ast.Expr parseMultiplication() { + var x = parseUnary(); + while (look.getId() == Token.Id.MUL || + look.getId() == Token.Id.DIV) { + var tok = look; + nextToken(); + x = new Ast.Binary(tok, x, parseUnary()); + } + return x; + } + + Ast.Expr parseUnary() { + if (look.getId() == Token.Id.SUB || + look.getId() == Token.Id.NOT) { + var tok = look; + nextToken(); + return new Ast.Unary(tok, parseUnary()); + } else { + return parsePrimary(); + } + } + + Ast.Expr parsePrimary() { + switch (look.getId()) { + case L_PAREN -> { + nextToken(); + var x = parseBool(); + match(Token.Id.R_PAREN); + return x; + } + case LIT_INT -> { + var x = new Ast.Constant(look); + nextToken(); + return x; + } + case IDENTIFIER -> { + var x = new Ast.Symbol(look); + nextToken(); + return x; + } + default -> { + error(look, "syntax error, expected nested expr, integer value or variable"); + return null; + } + } + } +} diff --git a/chapter01/src/test/java/com/seaofnodes/simple/lexer/TestParser.java b/chapter01/src/test/java/com/seaofnodes/simple/lexer/TestParser.java new file mode 100644 index 00000000..a89c1f6a --- /dev/null +++ b/chapter01/src/test/java/com/seaofnodes/simple/lexer/TestParser.java @@ -0,0 +1,68 @@ +/** + * DO NOT REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Contributor(s): + * + * This file is part of the Sea Of Nodes Simple Programming language + * implementation. See https://github.com/SeaOfNodes/Simple + * + * The contents of this file are subject to the terms of the + * Apache License Version 2 (the "APL"). You may not use this + * file except in compliance with the License. A copy of the + * APL may be obtained from: + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +package com.seaofnodes.simple.lexer; + +import com.seaofnodes.simple.common.ErrorListener; +import com.seaofnodes.simple.lexer.ecstasy.Lexer; +import com.seaofnodes.simple.lexer.ecstasy.Source; +import com.seaofnodes.simple.parser.Ast; +import com.seaofnodes.simple.parser.Parser; +import org.junit.Assert; +import org.junit.Test; + +public class TestParser { + + @Test + public void testParseSingleDecl() + { + final String source = "var i: Int;"; + Lexer lexer = new Lexer(new Source(source), new ErrorListener()); + Parser parser = new Parser(lexer); + var ast = parser.parse(); + Assert.assertTrue(ast instanceof Ast.Block + && ((Ast.Block) ast).stmtList.size() == 1); + } + + @Test + public void testParseTwoDecls() { + final String source = "var i: Int;\nvar j: Int;"; + Lexer lexer = new Lexer(new Source(source), new ErrorListener()); + Parser parser = new Parser(lexer); + var ast = parser.parse(); + Assert.assertTrue(ast instanceof Ast.Block + && ((Ast.Block) ast).stmtList.size() == 2); + } + + @Test + public void testIfAndWhile() { + final String source = + """ + var i: Int; + var j: Int; + + i = 0; + j = 100; + while ( 1 ) { + i = i + 1; + j = j - 1; + if ( i >= j ) break; + } + """; + Lexer lexer = new Lexer(new Source(source), new ErrorListener()); + Parser parser = new Parser(lexer); + var ast = parser.parse(); + } +} -- cgit v1.2.3 From b8f815a16ebad6675b6305d623196a8210145b75 Mon Sep 17 00:00:00 2001 From: dibyendumajumdar Date: Fri, 25 Aug 2023 23:26:23 +0100 Subject: Added a grammar definition for chapter 01. --- chapter01/README.md | 10 ++++++++++ chapter01/docs/01-grammar.md | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) (limited to 'chapter01') diff --git a/chapter01/README.md b/chapter01/README.md index e69de29b..d21a55e8 100644 --- a/chapter01/README.md +++ b/chapter01/README.md @@ -0,0 +1,10 @@ +# Chapter 1 +Simple control flow statements + +The chapter will implement: +* Control flow statements, `if` and `while`. +* Variables of `int` type. +* Simple arithmetic such as addition, subtraction, multiplication, division. +* Non-zero values will be truthy. + +* [Grammar](docs/01-grammar.md) \ No newline at end of file diff --git a/chapter01/docs/01-grammar.md b/chapter01/docs/01-grammar.md index e69de29b..92ed7357 100644 --- a/chapter01/docs/01-grammar.md +++ b/chapter01/docs/01-grammar.md @@ -0,0 +1,39 @@ +# Grammar for Chapter 1 + +```antlrv4 +grammar SimpleLanguage; + +program + : declaration* statement+ EOF + ; + +declaration + : type IDENTIFIER ';' + ; + +type + : 'int' + ; + +statement + : 'if' '(' expression ')' statement + | 'if' '(' expression ')' statement 'else' statement + | 'while' '(' expression ')' statement + | IDENTIFIER '=' expression ';' + | '{' declaration* statement+ '}' + | 'break' ';' + ; + +expression + : primaryExpression # PrimaryExpression_ + | expression ('*' | '/' ) expression # ArithmeticOrLogicalExpression + | expression ('+' | '-') expression # ArithmeticOrLogicalExpression + ; + +primaryExpression + : + | INTEGER_LITERAL + | IDENTIFIER + | '(' expression ')' + ; +``` \ No newline at end of file -- cgit v1.2.3 From 3b07be6d2c30f7539c5e71964c7a1e4c6790f91f Mon Sep 17 00:00:00 2001 From: dibyendumajumdar Date: Sat, 26 Aug 2023 12:28:48 +0100 Subject: Update the lexer, parser and AST to match the language definition. Add toString() method on AST nodes so that we can easily check the results of parsing. Remove AA lexer Move the Ecstasy lexer classes to one package, except Handy --- .../java/com/seaofnodes/simple/parser/Ast.java | 127 +++++++++++++++++---- .../java/com/seaofnodes/simple/parser/Parser.java | 75 ++++++------ .../com/seaofnodes/simple/lexer/TestParser.java | 20 ++-- 3 files changed, 148 insertions(+), 74 deletions(-) (limited to 'chapter01') diff --git a/chapter01/src/main/java/com/seaofnodes/simple/parser/Ast.java b/chapter01/src/main/java/com/seaofnodes/simple/parser/Ast.java index a93b230f..b11d1ddb 100644 --- a/chapter01/src/main/java/com/seaofnodes/simple/parser/Ast.java +++ b/chapter01/src/main/java/com/seaofnodes/simple/parser/Ast.java @@ -1,48 +1,94 @@ +/* + * DO NOT REMOVE COPYRIGHT NOTICES OR THIS HEADER. + *

+ * Contributor(s): + *

+ * The Initial Developer of the Original Software is Dibyendu Majumdar. + *

+ * This file is part of the Sea Of Nodes Simple Programming language + * implementation. See https://github.com/SeaOfNodes/Simple + *

+ * The contents of this file are subject to the terms of the + * Apache License Version 2 (the "APL"). You may not use this + * file except in compliance with the License. A copy of the + * APL may be obtained from: + * http://www.apache.org/licenses/LICENSE-2.0 + */ package com.seaofnodes.simple.parser; -import com.seaofnodes.simple.common.CompilerException; import com.seaofnodes.simple.lexer.ecstasy.Token; import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; -public abstract class Ast { +/** + * A simple AST definition. + */ +public class Ast { - public static class Node { + private Ast() { } - public static class Expr extends Node { + public interface Node { + } + + public abstract static class Expr implements Node { public final Token op; - public Expr(Token op) { + protected Expr(Token op) { this.op = op; } } + /** + * A name in the language + */ public static class Identifier extends Expr { public final String name; + public Identifier(Token op) { super(op); name = op.getValueText(); } + + @Override + public String toString() { + return name; + } } + /** + * Reference to a name in the language + */ public static class Symbol extends Expr { public final String name; + public Symbol(Token op) { super(op); name = op.getValueText(); } + + @Override + public String toString() { + return name; + } } public static class Binary extends Expr { - public final Expr expr1, expr2; + public final Expr expr1; + public final Expr expr2; public Binary(Token op, Expr expr1, Expr expr2) { super(op); this.expr1 = expr1; this.expr2 = expr2; } + + @Override + public String toString() { + return "(" + expr1 + op.getValueText() + expr2 + ")"; + } } public static class Unary extends Expr { @@ -52,49 +98,65 @@ public abstract class Ast { super(op); this.expr = expr; } + + @Override + public String toString() { + return "(" + op.getValueText() + "(" + expr + "))"; + } } public static class Constant extends Expr { public Constant(Token op) { super(op); } + + @Override + public String toString() { + return op.getValueText(); + } } - public static class Statement extends Node { + public abstract static class Statement implements Node { } public static class IfElse extends Statement { public final Expr expr; - public final Statement ifStmt, elseStmt; + public final Statement ifStmt; + public final Statement elseStmt; public IfElse(Expr expr, Statement ifStmt, Statement elseStmt) { this.expr = expr; this.ifStmt = ifStmt; this.elseStmt = elseStmt; } + + @Override + public String toString() { + if (elseStmt != null) + return "if(" + expr + ") " + ifStmt + " else " + elseStmt; + return "if(" + expr + ") " + ifStmt; + } } public static class While extends Statement { - public Expr expr; - public Statement stmt; + public final Expr expr; + public final Statement stmt; - public While() { - } - public void init(Expr expr, Statement stmt) { + public While(Expr expr, Statement stmt) { this.expr = expr; this.stmt = stmt; } + + @Override + public String toString() { + return "while(" + expr.toString() + ") " + stmt; + } } public static class Break extends Statement { - public final Statement stmt; - - public Break(Statement stmt) { - if (stmt == null) - { - throw new CompilerException("uneclosed break"); - } - this.stmt = stmt; + @Override + public String toString() { + return "break;"; } } @@ -106,14 +168,24 @@ public abstract class Ast { this.identifier = identifier; this.expr = expr; } + + @Override + public String toString() { + return identifier.toString() + "=" + expr.toString() + ";"; + } } - public static class Type extends Node { + public static class Type implements Node { public final Token.Id typeId; public Type(Token.Id typeId) { this.typeId = typeId; } + + @Override + public String toString() { + return typeId.TEXT; + } } public static class Declare extends Statement { @@ -124,9 +196,20 @@ public abstract class Ast { this.type = type; this.identifier = identifier; } + + @Override + public String toString() { + return type.toString() + " " + identifier.toString() + ";"; + } } public static class Block extends Statement { public final List stmtList = new ArrayList<>(); + + @Override + public String toString() { + return "{" + + stmtList.stream().map(Object::toString).collect(Collectors.joining()) + "}"; + } } } diff --git a/chapter01/src/main/java/com/seaofnodes/simple/parser/Parser.java b/chapter01/src/main/java/com/seaofnodes/simple/parser/Parser.java index b36942d4..263d0b41 100644 --- a/chapter01/src/main/java/com/seaofnodes/simple/parser/Parser.java +++ b/chapter01/src/main/java/com/seaofnodes/simple/parser/Parser.java @@ -1,4 +1,4 @@ -/** +/* * DO NOT REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Contributor(s): @@ -16,28 +16,28 @@ */ package com.seaofnodes.simple.parser; -import com.seaofnodes.simple.common.CompilerException; +import com.seaofnodes.simple.lexer.ecstasy.CompilerException; import com.seaofnodes.simple.lexer.ecstasy.Lexer; import com.seaofnodes.simple.lexer.ecstasy.Token; -import com.seaofnodes.simple.common.Severity; +import com.seaofnodes.simple.lexer.ecstasy.Severity; public class Parser { final Lexer lexer; Token look; - Ast.Statement statementEnclosing; public Parser(Lexer lexer) { this.lexer = lexer; } - Token nextOrEof() { + private Token nextOrEof() { if (lexer.hasNext()) return lexer.next(); return new Token(look.getStartPosition(), look.getEndPosition(), Token.Id.EOF); } - void nextToken() { + private void nextToken() { look = nextOrEof(); + // skip comments while (look.isComment()) { look = nextOrEof(); } @@ -47,12 +47,12 @@ public class Parser { } } - void error(Token t, String errorMessage) { + private void error(Token t, String errorMessage) { lexer.log(Severity.ERROR, errorMessage, t.getStartPosition(), t.getEndPosition()); throw new CompilerException(errorMessage + ": " + t.toDebugString()); } - void match(Token.Id tag) { + private void match(Token.Id tag) { if (look.getId() == tag) { nextToken(); } else { @@ -65,43 +65,40 @@ public class Parser { return parseProgram(); } - Ast.Statement parseProgram() { + private Ast.Statement parseProgram() { var block = new Ast.Block(); parseDeclarations(block); parseStatements(block); return block; } - void parseDeclarations(Ast.Block block) { - while (look.getId() == Token.Id.VAR) { + private boolean isTypeName(Token tok) { + return switch (tok.getId()) { + case TYPE_INT, TYPE_CHAR, TYPE_FLOAT -> true; + default -> false; + }; + } + + private void parseDeclarations(Ast.Block block) { + while (isTypeName(look)) { + var type = new Ast.Type(look.getId()); nextToken(); var tok = look; match(Token.Id.IDENTIFIER); - match(Token.Id.COLON); - var type = parseType(); match(Token.Id.SEMICOLON); var identifier = new Ast.Identifier(tok); block.stmtList.add(new Ast.Declare(type, identifier)); } } - Ast.Type parseType() { - if (look.getId() == Token.Id.LIT_INTA) { - nextToken(); - return new Ast.Type(look.getId()); - } - error(look, "syntax error: unsupported type (at present only Int type is allowed)"); - return null; - } - - void parseStatements(Ast.Block block) { + private void parseStatements(Ast.Block block) { while (look.getId() != Token.Id.EOF && look.getId() != Token.Id.R_CURLY) { block.stmtList.add(parseStatement()); } } - Ast.Statement parseStatement() { + private Ast.Statement parseStatement() { Ast.Expr x; Ast.Statement s1; Ast.Statement s2; @@ -120,22 +117,17 @@ public class Parser { return new Ast.IfElse(x, s1, s2); } case WHILE -> { - var whilenode = new Ast.While(); - var savedStmt = statementEnclosing; - statementEnclosing = whilenode; match(Token.Id.WHILE); match(Token.Id.L_PAREN); x = parseBool(); match(Token.Id.R_PAREN); s1 = parseStatement(); - whilenode.init(x, s1); - statementEnclosing = savedStmt; - return whilenode; + return new Ast.While(x, s1); } case BREAK -> { match(Token.Id.BREAK); match(Token.Id.SEMICOLON); - return new Ast.Break(statementEnclosing); + return new Ast.Break(); } case L_CURLY -> { return parseBlock(); @@ -146,7 +138,7 @@ public class Parser { } } - Ast.Statement parseBlock() { + private Ast.Statement parseBlock() { match(Token.Id.L_CURLY); var block = new Ast.Block(); parseDeclarations(block); @@ -155,16 +147,17 @@ public class Parser { return block; } - Ast.Statement parseAssign() { + private Ast.Statement parseAssign() { + Token tok = look; match(Token.Id.IDENTIFIER); - Ast.Identifier identifier = new Ast.Identifier(look); + Ast.Identifier identifier = new Ast.Identifier(tok); match(Token.Id.ASN); var s = new Ast.Assign(identifier, parseBool()); match(Token.Id.SEMICOLON); return s; } - Ast.Expr parseBool() { + private Ast.Expr parseBool() { var x = parseAnd(); while (look.getId() == Token.Id.COND_OR) { var tok = look; @@ -174,7 +167,7 @@ public class Parser { return x; } - Ast.Expr parseAnd() { + private Ast.Expr parseAnd() { var x = parseRelational(); while (look.getId() == Token.Id.COND_AND) { var tok = look; @@ -184,7 +177,7 @@ public class Parser { return x; } - Ast.Expr parseRelational() { + private Ast.Expr parseRelational() { var x = parseAddition(); while (look.getId() == Token.Id.COMP_EQ || look.getId() == Token.Id.COMP_NEQ || @@ -199,7 +192,7 @@ public class Parser { return x; } - Ast.Expr parseAddition() { + private Ast.Expr parseAddition() { var x = parseMultiplication(); while (look.getId() == Token.Id.ADD || look.getId() == Token.Id.SUB) { @@ -210,7 +203,7 @@ public class Parser { return x; } - Ast.Expr parseMultiplication() { + private Ast.Expr parseMultiplication() { var x = parseUnary(); while (look.getId() == Token.Id.MUL || look.getId() == Token.Id.DIV) { @@ -221,7 +214,7 @@ public class Parser { return x; } - Ast.Expr parseUnary() { + private Ast.Expr parseUnary() { if (look.getId() == Token.Id.SUB || look.getId() == Token.Id.NOT) { var tok = look; @@ -232,7 +225,7 @@ public class Parser { } } - Ast.Expr parsePrimary() { + private Ast.Expr parsePrimary() { switch (look.getId()) { case L_PAREN -> { nextToken(); diff --git a/chapter01/src/test/java/com/seaofnodes/simple/lexer/TestParser.java b/chapter01/src/test/java/com/seaofnodes/simple/lexer/TestParser.java index a89c1f6a..80917c0f 100644 --- a/chapter01/src/test/java/com/seaofnodes/simple/lexer/TestParser.java +++ b/chapter01/src/test/java/com/seaofnodes/simple/lexer/TestParser.java @@ -1,4 +1,4 @@ -/** +/* * DO NOT REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Contributor(s): @@ -15,10 +15,9 @@ package com.seaofnodes.simple.lexer; -import com.seaofnodes.simple.common.ErrorListener; +import com.seaofnodes.simple.lexer.ecstasy.ErrorListener; import com.seaofnodes.simple.lexer.ecstasy.Lexer; import com.seaofnodes.simple.lexer.ecstasy.Source; -import com.seaofnodes.simple.parser.Ast; import com.seaofnodes.simple.parser.Parser; import org.junit.Assert; import org.junit.Test; @@ -28,30 +27,28 @@ public class TestParser { @Test public void testParseSingleDecl() { - final String source = "var i: Int;"; + final String source = "int i;"; Lexer lexer = new Lexer(new Source(source), new ErrorListener()); Parser parser = new Parser(lexer); var ast = parser.parse(); - Assert.assertTrue(ast instanceof Ast.Block - && ((Ast.Block) ast).stmtList.size() == 1); + Assert.assertEquals("{int i;}", ast.toString()); } @Test public void testParseTwoDecls() { - final String source = "var i: Int;\nvar j: Int;"; + final String source = "int i;\nint j;"; Lexer lexer = new Lexer(new Source(source), new ErrorListener()); Parser parser = new Parser(lexer); var ast = parser.parse(); - Assert.assertTrue(ast instanceof Ast.Block - && ((Ast.Block) ast).stmtList.size() == 2); + Assert.assertEquals("{int i;int j;}", ast.toString()); } @Test public void testIfAndWhile() { final String source = """ - var i: Int; - var j: Int; + int i; + int j; i = 0; j = 100; @@ -64,5 +61,6 @@ public class TestParser { Lexer lexer = new Lexer(new Source(source), new ErrorListener()); Parser parser = new Parser(lexer); var ast = parser.parse(); + Assert.assertEquals("{int i;int j;i=0;j=100;while(1) {i=(i+1);j=(j-1);if((i>=j)) break;}}", ast.toString()); } } -- cgit v1.2.3 From 867405ea40d3c8acd93e5e0e6030e8ad96eb2470 Mon Sep 17 00:00:00 2001 From: dibyendumajumdar Date: Sat, 26 Aug 2023 15:39:13 +0100 Subject: Avoid using Node as a type as the IR will need this So just extends the top level Ast class in all the subtypes. Also avoid holding reference to lexer in parser. --- .../java/com/seaofnodes/simple/parser/Ast.java | 10 +- .../java/com/seaofnodes/simple/parser/Parser.java | 239 +++++++++++---------- .../com/seaofnodes/simple/lexer/TestParser.java | 12 +- 3 files changed, 130 insertions(+), 131 deletions(-) (limited to 'chapter01') diff --git a/chapter01/src/main/java/com/seaofnodes/simple/parser/Ast.java b/chapter01/src/main/java/com/seaofnodes/simple/parser/Ast.java index b11d1ddb..51c7b7ac 100644 --- a/chapter01/src/main/java/com/seaofnodes/simple/parser/Ast.java +++ b/chapter01/src/main/java/com/seaofnodes/simple/parser/Ast.java @@ -27,13 +27,11 @@ import java.util.stream.Collectors; */ public class Ast { - private Ast() { + protected Ast() { } - public interface Node { - } - public abstract static class Expr implements Node { + public abstract static class Expr extends Ast { public final Token op; protected Expr(Token op) { @@ -116,7 +114,7 @@ public class Ast { } } - public abstract static class Statement implements Node { + public abstract static class Statement extends Ast { } public static class IfElse extends Statement { @@ -175,7 +173,7 @@ public class Ast { } } - public static class Type implements Node { + public static class Type extends Ast { public final Token.Id typeId; public Type(Token.Id typeId) { diff --git a/chapter01/src/main/java/com/seaofnodes/simple/parser/Parser.java b/chapter01/src/main/java/com/seaofnodes/simple/parser/Parser.java index 263d0b41..fe224105 100644 --- a/chapter01/src/main/java/com/seaofnodes/simple/parser/Parser.java +++ b/chapter01/src/main/java/com/seaofnodes/simple/parser/Parser.java @@ -22,53 +22,54 @@ import com.seaofnodes.simple.lexer.ecstasy.Token; import com.seaofnodes.simple.lexer.ecstasy.Severity; public class Parser { - final Lexer lexer; - Token look; + private Token currentToken; - public Parser(Lexer lexer) { - this.lexer = lexer; + /** + * Parse and return a top level Ast + * At present this is an Ast.Block + * @see Ast.Block + */ + public Ast parse(Lexer lexer) { + nextToken(lexer); + return parseProgram(lexer); } - private Token nextOrEof() { + private Token nextOrEof(Lexer lexer) { if (lexer.hasNext()) return lexer.next(); - return new Token(look.getStartPosition(), look.getEndPosition(), Token.Id.EOF); + return new Token(currentToken.getStartPosition(), currentToken.getEndPosition(), Token.Id.EOF); } - private void nextToken() { - look = nextOrEof(); + private void nextToken(Lexer lexer) { + currentToken = nextOrEof(lexer); // skip comments - while (look.isComment()) { - look = nextOrEof(); + while (currentToken.isComment()) { + currentToken = nextOrEof(lexer); } - if (look.isContextSensitive() && - look.getId() == Token.Id.IDENTIFIER) { - look = look.convertToKeyword(); + if (currentToken.isContextSensitive() && + currentToken.getId() == Token.Id.IDENTIFIER) { + currentToken = currentToken.convertToKeyword(); } } - private void error(Token t, String errorMessage) { + private void error(Lexer lexer, Token t, String errorMessage) { lexer.log(Severity.ERROR, errorMessage, t.getStartPosition(), t.getEndPosition()); throw new CompilerException(errorMessage + ": " + t.toDebugString()); } - private void match(Token.Id tag) { - if (look.getId() == tag) { - nextToken(); + private void match(Lexer lexer, Token.Id tag) { + if (currentToken.getId() == tag) { + nextToken(lexer); } else { - error(look, "syntax error, expected " + tag); + error(lexer, currentToken, "syntax error, expected " + tag); } } - public Ast.Node parse() { - nextToken(); - return parseProgram(); - } - private Ast.Statement parseProgram() { + private Ast.Statement parseProgram(Lexer lexer) { var block = new Ast.Block(); - parseDeclarations(block); - parseStatements(block); + parseDeclarations(lexer, block); + parseStatements(lexer, block); return block; } @@ -79,172 +80,172 @@ public class Parser { }; } - private void parseDeclarations(Ast.Block block) { - while (isTypeName(look)) { - var type = new Ast.Type(look.getId()); - nextToken(); - var tok = look; - match(Token.Id.IDENTIFIER); - match(Token.Id.SEMICOLON); + private void parseDeclarations(Lexer lexer, Ast.Block block) { + while (isTypeName(currentToken)) { + var type = new Ast.Type(currentToken.getId()); + nextToken(lexer); + var tok = currentToken; + match(lexer, Token.Id.IDENTIFIER); + match(lexer, Token.Id.SEMICOLON); var identifier = new Ast.Identifier(tok); block.stmtList.add(new Ast.Declare(type, identifier)); } } - private void parseStatements(Ast.Block block) { - while (look.getId() != Token.Id.EOF && - look.getId() != Token.Id.R_CURLY) { - block.stmtList.add(parseStatement()); + private void parseStatements(Lexer lexer, Ast.Block block) { + while (currentToken.getId() != Token.Id.EOF && + currentToken.getId() != Token.Id.R_CURLY) { + block.stmtList.add(parseStatement(lexer)); } } - private Ast.Statement parseStatement() { + private Ast.Statement parseStatement(Lexer lexer) { Ast.Expr x; Ast.Statement s1; Ast.Statement s2; - switch (look.getId()) { + switch (currentToken.getId()) { case IF -> { - match(Token.Id.IF); - match(Token.Id.L_PAREN); - x = parseBool(); - match(Token.Id.R_PAREN); - s1 = parseStatement(); - if (look.getId() != Token.Id.ELSE) { + match(lexer, Token.Id.IF); + match(lexer, Token.Id.L_PAREN); + x = parseBool(lexer); + match(lexer, Token.Id.R_PAREN); + s1 = parseStatement(lexer); + if (currentToken.getId() != Token.Id.ELSE) { return new Ast.IfElse(x, s1, null); } - s2 = parseStatement(); + s2 = parseStatement(lexer); return new Ast.IfElse(x, s1, s2); } case WHILE -> { - match(Token.Id.WHILE); - match(Token.Id.L_PAREN); - x = parseBool(); - match(Token.Id.R_PAREN); - s1 = parseStatement(); + match(lexer, Token.Id.WHILE); + match(lexer, Token.Id.L_PAREN); + x = parseBool(lexer); + match(lexer, Token.Id.R_PAREN); + s1 = parseStatement(lexer); return new Ast.While(x, s1); } case BREAK -> { - match(Token.Id.BREAK); - match(Token.Id.SEMICOLON); + match(lexer, Token.Id.BREAK); + match(lexer, Token.Id.SEMICOLON); return new Ast.Break(); } case L_CURLY -> { - return parseBlock(); + return parseBlock(lexer); } default -> { - return parseAssign(); + return parseAssign(lexer); } } } - private Ast.Statement parseBlock() { - match(Token.Id.L_CURLY); + private Ast.Statement parseBlock(Lexer lexer) { + match(lexer, Token.Id.L_CURLY); var block = new Ast.Block(); - parseDeclarations(block); - parseStatements(block); - match(Token.Id.R_CURLY); + parseDeclarations(lexer, block); + parseStatements(lexer, block); + match(lexer, Token.Id.R_CURLY); return block; } - private Ast.Statement parseAssign() { - Token tok = look; - match(Token.Id.IDENTIFIER); + private Ast.Statement parseAssign(Lexer lexer) { + Token tok = currentToken; + match(lexer, Token.Id.IDENTIFIER); Ast.Identifier identifier = new Ast.Identifier(tok); - match(Token.Id.ASN); - var s = new Ast.Assign(identifier, parseBool()); - match(Token.Id.SEMICOLON); + match(lexer, Token.Id.ASN); + var s = new Ast.Assign(identifier, parseBool(lexer)); + match(lexer, Token.Id.SEMICOLON); return s; } - private Ast.Expr parseBool() { - var x = parseAnd(); - while (look.getId() == Token.Id.COND_OR) { - var tok = look; - nextToken(); - x = new Ast.Binary(tok, x, parseAnd()); + private Ast.Expr parseBool(Lexer lexer) { + var x = parseAnd(lexer); + while (currentToken.getId() == Token.Id.COND_OR) { + var tok = currentToken; + nextToken(lexer); + x = new Ast.Binary(tok, x, parseAnd(lexer)); } return x; } - private Ast.Expr parseAnd() { - var x = parseRelational(); - while (look.getId() == Token.Id.COND_AND) { - var tok = look; - nextToken(); - x = new Ast.Binary(tok, x, parseRelational()); + private Ast.Expr parseAnd(Lexer lexer) { + var x = parseRelational(lexer); + while (currentToken.getId() == Token.Id.COND_AND) { + var tok = currentToken; + nextToken(lexer); + x = new Ast.Binary(tok, x, parseRelational(lexer)); } return x; } - private Ast.Expr parseRelational() { - var x = parseAddition(); - while (look.getId() == Token.Id.COMP_EQ || - look.getId() == Token.Id.COMP_NEQ || - look.getId() == Token.Id.COMP_LT || - look.getId() == Token.Id.COMP_GT || - look.getId() == Token.Id.COMP_GTEQ || - look.getId() == Token.Id.COMP_LTEQ) { - var tok = look; - nextToken(); - x = new Ast.Binary(tok, x, parseAddition()); + private Ast.Expr parseRelational(Lexer lexer) { + var x = parseAddition(lexer); + while (currentToken.getId() == Token.Id.COMP_EQ || + currentToken.getId() == Token.Id.COMP_NEQ || + currentToken.getId() == Token.Id.COMP_LT || + currentToken.getId() == Token.Id.COMP_GT || + currentToken.getId() == Token.Id.COMP_GTEQ || + currentToken.getId() == Token.Id.COMP_LTEQ) { + var tok = currentToken; + nextToken(lexer); + x = new Ast.Binary(tok, x, parseAddition(lexer)); } return x; } - private Ast.Expr parseAddition() { - var x = parseMultiplication(); - while (look.getId() == Token.Id.ADD || - look.getId() == Token.Id.SUB) { - var tok = look; - nextToken(); - x = new Ast.Binary(tok, x, parseMultiplication()); + private Ast.Expr parseAddition(Lexer lexer) { + var x = parseMultiplication(lexer); + while (currentToken.getId() == Token.Id.ADD || + currentToken.getId() == Token.Id.SUB) { + var tok = currentToken; + nextToken(lexer); + x = new Ast.Binary(tok, x, parseMultiplication(lexer)); } return x; } - private Ast.Expr parseMultiplication() { - var x = parseUnary(); - while (look.getId() == Token.Id.MUL || - look.getId() == Token.Id.DIV) { - var tok = look; - nextToken(); - x = new Ast.Binary(tok, x, parseUnary()); + private Ast.Expr parseMultiplication(Lexer lexer) { + var x = parseUnary(lexer); + while (currentToken.getId() == Token.Id.MUL || + currentToken.getId() == Token.Id.DIV) { + var tok = currentToken; + nextToken(lexer); + x = new Ast.Binary(tok, x, parseUnary(lexer)); } return x; } - private Ast.Expr parseUnary() { - if (look.getId() == Token.Id.SUB || - look.getId() == Token.Id.NOT) { - var tok = look; - nextToken(); - return new Ast.Unary(tok, parseUnary()); + private Ast.Expr parseUnary(Lexer lexer) { + if (currentToken.getId() == Token.Id.SUB || + currentToken.getId() == Token.Id.NOT) { + var tok = currentToken; + nextToken(lexer); + return new Ast.Unary(tok, parseUnary(lexer)); } else { - return parsePrimary(); + return parsePrimary(lexer); } } - private Ast.Expr parsePrimary() { - switch (look.getId()) { + private Ast.Expr parsePrimary(Lexer lexer) { + switch (currentToken.getId()) { case L_PAREN -> { - nextToken(); - var x = parseBool(); - match(Token.Id.R_PAREN); + nextToken(lexer); + var x = parseBool(lexer); + match(lexer, Token.Id.R_PAREN); return x; } case LIT_INT -> { - var x = new Ast.Constant(look); - nextToken(); + var x = new Ast.Constant(currentToken); + nextToken(lexer); return x; } case IDENTIFIER -> { - var x = new Ast.Symbol(look); - nextToken(); + var x = new Ast.Symbol(currentToken); + nextToken(lexer); return x; } default -> { - error(look, "syntax error, expected nested expr, integer value or variable"); + error(lexer, currentToken, "syntax error, expected nested expr, integer value or variable"); return null; } } diff --git a/chapter01/src/test/java/com/seaofnodes/simple/lexer/TestParser.java b/chapter01/src/test/java/com/seaofnodes/simple/lexer/TestParser.java index 80917c0f..3f65e0f9 100644 --- a/chapter01/src/test/java/com/seaofnodes/simple/lexer/TestParser.java +++ b/chapter01/src/test/java/com/seaofnodes/simple/lexer/TestParser.java @@ -29,8 +29,8 @@ public class TestParser { { final String source = "int i;"; Lexer lexer = new Lexer(new Source(source), new ErrorListener()); - Parser parser = new Parser(lexer); - var ast = parser.parse(); + Parser parser = new Parser(); + var ast = parser.parse(lexer); Assert.assertEquals("{int i;}", ast.toString()); } @@ -38,8 +38,8 @@ public class TestParser { public void testParseTwoDecls() { final String source = "int i;\nint j;"; Lexer lexer = new Lexer(new Source(source), new ErrorListener()); - Parser parser = new Parser(lexer); - var ast = parser.parse(); + Parser parser = new Parser(); + var ast = parser.parse(lexer); Assert.assertEquals("{int i;int j;}", ast.toString()); } @@ -59,8 +59,8 @@ public class TestParser { } """; Lexer lexer = new Lexer(new Source(source), new ErrorListener()); - Parser parser = new Parser(lexer); - var ast = parser.parse(); + Parser parser = new Parser(); + var ast = parser.parse(lexer); Assert.assertEquals("{int i;int j;i=0;j=100;while(1) {i=(i+1);j=(j-1);if((i>=j)) break;}}", ast.toString()); } } -- cgit v1.2.3 From 85e5d41217d27fd481a21d17f061d31dc120873b Mon Sep 17 00:00:00 2001 From: dibyendumajumdar Date: Thu, 7 Sep 2023 19:33:52 +0100 Subject: Removed all external code. Removed the common chapter. Reimplemented Lexer. Revised the list of chapters to something more accurate. --- chapter01/pom.xml | 8 -- .../java/com/seaofnodes/simple/parser/Ast.java | 18 ++- .../simple/parser/CompilerException.java | 27 ++++ .../java/com/seaofnodes/simple/parser/Lexer.java | 151 ++++++++++++++++++++ .../java/com/seaofnodes/simple/parser/Parser.java | 153 +++++++++++---------- .../java/com/seaofnodes/simple/parser/Token.java | 63 +++++++++ .../com/seaofnodes/simple/lexer/TestParser.java | 66 --------- .../com/seaofnodes/simple/parser/TestParser.java | 62 +++++++++ 8 files changed, 392 insertions(+), 156 deletions(-) create mode 100644 chapter01/src/main/java/com/seaofnodes/simple/parser/CompilerException.java create mode 100644 chapter01/src/main/java/com/seaofnodes/simple/parser/Lexer.java create mode 100644 chapter01/src/main/java/com/seaofnodes/simple/parser/Token.java delete mode 100644 chapter01/src/test/java/com/seaofnodes/simple/lexer/TestParser.java create mode 100644 chapter01/src/test/java/com/seaofnodes/simple/parser/TestParser.java (limited to 'chapter01') diff --git a/chapter01/pom.xml b/chapter01/pom.xml index 842e67e9..2204bde8 100644 --- a/chapter01/pom.xml +++ b/chapter01/pom.xml @@ -11,12 +11,4 @@ chapter01 jar Chapter 1 - - - - com.seaofnodes - common - 1.0 - - \ No newline at end of file diff --git a/chapter01/src/main/java/com/seaofnodes/simple/parser/Ast.java b/chapter01/src/main/java/com/seaofnodes/simple/parser/Ast.java index 51c7b7ac..c271d4b6 100644 --- a/chapter01/src/main/java/com/seaofnodes/simple/parser/Ast.java +++ b/chapter01/src/main/java/com/seaofnodes/simple/parser/Ast.java @@ -16,8 +16,6 @@ */ package com.seaofnodes.simple.parser; -import com.seaofnodes.simple.lexer.ecstasy.Token; - import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -47,7 +45,7 @@ public class Ast { public Identifier(Token op) { super(op); - name = op.getValueText(); + name = op.str; } @Override @@ -64,7 +62,7 @@ public class Ast { public Symbol(Token op) { super(op); - name = op.getValueText(); + name = op.str; } @Override @@ -85,7 +83,7 @@ public class Ast { @Override public String toString() { - return "(" + expr1 + op.getValueText() + expr2 + ")"; + return "(" + expr1 + op.toString() + expr2 + ")"; } } @@ -99,7 +97,7 @@ public class Ast { @Override public String toString() { - return "(" + op.getValueText() + "(" + expr + "))"; + return "(" + op.toString() + "(" + expr + "))"; } } @@ -110,7 +108,7 @@ public class Ast { @Override public String toString() { - return op.getValueText(); + return op.toString(); } } @@ -174,15 +172,15 @@ public class Ast { } public static class Type extends Ast { - public final Token.Id typeId; + public final String typeId; - public Type(Token.Id typeId) { + public Type(String typeId) { this.typeId = typeId; } @Override public String toString() { - return typeId.TEXT; + return typeId; } } diff --git a/chapter01/src/main/java/com/seaofnodes/simple/parser/CompilerException.java b/chapter01/src/main/java/com/seaofnodes/simple/parser/CompilerException.java new file mode 100644 index 00000000..a62fa034 --- /dev/null +++ b/chapter01/src/main/java/com/seaofnodes/simple/parser/CompilerException.java @@ -0,0 +1,27 @@ +/* + * DO NOT REMOVE COPYRIGHT NOTICES OR THIS HEADER. + *

+ * Contributor(s): + *

+ * The Initial Developer of the Original Software is Dibyendu Majumdar. + *

+ * This file is part of the Sea Of Nodes Simple Programming language + * implementation. See https://github.com/SeaOfNodes/Simple + *

+ * The contents of this file are subject to the terms of the + * Apache License Version 2 (the "APL"). You may not use this + * file except in compliance with the License. A copy of the + * APL may be obtained from: + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package com.seaofnodes.simple.parser; + +public class CompilerException extends RuntimeException { + public CompilerException(String message) { + super(message); + } + + public CompilerException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/chapter01/src/main/java/com/seaofnodes/simple/parser/Lexer.java b/chapter01/src/main/java/com/seaofnodes/simple/parser/Lexer.java new file mode 100644 index 00000000..fbe8c239 --- /dev/null +++ b/chapter01/src/main/java/com/seaofnodes/simple/parser/Lexer.java @@ -0,0 +1,151 @@ +/* + * DO NOT REMOVE COPYRIGHT NOTICES OR THIS HEADER. + *

+ * Contributor(s): + *

+ * The Initial Developer of the Original Software is Dibyendu Majumdar. + *

+ * This file is part of the Sea Of Nodes Simple Programming language + * implementation. See https://github.com/SeaOfNodes/Simple + *

+ * The contents of this file are subject to the terms of the + * Apache License Version 2 (the "APL"). You may not use this + * file except in compliance with the License. A copy of the + * APL may be obtained from: + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package com.seaofnodes.simple.parser; + +import java.text.NumberFormat; +import java.text.ParseException; + +public class Lexer { + + private final char[] input; + /** + * Tracks current position in input buffer + */ + private int position = 0; + private char peek = ' '; + + private final NumberFormat numberFormat; + + public Lexer(String source) { + input = source.toCharArray(); + numberFormat = NumberFormat.getInstance(); + numberFormat.setGroupingUsed(false); + } + + private boolean isEOF() { + return position >= input.length; + } + + private char advance() { + if (isEOF()) + return 0; // Special value that causes parsing to terminate + return input[position++]; + } + + private boolean readch() { + peek = advance(); + return true; + } + + private boolean readch(char c) { + if (readch() && peek != c) + return false; + peek = ' '; + return true; + } + + /** + * Note that EOF is not whitespace! + */ + private boolean isWhiteSpace(char c) { + return c == ' ' || + c == '\t' || + c == '\n' || + c == '\r'; + } + + private void skipWhitespace() { + while (isWhiteSpace(peek)) + readch(); + } + + /** + * Parses number in format nnn[.nnn] + * where n is a digit + */ + private Token parseNumber() { + assert Character.isDigit(peek); + StringBuilder sb = new StringBuilder(); + sb.append(peek); + while (readch() && Character.isDigit(peek)) + sb.append(peek); + if (peek == '.') { + sb.append(peek); + while (readch() && Character.isDigit(peek)) + sb.append(peek); + } + String str = sb.toString(); + Number number = parseNumber(str); + return Token.newNum(number, str); + } + + private Number parseNumber(String str) { + try { + return numberFormat.parse(str); + } catch (ParseException e) { + throw new CompilerException("Failed to parse number " + str, e); + } + } + + private boolean isIdentifierStart(char ch) { + return Character.isAlphabetic(ch) || ch == '_'; + } + + private boolean isIdentifierLetter(char ch) { + return Character.isLetterOrDigit(ch) || ch == '_'; + } + + private Token parseIdentifier() { + assert isIdentifierStart(peek); + StringBuilder sb = new StringBuilder(); + sb.append(peek); + while (readch() && isIdentifierLetter(peek)) + sb.append(peek); + return Token.newIdent(sb.toString()); + } + + public Token scan() { + skipWhitespace(); + switch (peek) { + case 0: + return Token.EOF; + case '&': + return readch('&') ? Token.newPunct("&&") : Token.newPunct("&"); + case '|': + return readch('|') ? Token.newPunct("||") : Token.newPunct("|"); + case '=': + return readch('=') ? Token.newPunct("==") : Token.newPunct("="); + case '<': + return readch('=') ? Token.newPunct("<=") : Token.newPunct("<"); + case '>': + return readch('=') ? Token.newPunct(">=") : Token.newPunct(">"); + default: { + return scanOthers(); + } + } + } + + private Token scanOthers() { + if (Character.isDigit(peek)) + return parseNumber(); + else if (isIdentifierLetter(peek)) + return parseIdentifier(); + Token token = Token.newPunct(Character.toString(peek)); + peek = ' '; + return token; + } +} diff --git a/chapter01/src/main/java/com/seaofnodes/simple/parser/Parser.java b/chapter01/src/main/java/com/seaofnodes/simple/parser/Parser.java index fe224105..7e333b71 100644 --- a/chapter01/src/main/java/com/seaofnodes/simple/parser/Parser.java +++ b/chapter01/src/main/java/com/seaofnodes/simple/parser/Parser.java @@ -16,17 +16,13 @@ */ package com.seaofnodes.simple.parser; -import com.seaofnodes.simple.lexer.ecstasy.CompilerException; -import com.seaofnodes.simple.lexer.ecstasy.Lexer; -import com.seaofnodes.simple.lexer.ecstasy.Token; -import com.seaofnodes.simple.lexer.ecstasy.Severity; - public class Parser { private Token currentToken; /** * Parse and return a top level Ast * At present this is an Ast.Block + * * @see Ast.Block */ public Ast parse(Lexer lexer) { @@ -34,37 +30,41 @@ public class Parser { return parseProgram(lexer); } - private Token nextOrEof(Lexer lexer) { - if (lexer.hasNext()) - return lexer.next(); - return new Token(currentToken.getStartPosition(), currentToken.getEndPosition(), Token.Id.EOF); + private void nextToken(Lexer lexer) { + currentToken = lexer.scan(); } - private void nextToken(Lexer lexer) { - currentToken = nextOrEof(lexer); - // skip comments - while (currentToken.isComment()) { - currentToken = nextOrEof(lexer); - } - if (currentToken.isContextSensitive() && - currentToken.getId() == Token.Id.IDENTIFIER) { - currentToken = currentToken.convertToKeyword(); + private void error(Token t, String errorMessage) { + throw new CompilerException(errorMessage + ": " + t.toString()); + } + + private void matchKind(Lexer lexer, Token.Kind kind) { + if (currentToken.kind == kind) { + nextToken(lexer); + } else { + error(currentToken, "syntax error, expected " + kind); } } - private void error(Lexer lexer, Token t, String errorMessage) { - lexer.log(Severity.ERROR, errorMessage, t.getStartPosition(), t.getEndPosition()); - throw new CompilerException(errorMessage + ": " + t.toDebugString()); + private void matchPunctuation(Lexer lexer, String value) { + if (currentToken.kind == Token.Kind.PUNCT && isToken(currentToken, value)) { + nextToken(lexer); + } else { + error(currentToken, "syntax error, expected " + value + " got " + currentToken.str); + } } - private void match(Lexer lexer, Token.Id tag) { - if (currentToken.getId() == tag) { + private void matchIdentifier(Lexer lexer, String identifier) { + if (currentToken.kind == Token.Kind.IDENT && isToken(currentToken, identifier)) { nextToken(lexer); } else { - error(lexer, currentToken, "syntax error, expected " + tag); + error(currentToken, "syntax error, expected " + identifier); } } + private boolean isToken(Token token, String value) { + return token.str.equals(value); + } private Ast.Statement parseProgram(Lexer lexer) { var block = new Ast.Block(); @@ -74,27 +74,34 @@ public class Parser { } private boolean isTypeName(Token tok) { - return switch (tok.getId()) { - case TYPE_INT, TYPE_CHAR, TYPE_FLOAT -> true; - default -> false; - }; + return tok.kind == Token.Kind.IDENT && + (isToken(tok, "int") || + isToken(tok, "char") || + isToken(tok, "float")); } private void parseDeclarations(Lexer lexer, Ast.Block block) { while (isTypeName(currentToken)) { - var type = new Ast.Type(currentToken.getId()); + var type = new Ast.Type(currentToken.str); nextToken(lexer); var tok = currentToken; - match(lexer, Token.Id.IDENTIFIER); - match(lexer, Token.Id.SEMICOLON); + matchKind(lexer, Token.Kind.IDENT); + matchPunctuation(lexer, ";"); var identifier = new Ast.Identifier(tok); block.stmtList.add(new Ast.Declare(type, identifier)); } } + private boolean isEOF(Token tok) { + return tok.kind == Token.Kind.EOZ; + } + + private boolean isPunct(Token tok, String str) { + return tok.kind == Token.Kind.PUNCT && isToken(tok, str); + } + private void parseStatements(Lexer lexer, Ast.Block block) { - while (currentToken.getId() != Token.Id.EOF && - currentToken.getId() != Token.Id.R_CURLY) { + while (!isEOF(currentToken) && !isPunct(currentToken, "}")) { block.stmtList.add(parseStatement(lexer)); } } @@ -104,33 +111,33 @@ public class Parser { Ast.Statement s1; Ast.Statement s2; - switch (currentToken.getId()) { - case IF -> { - match(lexer, Token.Id.IF); - match(lexer, Token.Id.L_PAREN); + switch (currentToken.str) { + case "if" -> { + matchIdentifier(lexer, "if"); + matchPunctuation(lexer, "("); x = parseBool(lexer); - match(lexer, Token.Id.R_PAREN); + matchPunctuation(lexer, ")"); s1 = parseStatement(lexer); - if (currentToken.getId() != Token.Id.ELSE) { + if (!isToken(currentToken, "else")) { return new Ast.IfElse(x, s1, null); } s2 = parseStatement(lexer); return new Ast.IfElse(x, s1, s2); } - case WHILE -> { - match(lexer, Token.Id.WHILE); - match(lexer, Token.Id.L_PAREN); + case "while" -> { + matchIdentifier(lexer, "while"); + matchPunctuation(lexer, "("); x = parseBool(lexer); - match(lexer, Token.Id.R_PAREN); + matchPunctuation(lexer, ")"); s1 = parseStatement(lexer); return new Ast.While(x, s1); } - case BREAK -> { - match(lexer, Token.Id.BREAK); - match(lexer, Token.Id.SEMICOLON); + case "break" -> { + matchIdentifier(lexer, "break"); + matchPunctuation(lexer, ";"); return new Ast.Break(); } - case L_CURLY -> { + case "{" -> { return parseBlock(lexer); } default -> { @@ -140,27 +147,27 @@ public class Parser { } private Ast.Statement parseBlock(Lexer lexer) { - match(lexer, Token.Id.L_CURLY); + matchPunctuation(lexer, "{"); var block = new Ast.Block(); parseDeclarations(lexer, block); parseStatements(lexer, block); - match(lexer, Token.Id.R_CURLY); + matchPunctuation(lexer, "}"); return block; } private Ast.Statement parseAssign(Lexer lexer) { Token tok = currentToken; - match(lexer, Token.Id.IDENTIFIER); + matchKind(lexer, Token.Kind.IDENT); Ast.Identifier identifier = new Ast.Identifier(tok); - match(lexer, Token.Id.ASN); + matchPunctuation(lexer, "="); var s = new Ast.Assign(identifier, parseBool(lexer)); - match(lexer, Token.Id.SEMICOLON); + matchPunctuation(lexer, ";"); return s; } private Ast.Expr parseBool(Lexer lexer) { var x = parseAnd(lexer); - while (currentToken.getId() == Token.Id.COND_OR) { + while (isToken(currentToken, "||")) { var tok = currentToken; nextToken(lexer); x = new Ast.Binary(tok, x, parseAnd(lexer)); @@ -170,7 +177,7 @@ public class Parser { private Ast.Expr parseAnd(Lexer lexer) { var x = parseRelational(lexer); - while (currentToken.getId() == Token.Id.COND_AND) { + while (isToken(currentToken, "&&")) { var tok = currentToken; nextToken(lexer); x = new Ast.Binary(tok, x, parseRelational(lexer)); @@ -180,12 +187,12 @@ public class Parser { private Ast.Expr parseRelational(Lexer lexer) { var x = parseAddition(lexer); - while (currentToken.getId() == Token.Id.COMP_EQ || - currentToken.getId() == Token.Id.COMP_NEQ || - currentToken.getId() == Token.Id.COMP_LT || - currentToken.getId() == Token.Id.COMP_GT || - currentToken.getId() == Token.Id.COMP_GTEQ || - currentToken.getId() == Token.Id.COMP_LTEQ) { + while (isToken(currentToken, "==") || + isToken(currentToken, "!=") || + isToken(currentToken, "<=") || + isToken(currentToken, "<") || + isToken(currentToken, ">") || + isToken(currentToken, ">=")) { var tok = currentToken; nextToken(lexer); x = new Ast.Binary(tok, x, parseAddition(lexer)); @@ -195,8 +202,8 @@ public class Parser { private Ast.Expr parseAddition(Lexer lexer) { var x = parseMultiplication(lexer); - while (currentToken.getId() == Token.Id.ADD || - currentToken.getId() == Token.Id.SUB) { + while (isToken(currentToken, "-") || + isToken(currentToken, "+")) { var tok = currentToken; nextToken(lexer); x = new Ast.Binary(tok, x, parseMultiplication(lexer)); @@ -206,8 +213,8 @@ public class Parser { private Ast.Expr parseMultiplication(Lexer lexer) { var x = parseUnary(lexer); - while (currentToken.getId() == Token.Id.MUL || - currentToken.getId() == Token.Id.DIV) { + while (isToken(currentToken, "*") || + isToken(currentToken, "/")) { var tok = currentToken; nextToken(lexer); x = new Ast.Binary(tok, x, parseUnary(lexer)); @@ -216,8 +223,8 @@ public class Parser { } private Ast.Expr parseUnary(Lexer lexer) { - if (currentToken.getId() == Token.Id.SUB || - currentToken.getId() == Token.Id.NOT) { + if (isToken(currentToken, "-") || + isToken(currentToken, "!")) { var tok = currentToken; nextToken(lexer); return new Ast.Unary(tok, parseUnary(lexer)); @@ -227,25 +234,27 @@ public class Parser { } private Ast.Expr parsePrimary(Lexer lexer) { - switch (currentToken.getId()) { - case L_PAREN -> { + switch (currentToken.kind) { + case PUNCT -> { + /* Nested expression */ + matchPunctuation(lexer, "("); nextToken(lexer); var x = parseBool(lexer); - match(lexer, Token.Id.R_PAREN); + matchPunctuation(lexer, ")"); return x; } - case LIT_INT -> { + case NUM -> { var x = new Ast.Constant(currentToken); nextToken(lexer); return x; } - case IDENTIFIER -> { + case IDENT -> { var x = new Ast.Symbol(currentToken); nextToken(lexer); return x; } default -> { - error(lexer, currentToken, "syntax error, expected nested expr, integer value or variable"); + error(currentToken, "syntax error, expected nested expr, integer value or variable"); return null; } } diff --git a/chapter01/src/main/java/com/seaofnodes/simple/parser/Token.java b/chapter01/src/main/java/com/seaofnodes/simple/parser/Token.java new file mode 100644 index 00000000..d45bef5a --- /dev/null +++ b/chapter01/src/main/java/com/seaofnodes/simple/parser/Token.java @@ -0,0 +1,63 @@ +/* + * DO NOT REMOVE COPYRIGHT NOTICES OR THIS HEADER. + *

+ * Contributor(s): + *

+ * The Initial Developer of the Original Software is Dibyendu Majumdar. + *

+ * This file is part of the Sea Of Nodes Simple Programming language + * implementation. See https://github.com/SeaOfNodes/Simple + *

+ * The contents of this file are subject to the terms of the + * Apache License Version 2 (the "APL"). You may not use this + * file except in compliance with the License. A copy of the + * APL may be obtained from: + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package com.seaofnodes.simple.parser; + +public class Token { + + enum Kind { + IDENT, + NUM, + PUNCT, + EOZ // Special kind to signal end of file + } + + public final Kind kind; + /** + * String representation of a token - always + * populated + */ + public final String str; + /** + * The parsed number value, only populated for Kind.NUM + */ + public final Number num; + + public Token(Kind kind, String str, Number num) { + this.kind = kind; + this.str = str; + this.num = num; + } + + public static Token newIdent(String str) { + return new Token(Kind.IDENT, str.intern(), null); + } + public static Token newNum(Number num, String str) { + return new Token(Kind.NUM, str, num); + } + public static Token newPunct(String str) { + return new Token(Kind.PUNCT, str.intern(), null); + } + + /** + * Special token that indicates that source has been exhausted + */ + public static Token EOF = new Token(Kind.EOZ, "", null); + + public String toString() { + return str; + } +} diff --git a/chapter01/src/test/java/com/seaofnodes/simple/lexer/TestParser.java b/chapter01/src/test/java/com/seaofnodes/simple/lexer/TestParser.java deleted file mode 100644 index 3f65e0f9..00000000 --- a/chapter01/src/test/java/com/seaofnodes/simple/lexer/TestParser.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * DO NOT REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Contributor(s): - * - * This file is part of the Sea Of Nodes Simple Programming language - * implementation. See https://github.com/SeaOfNodes/Simple - * - * The contents of this file are subject to the terms of the - * Apache License Version 2 (the "APL"). You may not use this - * file except in compliance with the License. A copy of the - * APL may be obtained from: - * http://www.apache.org/licenses/LICENSE-2.0 - */ - -package com.seaofnodes.simple.lexer; - -import com.seaofnodes.simple.lexer.ecstasy.ErrorListener; -import com.seaofnodes.simple.lexer.ecstasy.Lexer; -import com.seaofnodes.simple.lexer.ecstasy.Source; -import com.seaofnodes.simple.parser.Parser; -import org.junit.Assert; -import org.junit.Test; - -public class TestParser { - - @Test - public void testParseSingleDecl() - { - final String source = "int i;"; - Lexer lexer = new Lexer(new Source(source), new ErrorListener()); - Parser parser = new Parser(); - var ast = parser.parse(lexer); - Assert.assertEquals("{int i;}", ast.toString()); - } - - @Test - public void testParseTwoDecls() { - final String source = "int i;\nint j;"; - Lexer lexer = new Lexer(new Source(source), new ErrorListener()); - Parser parser = new Parser(); - var ast = parser.parse(lexer); - Assert.assertEquals("{int i;int j;}", ast.toString()); - } - - @Test - public void testIfAndWhile() { - final String source = - """ - int i; - int j; - - i = 0; - j = 100; - while ( 1 ) { - i = i + 1; - j = j - 1; - if ( i >= j ) break; - } - """; - Lexer lexer = new Lexer(new Source(source), new ErrorListener()); - Parser parser = new Parser(); - var ast = parser.parse(lexer); - Assert.assertEquals("{int i;int j;i=0;j=100;while(1) {i=(i+1);j=(j-1);if((i>=j)) break;}}", ast.toString()); - } -} diff --git a/chapter01/src/test/java/com/seaofnodes/simple/parser/TestParser.java b/chapter01/src/test/java/com/seaofnodes/simple/parser/TestParser.java new file mode 100644 index 00000000..356420eb --- /dev/null +++ b/chapter01/src/test/java/com/seaofnodes/simple/parser/TestParser.java @@ -0,0 +1,62 @@ +/* + * DO NOT REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Contributor(s): + * + * This file is part of the Sea Of Nodes Simple Programming language + * implementation. See https://github.com/SeaOfNodes/Simple + * + * The contents of this file are subject to the terms of the + * Apache License Version 2 (the "APL"). You may not use this + * file except in compliance with the License. A copy of the + * APL may be obtained from: + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +package com.seaofnodes.simple.parser; + +import org.junit.Assert; +import org.junit.Test; + +public class TestParser { + + @Test + public void testParseSingleDecl() + { + final String source = "int i;"; + Lexer lexer = new Lexer(source); + Parser parser = new Parser(); + var ast = parser.parse(lexer); + Assert.assertEquals("{int i;}", ast.toString()); + } + + @Test + public void testParseTwoDecls() { + final String source = "int i;\nint j;"; + Lexer lexer = new Lexer(source); + Parser parser = new Parser(); + var ast = parser.parse(lexer); + Assert.assertEquals("{int i;int j;}", ast.toString()); + } + + @Test + public void testIfAndWhile() { + final String source = + """ + int i; + int j; + + i = 0; + j = 100; + while ( 1 ) { + i = i + 1; + j = j - 1; + if ( i >= j ) break; + } + """; + Lexer lexer = new Lexer(source); + Parser parser = new Parser(); + var ast = parser.parse(lexer); + Assert.assertEquals("{int i;int j;i=0;j=100;while(1) {i=(i+1);j=(j-1);if((i>=j)) break;}}", ast.toString()); + } +} -- cgit v1.2.3 From ddb1f805ed921bc5319b7f08f1e98f353cac1713 Mon Sep 17 00:00:00 2001 From: dibyendumajumdar Date: Thu, 7 Sep 2023 19:35:57 +0100 Subject: Renamed Ast to AST. --- .../java/com/seaofnodes/simple/parser/AST.java | 211 +++++++++++++++++++++ .../java/com/seaofnodes/simple/parser/Ast.java | 211 --------------------- .../java/com/seaofnodes/simple/parser/Parser.java | 74 ++++---- 3 files changed, 248 insertions(+), 248 deletions(-) create mode 100644 chapter01/src/main/java/com/seaofnodes/simple/parser/AST.java delete mode 100644 chapter01/src/main/java/com/seaofnodes/simple/parser/Ast.java (limited to 'chapter01') diff --git a/chapter01/src/main/java/com/seaofnodes/simple/parser/AST.java b/chapter01/src/main/java/com/seaofnodes/simple/parser/AST.java new file mode 100644 index 00000000..4e456416 --- /dev/null +++ b/chapter01/src/main/java/com/seaofnodes/simple/parser/AST.java @@ -0,0 +1,211 @@ +/* + * DO NOT REMOVE COPYRIGHT NOTICES OR THIS HEADER. + *

+ * Contributor(s): + *

+ * The Initial Developer of the Original Software is Dibyendu Majumdar. + *

+ * This file is part of the Sea Of Nodes Simple Programming language + * implementation. See https://github.com/SeaOfNodes/Simple + *

+ * The contents of this file are subject to the terms of the + * Apache License Version 2 (the "APL"). You may not use this + * file except in compliance with the License. A copy of the + * APL may be obtained from: + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package com.seaofnodes.simple.parser; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * A simple AST definition. + */ +public class AST { + + protected AST() { + } + + + public abstract static class Expr extends AST { + public final Token op; + + protected Expr(Token op) { + this.op = op; + } + } + + /** + * A name in the language + */ + public static class Identifier extends Expr { + public final String name; + + public Identifier(Token op) { + super(op); + name = op.str; + } + + @Override + public String toString() { + return name; + } + } + + /** + * Reference to a name in the language + */ + public static class Symbol extends Expr { + public final String name; + + public Symbol(Token op) { + super(op); + name = op.str; + } + + @Override + public String toString() { + return name; + } + } + + public static class Binary extends Expr { + public final Expr expr1; + public final Expr expr2; + + public Binary(Token op, Expr expr1, Expr expr2) { + super(op); + this.expr1 = expr1; + this.expr2 = expr2; + } + + @Override + public String toString() { + return "(" + expr1 + op.toString() + expr2 + ")"; + } + } + + public static class Unary extends Expr { + public final Expr expr; + + public Unary(Token op, Expr expr) { + super(op); + this.expr = expr; + } + + @Override + public String toString() { + return "(" + op.toString() + "(" + expr + "))"; + } + } + + public static class Constant extends Expr { + public Constant(Token op) { + super(op); + } + + @Override + public String toString() { + return op.toString(); + } + } + + public abstract static class Statement extends AST { + } + + public static class IfElse extends Statement { + public final Expr expr; + public final Statement ifStmt; + public final Statement elseStmt; + + public IfElse(Expr expr, Statement ifStmt, Statement elseStmt) { + this.expr = expr; + this.ifStmt = ifStmt; + this.elseStmt = elseStmt; + } + + @Override + public String toString() { + if (elseStmt != null) + return "if(" + expr + ") " + ifStmt + " else " + elseStmt; + return "if(" + expr + ") " + ifStmt; + } + } + + public static class While extends Statement { + public final Expr expr; + public final Statement stmt; + + public While(Expr expr, Statement stmt) { + this.expr = expr; + this.stmt = stmt; + } + + @Override + public String toString() { + return "while(" + expr.toString() + ") " + stmt; + } + } + + public static class Break extends Statement { + @Override + public String toString() { + return "break;"; + } + } + + public static class Assign extends Statement { + public final Identifier identifier; + public final Expr expr; + + public Assign(Identifier identifier, Expr expr) { + this.identifier = identifier; + this.expr = expr; + } + + @Override + public String toString() { + return identifier.toString() + "=" + expr.toString() + ";"; + } + } + + public static class Type extends AST { + public final String typeId; + + public Type(String typeId) { + this.typeId = typeId; + } + + @Override + public String toString() { + return typeId; + } + } + + public static class Declare extends Statement { + public final Type type; + public final Identifier identifier; + + public Declare(Type type, Identifier identifier) { + this.type = type; + this.identifier = identifier; + } + + @Override + public String toString() { + return type.toString() + " " + identifier.toString() + ";"; + } + } + + public static class Block extends Statement { + public final List stmtList = new ArrayList<>(); + + @Override + public String toString() { + return "{" + + stmtList.stream().map(Object::toString).collect(Collectors.joining()) + "}"; + } + } +} diff --git a/chapter01/src/main/java/com/seaofnodes/simple/parser/Ast.java b/chapter01/src/main/java/com/seaofnodes/simple/parser/Ast.java deleted file mode 100644 index c271d4b6..00000000 --- a/chapter01/src/main/java/com/seaofnodes/simple/parser/Ast.java +++ /dev/null @@ -1,211 +0,0 @@ -/* - * DO NOT REMOVE COPYRIGHT NOTICES OR THIS HEADER. - *

- * Contributor(s): - *

- * The Initial Developer of the Original Software is Dibyendu Majumdar. - *

- * This file is part of the Sea Of Nodes Simple Programming language - * implementation. See https://github.com/SeaOfNodes/Simple - *

- * The contents of this file are subject to the terms of the - * Apache License Version 2 (the "APL"). You may not use this - * file except in compliance with the License. A copy of the - * APL may be obtained from: - * http://www.apache.org/licenses/LICENSE-2.0 - */ -package com.seaofnodes.simple.parser; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -/** - * A simple AST definition. - */ -public class Ast { - - protected Ast() { - } - - - public abstract static class Expr extends Ast { - public final Token op; - - protected Expr(Token op) { - this.op = op; - } - } - - /** - * A name in the language - */ - public static class Identifier extends Expr { - public final String name; - - public Identifier(Token op) { - super(op); - name = op.str; - } - - @Override - public String toString() { - return name; - } - } - - /** - * Reference to a name in the language - */ - public static class Symbol extends Expr { - public final String name; - - public Symbol(Token op) { - super(op); - name = op.str; - } - - @Override - public String toString() { - return name; - } - } - - public static class Binary extends Expr { - public final Expr expr1; - public final Expr expr2; - - public Binary(Token op, Expr expr1, Expr expr2) { - super(op); - this.expr1 = expr1; - this.expr2 = expr2; - } - - @Override - public String toString() { - return "(" + expr1 + op.toString() + expr2 + ")"; - } - } - - public static class Unary extends Expr { - public final Expr expr; - - public Unary(Token op, Expr expr) { - super(op); - this.expr = expr; - } - - @Override - public String toString() { - return "(" + op.toString() + "(" + expr + "))"; - } - } - - public static class Constant extends Expr { - public Constant(Token op) { - super(op); - } - - @Override - public String toString() { - return op.toString(); - } - } - - public abstract static class Statement extends Ast { - } - - public static class IfElse extends Statement { - public final Expr expr; - public final Statement ifStmt; - public final Statement elseStmt; - - public IfElse(Expr expr, Statement ifStmt, Statement elseStmt) { - this.expr = expr; - this.ifStmt = ifStmt; - this.elseStmt = elseStmt; - } - - @Override - public String toString() { - if (elseStmt != null) - return "if(" + expr + ") " + ifStmt + " else " + elseStmt; - return "if(" + expr + ") " + ifStmt; - } - } - - public static class While extends Statement { - public final Expr expr; - public final Statement stmt; - - public While(Expr expr, Statement stmt) { - this.expr = expr; - this.stmt = stmt; - } - - @Override - public String toString() { - return "while(" + expr.toString() + ") " + stmt; - } - } - - public static class Break extends Statement { - @Override - public String toString() { - return "break;"; - } - } - - public static class Assign extends Statement { - public final Identifier identifier; - public final Expr expr; - - public Assign(Identifier identifier, Expr expr) { - this.identifier = identifier; - this.expr = expr; - } - - @Override - public String toString() { - return identifier.toString() + "=" + expr.toString() + ";"; - } - } - - public static class Type extends Ast { - public final String typeId; - - public Type(String typeId) { - this.typeId = typeId; - } - - @Override - public String toString() { - return typeId; - } - } - - public static class Declare extends Statement { - public final Type type; - public final Identifier identifier; - - public Declare(Type type, Identifier identifier) { - this.type = type; - this.identifier = identifier; - } - - @Override - public String toString() { - return type.toString() + " " + identifier.toString() + ";"; - } - } - - public static class Block extends Statement { - public final List stmtList = new ArrayList<>(); - - @Override - public String toString() { - return "{" + - stmtList.stream().map(Object::toString).collect(Collectors.joining()) + "}"; - } - } -} diff --git a/chapter01/src/main/java/com/seaofnodes/simple/parser/Parser.java b/chapter01/src/main/java/com/seaofnodes/simple/parser/Parser.java index 7e333b71..d8f80090 100644 --- a/chapter01/src/main/java/com/seaofnodes/simple/parser/Parser.java +++ b/chapter01/src/main/java/com/seaofnodes/simple/parser/Parser.java @@ -23,9 +23,9 @@ public class Parser { * Parse and return a top level Ast * At present this is an Ast.Block * - * @see Ast.Block + * @see AST.Block */ - public Ast parse(Lexer lexer) { + public AST parse(Lexer lexer) { nextToken(lexer); return parseProgram(lexer); } @@ -66,8 +66,8 @@ public class Parser { return token.str.equals(value); } - private Ast.Statement parseProgram(Lexer lexer) { - var block = new Ast.Block(); + private AST.Statement parseProgram(Lexer lexer) { + var block = new AST.Block(); parseDeclarations(lexer, block); parseStatements(lexer, block); return block; @@ -80,15 +80,15 @@ public class Parser { isToken(tok, "float")); } - private void parseDeclarations(Lexer lexer, Ast.Block block) { + private void parseDeclarations(Lexer lexer, AST.Block block) { while (isTypeName(currentToken)) { - var type = new Ast.Type(currentToken.str); + var type = new AST.Type(currentToken.str); nextToken(lexer); var tok = currentToken; matchKind(lexer, Token.Kind.IDENT); matchPunctuation(lexer, ";"); - var identifier = new Ast.Identifier(tok); - block.stmtList.add(new Ast.Declare(type, identifier)); + var identifier = new AST.Identifier(tok); + block.stmtList.add(new AST.Declare(type, identifier)); } } @@ -100,16 +100,16 @@ public class Parser { return tok.kind == Token.Kind.PUNCT && isToken(tok, str); } - private void parseStatements(Lexer lexer, Ast.Block block) { + private void parseStatements(Lexer lexer, AST.Block block) { while (!isEOF(currentToken) && !isPunct(currentToken, "}")) { block.stmtList.add(parseStatement(lexer)); } } - private Ast.Statement parseStatement(Lexer lexer) { - Ast.Expr x; - Ast.Statement s1; - Ast.Statement s2; + private AST.Statement parseStatement(Lexer lexer) { + AST.Expr x; + AST.Statement s1; + AST.Statement s2; switch (currentToken.str) { case "if" -> { @@ -119,10 +119,10 @@ public class Parser { matchPunctuation(lexer, ")"); s1 = parseStatement(lexer); if (!isToken(currentToken, "else")) { - return new Ast.IfElse(x, s1, null); + return new AST.IfElse(x, s1, null); } s2 = parseStatement(lexer); - return new Ast.IfElse(x, s1, s2); + return new AST.IfElse(x, s1, s2); } case "while" -> { matchIdentifier(lexer, "while"); @@ -130,12 +130,12 @@ public class Parser { x = parseBool(lexer); matchPunctuation(lexer, ")"); s1 = parseStatement(lexer); - return new Ast.While(x, s1); + return new AST.While(x, s1); } case "break" -> { matchIdentifier(lexer, "break"); matchPunctuation(lexer, ";"); - return new Ast.Break(); + return new AST.Break(); } case "{" -> { return parseBlock(lexer); @@ -146,46 +146,46 @@ public class Parser { } } - private Ast.Statement parseBlock(Lexer lexer) { + private AST.Statement parseBlock(Lexer lexer) { matchPunctuation(lexer, "{"); - var block = new Ast.Block(); + var block = new AST.Block(); parseDeclarations(lexer, block); parseStatements(lexer, block); matchPunctuation(lexer, "}"); return block; } - private Ast.Statement parseAssign(Lexer lexer) { + private AST.Statement parseAssign(Lexer lexer) { Token tok = currentToken; matchKind(lexer, Token.Kind.IDENT); - Ast.Identifier identifier = new Ast.Identifier(tok); + AST.Identifier identifier = new AST.Identifier(tok); matchPunctuation(lexer, "="); - var s = new Ast.Assign(identifier, parseBool(lexer)); + var s = new AST.Assign(identifier, parseBool(lexer)); matchPunctuation(lexer, ";"); return s; } - private Ast.Expr parseBool(Lexer lexer) { + private AST.Expr parseBool(Lexer lexer) { var x = parseAnd(lexer); while (isToken(currentToken, "||")) { var tok = currentToken; nextToken(lexer); - x = new Ast.Binary(tok, x, parseAnd(lexer)); + x = new AST.Binary(tok, x, parseAnd(lexer)); } return x; } - private Ast.Expr parseAnd(Lexer lexer) { + private AST.Expr parseAnd(Lexer lexer) { var x = parseRelational(lexer); while (isToken(currentToken, "&&")) { var tok = currentToken; nextToken(lexer); - x = new Ast.Binary(tok, x, parseRelational(lexer)); + x = new AST.Binary(tok, x, parseRelational(lexer)); } return x; } - private Ast.Expr parseRelational(Lexer lexer) { + private AST.Expr parseRelational(Lexer lexer) { var x = parseAddition(lexer); while (isToken(currentToken, "==") || isToken(currentToken, "!=") || @@ -195,45 +195,45 @@ public class Parser { isToken(currentToken, ">=")) { var tok = currentToken; nextToken(lexer); - x = new Ast.Binary(tok, x, parseAddition(lexer)); + x = new AST.Binary(tok, x, parseAddition(lexer)); } return x; } - private Ast.Expr parseAddition(Lexer lexer) { + private AST.Expr parseAddition(Lexer lexer) { var x = parseMultiplication(lexer); while (isToken(currentToken, "-") || isToken(currentToken, "+")) { var tok = currentToken; nextToken(lexer); - x = new Ast.Binary(tok, x, parseMultiplication(lexer)); + x = new AST.Binary(tok, x, parseMultiplication(lexer)); } return x; } - private Ast.Expr parseMultiplication(Lexer lexer) { + private AST.Expr parseMultiplication(Lexer lexer) { var x = parseUnary(lexer); while (isToken(currentToken, "*") || isToken(currentToken, "/")) { var tok = currentToken; nextToken(lexer); - x = new Ast.Binary(tok, x, parseUnary(lexer)); + x = new AST.Binary(tok, x, parseUnary(lexer)); } return x; } - private Ast.Expr parseUnary(Lexer lexer) { + private AST.Expr parseUnary(Lexer lexer) { if (isToken(currentToken, "-") || isToken(currentToken, "!")) { var tok = currentToken; nextToken(lexer); - return new Ast.Unary(tok, parseUnary(lexer)); + return new AST.Unary(tok, parseUnary(lexer)); } else { return parsePrimary(lexer); } } - private Ast.Expr parsePrimary(Lexer lexer) { + private AST.Expr parsePrimary(Lexer lexer) { switch (currentToken.kind) { case PUNCT -> { /* Nested expression */ @@ -244,12 +244,12 @@ public class Parser { return x; } case NUM -> { - var x = new Ast.Constant(currentToken); + var x = new AST.Constant(currentToken); nextToken(lexer); return x; } case IDENT -> { - var x = new Ast.Symbol(currentToken); + var x = new AST.Symbol(currentToken); nextToken(lexer); return x; } -- cgit v1.2.3 From 195ebcc0ce1fa2e0967182dfd10506135cff6ded Mon Sep 17 00:00:00 2001 From: dibyendumajumdar Date: Thu, 7 Sep 2023 19:39:50 +0100 Subject: Corrected README --- chapter01/README.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) (limited to 'chapter01') diff --git a/chapter01/README.md b/chapter01/README.md index d21a55e8..a641c4ad 100644 --- a/chapter01/README.md +++ b/chapter01/README.md @@ -1,10 +1,5 @@ # Chapter 1 -Simple control flow statements +Simple start and end node for basic empty script. -The chapter will implement: -* Control flow statements, `if` and `while`. -* Variables of `int` type. -* Simple arithmetic such as addition, subtraction, multiplication, division. -* Non-zero values will be truthy. * [Grammar](docs/01-grammar.md) \ No newline at end of file -- cgit v1.2.3 From dbd9798602c3b80bc00c93c797b942dcb0e234c1 Mon Sep 17 00:00:00 2001 From: dibyendumajumdar Date: Thu, 7 Sep 2023 22:28:11 +0100 Subject: Drop AST and parser and lexer --- .../java/com/seaofnodes/simple/parser/AST.java | 211 ----------------- .../simple/parser/CompilerException.java | 27 --- .../java/com/seaofnodes/simple/parser/Lexer.java | 151 ------------ .../java/com/seaofnodes/simple/parser/Parser.java | 262 --------------------- .../java/com/seaofnodes/simple/parser/Token.java | 63 ----- .../com/seaofnodes/simple/parser/TestParser.java | 62 ----- 6 files changed, 776 deletions(-) delete mode 100644 chapter01/src/main/java/com/seaofnodes/simple/parser/AST.java delete mode 100644 chapter01/src/main/java/com/seaofnodes/simple/parser/CompilerException.java delete mode 100644 chapter01/src/main/java/com/seaofnodes/simple/parser/Lexer.java delete mode 100644 chapter01/src/main/java/com/seaofnodes/simple/parser/Parser.java delete mode 100644 chapter01/src/main/java/com/seaofnodes/simple/parser/Token.java delete mode 100644 chapter01/src/test/java/com/seaofnodes/simple/parser/TestParser.java (limited to 'chapter01') diff --git a/chapter01/src/main/java/com/seaofnodes/simple/parser/AST.java b/chapter01/src/main/java/com/seaofnodes/simple/parser/AST.java deleted file mode 100644 index 4e456416..00000000 --- a/chapter01/src/main/java/com/seaofnodes/simple/parser/AST.java +++ /dev/null @@ -1,211 +0,0 @@ -/* - * DO NOT REMOVE COPYRIGHT NOTICES OR THIS HEADER. - *

- * Contributor(s): - *

- * The Initial Developer of the Original Software is Dibyendu Majumdar. - *

- * This file is part of the Sea Of Nodes Simple Programming language - * implementation. See https://github.com/SeaOfNodes/Simple - *

- * The contents of this file are subject to the terms of the - * Apache License Version 2 (the "APL"). You may not use this - * file except in compliance with the License. A copy of the - * APL may be obtained from: - * http://www.apache.org/licenses/LICENSE-2.0 - */ -package com.seaofnodes.simple.parser; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -/** - * A simple AST definition. - */ -public class AST { - - protected AST() { - } - - - public abstract static class Expr extends AST { - public final Token op; - - protected Expr(Token op) { - this.op = op; - } - } - - /** - * A name in the language - */ - public static class Identifier extends Expr { - public final String name; - - public Identifier(Token op) { - super(op); - name = op.str; - } - - @Override - public String toString() { - return name; - } - } - - /** - * Reference to a name in the language - */ - public static class Symbol extends Expr { - public final String name; - - public Symbol(Token op) { - super(op); - name = op.str; - } - - @Override - public String toString() { - return name; - } - } - - public static class Binary extends Expr { - public final Expr expr1; - public final Expr expr2; - - public Binary(Token op, Expr expr1, Expr expr2) { - super(op); - this.expr1 = expr1; - this.expr2 = expr2; - } - - @Override - public String toString() { - return "(" + expr1 + op.toString() + expr2 + ")"; - } - } - - public static class Unary extends Expr { - public final Expr expr; - - public Unary(Token op, Expr expr) { - super(op); - this.expr = expr; - } - - @Override - public String toString() { - return "(" + op.toString() + "(" + expr + "))"; - } - } - - public static class Constant extends Expr { - public Constant(Token op) { - super(op); - } - - @Override - public String toString() { - return op.toString(); - } - } - - public abstract static class Statement extends AST { - } - - public static class IfElse extends Statement { - public final Expr expr; - public final Statement ifStmt; - public final Statement elseStmt; - - public IfElse(Expr expr, Statement ifStmt, Statement elseStmt) { - this.expr = expr; - this.ifStmt = ifStmt; - this.elseStmt = elseStmt; - } - - @Override - public String toString() { - if (elseStmt != null) - return "if(" + expr + ") " + ifStmt + " else " + elseStmt; - return "if(" + expr + ") " + ifStmt; - } - } - - public static class While extends Statement { - public final Expr expr; - public final Statement stmt; - - public While(Expr expr, Statement stmt) { - this.expr = expr; - this.stmt = stmt; - } - - @Override - public String toString() { - return "while(" + expr.toString() + ") " + stmt; - } - } - - public static class Break extends Statement { - @Override - public String toString() { - return "break;"; - } - } - - public static class Assign extends Statement { - public final Identifier identifier; - public final Expr expr; - - public Assign(Identifier identifier, Expr expr) { - this.identifier = identifier; - this.expr = expr; - } - - @Override - public String toString() { - return identifier.toString() + "=" + expr.toString() + ";"; - } - } - - public static class Type extends AST { - public final String typeId; - - public Type(String typeId) { - this.typeId = typeId; - } - - @Override - public String toString() { - return typeId; - } - } - - public static class Declare extends Statement { - public final Type type; - public final Identifier identifier; - - public Declare(Type type, Identifier identifier) { - this.type = type; - this.identifier = identifier; - } - - @Override - public String toString() { - return type.toString() + " " + identifier.toString() + ";"; - } - } - - public static class Block extends Statement { - public final List stmtList = new ArrayList<>(); - - @Override - public String toString() { - return "{" + - stmtList.stream().map(Object::toString).collect(Collectors.joining()) + "}"; - } - } -} diff --git a/chapter01/src/main/java/com/seaofnodes/simple/parser/CompilerException.java b/chapter01/src/main/java/com/seaofnodes/simple/parser/CompilerException.java deleted file mode 100644 index a62fa034..00000000 --- a/chapter01/src/main/java/com/seaofnodes/simple/parser/CompilerException.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * DO NOT REMOVE COPYRIGHT NOTICES OR THIS HEADER. - *

- * Contributor(s): - *

- * The Initial Developer of the Original Software is Dibyendu Majumdar. - *

- * This file is part of the Sea Of Nodes Simple Programming language - * implementation. See https://github.com/SeaOfNodes/Simple - *

- * The contents of this file are subject to the terms of the - * Apache License Version 2 (the "APL"). You may not use this - * file except in compliance with the License. A copy of the - * APL may be obtained from: - * http://www.apache.org/licenses/LICENSE-2.0 - */ -package com.seaofnodes.simple.parser; - -public class CompilerException extends RuntimeException { - public CompilerException(String message) { - super(message); - } - - public CompilerException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/chapter01/src/main/java/com/seaofnodes/simple/parser/Lexer.java b/chapter01/src/main/java/com/seaofnodes/simple/parser/Lexer.java deleted file mode 100644 index fbe8c239..00000000 --- a/chapter01/src/main/java/com/seaofnodes/simple/parser/Lexer.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * DO NOT REMOVE COPYRIGHT NOTICES OR THIS HEADER. - *

- * Contributor(s): - *

- * The Initial Developer of the Original Software is Dibyendu Majumdar. - *

- * This file is part of the Sea Of Nodes Simple Programming language - * implementation. See https://github.com/SeaOfNodes/Simple - *

- * The contents of this file are subject to the terms of the - * Apache License Version 2 (the "APL"). You may not use this - * file except in compliance with the License. A copy of the - * APL may be obtained from: - * http://www.apache.org/licenses/LICENSE-2.0 - */ -package com.seaofnodes.simple.parser; - -import java.text.NumberFormat; -import java.text.ParseException; - -public class Lexer { - - private final char[] input; - /** - * Tracks current position in input buffer - */ - private int position = 0; - private char peek = ' '; - - private final NumberFormat numberFormat; - - public Lexer(String source) { - input = source.toCharArray(); - numberFormat = NumberFormat.getInstance(); - numberFormat.setGroupingUsed(false); - } - - private boolean isEOF() { - return position >= input.length; - } - - private char advance() { - if (isEOF()) - return 0; // Special value that causes parsing to terminate - return input[position++]; - } - - private boolean readch() { - peek = advance(); - return true; - } - - private boolean readch(char c) { - if (readch() && peek != c) - return false; - peek = ' '; - return true; - } - - /** - * Note that EOF is not whitespace! - */ - private boolean isWhiteSpace(char c) { - return c == ' ' || - c == '\t' || - c == '\n' || - c == '\r'; - } - - private void skipWhitespace() { - while (isWhiteSpace(peek)) - readch(); - } - - /** - * Parses number in format nnn[.nnn] - * where n is a digit - */ - private Token parseNumber() { - assert Character.isDigit(peek); - StringBuilder sb = new StringBuilder(); - sb.append(peek); - while (readch() && Character.isDigit(peek)) - sb.append(peek); - if (peek == '.') { - sb.append(peek); - while (readch() && Character.isDigit(peek)) - sb.append(peek); - } - String str = sb.toString(); - Number number = parseNumber(str); - return Token.newNum(number, str); - } - - private Number parseNumber(String str) { - try { - return numberFormat.parse(str); - } catch (ParseException e) { - throw new CompilerException("Failed to parse number " + str, e); - } - } - - private boolean isIdentifierStart(char ch) { - return Character.isAlphabetic(ch) || ch == '_'; - } - - private boolean isIdentifierLetter(char ch) { - return Character.isLetterOrDigit(ch) || ch == '_'; - } - - private Token parseIdentifier() { - assert isIdentifierStart(peek); - StringBuilder sb = new StringBuilder(); - sb.append(peek); - while (readch() && isIdentifierLetter(peek)) - sb.append(peek); - return Token.newIdent(sb.toString()); - } - - public Token scan() { - skipWhitespace(); - switch (peek) { - case 0: - return Token.EOF; - case '&': - return readch('&') ? Token.newPunct("&&") : Token.newPunct("&"); - case '|': - return readch('|') ? Token.newPunct("||") : Token.newPunct("|"); - case '=': - return readch('=') ? Token.newPunct("==") : Token.newPunct("="); - case '<': - return readch('=') ? Token.newPunct("<=") : Token.newPunct("<"); - case '>': - return readch('=') ? Token.newPunct(">=") : Token.newPunct(">"); - default: { - return scanOthers(); - } - } - } - - private Token scanOthers() { - if (Character.isDigit(peek)) - return parseNumber(); - else if (isIdentifierLetter(peek)) - return parseIdentifier(); - Token token = Token.newPunct(Character.toString(peek)); - peek = ' '; - return token; - } -} diff --git a/chapter01/src/main/java/com/seaofnodes/simple/parser/Parser.java b/chapter01/src/main/java/com/seaofnodes/simple/parser/Parser.java deleted file mode 100644 index d8f80090..00000000 --- a/chapter01/src/main/java/com/seaofnodes/simple/parser/Parser.java +++ /dev/null @@ -1,262 +0,0 @@ -/* - * DO NOT REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Contributor(s): - * - * The Initial Developer of the Original Software is Dibyendu Majumdar. - * - * This file is part of the Sea Of Nodes Simple Programming language - * implementation. See https://github.com/SeaOfNodes/Simple - * - * The contents of this file are subject to the terms of the - * Apache License Version 2 (the "APL"). You may not use this - * file except in compliance with the License. A copy of the - * APL may be obtained from: - * http://www.apache.org/licenses/LICENSE-2.0 - */ -package com.seaofnodes.simple.parser; - -public class Parser { - private Token currentToken; - - /** - * Parse and return a top level Ast - * At present this is an Ast.Block - * - * @see AST.Block - */ - public AST parse(Lexer lexer) { - nextToken(lexer); - return parseProgram(lexer); - } - - private void nextToken(Lexer lexer) { - currentToken = lexer.scan(); - } - - private void error(Token t, String errorMessage) { - throw new CompilerException(errorMessage + ": " + t.toString()); - } - - private void matchKind(Lexer lexer, Token.Kind kind) { - if (currentToken.kind == kind) { - nextToken(lexer); - } else { - error(currentToken, "syntax error, expected " + kind); - } - } - - private void matchPunctuation(Lexer lexer, String value) { - if (currentToken.kind == Token.Kind.PUNCT && isToken(currentToken, value)) { - nextToken(lexer); - } else { - error(currentToken, "syntax error, expected " + value + " got " + currentToken.str); - } - } - - private void matchIdentifier(Lexer lexer, String identifier) { - if (currentToken.kind == Token.Kind.IDENT && isToken(currentToken, identifier)) { - nextToken(lexer); - } else { - error(currentToken, "syntax error, expected " + identifier); - } - } - - private boolean isToken(Token token, String value) { - return token.str.equals(value); - } - - private AST.Statement parseProgram(Lexer lexer) { - var block = new AST.Block(); - parseDeclarations(lexer, block); - parseStatements(lexer, block); - return block; - } - - private boolean isTypeName(Token tok) { - return tok.kind == Token.Kind.IDENT && - (isToken(tok, "int") || - isToken(tok, "char") || - isToken(tok, "float")); - } - - private void parseDeclarations(Lexer lexer, AST.Block block) { - while (isTypeName(currentToken)) { - var type = new AST.Type(currentToken.str); - nextToken(lexer); - var tok = currentToken; - matchKind(lexer, Token.Kind.IDENT); - matchPunctuation(lexer, ";"); - var identifier = new AST.Identifier(tok); - block.stmtList.add(new AST.Declare(type, identifier)); - } - } - - private boolean isEOF(Token tok) { - return tok.kind == Token.Kind.EOZ; - } - - private boolean isPunct(Token tok, String str) { - return tok.kind == Token.Kind.PUNCT && isToken(tok, str); - } - - private void parseStatements(Lexer lexer, AST.Block block) { - while (!isEOF(currentToken) && !isPunct(currentToken, "}")) { - block.stmtList.add(parseStatement(lexer)); - } - } - - private AST.Statement parseStatement(Lexer lexer) { - AST.Expr x; - AST.Statement s1; - AST.Statement s2; - - switch (currentToken.str) { - case "if" -> { - matchIdentifier(lexer, "if"); - matchPunctuation(lexer, "("); - x = parseBool(lexer); - matchPunctuation(lexer, ")"); - s1 = parseStatement(lexer); - if (!isToken(currentToken, "else")) { - return new AST.IfElse(x, s1, null); - } - s2 = parseStatement(lexer); - return new AST.IfElse(x, s1, s2); - } - case "while" -> { - matchIdentifier(lexer, "while"); - matchPunctuation(lexer, "("); - x = parseBool(lexer); - matchPunctuation(lexer, ")"); - s1 = parseStatement(lexer); - return new AST.While(x, s1); - } - case "break" -> { - matchIdentifier(lexer, "break"); - matchPunctuation(lexer, ";"); - return new AST.Break(); - } - case "{" -> { - return parseBlock(lexer); - } - default -> { - return parseAssign(lexer); - } - } - } - - private AST.Statement parseBlock(Lexer lexer) { - matchPunctuation(lexer, "{"); - var block = new AST.Block(); - parseDeclarations(lexer, block); - parseStatements(lexer, block); - matchPunctuation(lexer, "}"); - return block; - } - - private AST.Statement parseAssign(Lexer lexer) { - Token tok = currentToken; - matchKind(lexer, Token.Kind.IDENT); - AST.Identifier identifier = new AST.Identifier(tok); - matchPunctuation(lexer, "="); - var s = new AST.Assign(identifier, parseBool(lexer)); - matchPunctuation(lexer, ";"); - return s; - } - - private AST.Expr parseBool(Lexer lexer) { - var x = parseAnd(lexer); - while (isToken(currentToken, "||")) { - var tok = currentToken; - nextToken(lexer); - x = new AST.Binary(tok, x, parseAnd(lexer)); - } - return x; - } - - private AST.Expr parseAnd(Lexer lexer) { - var x = parseRelational(lexer); - while (isToken(currentToken, "&&")) { - var tok = currentToken; - nextToken(lexer); - x = new AST.Binary(tok, x, parseRelational(lexer)); - } - return x; - } - - private AST.Expr parseRelational(Lexer lexer) { - var x = parseAddition(lexer); - while (isToken(currentToken, "==") || - isToken(currentToken, "!=") || - isToken(currentToken, "<=") || - isToken(currentToken, "<") || - isToken(currentToken, ">") || - isToken(currentToken, ">=")) { - var tok = currentToken; - nextToken(lexer); - x = new AST.Binary(tok, x, parseAddition(lexer)); - } - return x; - } - - private AST.Expr parseAddition(Lexer lexer) { - var x = parseMultiplication(lexer); - while (isToken(currentToken, "-") || - isToken(currentToken, "+")) { - var tok = currentToken; - nextToken(lexer); - x = new AST.Binary(tok, x, parseMultiplication(lexer)); - } - return x; - } - - private AST.Expr parseMultiplication(Lexer lexer) { - var x = parseUnary(lexer); - while (isToken(currentToken, "*") || - isToken(currentToken, "/")) { - var tok = currentToken; - nextToken(lexer); - x = new AST.Binary(tok, x, parseUnary(lexer)); - } - return x; - } - - private AST.Expr parseUnary(Lexer lexer) { - if (isToken(currentToken, "-") || - isToken(currentToken, "!")) { - var tok = currentToken; - nextToken(lexer); - return new AST.Unary(tok, parseUnary(lexer)); - } else { - return parsePrimary(lexer); - } - } - - private AST.Expr parsePrimary(Lexer lexer) { - switch (currentToken.kind) { - case PUNCT -> { - /* Nested expression */ - matchPunctuation(lexer, "("); - nextToken(lexer); - var x = parseBool(lexer); - matchPunctuation(lexer, ")"); - return x; - } - case NUM -> { - var x = new AST.Constant(currentToken); - nextToken(lexer); - return x; - } - case IDENT -> { - var x = new AST.Symbol(currentToken); - nextToken(lexer); - return x; - } - default -> { - error(currentToken, "syntax error, expected nested expr, integer value or variable"); - return null; - } - } - } -} diff --git a/chapter01/src/main/java/com/seaofnodes/simple/parser/Token.java b/chapter01/src/main/java/com/seaofnodes/simple/parser/Token.java deleted file mode 100644 index d45bef5a..00000000 --- a/chapter01/src/main/java/com/seaofnodes/simple/parser/Token.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * DO NOT REMOVE COPYRIGHT NOTICES OR THIS HEADER. - *

- * Contributor(s): - *

- * The Initial Developer of the Original Software is Dibyendu Majumdar. - *

- * This file is part of the Sea Of Nodes Simple Programming language - * implementation. See https://github.com/SeaOfNodes/Simple - *

- * The contents of this file are subject to the terms of the - * Apache License Version 2 (the "APL"). You may not use this - * file except in compliance with the License. A copy of the - * APL may be obtained from: - * http://www.apache.org/licenses/LICENSE-2.0 - */ -package com.seaofnodes.simple.parser; - -public class Token { - - enum Kind { - IDENT, - NUM, - PUNCT, - EOZ // Special kind to signal end of file - } - - public final Kind kind; - /** - * String representation of a token - always - * populated - */ - public final String str; - /** - * The parsed number value, only populated for Kind.NUM - */ - public final Number num; - - public Token(Kind kind, String str, Number num) { - this.kind = kind; - this.str = str; - this.num = num; - } - - public static Token newIdent(String str) { - return new Token(Kind.IDENT, str.intern(), null); - } - public static Token newNum(Number num, String str) { - return new Token(Kind.NUM, str, num); - } - public static Token newPunct(String str) { - return new Token(Kind.PUNCT, str.intern(), null); - } - - /** - * Special token that indicates that source has been exhausted - */ - public static Token EOF = new Token(Kind.EOZ, "", null); - - public String toString() { - return str; - } -} diff --git a/chapter01/src/test/java/com/seaofnodes/simple/parser/TestParser.java b/chapter01/src/test/java/com/seaofnodes/simple/parser/TestParser.java deleted file mode 100644 index 356420eb..00000000 --- a/chapter01/src/test/java/com/seaofnodes/simple/parser/TestParser.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * DO NOT REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Contributor(s): - * - * This file is part of the Sea Of Nodes Simple Programming language - * implementation. See https://github.com/SeaOfNodes/Simple - * - * The contents of this file are subject to the terms of the - * Apache License Version 2 (the "APL"). You may not use this - * file except in compliance with the License. A copy of the - * APL may be obtained from: - * http://www.apache.org/licenses/LICENSE-2.0 - */ - -package com.seaofnodes.simple.parser; - -import org.junit.Assert; -import org.junit.Test; - -public class TestParser { - - @Test - public void testParseSingleDecl() - { - final String source = "int i;"; - Lexer lexer = new Lexer(source); - Parser parser = new Parser(); - var ast = parser.parse(lexer); - Assert.assertEquals("{int i;}", ast.toString()); - } - - @Test - public void testParseTwoDecls() { - final String source = "int i;\nint j;"; - Lexer lexer = new Lexer(source); - Parser parser = new Parser(); - var ast = parser.parse(lexer); - Assert.assertEquals("{int i;int j;}", ast.toString()); - } - - @Test - public void testIfAndWhile() { - final String source = - """ - int i; - int j; - - i = 0; - j = 100; - while ( 1 ) { - i = i + 1; - j = j - 1; - if ( i >= j ) break; - } - """; - Lexer lexer = new Lexer(source); - Parser parser = new Parser(); - var ast = parser.parse(lexer); - Assert.assertEquals("{int i;int j;i=0;j=100;while(1) {i=(i+1);j=(j-1);if((i>=j)) break;}}", ast.toString()); - } -} -- cgit v1.2.3