From 36b574bf5b3a7cc9c2b500da9004e4767977723e Mon Sep 17 00:00:00 2001 From: a dinosaur Date: Mon, 11 Sep 2023 14:13:08 +1000 Subject: [PATCH] language: add unary plus & minus, post increment & decrement operators, non-newline print builtin. fix block comments --- ast/src/main/ast/pork.yml | 24 +++++++ ast/src/main/graph/types.dot | 5 ++ .../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 + .../gay/pizza/pork/ast/PrefixOperator.kt | 4 +- .../gay/pizza/pork/ast/SuffixOperation.kt | 29 ++++++++ .../gay/pizza/pork/ast/SuffixOperator.kt | 12 ++++ .../pizza/pork/evaluator/EvaluationVisitor.kt | 70 +++++++++++++++++++ .../pork/evaluator/InternalNativeProvider.kt | 17 ++--- examples/suffix.pork | 20 ++++++ examples/unary.pork | 6 ++ .../kotlin/gay/pizza/pork/parser/Parser.kt | 56 +++++++++------ .../kotlin/gay/pizza/pork/parser/Printer.kt | 5 ++ .../kotlin/gay/pizza/pork/parser/TokenType.kt | 6 +- .../kotlin/gay/pizza/pork/parser/Tokenizer.kt | 1 + stdlib/src/main/pork/lang/prelude.pork | 5 +- 18 files changed, 235 insertions(+), 32 deletions(-) create mode 100644 ast/src/main/kotlin/gay/pizza/pork/ast/SuffixOperation.kt create mode 100644 ast/src/main/kotlin/gay/pizza/pork/ast/SuffixOperator.kt create mode 100644 examples/suffix.pork create mode 100644 examples/unary.pork diff --git a/ast/src/main/ast/pork.yml b/ast/src/main/ast/pork.yml index 9221695..608ad1e 100644 --- a/ast/src/main/ast/pork.yml +++ b/ast/src/main/ast/pork.yml @@ -181,6 +181,12 @@ types: - name: Negate values: token: "!" + - name: UnaryPlus + values: + token: "+" + - name: UnaryMinus + values: + token: "-" PrefixOperation: parent: Expression values: @@ -188,6 +194,24 @@ types: type: PrefixOperator - name: expression type: Expression + SuffixOperator: + values: + - name: token + type: String + enums: + - name: Increment + values: + token: "++" + - name: Decrement + values: + token: "--" + SuffixOperation: + parent: Expression + values: + - name: op + type: SuffixOperator + - name: reference + type: SymbolReference StringLiteral: parent: Expression values: diff --git a/ast/src/main/graph/types.dot b/ast/src/main/graph/types.dot index 6d3fc4a..aaf3fa6 100644 --- a/ast/src/main/graph/types.dot +++ b/ast/src/main/graph/types.dot @@ -24,6 +24,8 @@ digraph A { type_Parentheses [shape=box,label="Parentheses"] type_PrefixOperator [shape=box,label="PrefixOperator"] type_PrefixOperation [shape=box,label="PrefixOperation"] + type_SuffixOperator [shape=box,label="SuffixOperator"] + type_SuffixOperation [shape=box,label="SuffixOperation"] type_StringLiteral [shape=box,label="StringLiteral"] type_SymbolReference [shape=box,label="SymbolReference"] type_While [shape=box,label="While"] @@ -49,6 +51,7 @@ digraph A { type_Expression -> type_ListLiteral type_Expression -> type_Parentheses type_Expression -> type_PrefixOperation + type_Expression -> type_SuffixOperation type_Expression -> type_StringLiteral type_Expression -> type_SymbolReference type_Expression -> type_While @@ -84,6 +87,8 @@ digraph A { type_Parentheses -> type_Expression [style=dotted] type_PrefixOperation -> type_PrefixOperator [style=dotted] type_PrefixOperation -> type_Expression [style=dotted] + type_SuffixOperation -> type_SuffixOperator [style=dotted] + type_SuffixOperation -> type_SymbolReference [style=dotted] type_SymbolReference -> type_Symbol [style=dotted] type_While -> type_Expression [style=dotted] type_While -> type_Block [style=dotted] 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 a434cbd..4a4e34d 100644 --- a/ast/src/main/kotlin/gay/pizza/pork/ast/NodeCoalescer.kt +++ b/ast/src/main/kotlin/gay/pizza/pork/ast/NodeCoalescer.kt @@ -59,6 +59,9 @@ class NodeCoalescer(val handler: (Node) -> Unit) : NodeVisitor { override fun visitStringLiteral(node: StringLiteral): Unit = handle(node) + override fun visitSuffixOperation(node: SuffixOperation): Unit = + handle(node) + override fun visitSymbol(node: Symbol): 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 eb7b914..abb3d64 100644 --- a/ast/src/main/kotlin/gay/pizza/pork/ast/NodeType.kt +++ b/ast/src/main/kotlin/gay/pizza/pork/ast/NodeType.kt @@ -25,6 +25,7 @@ enum class NodeType(val parent: NodeType? = null) { PrefixOperation(Expression), SetAssignment(Expression), StringLiteral(Expression), + SuffixOperation(Expression), Symbol(Node), SymbolReference(Expression), VarAssignment(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 75a85d3..886f047 100644 --- a/ast/src/main/kotlin/gay/pizza/pork/ast/NodeVisitor.kt +++ b/ast/src/main/kotlin/gay/pizza/pork/ast/NodeVisitor.kt @@ -40,6 +40,8 @@ interface NodeVisitor { fun visitStringLiteral(node: StringLiteral): T + fun visitSuffixOperation(node: SuffixOperation): T + fun visitSymbol(node: Symbol): T fun visitSymbolReference(node: SymbolReference): 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 bebdb9a..ae6ce01 100644 --- a/ast/src/main/kotlin/gay/pizza/pork/ast/NodeVisitorExtensions.kt +++ b/ast/src/main/kotlin/gay/pizza/pork/ast/NodeVisitorExtensions.kt @@ -20,6 +20,7 @@ fun NodeVisitor.visit(node: Node): T = is ListLiteral -> visitListLiteral(node) is Parentheses -> visitParentheses(node) is PrefixOperation -> visitPrefixOperation(node) + is SuffixOperation -> visitSuffixOperation(node) is StringLiteral -> visitStringLiteral(node) is SymbolReference -> visitSymbolReference(node) is While -> visitWhile(node) diff --git a/ast/src/main/kotlin/gay/pizza/pork/ast/PrefixOperator.kt b/ast/src/main/kotlin/gay/pizza/pork/ast/PrefixOperator.kt index 3bc9094..fa0e797 100644 --- a/ast/src/main/kotlin/gay/pizza/pork/ast/PrefixOperator.kt +++ b/ast/src/main/kotlin/gay/pizza/pork/ast/PrefixOperator.kt @@ -7,5 +7,7 @@ import kotlinx.serialization.Serializable @Serializable @SerialName("prefixOperator") enum class PrefixOperator(val token: String) { - Negate("!") + Negate("!"), + UnaryPlus("+"), + UnaryMinus("-") } diff --git a/ast/src/main/kotlin/gay/pizza/pork/ast/SuffixOperation.kt b/ast/src/main/kotlin/gay/pizza/pork/ast/SuffixOperation.kt new file mode 100644 index 0000000..df9c3e8 --- /dev/null +++ b/ast/src/main/kotlin/gay/pizza/pork/ast/SuffixOperation.kt @@ -0,0 +1,29 @@ +// GENERATED CODE FROM PORK AST CODEGEN +package gay.pizza.pork.ast + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +@SerialName("suffixOperation") +class SuffixOperation(val op: SuffixOperator, val reference: SymbolReference) : Expression() { + override val type: NodeType = NodeType.SuffixOperation + + override fun visitChildren(visitor: NodeVisitor): List = + visitor.visitNodes(reference) + + override fun visit(visitor: NodeVisitor): T = + visitor.visitSuffixOperation(this) + + override fun equals(other: Any?): Boolean { + if (other !is SuffixOperation) return false + return other.op == op && other.reference == reference + } + + override fun hashCode(): Int { + var result = op.hashCode() + result = 31 * result + reference.hashCode() + result = 31 * result + type.hashCode() + return result + } +} diff --git a/ast/src/main/kotlin/gay/pizza/pork/ast/SuffixOperator.kt b/ast/src/main/kotlin/gay/pizza/pork/ast/SuffixOperator.kt new file mode 100644 index 0000000..612e32c --- /dev/null +++ b/ast/src/main/kotlin/gay/pizza/pork/ast/SuffixOperator.kt @@ -0,0 +1,12 @@ +// GENERATED CODE FROM PORK AST CODEGEN +package gay.pizza.pork.ast + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +@SerialName("suffixOperator") +enum class SuffixOperator(val token: String) { + Increment("++"), + Decrement("--") +} 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 2d7a903..8cb023b 100644 --- a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/EvaluationVisitor.kt +++ b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/EvaluationVisitor.kt @@ -71,9 +71,65 @@ class EvaluationVisitor(root: Scope) : NodeVisitor { } !value } + PrefixOperator.UnaryPlus, PrefixOperator.UnaryMinus -> { + if (value !is Number) { + throw RuntimeException("Numeric unary '${node.op.token}' illegal on non-numeric type '${value.javaClass.simpleName}'") + } + unaryNumericOperation(node, value) + } } } + private fun unaryNumericOperation(node: PrefixOperation, value: Number) = when (value) { + is Double -> { + unaryNumericOperation( + node.op, + value, + convert = { it.toDouble() }, + plus = { +it }, + minus = { -it } + ) + } + is Float -> { + unaryNumericOperation( + node.op, + value, + convert = { it.toFloat() }, + plus = { +it }, + minus = { -it } + ) + } + is Long -> { + unaryNumericOperation( + node.op, + value, + convert = { it.toLong() }, + plus = { +it }, + minus = { -it } + ) + } + is Int -> { + unaryNumericOperation( + node.op, + value, + convert = { it.toInt() }, + plus = { +it }, + minus = { -it } + ) + } + else -> throw RuntimeException("Unknown numeric type: ${value.javaClass.name}") + } + + override fun visitSuffixOperation(node: SuffixOperation): Any { + val previousValue = currentScope.value(node.reference.symbol.id) + val infix = visitInfixOperation(InfixOperation(node.reference, when (node.op) { + SuffixOperator.Increment -> InfixOperator.Plus + SuffixOperator.Decrement -> InfixOperator.Minus + }, IntegerLiteral(1))) + currentScope.set(node.reference.symbol.id, infix) + return previousValue + } + override fun visitSetAssignment(node: SetAssignment): Any { val value = node.value.visit(this) currentScope.set(node.symbol.id, value) @@ -219,6 +275,20 @@ class EvaluationVisitor(root: Scope) : NodeVisitor { } } + private inline fun unaryNumericOperation( + op: PrefixOperator, + value: Number, + convert: (Number) -> T, + plus: (T) -> T, + minus: (T) -> T + ): Any { + return when (op) { + PrefixOperator.UnaryPlus -> plus(convert(value)) + PrefixOperator.UnaryMinus -> minus(convert(value)) + else -> throw RuntimeException("Unable to handle operation $op") + } + } + override fun visitBlock(node: Block): BlockFunction = BlockFunction { var value: Any? = null for (expression in node.expressions) { diff --git a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/InternalNativeProvider.kt b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/InternalNativeProvider.kt index a84750a..a276711 100644 --- a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/InternalNativeProvider.kt +++ b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/InternalNativeProvider.kt @@ -2,6 +2,7 @@ package gay.pizza.pork.evaluator class InternalNativeProvider(val quiet: Boolean = false) : NativeProvider { private val functions = mutableMapOf( + "print" to CallableFunction(::printValues), "println" to CallableFunction(::printLine) ) @@ -9,15 +10,15 @@ class InternalNativeProvider(val quiet: Boolean = false) : NativeProvider { return functions[definition] ?: throw RuntimeException("Unknown Internal Function: $definition") } + private fun printValues(arguments: Arguments): Any { + if (quiet || arguments.values.isEmpty()) return None + print(arguments.values.joinToString(" ")) + return None + } + private fun printLine(arguments: Arguments): Any { - if (quiet) { - return None - } - when (arguments.values.count()) { - 0 -> println() - 1 -> println(arguments.values[0]) - else -> println(arguments.values.joinToString(" ")) - } + if (quiet) return None + println(arguments.values.joinToString(" ")) return None } } diff --git a/examples/suffix.pork b/examples/suffix.pork new file mode 100644 index 0000000..506a4c0 --- /dev/null +++ b/examples/suffix.pork @@ -0,0 +1,20 @@ +/* Barber shop sign generator! */ +func generatePole(width, height) { + var i = 0 + while i < height { + let index = i++ mod width + var j = 0 + while j < width { + if j == index { print("#") } else { print("_") } + j++ + } + println() + } +} + +export func main() { + println("nobody:") + println() + println("barber pole:") + generatePole(6, 18) +} diff --git a/examples/unary.pork b/examples/unary.pork new file mode 100644 index 0000000..8f28758 --- /dev/null +++ b/examples/unary.pork @@ -0,0 +1,6 @@ +export func main() { + let constant = -42 + println("constant set by unary minus: ", constant) + println("constant modified by unary minus:", -constant) + println("constant modified by unary plus: ", +constant) +} 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 03006fe..08ac4fe 100644 --- a/parser/src/main/kotlin/gay/pizza/pork/parser/Parser.kt +++ b/parser/src/main/kotlin/gay/pizza/pork/parser/Parser.kt @@ -66,7 +66,12 @@ class Parser(source: PeekableSource, val attribution: NodeAttribution) { expect(TokenType.RightParentheses) FunctionCall(symbol, arguments) } else { - SymbolReference(symbol) + val reference = SymbolReference(symbol) + if (peek(TokenType.PlusPlus, TokenType.MinusMinus)) { + expect(TokenType.PlusPlus, TokenType.MinusMinus) { + SuffixOperation(convertSuffixOperator(it), reference) + } + } else reference } } @@ -78,8 +83,8 @@ class Parser(source: PeekableSource, val attribution: NodeAttribution) { } private fun readPrefixOperation(): PrefixOperation = within { - expect(TokenType.Negation) { - PrefixOperation(PrefixOperator.Negate, readExpression()) + expect(TokenType.Negation, TokenType.Plus, TokenType.Minus) { + PrefixOperation(convertPrefixOperator(it), readExpression()) } } @@ -143,7 +148,7 @@ class Parser(source: PeekableSource, val attribution: NodeAttribution) { readParentheses() } - TokenType.Negation -> { + TokenType.Negation, TokenType.Plus, TokenType.Minus -> { readPrefixOperation() } @@ -288,22 +293,33 @@ class Parser(source: PeekableSource, val attribution: NodeAttribution) { } } - private fun convertInfixOperator(token: Token): InfixOperator = - when (token.type) { - TokenType.Plus -> InfixOperator.Plus - TokenType.Minus -> InfixOperator.Minus - TokenType.Multiply -> InfixOperator.Multiply - TokenType.Divide -> InfixOperator.Divide - TokenType.Equality -> InfixOperator.Equals - TokenType.Inequality -> InfixOperator.NotEquals - TokenType.Mod -> InfixOperator.EuclideanModulo - TokenType.Rem -> InfixOperator.Remainder - TokenType.Lesser -> InfixOperator.Lesser - TokenType.Greater -> InfixOperator.Greater - TokenType.LesserEqual -> InfixOperator.LesserEqual - TokenType.GreaterEqual -> InfixOperator.GreaterEqual - else -> throw RuntimeException("Unknown Infix Operator") - } + private fun convertInfixOperator(token: Token): InfixOperator = when (token.type) { + TokenType.Plus -> InfixOperator.Plus + TokenType.Minus -> InfixOperator.Minus + TokenType.Multiply -> InfixOperator.Multiply + TokenType.Divide -> InfixOperator.Divide + TokenType.Equality -> InfixOperator.Equals + TokenType.Inequality -> InfixOperator.NotEquals + TokenType.Mod -> InfixOperator.EuclideanModulo + TokenType.Rem -> InfixOperator.Remainder + TokenType.Lesser -> InfixOperator.Lesser + TokenType.Greater -> InfixOperator.Greater + TokenType.LesserEqual -> InfixOperator.LesserEqual + TokenType.GreaterEqual -> InfixOperator.GreaterEqual + else -> throw RuntimeException("Unknown Infix Operator") + } + + private fun convertPrefixOperator(token: Token): PrefixOperator = when (token.type) { + TokenType.Plus -> PrefixOperator.UnaryPlus + TokenType.Minus -> PrefixOperator.UnaryMinus + else -> throw RuntimeException("Unknown Prefix Operator") + } + + private fun convertSuffixOperator(token: Token): SuffixOperator = when (token.type) { + TokenType.PlusPlus -> SuffixOperator.Increment + TokenType.MinusMinus -> SuffixOperator.Decrement + else -> throw RuntimeException("Unknown Suffix Operator") + } fun readCompilationUnit(): CompilationUnit = within { val declarations = mutableListOf() 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 33c778d..e0de25e 100644 --- a/parser/src/main/kotlin/gay/pizza/pork/parser/Printer.kt +++ b/parser/src/main/kotlin/gay/pizza/pork/parser/Printer.kt @@ -120,6 +120,11 @@ class Printer(buffer: StringBuilder) : NodeVisitor { visit(node.expression) } + override fun visitSuffixOperation(node: SuffixOperation) { + visit(node.reference) + append(node.op.token) + } + override fun visitSetAssignment(node: SetAssignment) { visit(node.symbol) append(" = ") 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 c980ff7..413a232 100644 --- a/parser/src/main/kotlin/gay/pizza/pork/parser/TokenType.kt +++ b/parser/src/main/kotlin/gay/pizza/pork/parser/TokenType.kt @@ -15,8 +15,10 @@ enum class TokenType(vararg properties: TokenTypeProperty) { Equality(OperatorFamily), Inequality(OperatorFamily), Equals(SingleChar('='), Promotion('=', Equality)), - Plus(SingleChar('+'), OperatorFamily), - Minus(SingleChar('-'), OperatorFamily), + PlusPlus(ManyChars("++"), OperatorFamily), + MinusMinus(ManyChars("--"), OperatorFamily), + Plus(SingleChar('+'), OperatorFamily, Promotion('+', PlusPlus)), + Minus(SingleChar('-'), OperatorFamily, Promotion('-', MinusMinus)), Multiply(SingleChar('*'), OperatorFamily), Divide(SingleChar('/'), OperatorFamily), LesserEqual(OperatorFamily), diff --git a/parser/src/main/kotlin/gay/pizza/pork/parser/Tokenizer.kt b/parser/src/main/kotlin/gay/pizza/pork/parser/Tokenizer.kt index 5ee25e3..d985c4c 100644 --- a/parser/src/main/kotlin/gay/pizza/pork/parser/Tokenizer.kt +++ b/parser/src/main/kotlin/gay/pizza/pork/parser/Tokenizer.kt @@ -9,6 +9,7 @@ class Tokenizer(val source: CharSource) { var endOfComment = false while (true) { val char = source.next() + if (char == CharSource.NullChar) throw RuntimeException("Unterminated block comment") append(char) if (endOfComment) { diff --git a/stdlib/src/main/pork/lang/prelude.pork b/stdlib/src/main/pork/lang/prelude.pork index bdaba4e..71da5b6 100644 --- a/stdlib/src/main/pork/lang/prelude.pork +++ b/stdlib/src/main/pork/lang/prelude.pork @@ -1,2 +1,5 @@ -export func println(messages...) +export func print(values...) + native internal "print" + +export func println(values...) native internal "println"