mirror of
https://github.com/GayPizzaSpecifications/pork.git
synced 2025-08-02 12:50:55 +00:00
Implement token node attribution and a Dart compiler.
This commit is contained in:
parent
743bc00bab
commit
900e3f1a1c
@ -1,7 +1,11 @@
|
||||
package gay.pizza.pork.ast.nodes
|
||||
|
||||
import gay.pizza.pork.ast.NodeType
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
@SerialName("stringLiteral")
|
||||
class StringLiteral(val text: String) : Expression() {
|
||||
override val type: NodeType = NodeType.StringLiteral
|
||||
|
||||
|
16
src/main/kotlin/gay/pizza/pork/cli/GenerateDartCommand.kt
Normal file
16
src/main/kotlin/gay/pizza/pork/cli/GenerateDartCommand.kt
Normal file
@ -0,0 +1,16 @@
|
||||
package gay.pizza.pork.cli
|
||||
|
||||
import com.github.ajalt.clikt.core.CliktCommand
|
||||
import com.github.ajalt.clikt.parameters.arguments.argument
|
||||
import com.github.ajalt.clikt.parameters.types.path
|
||||
import gay.pizza.pork.compiler.DartCompiler
|
||||
import gay.pizza.pork.frontend.FileFrontend
|
||||
|
||||
class GenerateDartCommand : CliktCommand(help = "Generate Dart Code", name = "generate-dart") {
|
||||
val path by argument("file").path(mustExist = true, canBeDir = false)
|
||||
|
||||
override fun run() {
|
||||
val frontend = FileFrontend(path)
|
||||
println(frontend.visit(DartCompiler()))
|
||||
}
|
||||
}
|
@ -14,7 +14,8 @@ class RootCommand : CliktCommand(
|
||||
TokenizeCommand(),
|
||||
ReprintCommand(),
|
||||
AstCommand(),
|
||||
GenerateKotlinCommand()
|
||||
GenerateKotlinCommand(),
|
||||
GenerateDartCommand()
|
||||
)
|
||||
}
|
||||
|
||||
|
90
src/main/kotlin/gay/pizza/pork/compiler/DartCompiler.kt
Normal file
90
src/main/kotlin/gay/pizza/pork/compiler/DartCompiler.kt
Normal file
@ -0,0 +1,90 @@
|
||||
package gay.pizza.pork.compiler
|
||||
|
||||
import gay.pizza.pork.ast.NodeVisitor
|
||||
import gay.pizza.pork.ast.nodes.*
|
||||
import gay.pizza.pork.util.StringEscape
|
||||
|
||||
class DartCompiler : NodeVisitor<String> {
|
||||
override fun visitDefine(node: Define): String =
|
||||
"final ${visit(node.symbol)} = ${visit(node.value)};"
|
||||
|
||||
override fun visitFunctionCall(node: FunctionCall): String =
|
||||
"${visit(node.symbol)}(${node.arguments.joinToString(", ") { visit(it) }})"
|
||||
|
||||
override fun visitReference(node: SymbolReference): String =
|
||||
visit(node.symbol)
|
||||
|
||||
override fun visitIf(node: If): String = buildString {
|
||||
append("if (")
|
||||
append(visit(node.condition))
|
||||
append(") {")
|
||||
append(visit(node.thenExpression))
|
||||
append("}")
|
||||
if (node.elseExpression != null) {
|
||||
append(" else {")
|
||||
append(visit(node.elseExpression))
|
||||
append("}")
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitSymbol(node: Symbol): String =
|
||||
node.id
|
||||
|
||||
override fun visitLambda(node: Lambda): String = buildString {
|
||||
append("(${node.arguments.joinToString(", ") { visit(it) }}) {")
|
||||
appendLine()
|
||||
for ((index, expression) in node.expressions.withIndex()) {
|
||||
val code = visit(expression)
|
||||
if (index == node.expressions.size - 1) {
|
||||
append("return ");
|
||||
}
|
||||
append(code)
|
||||
append(";")
|
||||
}
|
||||
appendLine()
|
||||
append("}")
|
||||
}
|
||||
|
||||
override fun visitIntLiteral(node: IntLiteral): String =
|
||||
node.value.toString()
|
||||
|
||||
override fun visitBooleanLiteral(node: BooleanLiteral): String =
|
||||
node.value.toString()
|
||||
|
||||
override fun visitListLiteral(node: ListLiteral): String = buildString {
|
||||
append("[")
|
||||
for ((index, item) in node.items.withIndex()) {
|
||||
appendLine()
|
||||
append(visit(item))
|
||||
if (index + 1 != node.items.size) {
|
||||
append(",")
|
||||
}
|
||||
}
|
||||
append("]")
|
||||
}
|
||||
|
||||
override fun visitStringLiteral(node: StringLiteral): String =
|
||||
"\"" + StringEscape.escape(node.text) + "\""
|
||||
|
||||
override fun visitParentheses(node: Parentheses): String =
|
||||
"(${visit(node.expression)})"
|
||||
|
||||
override fun visitPrefixOperation(node: PrefixOperation): String =
|
||||
"${node.op.token}${visit(node.expression)}"
|
||||
|
||||
override fun visitInfixOperation(node: InfixOperation): String =
|
||||
"${visit(node.left)} ${node.op.token} ${visit(node.right)}"
|
||||
|
||||
override fun visitProgram(node: Program): String = buildString {
|
||||
appendLine("void main() {")
|
||||
for (item in node.expressions) {
|
||||
append(visit(item))
|
||||
if (!endsWith(";")) {
|
||||
append(";")
|
||||
}
|
||||
append(";")
|
||||
appendLine()
|
||||
}
|
||||
appendLine("}")
|
||||
}
|
||||
}
|
@ -13,8 +13,8 @@ abstract class Frontend {
|
||||
fun tokenize(): TokenStream =
|
||||
Tokenizer(createCharSource()).tokenize()
|
||||
|
||||
fun parse(): Program =
|
||||
Parser(TokenStreamSource(tokenize())).readProgram()
|
||||
fun parse(attribution: NodeAttribution = DiscardNodeAttribution): Program =
|
||||
Parser(TokenStreamSource(tokenize()), attribution).readProgram()
|
||||
|
||||
fun highlight(scheme: HighlightScheme): List<Highlight> =
|
||||
Highlighter(scheme).highlight(tokenize())
|
||||
|
@ -0,0 +1,9 @@
|
||||
package gay.pizza.pork.parse
|
||||
|
||||
import gay.pizza.pork.ast.nodes.Node
|
||||
|
||||
object DiscardNodeAttribution : NodeAttribution {
|
||||
override fun enter() {}
|
||||
override fun push(token: Token) {}
|
||||
override fun <T : Node> exit(node: T): T = node
|
||||
}
|
9
src/main/kotlin/gay/pizza/pork/parse/NodeAttribution.kt
Normal file
9
src/main/kotlin/gay/pizza/pork/parse/NodeAttribution.kt
Normal file
@ -0,0 +1,9 @@
|
||||
package gay.pizza.pork.parse
|
||||
|
||||
import gay.pizza.pork.ast.nodes.Node
|
||||
|
||||
interface NodeAttribution {
|
||||
fun enter()
|
||||
fun push(token: Token)
|
||||
fun <T: Node> exit(node: T): T
|
||||
}
|
@ -3,38 +3,41 @@ package gay.pizza.pork.parse
|
||||
import gay.pizza.pork.ast.nodes.*
|
||||
import gay.pizza.pork.util.StringEscape
|
||||
|
||||
class Parser(source: PeekableSource<Token>) {
|
||||
class Parser(source: PeekableSource<Token>, val attribution: NodeAttribution) {
|
||||
private val unsanitizedSource = source
|
||||
|
||||
private fun readIntLiteral(): IntLiteral =
|
||||
private fun readIntLiteral(): IntLiteral = within {
|
||||
expect(TokenType.IntLiteral) { IntLiteral(it.text.toInt()) }
|
||||
}
|
||||
|
||||
private fun readStringLiteral(): StringLiteral =
|
||||
private fun readStringLiteral(): StringLiteral = within {
|
||||
expect(TokenType.StringLiteral) {
|
||||
val content = StringEscape.unescape(StringEscape.unquote(it.text))
|
||||
StringLiteral(content)
|
||||
}
|
||||
}
|
||||
|
||||
private fun readBooleanLiteral(): BooleanLiteral =
|
||||
private fun readBooleanLiteral(): BooleanLiteral = within {
|
||||
expect(TokenType.True, TokenType.False) {
|
||||
BooleanLiteral(it.type == TokenType.True)
|
||||
}
|
||||
}
|
||||
|
||||
private fun readListLiteral(): ListLiteral {
|
||||
private fun readListLiteral(): ListLiteral = within {
|
||||
expect(TokenType.LeftBracket)
|
||||
val items = collect(TokenType.RightBracket, TokenType.Comma) {
|
||||
readExpression()
|
||||
}
|
||||
expect(TokenType.RightBracket)
|
||||
return ListLiteral(items)
|
||||
ListLiteral(items)
|
||||
}
|
||||
|
||||
private fun readSymbol(): Symbol =
|
||||
private fun readSymbolRaw(): Symbol =
|
||||
expect(TokenType.Symbol) { Symbol(it.text) }
|
||||
|
||||
private fun readSymbolCases(): Expression {
|
||||
val symbol = readSymbol()
|
||||
return if (next(TokenType.LeftParentheses)) {
|
||||
private fun readSymbolCases(): Expression = within {
|
||||
val symbol = readSymbolRaw()
|
||||
if (next(TokenType.LeftParentheses)) {
|
||||
val arguments = collect(TokenType.RightParentheses, TokenType.Comma) {
|
||||
readExpression()
|
||||
}
|
||||
@ -47,11 +50,11 @@ class Parser(source: PeekableSource<Token>) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun readLambda(): Lambda {
|
||||
private fun readLambda(): Lambda = within {
|
||||
expect(TokenType.LeftCurly)
|
||||
val arguments = mutableListOf<Symbol>()
|
||||
while (!peek(TokenType.In)) {
|
||||
val symbol = readSymbol()
|
||||
val symbol = readSymbolRaw()
|
||||
arguments.add(symbol)
|
||||
if (next(TokenType.Comma)) {
|
||||
continue
|
||||
@ -64,22 +67,23 @@ class Parser(source: PeekableSource<Token>) {
|
||||
readExpression()
|
||||
}
|
||||
expect(TokenType.RightCurly)
|
||||
return Lambda(arguments, items)
|
||||
Lambda(arguments, items)
|
||||
}
|
||||
|
||||
private fun readParentheses(): Parentheses {
|
||||
private fun readParentheses(): Parentheses = within {
|
||||
expect(TokenType.LeftParentheses)
|
||||
val expression = readExpression()
|
||||
expect(TokenType.RightParentheses)
|
||||
return Parentheses(expression)
|
||||
Parentheses(expression)
|
||||
}
|
||||
|
||||
private fun readNegation(): PrefixOperation =
|
||||
private fun readNegation(): PrefixOperation = within {
|
||||
expect(TokenType.Negation) {
|
||||
PrefixOperation(PrefixOperator.Negate, readExpression())
|
||||
}
|
||||
}
|
||||
|
||||
private fun readIf(): If {
|
||||
private fun readIf(): If = within {
|
||||
expect(TokenType.If)
|
||||
val condition = readExpression()
|
||||
expect(TokenType.Then)
|
||||
@ -88,7 +92,7 @@ class Parser(source: PeekableSource<Token>) {
|
||||
if (next(TokenType.Else)) {
|
||||
elseExpression = readExpression()
|
||||
}
|
||||
return If(condition, thenExpression, elseExpression)
|
||||
If(condition, thenExpression, elseExpression)
|
||||
}
|
||||
|
||||
fun readExpression(): Expression {
|
||||
@ -133,7 +137,8 @@ class Parser(source: PeekableSource<Token>) {
|
||||
else -> {
|
||||
throw RuntimeException(
|
||||
"Failed to parse token: ${token.type} '${token.text}' as" +
|
||||
" expression (index ${unsanitizedSource.currentIndex})")
|
||||
" expression (index ${unsanitizedSource.currentIndex})"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -143,7 +148,9 @@ class Parser(source: PeekableSource<Token>) {
|
||||
TokenType.Multiply,
|
||||
TokenType.Divide,
|
||||
TokenType.Equality,
|
||||
TokenType.Inequality)) {
|
||||
TokenType.Inequality
|
||||
)
|
||||
) {
|
||||
val infixToken = next()
|
||||
val infixOperator = convertInfixOperator(infixToken)
|
||||
return InfixOperation(expression, infixOperator, readExpression())
|
||||
@ -200,18 +207,21 @@ class Parser(source: PeekableSource<Token>) {
|
||||
private fun expect(vararg types: TokenType): Token {
|
||||
val token = next()
|
||||
if (!types.contains(token.type)) {
|
||||
throw RuntimeException("Expected one of ${types.joinToString(", ")} " +
|
||||
" but got type ${token.type} '${token.text}'")
|
||||
throw RuntimeException(
|
||||
"Expected one of ${types.joinToString(", ")} " +
|
||||
" but got type ${token.type} '${token.text}'"
|
||||
)
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
private fun <T> expect(vararg types: TokenType, consume: (Token) -> T): T =
|
||||
private fun <T: Node> expect(vararg types: TokenType, consume: (Token) -> T): T =
|
||||
consume(expect(*types))
|
||||
|
||||
private fun next(): Token {
|
||||
while (true) {
|
||||
val token = unsanitizedSource.next()
|
||||
attribution.push(token)
|
||||
if (ignoredByParser(token.type)) {
|
||||
continue
|
||||
}
|
||||
@ -230,6 +240,11 @@ class Parser(source: PeekableSource<Token>) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T: Node> within(block: () -> T): T {
|
||||
attribution.enter()
|
||||
return attribution.exit(block())
|
||||
}
|
||||
|
||||
private fun ignoredByParser(type: TokenType): Boolean = when (type) {
|
||||
TokenType.BlockComment -> true
|
||||
TokenType.LineComment -> true
|
||||
|
43
src/main/kotlin/gay/pizza/pork/parse/TokenNodeAttribution.kt
Normal file
43
src/main/kotlin/gay/pizza/pork/parse/TokenNodeAttribution.kt
Normal file
@ -0,0 +1,43 @@
|
||||
package gay.pizza.pork.parse
|
||||
|
||||
import gay.pizza.pork.ast.NodeCoalescer
|
||||
import gay.pizza.pork.ast.nodes.Node
|
||||
import java.util.IdentityHashMap
|
||||
|
||||
class TokenNodeAttribution : NodeAttribution {
|
||||
private val map: MutableMap<Node, List<Token>> = IdentityHashMap()
|
||||
|
||||
private val stack = mutableListOf<MutableList<Token>>()
|
||||
private var current: MutableList<Token>? = null
|
||||
|
||||
override fun enter() {
|
||||
val store = mutableListOf<Token>()
|
||||
current = store
|
||||
stack.add(store)
|
||||
}
|
||||
|
||||
override fun push(token: Token) {
|
||||
val store = current ?: throw RuntimeException("enter() not called!")
|
||||
store.add(token)
|
||||
}
|
||||
|
||||
override fun <T: Node> exit(node: T): T {
|
||||
val store = stack.removeLast()
|
||||
map[node] = store
|
||||
return node
|
||||
}
|
||||
|
||||
fun tokensOf(node: Node): List<Token>? = map[node]
|
||||
|
||||
fun assembleTokens(node: Node): List<Token> {
|
||||
val allTokens = mutableListOf<Token>()
|
||||
val coalescer = NodeCoalescer { item ->
|
||||
val tokens = tokensOf(item)
|
||||
if (tokens != null) {
|
||||
allTokens.addAll(tokens)
|
||||
}
|
||||
}
|
||||
coalescer.visit(node)
|
||||
return allTokens
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user