diff --git a/src/main/kotlin/gay/pizza/pork/ast/nodes/StringLiteral.kt b/src/main/kotlin/gay/pizza/pork/ast/nodes/StringLiteral.kt index 7c3eba2..c7ee199 100644 --- a/src/main/kotlin/gay/pizza/pork/ast/nodes/StringLiteral.kt +++ b/src/main/kotlin/gay/pizza/pork/ast/nodes/StringLiteral.kt @@ -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 diff --git a/src/main/kotlin/gay/pizza/pork/cli/GenerateDartCommand.kt b/src/main/kotlin/gay/pizza/pork/cli/GenerateDartCommand.kt new file mode 100644 index 0000000..1b86212 --- /dev/null +++ b/src/main/kotlin/gay/pizza/pork/cli/GenerateDartCommand.kt @@ -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())) + } +} diff --git a/src/main/kotlin/gay/pizza/pork/cli/RootCommand.kt b/src/main/kotlin/gay/pizza/pork/cli/RootCommand.kt index fae9406..cf853bd 100644 --- a/src/main/kotlin/gay/pizza/pork/cli/RootCommand.kt +++ b/src/main/kotlin/gay/pizza/pork/cli/RootCommand.kt @@ -14,7 +14,8 @@ class RootCommand : CliktCommand( TokenizeCommand(), ReprintCommand(), AstCommand(), - GenerateKotlinCommand() + GenerateKotlinCommand(), + GenerateDartCommand() ) } diff --git a/src/main/kotlin/gay/pizza/pork/compiler/DartCompiler.kt b/src/main/kotlin/gay/pizza/pork/compiler/DartCompiler.kt new file mode 100644 index 0000000..a5191be --- /dev/null +++ b/src/main/kotlin/gay/pizza/pork/compiler/DartCompiler.kt @@ -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 { + 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("}") + } +} diff --git a/src/main/kotlin/gay/pizza/pork/frontend/Frontend.kt b/src/main/kotlin/gay/pizza/pork/frontend/Frontend.kt index ecd3fcb..9e36045 100644 --- a/src/main/kotlin/gay/pizza/pork/frontend/Frontend.kt +++ b/src/main/kotlin/gay/pizza/pork/frontend/Frontend.kt @@ -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 = Highlighter(scheme).highlight(tokenize()) diff --git a/src/main/kotlin/gay/pizza/pork/parse/DiscardNodeAttribution.kt b/src/main/kotlin/gay/pizza/pork/parse/DiscardNodeAttribution.kt new file mode 100644 index 0000000..d33ecce --- /dev/null +++ b/src/main/kotlin/gay/pizza/pork/parse/DiscardNodeAttribution.kt @@ -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 exit(node: T): T = node +} diff --git a/src/main/kotlin/gay/pizza/pork/parse/NodeAttribution.kt b/src/main/kotlin/gay/pizza/pork/parse/NodeAttribution.kt new file mode 100644 index 0000000..9bba5f9 --- /dev/null +++ b/src/main/kotlin/gay/pizza/pork/parse/NodeAttribution.kt @@ -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 exit(node: T): T +} diff --git a/src/main/kotlin/gay/pizza/pork/parse/Parser.kt b/src/main/kotlin/gay/pizza/pork/parse/Parser.kt index 1316d7b..181d4db 100644 --- a/src/main/kotlin/gay/pizza/pork/parse/Parser.kt +++ b/src/main/kotlin/gay/pizza/pork/parse/Parser.kt @@ -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) { +class Parser(source: PeekableSource, 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) { } } - private fun readLambda(): Lambda { + private fun readLambda(): Lambda = within { expect(TokenType.LeftCurly) val arguments = mutableListOf() 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) { 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) { 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) { 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) { 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) { 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 expect(vararg types: TokenType, consume: (Token) -> T): T = + private fun 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) { } } + private fun within(block: () -> T): T { + attribution.enter() + return attribution.exit(block()) + } + private fun ignoredByParser(type: TokenType): Boolean = when (type) { TokenType.BlockComment -> true TokenType.LineComment -> true diff --git a/src/main/kotlin/gay/pizza/pork/parse/TokenNodeAttribution.kt b/src/main/kotlin/gay/pizza/pork/parse/TokenNodeAttribution.kt new file mode 100644 index 0000000..0edb5d1 --- /dev/null +++ b/src/main/kotlin/gay/pizza/pork/parse/TokenNodeAttribution.kt @@ -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> = IdentityHashMap() + + private val stack = mutableListOf>() + private var current: MutableList? = null + + override fun enter() { + val store = mutableListOf() + current = store + stack.add(store) + } + + override fun push(token: Token) { + val store = current ?: throw RuntimeException("enter() not called!") + store.add(token) + } + + override fun exit(node: T): T { + val store = stack.removeLast() + map[node] = store + return node + } + + fun tokensOf(node: Node): List? = map[node] + + fun assembleTokens(node: Node): List { + val allTokens = mutableListOf() + val coalescer = NodeCoalescer { item -> + val tokens = tokensOf(item) + if (tokens != null) { + allTokens.addAll(tokens) + } + } + coalescer.visit(node) + return allTokens + } +}