Skip to content

Commit

Permalink
Refactor (#4)
Browse files Browse the repository at this point in the history
* WIP: Refactor Lexer

* WIP: Refactor the Lexer

* WIP: Refactor the Lexer

* WIP: Refactor the Lexer

* Simplify the Lexer

* Rename `Token` -> `QuickCodeToken`

* WIP: Refactor to ArrowKt

* Refactor to Arrow (tests migrated)

* Cleanup and reorganize

* Extract classes from QuickCodeAst

* Move `QCVariable` to `interpreter`

* Remove unnecessary blank line

* Refactor the `QuickCodeParser` and add one more test

* Improve `Main`

* Improve `Main`
  • Loading branch information
ILIYANGERMANOV authored Oct 11, 2023
1 parent 2e93673 commit b35317c
Show file tree
Hide file tree
Showing 27 changed files with 472 additions and 454 deletions.
6 changes: 6 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ application {
applicationName = "qc"
}

tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
kotlinOptions {
freeCompilerArgs += "-XXLanguage:+ContextReceivers"
}
}

group = "com.ivy"
version = "1.0-SNAPSHOT"

Expand Down
1 change: 1 addition & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ testing = [
"kotest-assertions",
"kotest-property",
"kotest-property-arrow",
"kotest-assertions-arrow",
"kotest-datatest",
"kotlin-coroutines-test"
]
Expand Down
75 changes: 50 additions & 25 deletions src/main/kotlin/com/ivy/quickcode/Main.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.ivy.quickcode

import com.ivy.quickcode.data.QCVariableValue
import arrow.core.Either
import arrow.core.raise.either
import com.ivy.quickcode.interpreter.model.QCVariableValue
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.boolean
Expand All @@ -10,42 +12,60 @@ import java.nio.file.Paths
import kotlin.io.path.name

fun main(args: Array<String>) {
// TODO: Extract this as a class, handle errors cases and test it
either {
val input = parseInput(args).bind()
println("Input:")
println(input.variables)

val compiler = QuickCodeCompiler()
compiler.execute(input.templateText, input.variables)
.onRight { outputText ->
println("----------------")
produceOutputFile(
templatePath = args[0],
result = outputText,
)
println("----------------")
println(outputText)
}
.onLeft {
println("----------------")
println("Compilation error: $it")
println("----------------")
}
}.onLeft {
println("Input error: $it")
}
}

