Implement frontend layer to simplify cli commands.

This commit is contained in:
Alex Zenla 2023-08-22 19:54:21 -07:00
parent 4ec53e1209
commit c418694307
Signed by: alex
GPG Key ID: C0780728420EBFE5
12 changed files with 62 additions and 58 deletions

View File

@ -10,5 +10,6 @@ sealed class Node {
abstract val type: NodeType abstract val type: NodeType
open fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> = emptyList() open fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> = emptyList()
override fun toString(): String = let { node -> buildString { Printer(this).visit(node) } } override fun toString(): String =
let { node -> buildString { Printer(this).visit(node) } }
} }

View File

@ -4,13 +4,9 @@ import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.types.path import com.github.ajalt.clikt.parameters.types.path
import gay.pizza.pork.ast.nodes.Node import gay.pizza.pork.ast.nodes.Node
import gay.pizza.pork.parse.PorkParser import gay.pizza.pork.frontend.FileFrontend
import gay.pizza.pork.parse.PorkTokenizer
import gay.pizza.pork.parse.StringCharSource
import gay.pizza.pork.parse.TokenStreamSource
import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlin.io.path.readText
@OptIn(ExperimentalSerializationApi::class) @OptIn(ExperimentalSerializationApi::class)
class AstCommand : CliktCommand(help = "Print AST", name = "ast") { class AstCommand : CliktCommand(help = "Print AST", name = "ast") {
@ -23,9 +19,7 @@ class AstCommand : CliktCommand(help = "Print AST", name = "ast") {
} }
override fun run() { override fun run() {
val content = path.readText() val frontend = FileFrontend(path)
val tokenStream = PorkTokenizer(StringCharSource(content)).tokenize() println(json.encodeToString(Node.serializer(), frontend.parse()))
val program = PorkParser(TokenStreamSource(tokenStream)).readProgram()
println(json.encodeToString(Node.serializer(), program))
} }
} }

View File

@ -4,20 +4,13 @@ import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.types.path import com.github.ajalt.clikt.parameters.types.path
import gay.pizza.pork.compiler.KotlinCompiler import gay.pizza.pork.compiler.KotlinCompiler
import gay.pizza.pork.parse.PorkParser import gay.pizza.pork.frontend.FileFrontend
import gay.pizza.pork.parse.PorkTokenizer
import gay.pizza.pork.parse.StringCharSource
import gay.pizza.pork.parse.TokenStreamSource
import kotlin.io.path.readText
class GenerateKotlinCommand : CliktCommand(help = "Generate Kotlin Code", name = "generate-kotlin") { class GenerateKotlinCommand : CliktCommand(help = "Generate Kotlin Code", name = "generate-kotlin") {
val path by argument("file").path(mustExist = true, canBeDir = false) val path by argument("file").path(mustExist = true, canBeDir = false)
override fun run() { override fun run() {
val content = path.readText() val frontend = FileFrontend(path)
val tokenStream = PorkTokenizer(StringCharSource(content)).tokenize() println(frontend.visit(KotlinCompiler()))
val program = PorkParser(TokenStreamSource(tokenStream)).readProgram()
val compiler = KotlinCompiler()
println(compiler.visit(program))
} }
} }

View File

@ -3,19 +3,14 @@ package gay.pizza.pork.cli
import com.github.ajalt.clikt.core.CliktCommand import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.types.path import com.github.ajalt.clikt.parameters.types.path
import gay.pizza.pork.frontend.FileFrontend
import gay.pizza.pork.parse.AnsiHighlightScheme import gay.pizza.pork.parse.AnsiHighlightScheme
import gay.pizza.pork.parse.Highlighter
import gay.pizza.pork.parse.PorkTokenizer
import gay.pizza.pork.parse.StringCharSource
import kotlin.io.path.readText
class HighlightCommand : CliktCommand(help = "Syntax Highlighter", name = "highlight") { class HighlightCommand : CliktCommand(help = "Syntax Highlighter", name = "highlight") {
val path by argument("file").path(mustExist = true, canBeDir = false) val path by argument("file").path(mustExist = true, canBeDir = false)
override fun run() { override fun run() {
val content = path.readText() val frontend = FileFrontend(path)
val tokenStream = PorkTokenizer(StringCharSource(content)).tokenize() print(frontend.highlight(AnsiHighlightScheme()).joinToString(""))
val highlighter = Highlighter(AnsiHighlightScheme())
print(highlighter.highlight(tokenStream).joinToString(""))
} }
} }

View File

@ -3,20 +3,13 @@ package gay.pizza.pork.cli
import com.github.ajalt.clikt.core.CliktCommand import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.types.path import com.github.ajalt.clikt.parameters.types.path
import gay.pizza.pork.ast.Printer import gay.pizza.pork.frontend.FileFrontend
import gay.pizza.pork.parse.PorkParser
import gay.pizza.pork.parse.PorkTokenizer
import gay.pizza.pork.parse.StringCharSource
import gay.pizza.pork.parse.TokenStreamSource
import kotlin.io.path.readText
class ReprintCommand : CliktCommand(help = "Reprint Parsed Program", name = "reprint") { class ReprintCommand : CliktCommand(help = "Reprint Parsed Program", name = "reprint") {
val path by argument("file").path(mustExist = true, canBeDir = false) val path by argument("file").path(mustExist = true, canBeDir = false)
override fun run() { override fun run() {
val content = path.readText() val frontend = FileFrontend(path)
val tokenStream = PorkTokenizer(StringCharSource(content)).tokenize() print(frontend.reprint())
val program = PorkParser(TokenStreamSource(tokenStream)).readProgram()
print(buildString { Printer(this).visit(program) })
} }
} }

