diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c9099a9 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,2 @@ +[*.{kt,kts}] +ij_kotlin_allow_trailing_comma = true diff --git a/src/main/kotlin/Exceptions/SyntaxError.kt b/src/main/kotlin/Exceptions/SyntaxError.kt index 795a668..88edb33 100644 --- a/src/main/kotlin/Exceptions/SyntaxError.kt +++ b/src/main/kotlin/Exceptions/SyntaxError.kt @@ -3,13 +3,13 @@ package org.example.Exceptions class SyntaxError( message: String, val line: Int? = null, - val column: Int? = null + val column: Int? = null, ) : Exception(buildMessage(message, line, column)) { companion object { private fun buildMessage( message: String, line: Int?, - column: Int? + column: Int?, ): String = when { line != null && column != null -> "Syntax error at line $line, column $column: $message" diff --git a/src/main/kotlin/parser/ASTNode.kt b/src/main/kotlin/parser/ASTNode.kt index 11f82f4..bc6a909 100644 --- a/src/main/kotlin/parser/ASTNode.kt +++ b/src/main/kotlin/parser/ASTNode.kt @@ -1,6 +1,8 @@ package org.example.parser sealed class ASTNode { + abstract val line: Int + abstract val column: Int abstract fun prettyPrint(indent: Int = 0): String fun indent(level: Int): String = " ".repeat(level) // 4 spaces per level } diff --git a/src/main/kotlin/parser/Expression.kt b/src/main/kotlin/parser/Expressions.kt similarity index 57% rename from src/main/kotlin/parser/Expression.kt rename to src/main/kotlin/parser/Expressions.kt index 6709de6..eb794c7 100644 --- a/src/main/kotlin/parser/Expression.kt +++ b/src/main/kotlin/parser/Expressions.kt @@ -1,13 +1,13 @@ package org.example.parser -import lexer.Token - sealed class Expression : ASTNode() data class IntExpression( - val value: Token + val value: Int, + override val line: Int, + override val column: Int, ) : Expression() { override fun prettyPrint(indent: Int): String { - return "${indent(indent)}Int(${value.lexeme})" + return "${indent(indent)}Int($value)" } } diff --git a/src/main/kotlin/parser/Functions.kt b/src/main/kotlin/parser/Functions.kt index 18ea668..d272025 100644 --- a/src/main/kotlin/parser/Functions.kt +++ b/src/main/kotlin/parser/Functions.kt @@ -4,7 +4,9 @@ sealed class FunctionDefinition : ASTNode() data class SimpleFunction( val name: Identifier, - val body: Statement + val body: Statement, + override val line: Int, + override val column: Int, ) : FunctionDefinition() { override fun prettyPrint(indent: Int): String { return buildString { diff --git a/src/main/kotlin/parser/Identifier.kt b/src/main/kotlin/parser/Identifier.kt index 788d1ba..45356aa 100644 --- a/src/main/kotlin/parser/Identifier.kt +++ b/src/main/kotlin/parser/Identifier.kt @@ -1,11 +1,11 @@ package org.example.parser -import lexer.Token - data class Identifier( - val token: Token + val value: String, + override val line: Int, + override val column: Int, ) : ASTNode() { override fun prettyPrint(indent: Int): String { - return buildString { appendLine("${indent(indent)}\"${token.lexeme}\"") } + return buildString { appendLine("${indent(indent)}\"${this@Identifier.value}\"") } } } diff --git a/src/main/kotlin/parser/Parser.kt b/src/main/kotlin/parser/Parser.kt index c478469..5c8bd92 100644 --- a/src/main/kotlin/parser/Parser.kt +++ b/src/main/kotlin/parser/Parser.kt @@ -6,11 +6,11 @@ import org.example.Exceptions.SyntaxError class Parser { fun parseTokens(tokens: List): ASTNode { - val tokens = tokens.toMutableList() - val ast = parseProgram(tokens) + val tokenSet = tokens.toMutableList() + val ast = parseProgram(tokenSet) - val lastToken = tokens.removeFirst() - if (lastToken.type != TokenType.EOF) { + val lastToken = tokenSet.removeFirst() + if (lastToken.type != TokenType.EOF || !tokenSet.isEmpty()) { throw SyntaxError( line = lastToken.line, column = lastToken.column, @@ -23,10 +23,15 @@ class Parser { private fun parseProgram(tokens: MutableList): SimpleProgram { val function = parseFunction(tokens) - return SimpleProgram(functionDefinition = function) + return SimpleProgram( + functionDefinition = function, + line = function.line, + column = function.column + ) } private fun parseFunction(tokens: MutableList): FunctionDefinition { + val first = tokens.firstOrNull() expect(TokenType.KEYWORD_INT, tokens) val name = parseIdentifier(tokens) expect(TokenType.LEFT_PAREN, tokens) @@ -36,12 +41,17 @@ class Parser { val body = parseStatement(tokens) expect(TokenType.RIGHT_BRACK, tokens) - return SimpleFunction(name = name, body = body) + return SimpleFunction( + name = name, + body = body, + line = first?.line!!, + column = first.column + ) } private fun expect( expected: TokenType, - tokens: MutableList + tokens: MutableList, ): Token { val token = tokens.removeFirst() @@ -66,15 +76,24 @@ class Parser { ) } - return Identifier(token = token) + return Identifier( + value = token.lexeme, + line = token.line, + column = token.column + ) } private fun parseStatement(tokens: MutableList): Statement { + val first = tokens.firstOrNull() expect(TokenType.KEYWORD_RETURN, tokens) val expression = parseExpression(tokens) expect(TokenType.SEMICOLON, tokens) - return ReturnStatement(expression = expression) + return ReturnStatement( + expression = expression, + line = first?.line!!, + column = first.column + ) } private fun parseExpression(tokens: MutableList): Expression { @@ -86,6 +105,10 @@ class Parser { message = "Expected token: ${TokenType.INT_LITERAL}, got ${token.type}" ) } - return IntExpression(value = token) + return IntExpression( + value = token.lexeme.toInt(), + line = token.line, + column = token.column + ) } } diff --git a/src/main/kotlin/parser/Programs.kt b/src/main/kotlin/parser/Programs.kt index 008cdef..3bca50c 100644 --- a/src/main/kotlin/parser/Programs.kt +++ b/src/main/kotlin/parser/Programs.kt @@ -3,7 +3,9 @@ package org.example.parser sealed class Program : ASTNode() data class SimpleProgram( - val functionDefinition: FunctionDefinition + val functionDefinition: FunctionDefinition, + override val line: Int, + override val column: Int, ) : Program() { override fun prettyPrint(indent: Int): String { return buildString { diff --git a/src/main/kotlin/parser/Statement.kt b/src/main/kotlin/parser/Statements.kt similarity index 80% rename from src/main/kotlin/parser/Statement.kt rename to src/main/kotlin/parser/Statements.kt index 64e1866..bc25726 100644 --- a/src/main/kotlin/parser/Statement.kt +++ b/src/main/kotlin/parser/Statements.kt @@ -3,7 +3,9 @@ package org.example.parser sealed class Statement : ASTNode() data class ReturnStatement( - val expression: Expression + val expression: Expression, + override val line: Int, + override val column: Int, ) : Statement() { override fun prettyPrint(indent: Int): String { return buildString { diff --git a/src/main/kotlin/wasm/CodeGenerator.kt b/src/main/kotlin/wasm/CodeGenerator.kt new file mode 100644 index 0000000..f66c24d --- /dev/null +++ b/src/main/kotlin/wasm/CodeGenerator.kt @@ -0,0 +1,49 @@ +package org.example.wasm + +import org.example.parser.ASTNode +import org.example.parser.Identifier +import org.example.parser.IntExpression +import org.example.parser.ReturnStatement +import org.example.parser.SimpleFunction +import org.example.parser.SimpleProgram + +class CodeGenerator { + fun generateWat(ast: ASTNode): List { + val wasmTree = visit(ast) + return wasmTree.toWat().lines() + } + + private fun visit(ast: ASTNode): WASMConstruct = + when (ast) { + is SimpleProgram -> SimpleModule( + visit(ast.functionDefinition) as WASMFunction, + line = ast.line, + column = ast.column + ) + is SimpleFunction -> + WASMFunction( + name = ast.name.value, + body = listOf(visit(ast.body) as Instruction), + line = ast.line, + column = ast.column + ) + is ReturnStatement -> { + Return( + visit(ast.expression) as Operand, + line = ast.line, + column = ast.column + ) + } + is IntExpression -> Imm( + ast.value, + line = ast.line, + column = ast.column + ) + + is Identifier -> WASMIdentifier( + ast.value, + line = ast.line, + column = ast.column + ) + } +} diff --git a/src/main/kotlin/wasm/Functions.kt b/src/main/kotlin/wasm/Functions.kt new file mode 100644 index 0000000..560970a --- /dev/null +++ b/src/main/kotlin/wasm/Functions.kt @@ -0,0 +1,19 @@ +package org.example.wasm + +sealed class Function : WASMConstruct() + +data class WASMFunction( + val name: String, + val body: List, + override val line: Int, + override val column: Int, +) : Function() { + override fun toWat(indent: Int): String { + val bodyWat = body.joinToString("") { it.toWat(indent + 1) } + return buildString { + appendLine("${indent(indent)}(func $$name") + append(bodyWat) + appendLine("${indent(indent)})") + } + } +} diff --git a/src/main/kotlin/wasm/Identifier.kt b/src/main/kotlin/wasm/Identifier.kt new file mode 100644 index 0000000..a1ddd27 --- /dev/null +++ b/src/main/kotlin/wasm/Identifier.kt @@ -0,0 +1,11 @@ +package org.example.wasm + +sealed class Identifier : WASMConstruct() + +data class WASMIdentifier( + val name: String, + override val line: Int, + override val column: Int, +) : Identifier() { + override fun toWat(indent: Int): String = "${indent(indent)}(local $${this.name} i32)\n" +} diff --git a/src/main/kotlin/wasm/Instructions.kt b/src/main/kotlin/wasm/Instructions.kt new file mode 100644 index 0000000..71dbff9 --- /dev/null +++ b/src/main/kotlin/wasm/Instructions.kt @@ -0,0 +1,15 @@ +package org.example.wasm + +sealed class Instruction : WASMConstruct() + +data class Return( + val operand: Operand, + override val line: Int, + override val column: Int, +) : Instruction() { + override fun toWat(indent: Int): String = + buildString { + append("${indent(indent)}${operand.toWat()}") + appendLine("${indent(indent)}return") + } +} diff --git a/src/main/kotlin/wasm/Modules.kt b/src/main/kotlin/wasm/Modules.kt new file mode 100644 index 0000000..1062997 --- /dev/null +++ b/src/main/kotlin/wasm/Modules.kt @@ -0,0 +1,19 @@ +package org.example.wasm + +sealed class Module : WASMConstruct() + +data class SimpleModule( + val function: Function, + override val line: Int, + override val column: Int, +) : Module() { + override fun toWat(indent: Int): String = + buildString { + appendLine("(module") + append(function.toWat(indent + 1)) + if (function is WASMFunction) { + appendLine("${indent(indent + 1)}(export \"${function.name}\" (func $${function.name}))") + } + appendLine(")") + } +} diff --git a/src/main/kotlin/wasm/Operands.kt b/src/main/kotlin/wasm/Operands.kt new file mode 100644 index 0000000..d24d092 --- /dev/null +++ b/src/main/kotlin/wasm/Operands.kt @@ -0,0 +1,11 @@ +package org.example.wasm + +sealed class Operand : WASMConstruct() + +data class Imm( + val value: Int, + override val line: Int, + override val column: Int, +) : Operand() { + override fun toWat(indent: Int): String = "${indent(indent)}i32.const $value\n" +} diff --git a/src/main/kotlin/wasm/WASMConstruct.kt b/src/main/kotlin/wasm/WASMConstruct.kt new file mode 100644 index 0000000..24fbc65 --- /dev/null +++ b/src/main/kotlin/wasm/WASMConstruct.kt @@ -0,0 +1,8 @@ +package org.example.wasm + +sealed class WASMConstruct { + abstract val line: Int + abstract val column: Int + abstract fun toWat(indent: Int = 0): String + protected fun indent(level: Int): String = " ".repeat(level) +} diff --git a/src/test/kotlin/LexerTest.kt b/src/test/kotlin/LexerTest.kt index 944d557..c7ba404 100644 --- a/src/test/kotlin/LexerTest.kt +++ b/src/test/kotlin/LexerTest.kt @@ -1,4 +1,3 @@ - import lexer.Lexer import lexer.TokenType import org.junit.jupiter.api.Test @@ -34,17 +33,18 @@ class LexerTest { val lexer = Lexer(source) val tokens = lexer.tokenize() val types = tokens.map { it.type } - val expected_tokens = listOf( - TokenType.IDENTIFIER, - TokenType.ASSIGN, - TokenType.IDENTIFIER, - TokenType.PLUS, - TokenType.IDENTIFIER, - TokenType.MINUS, - TokenType.INT_LITERAL, - TokenType.SEMICOLON, - TokenType.EOF - ) + val expected_tokens = + listOf( + TokenType.IDENTIFIER, + TokenType.ASSIGN, + TokenType.IDENTIFIER, + TokenType.PLUS, + TokenType.IDENTIFIER, + TokenType.MINUS, + TokenType.INT_LITERAL, + TokenType.SEMICOLON, + TokenType.EOF + ) assertEquals(expected_tokens, types) } @@ -55,18 +55,19 @@ class LexerTest { val lexer = Lexer(source) val tokens = lexer.tokenize() - val expected_tokens = listOf( - TokenType.IDENTIFIER, // remove it when we add if as a keyword - TokenType.LEFT_PAREN, - TokenType.IDENTIFIER, - TokenType.RIGHT_PAREN, - TokenType.LEFT_BRACK, - TokenType.KEYWORD_RETURN, - TokenType.INT_LITERAL, - TokenType.SEMICOLON, - TokenType.RIGHT_BRACK, - TokenType.EOF - ) + val expected_tokens = + listOf( + TokenType.IDENTIFIER, // remove it when we add if as a keyword + TokenType.LEFT_PAREN, + TokenType.IDENTIFIER, + TokenType.RIGHT_PAREN, + TokenType.LEFT_BRACK, + TokenType.KEYWORD_RETURN, + TokenType.INT_LITERAL, + TokenType.SEMICOLON, + TokenType.RIGHT_BRACK, + TokenType.EOF + ) val types = tokens.map { it.type } diff --git a/src/test/kotlin/integration/LexerParserIntegrationTest.kt b/src/test/kotlin/integration/LexerParserIntegrationTest.kt index 30a7918..7abfcee 100644 --- a/src/test/kotlin/integration/LexerParserIntegrationTest.kt +++ b/src/test/kotlin/integration/LexerParserIntegrationTest.kt @@ -6,6 +6,7 @@ import org.example.parser.Parser import org.example.parser.ReturnStatement import org.example.parser.SimpleFunction import org.example.parser.SimpleProgram +import org.example.wasm.CodeGenerator import org.junit.jupiter.api.Test import kotlin.test.assertEquals import kotlin.test.assertIs @@ -33,7 +34,7 @@ class LexerParserIntegrationTest { val function = program.functionDefinition assertIs(function) val simpleFunction = function - assertEquals("main", simpleFunction.name.token.lexeme) + assertEquals("main", simpleFunction.name.value) // Check return value val returnStatement = simpleFunction.body @@ -43,6 +44,23 @@ class LexerParserIntegrationTest { val expression = returnStmt.expression assertIs(expression) val intExpr = expression - assertEquals("42", intExpr.value.lexeme) + assertEquals(42, intExpr.value) + + // Generate WAT code + val codeGenerator = CodeGenerator() + val watCode = codeGenerator.generateWat(ast) + + // Verify the generated WAT code + val expectedWat = listOf( + "(module", + " (func \$main", + " i32.const 42", + " return", + " )", + " (export \"main\" (func \$main))", + ")", + "" + ) + assertEquals(expectedWat, watCode) } } diff --git a/src/test/kotlin/parser/ParserTest.kt b/src/test/kotlin/parser/ParserTest.kt index 9287407..75d1a64 100644 --- a/src/test/kotlin/parser/ParserTest.kt +++ b/src/test/kotlin/parser/ParserTest.kt @@ -66,7 +66,7 @@ class ParserTest { val simpleFunction = function // Check function name - assertEquals("main", simpleFunction.name.token.lexeme) + assertEquals("main", simpleFunction.name.value) // Check return statement val returnStatement = simpleFunction.body @@ -77,7 +77,7 @@ class ParserTest { val expression = returnStmt.expression assertIs(expression) val intExpr = expression - assertEquals("42", intExpr.value.lexeme) + assertEquals(42, intExpr.value) } @Test @@ -149,9 +149,9 @@ class ParserTest { Token(TokenType.INT_LITERAL, "42", 1, 25), Token(TokenType.SEMICOLON, ";", 1, 27), Token(TokenType.RIGHT_BRACK, "}", 1, 29), + Token(TokenType.EOF, "", 1, 31), // Extra tokens here - Token(TokenType.IDENTIFIER, "extra", 1, 31), - Token(TokenType.EOF, "", 1, 36) + Token(TokenType.IDENTIFIER, "extra", 1, 32) ) val parser = Parser() diff --git a/src/test/kotlin/wat/CodeGeneratorTest.kt b/src/test/kotlin/wat/CodeGeneratorTest.kt new file mode 100644 index 0000000..de1bdee --- /dev/null +++ b/src/test/kotlin/wat/CodeGeneratorTest.kt @@ -0,0 +1,69 @@ +package wat + +import org.example.parser.Identifier +import org.example.parser.IntExpression +import org.example.parser.ReturnStatement +import org.example.parser.SimpleFunction +import org.example.parser.SimpleProgram +import org.example.wasm.CodeGenerator +import org.junit.jupiter.api.Test +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals + +class CodeGeneratorTest { + private val codeGenerator = CodeGenerator() + + @Test + fun `test generate simple return statement with integer`() { + val intExpr = IntExpression(42, line = 1, column = 8) + val returnStatement = ReturnStatement(intExpr, line = 1, column = 1) + val function = SimpleFunction( + name = Identifier("test", line = 1, column = 1), + body = returnStatement, + line = 1, + column = 1 + ) + val program = SimpleProgram(function, line = 1, column = 1) + + val result = codeGenerator.generateWat(program) + + val expected = listOf( + "(module", + " (func \$test", + " i32.const 42", + " return", + " )", + " (export \"test\" (func \$test))", + ")", + "" + ) + assertContentEquals(expected, result) + } + + @Test + fun `test generate negative integer return value`() { + val intExpr = IntExpression(-123, line = 1, column = 8) + val returnStatement = ReturnStatement(intExpr, line = 1, column = 1) + val function = SimpleFunction( + name = Identifier("negative", line = 1, column = 1), + body = returnStatement, + line = 1, + column = 1 + ) + val program = SimpleProgram(function, line = 1, column = 1) + + val result = codeGenerator.generateWat(program) + + val expected = listOf( + "(module", + " (func \$negative", + " i32.const -123", + " return", + " )", + " (export \"negative\" (func \$negative))", + ")", + "" + ) + assertEquals(expected, result) + } +}