From 6b139769f5ac88221991689ba1917d3e307dc2ef Mon Sep 17 00:00:00 2001 From: Elias Haugsbakk Date: Sat, 19 Sep 2026 19:38:04 +0200 Subject: add variable declaration to parser --- .../eliashaugsbakk/kompilator/parsing/Parser.java | 248 +++++++++++++++++---- 1 file changed, 210 insertions(+), 38 deletions(-) (limited to 'src/main/java/no/eliashaugsbakk/kompilator/parsing/Parser.java') diff --git a/src/main/java/no/eliashaugsbakk/kompilator/parsing/Parser.java b/src/main/java/no/eliashaugsbakk/kompilator/parsing/Parser.java index 712df22..7f9b6c9 100644 --- a/src/main/java/no/eliashaugsbakk/kompilator/parsing/Parser.java +++ b/src/main/java/no/eliashaugsbakk/kompilator/parsing/Parser.java @@ -1,83 +1,255 @@ package no.eliashaugsbakk.kompilator.parsing; +import static no.eliashaugsbakk.kompilator.tokenization.TokenType.ASSIGN; +import static no.eliashaugsbakk.kompilator.tokenization.TokenType.COLON; +import static no.eliashaugsbakk.kompilator.tokenization.TokenType.COMMA; import static no.eliashaugsbakk.kompilator.tokenization.TokenType.EOF; +import static no.eliashaugsbakk.kompilator.tokenization.TokenType.IDENTIFIER; import static no.eliashaugsbakk.kompilator.tokenization.TokenType.KEYWORD; import static no.eliashaugsbakk.kompilator.tokenization.TokenType.LPAREN; +import static no.eliashaugsbakk.kompilator.tokenization.TokenType.NULLABLE; import static no.eliashaugsbakk.kompilator.tokenization.TokenType.RPAREN; import static no.eliashaugsbakk.kompilator.tokenization.TokenType.SEMICOLON; -import static no.eliashaugsbakk.kompilator.tokenization.TokenType.STRING; +import static no.eliashaugsbakk.kompilator.tokenization.TokenType.STRING_LITERAL; +import static no.eliashaugsbakk.kompilator.tokenization.TokenType.TYPE; import java.util.ArrayList; import java.util.List; import no.eliashaugsbakk.kompilator.parsing.node.Program; import no.eliashaugsbakk.kompilator.parsing.node.expression.Expression; import no.eliashaugsbakk.kompilator.parsing.node.expression.FunctionCall; -import no.eliashaugsbakk.kompilator.parsing.node.expression.StringLiteral; +import no.eliashaugsbakk.kompilator.parsing.node.expression.Identifier; +import no.eliashaugsbakk.kompilator.parsing.node.expression.literal.StringLiteral; +import no.eliashaugsbakk.kompilator.parsing.node.statement.Assignment; import no.eliashaugsbakk.kompilator.parsing.node.statement.ExpressionStatement; +import no.eliashaugsbakk.kompilator.parsing.node.statement.IdentifierDeclaration; +import no.eliashaugsbakk.kompilator.parsing.node.statement.Statement; import no.eliashaugsbakk.kompilator.tokenization.Token; +/* + The goal of parsing is to create the AST. + The root node is the program itself, which can only hold statements. + + Statements: + - Is an action which executes something + - Has no return or implicit value + - Must end with a semicolon + - A statement may contain expressions + - An expression cannot contain a statement + + Expressions: + - Returns or produces a value + - Does not end with a semicolon + - Can be nested instide other expressions or a statement + - Examples: + - Literals + - Identifiers + - Binary Operations + - Function calls + */ + + public class Parser { private final List tokens; - private int position = 0; + private final Program rootNode; + private int current = 0; public Parser(List tokens) { this.tokens = tokens; + this.rootNode = new Program(); } public AST parse() throws ParserException { - Program rootNode = new Program(); + while (current < tokens.size()) { + Token token = tokens.get(current); + + // End of File + if (token.type() == EOF) { + break; + } + + // root node must contain only statements + Statement stmt = parseStatement(); + rootNode.addStatement(stmt); + } + return new AST(rootNode); + } - while (position < tokens.size()) { - Token token = tokens.get(position); + private Statement parseStatement() throws ParserException { + // multiple statements are supported for v0.0.2; + // identifier declaration: x: string = "hello"; (with expression) + // identifier declaration: x: string; (without expression) + // assignment: x = "hello"; (identifier gets assigned an expression) + // expression statements: print(x); (function without a return value) - if (token.type() == KEYWORD) { - String functionName = token.value(); - position++; - List arguments = parseArguments(); + // each statement must either start with a keyword or an identifier + Token token = tokens.get(current); - FunctionCall functionCall = new FunctionCall(functionName, arguments); - rootNode.addStatement(new ExpressionStatement(functionCall)); - } else if (token.type() == EOF) { - break; - } else { - throw new ParserException(token.line(), token.colum(), - "Unexpected token: " + token.value()); - } + // only keyword implemented is "skriv", so calling parseExpression right away + if (token.type() == KEYWORD) { + return parseExpressionStatement(); } - return new AST(rootNode); + // must be identifier declaration or an assignment + else if (token.type() == IDENTIFIER) { + return parseIdentifierStatement(); + } + + // must be some other token which is not a statement + else { + throw new ParserException(token.line(), token.column(), "Unexpected token: " + token.value()); + } } - List parseArguments() throws ParserException { - List arguments = new ArrayList<>(); + private ExpressionStatement parseExpressionStatement() throws ParserException { + // this can be any expression used as a statement + // print(x); + // 5 + 6; + // "hello"; + + Expression expression = parseExpression(); + expectSemicolon(); + return new ExpressionStatement(expression); + } + + + + private Statement parseIdentifierStatement() throws ParserException { + // this is either: + // - declaration of a new variable with an associated value + // - declaration of a new variable without an associated value + // - reassigning an existing variable + + /* + x: string?; IDENTIFIER, COLON, TYPE, SEMICOLON + x: string = "string"; IDENTIFIER, COLON, TYPE, ASSIGN, STRING, SEMICOLON + x = "string"; IDENTIFIER, ASSIGN, STRING, SEMICOLON + */ + + String identifier = tokens.get(current).value(); + current++; + + Token next = tokens.get(current); + + if (next.type() == COLON) { + // x: string = "hello"; + return parseDeclaration(identifier); + } else if (next.type() == ASSIGN) { + // x = "hello"; + return parseAssignment(identifier); + } else { + throw new ParserException(next.line(), next.column(), "Expected : or = after identifier"); + } + } - if (position >= tokens.size() || tokens.get(position).type() != LPAREN) { - throw new ParserException(tokens.get(position).line(), tokens.get(position).colum(), - "Unexpected token:" + tokens.get(position).value() + "\n Expected: ("); + private Statement parseDeclaration(String identifier) throws ParserException { + // x: type; + current++; // skip colon + + Type type = parseType(); + Expression initializer = null; + + // x: type = "hello"; + if (tokens.get(current).type() == ASSIGN) { + current++; // skip = + initializer = parseExpression(); + } + + expectSemicolon(); + return new IdentifierDeclaration(identifier, type, initializer); + } + + private Statement parseAssignment(String identifier) throws ParserException { + // x = value; + current++; // skip = + + Expression value = parseExpression(); + expectSemicolon(); + + return new Assignment(identifier, value); + } + + private Type parseType() throws ParserException { + Token token = tokens.get(current); + if (token.type() != TYPE) { + throw new ParserException(token.line(), token.column(), + "Expected type, got: " + token.value()); } - position++; + current++; + + String typeName = token.value(); + boolean nullable = false; - while (position < tokens.size() && tokens.get(position).type() != RPAREN) { - if (tokens.get(position).type() == STRING) { - arguments.add(new StringLiteral(tokens.get(position).value())); + if (current < tokens.size() && tokens.get(current).type() == NULLABLE) { + nullable = true; + current++; + } + + return new Type(typeName, nullable); + } + + private Expression parseExpression() throws ParserException { + // an expression produces a value and may contain other expressions + // implemented for v0.0.2 are: + // "string" - STRING_LITERAL + // my_var - IDENTIFIER + + Token token = tokens.get(current); + current++; + + if (token.type() == STRING_LITERAL) { + return new StringLiteral(token.value()); + } else if (token.type() == IDENTIFIER || token.type() == KEYWORD) { + // could be function call or just identifier reference + if (current < tokens.size() && tokens.get(current).type() == LPAREN) { + current--; + return parseFunctionCall(); } - position++; + return new Identifier(token.value()); + } else { + throw new ParserException(token.line(), token.column(), + "Unexpected token: " + token.value() + ". Expected an expression"); } + } + + private FunctionCall parseFunctionCall() throws ParserException { + String name = tokens.get(current).value(); + current++; + List arguments = parseFunctionArguments(); + return new FunctionCall(name, arguments); + } - if (position >= tokens.size()) { - throw new ParserException(-1, -1, "Unexpected end of file, expected )"); + private List parseFunctionArguments() throws ParserException { + List arguments = new ArrayList<>(); + + if (tokens.get(current).type() != LPAREN) { + throw new ParserException(tokens.get(current).line(), tokens.get(current).column(), + "Expected ("); } - position++; + current++; // skip ( + + while (current < tokens.size() && tokens.get(current).type() != RPAREN) { + arguments.add(parseExpression()); // parseExpression() increments current - if (position >= tokens.size() || tokens.get(position).type() != SEMICOLON) { - throw new ParserException(position < tokens.size() ? tokens.get(position).line() : -1, - position < tokens.size() ? tokens.get(position).colum() : -1, - "Unexpected token: " + (position < tokens.size() ? tokens.get(position).value() : "EOF") + - "\n" + "Expected: ;"); + if (tokens.get(current).type() == COMMA) { + current++; // skip comma + } + } + + if (current >= tokens.size() || tokens.get(current).type() != RPAREN) { + throw new ParserException(tokens.get(current).line(), tokens.get(current).column(), + "Expected )"); } - position++; + current++; // skip ) return arguments; } + + private void expectSemicolon() throws ParserException { + if (current >= tokens.size() || tokens.get(current).type() != SEMICOLON) { + throw new ParserException(-1, -1, "Expected ;"); + } + current++; + } } -- cgit v1.2.3