private fun parseInput(
args: Array<String>
): Either<String, Input> = either {
if (args.size != 2) {
println("Invalid arguments!")
return
raise("Invalid arguments! Pass exactly 2 arguments.")
}
val template = readFileContent(args[0])
val inputJson = readFileContent(args[1])
val templateText = readFileContent(args[0]).bind()
val inputJson = readFileContent(args[1]).bind()
val rawInput: Map<String, JsonPrimitive> = Json.decodeFromString(inputJson)
val input = rawInput.map { (key, value) ->
val variables = rawInput.map { (key, value) ->
key to when {
value.isString -> QCVariableValue.Str(value.content)
value.booleanOrNull != null -> QCVariableValue.Bool(value.boolean)
else -> error("Unsupported input type \"$key\"")
}
}.toMap()

println("Input:")
println(input)

val compiler = QuickCodeCompiler()
val result = compiler.execute(template, input)
println("----------------")
produceOutputFile(
templatePath = args[0],
result = result,
)
println("----------------")
println(result)
Input(templateText, variables)
}

fun readFileContent(relativePath: String): String {

private fun readFileContent(
relativePath: String
): Either<String, String> = Either.catch {
val path = Paths.get(relativePath)
return Files.readString(path)
}
Files.readString(path)
}.mapLeft { it.toString() }

fun produceOutputFile(templatePath: String, result: String) {
private fun produceOutputFile(templatePath: String, result: String) {
val path = Paths.get(templatePath)
val fileName = path.fileName.name
val outputFilename = fileName.dropLast(3)
Expand All @@ -54,4 +74,9 @@ fun produceOutputFile(templatePath: String, result: String) {
result
)
println("'$outputFilename' created.")
}
}

data class Input(
val templateText: String,
val variables: Map<String, QCVariableValue>
)
51 changes: 22 additions & 29 deletions src/main/kotlin/com/ivy/quickcode/QuickCodeCompiler.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
package com.ivy.quickcode

import com.ivy.quickcode.data.*
import com.ivy.quickcode.parser.ParseResult
import arrow.core.Either
import arrow.core.raise.either
import com.ivy.quickcode.interpreter.QuickCodeInterpreter
import com.ivy.quickcode.interpreter.model.QCVariable
import com.ivy.quickcode.interpreter.model.QCVariableValue
import com.ivy.quickcode.lexer.QuickCodeLexer
import com.ivy.quickcode.parser.QuickCodeParser
import com.ivy.quickcode.parser.model.IfStatement
import com.ivy.quickcode.parser.model.QuickCodeAst
import com.ivy.quickcode.parser.model.RawText
import com.ivy.quickcode.parser.model.Variable

class QuickCodeCompiler {
private val lexer = QuickCodeLexer()
Expand All @@ -11,29 +19,18 @@ class QuickCodeCompiler {
fun execute(
codeTemplate: String,
vars: Map<String, QCVariableValue>
): String {
): Either<String, String> = either {
val tokens = lexer.tokenize(codeTemplate)
return when (val res = parser.parse(tokens)) {
is ParseResult.Failure -> codeTemplate
is ParseResult.Success -> {
val interpreter = QuickCodeInterpreter(vars)
interpreter.evaluate(res.ast)
}
}
val ast = parser.parse(tokens).bind()
val interpreter = QuickCodeInterpreter(vars)
interpreter.evaluate(ast)
}

fun compile(codeTemplate: String): CompilationResult {
fun compile(codeTemplate: String): Either<String, CompilationOutput> = either {
val tokens = lexer.tokenize(codeTemplate)
return when (val res = parser.parse(tokens)) {
is ParseResult.Failure -> CompilationResult.Invalid(res.errorMsg)
is ParseResult.Success -> {
CompilationResult.Valid(
ast = res.ast,
variables = res.ast.extractAllVars()
.fixVariableConflicts()
)
}
}
val ast = parser.parse(tokens).bind()
val variables = ast.extractAllVars().fixVariableConflicts()
CompilationOutput(ast, variables)
}

private fun List<QCVariable>.fixVariableConflicts(): List<QCVariable> {
Expand Down Expand Up @@ -95,12 +92,8 @@ class QuickCodeCompiler {
}
}

sealed interface CompilationResult {
data class Valid(
val ast: QuickCodeAst,
val variables: List<QCVariable>
) : CompilationResult

data class Invalid(val errMsg: String) : CompilationResult
}
data class CompilationOutput(
val ast: QuickCodeAst,
val variables: List<QCVariable>
)
}
8 changes: 0 additions & 8 deletions src/main/kotlin/com/ivy/quickcode/QuickCodeDetector.kt

This file was deleted.

111 changes: 0 additions & 111 deletions src/main/kotlin/com/ivy/quickcode/data/QuickCodeToken.kt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package com.ivy.quickcode
package com.ivy.quickcode.interpreter

import com.ivy.quickcode.data.*
import com.ivy.quickcode.interpreter.model.QCVariableValue
import com.ivy.quickcode.parser.model.IfStatement
import com.ivy.quickcode.parser.model.QuickCodeAst
import com.ivy.quickcode.parser.model.RawText
import com.ivy.quickcode.parser.model.Variable

class QuickCodeInterpreter(
private val variables: Map<String, QCVariableValue>,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.ivy.quickcode.data
package com.ivy.quickcode.interpreter.model

sealed interface QCVariable {
val name: String
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.ivy.quickcode.data
package com.ivy.quickcode.interpreter.model

sealed interface QCVariableValue {
data class Str(val value: String) : QCVariableValue
Expand Down
Loading

0 comments on commit b35317c

Please sign in to comment.