Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[*.{kt,kts}]
ij_kotlin_allow_trailing_comma = true
4 changes: 2 additions & 2 deletions src/main/kotlin/Exceptions/SyntaxError.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 2 additions & 0 deletions src/main/kotlin/parser/ASTNode.kt
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -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)"
}
}
4 changes: 3 additions & 1 deletion src/main/kotlin/parser/Functions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
8 changes: 4 additions & 4 deletions src/main/kotlin/parser/Identifier.kt
Original file line number Diff line number Diff line change
@@ -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)}\"${[email protected]}\"") }
}
}
43 changes: 33 additions & 10 deletions src/main/kotlin/parser/Parser.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import org.example.Exceptions.SyntaxError

class Parser {
fun parseTokens(tokens: List<Token>): 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,
Expand All @@ -23,10 +23,15 @@ class Parser {
private fun parseProgram(tokens: MutableList<Token>): SimpleProgram {
val function = parseFunction(tokens)

return SimpleProgram(functionDefinition = function)
return SimpleProgram(
functionDefinition = function,
line = function.line,
column = function.column
)
}

private fun parseFunction(tokens: MutableList<Token>): FunctionDefinition {
val first = tokens.firstOrNull()
expect(TokenType.KEYWORD_INT, tokens)
val name = parseIdentifier(tokens)
expect(TokenType.LEFT_PAREN, tokens)
Expand All @@ -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<Token>
tokens: MutableList<Token>,
): Token {
val token = tokens.removeFirst()

Expand All @@ -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<Token>): 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<Token>): Expression {
Expand All @@ -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
)
}
}
4 changes: 3 additions & 1 deletion src/main/kotlin/parser/Programs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
49 changes: 49 additions & 0 deletions src/main/kotlin/wasm/CodeGenerator.kt
Original file line number Diff line number Diff line change
@@ -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<String> {
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
)
}
}
19 changes: 19 additions & 0 deletions src/main/kotlin/wasm/Functions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.example.wasm

sealed class Function : WASMConstruct()

data class WASMFunction(
val name: String,
val body: List<Instruction>,
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)})")
}
}
}
11 changes: 11 additions & 0 deletions src/main/kotlin/wasm/Identifier.kt
Original file line number Diff line number Diff line change
@@ -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"
}
15 changes: 15 additions & 0 deletions src/main/kotlin/wasm/Instructions.kt
Original file line number Diff line number Diff line change
@@ -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")
}
}
19 changes: 19 additions & 0 deletions src/main/kotlin/wasm/Modules.kt
Original file line number Diff line number Diff line change
@@ -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(")")
}
}
11 changes: 11 additions & 0 deletions src/main/kotlin/wasm/Operands.kt
Original file line number Diff line number Diff line change
@@ -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"
}
8 changes: 8 additions & 0 deletions src/main/kotlin/wasm/WASMConstruct.kt
Original file line number Diff line number Diff line change
@@ -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)
}
49 changes: 25 additions & 24 deletions src/test/kotlin/LexerTest.kt
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

import lexer.Lexer
import lexer.TokenType
import org.junit.jupiter.api.Test
Expand Down Expand Up @@ -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)
}
Expand All @@ -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 }

Expand Down
Loading