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
46 changes: 28 additions & 18 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
plugins {
kotlin("multiplatform") version "2.2.0"
id("org.jlleitschuh.gradle.ktlint") version "11.3.2"
id("org.jetbrains.kotlinx.kover") version "0.7.6"
id("org.jetbrains.kotlinx.kover") version "0.9.1"
kotlin("plugin.serialization") version "2.2.0"
}

Expand Down Expand Up @@ -54,21 +54,6 @@ kotlin {
}
}

koverReport {
filters {
includes {
classes("*")
}
}
verify {
rule {
bound {
minValue = 0
}
}
}
}

// Task to automatically sync JVM sources from JS sources
tasks.register("syncJvmSources") {
group = "build"
Expand Down Expand Up @@ -103,6 +88,11 @@ tasks.register("syncJvmSources") {
}
}

// Ensure syncJvmSources runs before any compilation tasks
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
dependsOn("syncJvmSources")
}

listOf(
"runKtlintCheckOverJsMainSourceSet",
"runKtlintCheckOverJsTestSourceSet",
Expand All @@ -118,15 +108,35 @@ tasks.named("jvmTest") {
dependsOn("syncJvmSources")
}

// Also make jsTest depend on syncJvmSources to ensure consistency
tasks.named("jsTest") {
dependsOn("syncJvmSources")
}

// Also make jsTest depend on syncJvmSources to ensure consistency
tasks.named("jsTest") {
dependsOn("syncJvmSources")
}

tasks.named<Delete>("clean") {
delete(
file("src/jvmMain"),
file("src/jvmTest")
)
}

// Ensure Kover HTML report is generated when running the standard build
// Using finalizedBy so the report runs after a successful build without affecting task up-to-date checks
// Ensure tests run and Kover HTML report is generated when running the standard build
tasks.named("build") {
dependsOn("jsTest", "jvmTest")
finalizedBy("koverHtmlReport")
}

// Ensure koverHtmlReport depends on test execution to have coverage data
tasks.named("koverHtmlReport") {
dependsOn("jsTest", "jvmTest")
}

// Ensure koverHtmlReport depends on test execution to have coverage data
tasks.named("koverHtmlReport") {
dependsOn("jsTest", "jvmTest")
}
12 changes: 12 additions & 0 deletions src/jsMain/kotlin/exceptions/CompilationExceptions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,18 @@ class UndeclaredVariableException(
column: Int? = null
) : CompilationExceptions(CompilerStage.PARSER, "Variable is used before being declared", line, column)

class UndeclaredLabelException(
label: String,
line: Int? = null,
column: Int? = null
) : CompilationExceptions(CompilerStage.PARSER, "Goto target '$label' is not defined.", line, column)

class DuplicateLabelException(
label: String,
line: Int? = null,
column: Int? = null
) : CompilationExceptions(CompilerStage.PARSER, "Label '$label' is already defined.", line, column)

class InvalidLValueException(
line: Int? = null,
column: Int? = null
Expand Down
86 changes: 86 additions & 0 deletions src/jsMain/kotlin/export/ASTExport.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@ import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import parser.AssignmentExpression
import parser.BinaryExpression
import parser.ConditionalExpression
import parser.D
import parser.Declaration
import parser.ExpressionStatement
import parser.Function
import parser.GotoStatement
import parser.IfStatement
import parser.IntExpression
import parser.LabeledStatement
import parser.NullStatement
import parser.ReturnStatement
import parser.S
Expand Down Expand Up @@ -181,6 +185,88 @@ class ASTExport : Visitor<String> {
return Json.encodeToString(jsonNode)
}

override fun visit(node: IfStatement): String {
val childrenMap =
mutableMapOf(
"condition" to JsonPrimitive(node.condition.accept(this)),
"then" to JsonPrimitive(node.then.accept(this))
)
// Handle the optional 'else' branch
node._else?.let {
childrenMap["else"] = JsonPrimitive(it.accept(this))
}

val jsonNode =
JsonObject(
mapOf(
"type" to JsonPrimitive("IfStatement"),
"label" to JsonPrimitive("if-then-else"),
"children" to JsonObject(childrenMap)
)
)
return Json.encodeToString(jsonNode)
}

override fun visit(node: ConditionalExpression): String {
val children =
JsonObject(
mapOf(
"condition" to JsonPrimitive(node.codition.accept(this)),
"thenExpression" to JsonPrimitive(node.thenExpression.accept(this)),
"elseExpression" to JsonPrimitive(node.elseExpression.accept(this))
)
)

val jsonNode =
JsonObject(
mapOf(
"type" to JsonPrimitive("ConditionalExpression"),
"label" to JsonPrimitive("cond ? then : else"),
"children" to children
)
)
return Json.encodeToString(jsonNode)
}

override fun visit(node: GotoStatement): String {
val children =
JsonObject(
mapOf(
"targetLabel" to JsonPrimitive(node.label)
)
)

val jsonNode =
JsonObject(
mapOf(
"type" to JsonPrimitive("GotoStatement"),
"label" to JsonPrimitive("goto"),
"children" to children
)
)
return Json.encodeToString(jsonNode)
}

override fun visit(node: LabeledStatement): String {
val children =
JsonObject(
mapOf(
"label" to JsonPrimitive(node.label),
"statement" to JsonPrimitive(node.statement.accept(this))
)
)

val jsonNode =
JsonObject(
mapOf(
"type" to JsonPrimitive("LabeledStatement"),
"label" to JsonPrimitive("label: statement"),
"children" to children
)
)
return Json.encodeToString(jsonNode)
}

override fun visit(node: AssignmentExpression): String {
val children =
JsonObject(
Expand Down
3 changes: 3 additions & 0 deletions src/jsMain/kotlin/export/CompilerExport.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import assembly.AsmProgram
import assembly.CodeEmitter
import assembly.InstructionFixer
import assembly.PseudoEliminator
import compiler.parser.LabelAnalysis
import compiler.parser.VariableResolution
import exceptions.CodeGenerationException
import exceptions.CompilationExceptions
Expand All @@ -29,6 +30,7 @@ class CompilerExport {

val parser = Parser()
val variableResolution = VariableResolution()
val labelAnalysis = LabelAnalysis()
val tackyGenVisitor = TackyGenVisitor()
val tackyToAsmConverter = TackyToAsm()
val pseudoEliminator = PseudoEliminator()
Expand Down Expand Up @@ -68,6 +70,7 @@ class CompilerExport {
if (lexerOutput.errors.isEmpty() && tokens != null) {
try {
ast = parser.parseTokens(tokens)
labelAnalysis.analyze(ast)
ast = ast.accept(variableResolution)
ParserOutput(
errors = emptyArray(),
Expand Down
17 changes: 16 additions & 1 deletion src/jsMain/kotlin/lexer/Lexer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ sealed class TokenType {

object IDENTIFIER : TokenType()

object IF : TokenType()

object ELSE : TokenType()

object GOTO : TokenType()

// literals
object INT_LITERAL : TokenType()

Expand Down Expand Up @@ -64,6 +70,10 @@ sealed class TokenType {

object RIGHT_BRACK : TokenType()

object QUESTION_MARK : TokenType()

object COLON : TokenType()

// Special token for End of File
object EOF : TokenType()

Expand Down Expand Up @@ -93,7 +103,10 @@ class Lexer(
mapOf(
"int" to TokenType.KEYWORD_INT,
"void" to TokenType.KEYWORD_VOID,
"return" to TokenType.KEYWORD_RETURN
"return" to TokenType.KEYWORD_RETURN,
"if" to TokenType.IF,
"else" to TokenType.ELSE,
"goto" to TokenType.GOTO
)

fun tokenize(): List<Token> {
Expand Down Expand Up @@ -130,6 +143,8 @@ class Lexer(
'%' -> addToken(TokenType.REMAINDER)
'*' -> addToken(TokenType.MULTIPLY)
'/' -> addToken(TokenType.DIVIDE)
'?' -> addToken(TokenType.QUESTION_MARK)
':' -> addToken(TokenType.COLON)

'~' -> addToken(TokenType.TILDE)
'-' -> {
Expand Down
21 changes: 21 additions & 0 deletions src/jsMain/kotlin/parser/BlockItems.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,27 @@ class NullStatement : Statement() {
override fun equals(other: Any?): Boolean = other is NullStatement
}

class IfStatement(
val condition: Expression,
val then: Statement,
val _else: Statement?
) : Statement() {
override fun <T> accept(visitor: Visitor<T>): T = visitor.visit(this)
}

class GotoStatement(
val label: String
) : Statement() {
override fun <T> accept(visitor: Visitor<T>): T = visitor.visit(this)
}

class LabeledStatement(
val label: String,
val statement: Statement
) : Statement() {
override fun <T> accept(visitor: Visitor<T>): T = visitor.visit(this)
}

data class Declaration(
val name: String,
val init: Expression?
Expand Down
8 changes: 8 additions & 0 deletions src/jsMain/kotlin/parser/Expressions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,11 @@ data class AssignmentExpression(
) : Expression() {
override fun <T> accept(visitor: Visitor<T>): T = visitor.visit(this)
}

data class ConditionalExpression(
val codition: Expression,
val thenExpression: Expression,
val elseExpression: Expression
) : Expression() {
override fun <T> accept(visitor: Visitor<T>): T = visitor.visit(this)
}
Loading