diff options
Diffstat (limited to 'chapter01')
| -rw-r--r-- | chapter01/README.md | 0 | ||||
| -rw-r--r-- | chapter01/docs/01-grammar.md | 0 | ||||
| -rw-r--r-- | chapter01/pom.xml | 22 | ||||
| -rw-r--r-- | chapter01/src/main/java/com/seaofnodes/simple/parser/Ast.java | 132 | ||||
| -rw-r--r-- | chapter01/src/main/java/com/seaofnodes/simple/parser/Parser.java | 259 | ||||
| -rw-r--r-- | chapter01/src/test/java/com/seaofnodes/simple/lexer/TestParser.java | 68 |
6 files changed, 481 insertions, 0 deletions
diff --git a/chapter01/README.md b/chapter01/README.md new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/chapter01/README.md diff --git a/chapter01/docs/01-grammar.md b/chapter01/docs/01-grammar.md new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/chapter01/docs/01-grammar.md 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>com.seaofnodes</groupId> + <artifactId>simple</artifactId> + <version>1.0</version> + </parent> + <artifactId>chapter01</artifactId> + <packaging>jar</packaging> + <name>Chapter 1</name> + + <dependencies> + <dependency> + <groupId>com.seaofnodes</groupId> + <artifactId>common</artifactId> + <version>1.0</version> + </dependency> + </dependencies> +</project>
\ 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<Statement> 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(); + } +} |
