From 0aab45094ada1bb0b6122a5a1b9f8bf145ebd38d Mon Sep 17 00:00:00 2001 From: Alex Zenla Date: Mon, 11 Sep 2023 05:34:09 -0400 Subject: [PATCH] language: implement for in --- ast/src/main/ast/pork.yml | 9 ++++++ ast/src/main/graph/types.dot | 5 ++++ .../main/kotlin/gay/pizza/pork/ast/ForIn.kt | 30 +++++++++++++++++++ .../gay/pizza/pork/ast/NodeCoalescer.kt | 3 ++ .../kotlin/gay/pizza/pork/ast/NodeType.kt | 1 + .../kotlin/gay/pizza/pork/ast/NodeVisitor.kt | 2 ++ .../pizza/pork/ast/NodeVisitorExtensions.kt | 1 + .../pizza/pork/evaluator/EvaluationVisitor.kt | 23 ++++++++++++++ examples/for.pork | 7 +++++ .../kotlin/gay/pizza/pork/parser/Parser.kt | 13 ++++++++ .../kotlin/gay/pizza/pork/parser/Printer.kt | 9 ++++++ .../kotlin/gay/pizza/pork/parser/TokenType.kt | 2 ++ 12 files changed, 105 insertions(+) create mode 100644 ast/src/main/kotlin/gay/pizza/pork/ast/ForIn.kt create mode 100644 examples/for.pork diff --git a/ast/src/main/ast/pork.yml b/ast/src/main/ast/pork.yml index 1d2436b..c725c00 100644 --- a/ast/src/main/ast/pork.yml +++ b/ast/src/main/ast/pork.yml @@ -250,6 +250,15 @@ types: type: Expression - name: block type: Block + ForIn: + parent: Expression + values: + - name: symbol + type: Symbol + - name: expression + type: Expression + - name: block + type: Block Break: parent: Expression values: [] diff --git a/ast/src/main/graph/types.dot b/ast/src/main/graph/types.dot index 99e31bc..0bd1e5e 100644 --- a/ast/src/main/graph/types.dot +++ b/ast/src/main/graph/types.dot @@ -30,6 +30,7 @@ digraph A { type_StringLiteral [shape=box,label="StringLiteral"] type_SymbolReference [shape=box,label="SymbolReference"] type_While [shape=box,label="While"] + type_ForIn [shape=box,label="ForIn"] type_Break [shape=box,label="Break"] type_Continue [shape=box,label="Continue"] type_Native [shape=box,label="Native"] @@ -56,6 +57,7 @@ digraph A { type_Expression -> type_StringLiteral type_Expression -> type_SymbolReference type_Expression -> type_While + type_Expression -> type_ForIn type_Expression -> type_Break type_Expression -> type_Continue type_Definition -> type_FunctionDefinition @@ -97,6 +99,9 @@ digraph A { type_SymbolReference -> type_Symbol [style=dotted] type_While -> type_Expression [style=dotted] type_While -> type_Block [style=dotted] + type_ForIn -> type_Symbol [style=dotted] + type_ForIn -> type_Expression [style=dotted] + type_ForIn -> type_Block [style=dotted] type_Native -> type_Symbol [style=dotted] type_Native -> type_StringLiteral [style=dotted] } diff --git a/ast/src/main/kotlin/gay/pizza/pork/ast/ForIn.kt b/ast/src/main/kotlin/gay/pizza/pork/ast/ForIn.kt new file mode 100644 index 0000000..b934fdd --- /dev/null +++ b/ast/src/main/kotlin/gay/pizza/pork/ast/ForIn.kt @@ -0,0 +1,30 @@ +// GENERATED CODE FROM PORK AST CODEGEN +package gay.pizza.pork.ast + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +@SerialName("forIn") +class ForIn(val symbol: Symbol, val expression: Expression, val block: Block) : Expression() { + override val type: NodeType = NodeType.ForIn + + override fun visitChildren(visitor: NodeVisitor): List = + visitor.visitNodes(symbol, expression, block) + + override fun visit(visitor: NodeVisitor): T = + visitor.visitForIn(this) + + override fun equals(other: Any?): Boolean { + if (other !is ForIn) return false + return other.symbol == symbol && other.expression == expression && other.block == block + } + + override fun hashCode(): Int { + var result = symbol.hashCode() + result = 31 * result + expression.hashCode() + result = 31 * result + block.hashCode() + result = 31 * result + type.hashCode() + return result + } +} diff --git a/ast/src/main/kotlin/gay/pizza/pork/ast/NodeCoalescer.kt b/ast/src/main/kotlin/gay/pizza/pork/ast/NodeCoalescer.kt index 24472ab..ba45370 100644 --- a/ast/src/main/kotlin/gay/pizza/pork/ast/NodeCoalescer.kt +++ b/ast/src/main/kotlin/gay/pizza/pork/ast/NodeCoalescer.kt @@ -20,6 +20,9 @@ class NodeCoalescer(val handler: (Node) -> Unit) : NodeVisitor { override fun visitDoubleLiteral(node: DoubleLiteral): Unit = handle(node) + override fun visitForIn(node: ForIn): Unit = + handle(node) + override fun visitFunctionCall(node: FunctionCall): Unit = handle(node) diff --git a/ast/src/main/kotlin/gay/pizza/pork/ast/NodeType.kt b/ast/src/main/kotlin/gay/pizza/pork/ast/NodeType.kt index 859960a..8860bdc 100644 --- a/ast/src/main/kotlin/gay/pizza/pork/ast/NodeType.kt +++ b/ast/src/main/kotlin/gay/pizza/pork/ast/NodeType.kt @@ -12,6 +12,7 @@ enum class NodeType(val parent: NodeType? = null) { Declaration(Node), Definition(Node), DoubleLiteral(Expression), + ForIn(Expression), FunctionCall(Expression), FunctionDefinition(Definition), If(Expression), diff --git a/ast/src/main/kotlin/gay/pizza/pork/ast/NodeVisitor.kt b/ast/src/main/kotlin/gay/pizza/pork/ast/NodeVisitor.kt index 7278924..faff876 100644 --- a/ast/src/main/kotlin/gay/pizza/pork/ast/NodeVisitor.kt +++ b/ast/src/main/kotlin/gay/pizza/pork/ast/NodeVisitor.kt @@ -14,6 +14,8 @@ interface NodeVisitor { fun visitDoubleLiteral(node: DoubleLiteral): T + fun visitForIn(node: ForIn): T + fun visitFunctionCall(node: FunctionCall): T fun visitFunctionDefinition(node: FunctionDefinition): T diff --git a/ast/src/main/kotlin/gay/pizza/pork/ast/NodeVisitorExtensions.kt b/ast/src/main/kotlin/gay/pizza/pork/ast/NodeVisitorExtensions.kt index ce4ab1b..8d45fbd 100644 --- a/ast/src/main/kotlin/gay/pizza/pork/ast/NodeVisitorExtensions.kt +++ b/ast/src/main/kotlin/gay/pizza/pork/ast/NodeVisitorExtensions.kt @@ -25,6 +25,7 @@ fun NodeVisitor.visit(node: Node): T = is StringLiteral -> visitStringLiteral(node) is SymbolReference -> visitSymbolReference(node) is While -> visitWhile(node) + is ForIn -> visitForIn(node) is Break -> visitBreak(node) is Continue -> visitContinue(node) is Native -> visitNative(node) diff --git a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/EvaluationVisitor.kt b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/EvaluationVisitor.kt index ba3b21b..420415e 100644 --- a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/EvaluationVisitor.kt +++ b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/EvaluationVisitor.kt @@ -8,6 +8,29 @@ class EvaluationVisitor(root: Scope) : NodeVisitor { override fun visitIntegerLiteral(node: IntegerLiteral): Any = node.value override fun visitDoubleLiteral(node: DoubleLiteral): Any = node.value + override fun visitForIn(node: ForIn): Any { + val blockFunction = node.block.visit(this) as BlockFunction + var result: Any? = null + val value = node.expression.visit(this) + if (value !is Iterable<*>) { + throw RuntimeException("Unable to iterate on value that is not a iterable.") + } + + for (item in value) { + try { + scoped { + currentScope.define(node.symbol.id, item ?: None) + result = blockFunction.call() + } + } catch (_: BreakMarker) { + break + } catch (_: ContinueMarker) { + continue + } + } + return result ?: None + } + override fun visitStringLiteral(node: StringLiteral): Any = node.text override fun visitBooleanLiteral(node: BooleanLiteral): Any = node.value diff --git a/examples/for.pork b/examples/for.pork new file mode 100644 index 0000000..7c5f333 --- /dev/null +++ b/examples/for.pork @@ -0,0 +1,7 @@ +let items = ["Hello", "Goodbye"] + +export func main() { + for item in items { + println(item) + } +} diff --git a/parser/src/main/kotlin/gay/pizza/pork/parser/Parser.kt b/parser/src/main/kotlin/gay/pizza/pork/parser/Parser.kt index 53a7206..9390831 100644 --- a/parser/src/main/kotlin/gay/pizza/pork/parser/Parser.kt +++ b/parser/src/main/kotlin/gay/pizza/pork/parser/Parser.kt @@ -106,6 +106,15 @@ class Parser(source: PeekableSource, val attribution: NodeAttribution) { While(condition, block) } + private fun readForIn(): ForIn = within { + expect(TokenType.For) + val symbol = readSymbolRaw() + expect(TokenType.In) + val value = readExpression() + val block = readBlock() + ForIn(symbol, value, block) + } + private fun readNative(): Native = within { expect(TokenType.Native) val form = readSymbolRaw() @@ -160,6 +169,10 @@ class Parser(source: PeekableSource, val attribution: NodeAttribution) { readWhile() } + TokenType.For -> { + readForIn() + } + TokenType.Break -> { expect(TokenType.Break) Break() diff --git a/parser/src/main/kotlin/gay/pizza/pork/parser/Printer.kt b/parser/src/main/kotlin/gay/pizza/pork/parser/Printer.kt index 2f1ed39..146ebb7 100644 --- a/parser/src/main/kotlin/gay/pizza/pork/parser/Printer.kt +++ b/parser/src/main/kotlin/gay/pizza/pork/parser/Printer.kt @@ -28,6 +28,15 @@ class Printer(buffer: StringBuilder) : NodeVisitor { append(node.value.toString()) } + override fun visitForIn(node: ForIn) { + append("for ") + visit(node.symbol) + append(" in ") + visit(node.expression) + append(" ") + visit(node.block) + } + override fun visitStringLiteral(node: StringLiteral) { append("\"") append(StringEscape.escape(node.text)) diff --git a/parser/src/main/kotlin/gay/pizza/pork/parser/TokenType.kt b/parser/src/main/kotlin/gay/pizza/pork/parser/TokenType.kt index cc4b5a2..6e68058 100644 --- a/parser/src/main/kotlin/gay/pizza/pork/parser/TokenType.kt +++ b/parser/src/main/kotlin/gay/pizza/pork/parser/TokenType.kt @@ -47,6 +47,8 @@ enum class TokenType(vararg properties: TokenTypeProperty) { If(ManyChars("if"), KeywordFamily), Else(ManyChars("else"), KeywordFamily), While(ManyChars("while"), KeywordFamily), + For(ManyChars("for"), KeywordFamily), + In(ManyChars("in"), KeywordFamily), Continue(ManyChars("continue"), KeywordFamily), Break(ManyChars("break"), KeywordFamily), Import(ManyChars("import"), KeywordFamily),