View File

@ -4,29 +4,20 @@ import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.types.path import com.github.ajalt.clikt.parameters.types.path
import gay.pizza.pork.eval.CallableFunction import gay.pizza.pork.eval.CallableFunction
import gay.pizza.pork.eval.PorkEvaluator
import gay.pizza.pork.eval.Scope import gay.pizza.pork.eval.Scope
import gay.pizza.pork.parse.PorkParser import gay.pizza.pork.frontend.FileFrontend
import gay.pizza.pork.parse.PorkTokenizer
import gay.pizza.pork.parse.StringCharSource
import gay.pizza.pork.parse.TokenStreamSource
import kotlin.io.path.readText
class RunCommand : CliktCommand(help = "Run Program", name = "run") { class RunCommand : CliktCommand(help = "Run Program", name = "run") {
val path by argument("file").path(mustExist = true, canBeDir = false) val path by argument("file").path(mustExist = true, canBeDir = false)
override fun run() { override fun run() {
val content = path.readText() val frontend = FileFrontend(path)
val tokenStream = PorkTokenizer(StringCharSource(content)).tokenize()
val program = PorkParser(TokenStreamSource(tokenStream)).readProgram()
val scope = Scope() val scope = Scope()
scope.define("println", CallableFunction { arguments -> scope.define("println", CallableFunction { arguments ->
for (argument in arguments.values) { for (argument in arguments.values) {
println(argument) println(argument)
} }
}) })
val evaluator = PorkEvaluator(scope) frontend.evaluate(scope)
evaluator.visit(program)
} }
} }

View File

@ -3,16 +3,14 @@ package gay.pizza.pork.cli
import com.github.ajalt.clikt.core.CliktCommand import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.types.path import com.github.ajalt.clikt.parameters.types.path
import gay.pizza.pork.parse.PorkTokenizer import gay.pizza.pork.frontend.FileFrontend
import gay.pizza.pork.parse.StringCharSource
import kotlin.io.path.readText
class TokenizeCommand : CliktCommand(help = "Tokenize Program", name = "tokenize") { class TokenizeCommand : CliktCommand(help = "Tokenize Program", name = "tokenize") {
val path by argument("file").path(mustExist = true, canBeDir = false) val path by argument("file").path(mustExist = true, canBeDir = false)
override fun run() { override fun run() {
val content = path.readText() val frontend = FileFrontend(path)
val tokenStream = PorkTokenizer(StringCharSource(content)).tokenize() val tokenStream = frontend.tokenize()
for (token in tokenStream.tokens) { for (token in tokenStream.tokens) {
println("${token.start} ${token.type.name} '${sanitize(token.text)}'") println("${token.start} ${token.type.name} '${sanitize(token.text)}'")
} }

View File

@ -3,7 +3,7 @@ package gay.pizza.pork.eval
import gay.pizza.pork.ast.* import gay.pizza.pork.ast.*
import gay.pizza.pork.ast.nodes.* import gay.pizza.pork.ast.nodes.*
class PorkEvaluator(root: Scope) : NodeVisitor<Any> { class Evaluator(root: Scope) : NodeVisitor<Any> {
private var currentScope: Scope = root private var currentScope: Scope = root
override fun visitDefine(node: Define): Any { override fun visitDefine(node: Define): Any {

View File

@ -0,0 +1,11 @@
package gay.pizza.pork.frontend
import gay.pizza.pork.ast.NodeVisitor
import gay.pizza.pork.parse.CharSource
import gay.pizza.pork.parse.StringCharSource
import java.nio.file.Path
import kotlin.io.path.readText
class FileFrontend(val path: Path) : Frontend() {
override fun createCharSource(): CharSource = StringCharSource(path.readText())
}

View File

@ -0,0 +1,28 @@
package gay.pizza.pork.frontend
import gay.pizza.pork.ast.NodeVisitor
import gay.pizza.pork.ast.Printer
import gay.pizza.pork.ast.nodes.Program
import gay.pizza.pork.eval.Evaluator
import gay.pizza.pork.eval.Scope
import gay.pizza.pork.parse.*
abstract class Frontend {
abstract fun createCharSource(): CharSource
fun tokenize(): TokenStream =
Tokenizer(createCharSource()).tokenize()
fun parse(): Program =
Parser(TokenStreamSource(tokenize())).readProgram()
fun highlight(scheme: HighlightScheme): List<Highlight> =
Highlighter(scheme).highlight(tokenize())
fun evaluate(scope: Scope = Scope()): Any =
visit(Evaluator(scope))
fun reprint(): String = buildString { visit(Printer(this)) }
fun <T> visit(visitor: NodeVisitor<T>): T = visitor.visit(parse())
}

View File

@ -2,7 +2,7 @@ package gay.pizza.pork.parse
import gay.pizza.pork.ast.nodes.* import gay.pizza.pork.ast.nodes.*
class PorkParser(source: PeekableSource<Token>) { class Parser(source: PeekableSource<Token>) {
private val unsanitizedSource = source private val unsanitizedSource = source
private fun readIntLiteral(): IntLiteral { private fun readIntLiteral(): IntLiteral {

View File

@ -1,6 +1,6 @@
package gay.pizza.pork.parse package gay.pizza.pork.parse
class PorkTokenizer(val source: CharSource) { class Tokenizer(val source: CharSource) {
private var tokenStart: Int = 0 private var tokenStart: Int = 0
private fun isSymbol(c: Char): Boolean = private fun isSymbol(c: Char): Boolean =