From e8766323ee56bd0e35e4b7591fbf7d7bc41ef398 Mon Sep 17 00:00:00 2001 From: Alex Zenla Date: Sat, 9 Sep 2023 00:08:30 -0400 Subject: [PATCH] language: floating point support --- ast/src/main/ast/pork.yml | 7 +- .../ast/{IntLiteral.kt => DoubleLiteral.kt} | 10 +-- .../gay/pizza/pork/ast/IntegerLiteral.kt | 25 ++++++ .../gay/pizza/pork/ast/NodeCoalescer.kt | 5 +- .../kotlin/gay/pizza/pork/ast/NodeType.kt | 3 +- .../kotlin/gay/pizza/pork/ast/NodeVisitor.kt | 4 +- .../pizza/pork/ast/NodeVisitorExtensions.kt | 3 +- .../pizza/pork/buildext/ast/AstPrimitive.kt | 3 +- .../pizza/pork/evaluator/EvaluationVisitor.kt | 81 ++++++++++++++++--- examples/java.pork | 3 + examples/numbers.pork | 4 + .../pizza/pork/ffi/FfiFunctionDefinition.kt | 4 +- .../pizza/pork/ffi/JavaFunctionDefinition.kt | 3 +- .../gay/pizza/pork/ffi/JavaNativeProvider.kt | 2 + .../kotlin/gay/pizza/pork/parser/Parser.kt | 14 +++- .../kotlin/gay/pizza/pork/parser/Printer.kt | 6 +- .../kotlin/gay/pizza/pork/parser/TokenType.kt | 26 ++++-- .../pizza/pork/parser/TokenTypeProperty.kt | 1 + .../kotlin/gay/pizza/pork/parser/Tokenizer.kt | 22 ++++- 19 files changed, 187 insertions(+), 39 deletions(-) rename ast/src/main/kotlin/gay/pizza/pork/ast/{IntLiteral.kt => DoubleLiteral.kt} (66%) create mode 100644 ast/src/main/kotlin/gay/pizza/pork/ast/IntegerLiteral.kt create mode 100644 examples/numbers.pork diff --git a/ast/src/main/ast/pork.yml b/ast/src/main/ast/pork.yml index 8cdae1b..774d842 100644 --- a/ast/src/main/ast/pork.yml +++ b/ast/src/main/ast/pork.yml @@ -114,11 +114,16 @@ types: type: Symbol - name: components type: List - IntLiteral: + IntegerLiteral: parent: Expression values: - name: value type: Int + DoubleLiteral: + parent: Expression + values: + - name: value + type: Double ListLiteral: parent: Expression values: diff --git a/ast/src/main/kotlin/gay/pizza/pork/ast/IntLiteral.kt b/ast/src/main/kotlin/gay/pizza/pork/ast/DoubleLiteral.kt similarity index 66% rename from ast/src/main/kotlin/gay/pizza/pork/ast/IntLiteral.kt rename to ast/src/main/kotlin/gay/pizza/pork/ast/DoubleLiteral.kt index 4ae3a76..754fe8e 100644 --- a/ast/src/main/kotlin/gay/pizza/pork/ast/IntLiteral.kt +++ b/ast/src/main/kotlin/gay/pizza/pork/ast/DoubleLiteral.kt @@ -5,15 +5,15 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -@SerialName("intLiteral") -class IntLiteral(val value: Int) : Expression() { - override val type: NodeType = NodeType.IntLiteral +@SerialName("doubleLiteral") +class DoubleLiteral(val value: Double) : Expression() { + override val type: NodeType = NodeType.DoubleLiteral override fun visit(visitor: NodeVisitor): T = - visitor.visitIntLiteral(this) + visitor.visitDoubleLiteral(this) override fun equals(other: Any?): Boolean { - if (other !is IntLiteral) return false + if (other !is DoubleLiteral) return false return other.value == value } diff --git a/ast/src/main/kotlin/gay/pizza/pork/ast/IntegerLiteral.kt b/ast/src/main/kotlin/gay/pizza/pork/ast/IntegerLiteral.kt new file mode 100644 index 0000000..5261c3a --- /dev/null +++ b/ast/src/main/kotlin/gay/pizza/pork/ast/IntegerLiteral.kt @@ -0,0 +1,25 @@ +// GENERATED CODE FROM PORK AST CODEGEN +package gay.pizza.pork.ast + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +@SerialName("integerLiteral") +class IntegerLiteral(val value: Int) : Expression() { + override val type: NodeType = NodeType.IntegerLiteral + + override fun visit(visitor: NodeVisitor): T = + visitor.visitIntegerLiteral(this) + + override fun equals(other: Any?): Boolean { + if (other !is IntegerLiteral) return false + return other.value == value + } + + override fun hashCode(): Int { + var result = value.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 9be5ca7..7632418 100644 --- a/ast/src/main/kotlin/gay/pizza/pork/ast/NodeCoalescer.kt +++ b/ast/src/main/kotlin/gay/pizza/pork/ast/NodeCoalescer.kt @@ -17,6 +17,9 @@ class NodeCoalescer(val handler: (Node) -> Unit) : NodeVisitor { override fun visitContinue(node: Continue): Unit = handle(node) + override fun visitDoubleLiteral(node: DoubleLiteral): Unit = + handle(node) + override fun visitFunctionCall(node: FunctionCall): Unit = handle(node) @@ -32,7 +35,7 @@ class NodeCoalescer(val handler: (Node) -> Unit) : NodeVisitor { override fun visitInfixOperation(node: InfixOperation): Unit = handle(node) - override fun visitIntLiteral(node: IntLiteral): Unit = + override fun visitIntegerLiteral(node: IntegerLiteral): Unit = handle(node) override fun visitLetAssignment(node: LetAssignment): Unit = 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 68bc562..4edc284 100644 --- a/ast/src/main/kotlin/gay/pizza/pork/ast/NodeType.kt +++ b/ast/src/main/kotlin/gay/pizza/pork/ast/NodeType.kt @@ -11,12 +11,13 @@ enum class NodeType(val parent: NodeType? = null) { Continue(Expression), Declaration(Node), Definition(Node), + DoubleLiteral(Expression), FunctionCall(Expression), FunctionDefinition(Definition), If(Expression), ImportDeclaration(Declaration), InfixOperation(Expression), - IntLiteral(Expression), + IntegerLiteral(Expression), LetAssignment(Expression), ListLiteral(Expression), Native(Node), 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 b6e22ac..22eb6c1 100644 --- a/ast/src/main/kotlin/gay/pizza/pork/ast/NodeVisitor.kt +++ b/ast/src/main/kotlin/gay/pizza/pork/ast/NodeVisitor.kt @@ -12,6 +12,8 @@ interface NodeVisitor { fun visitContinue(node: Continue): T + fun visitDoubleLiteral(node: DoubleLiteral): T + fun visitFunctionCall(node: FunctionCall): T fun visitFunctionDefinition(node: FunctionDefinition): T @@ -22,7 +24,7 @@ interface NodeVisitor { fun visitInfixOperation(node: InfixOperation): T - fun visitIntLiteral(node: IntLiteral): T + fun visitIntegerLiteral(node: IntegerLiteral): T fun visitLetAssignment(node: LetAssignment): 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 fb4f404..e68e4e4 100644 --- a/ast/src/main/kotlin/gay/pizza/pork/ast/NodeVisitorExtensions.kt +++ b/ast/src/main/kotlin/gay/pizza/pork/ast/NodeVisitorExtensions.kt @@ -13,7 +13,8 @@ fun NodeVisitor.visit(node: Node): T = is FunctionDefinition -> visitFunctionDefinition(node) is If -> visitIf(node) is ImportDeclaration -> visitImportDeclaration(node) - is IntLiteral -> visitIntLiteral(node) + is IntegerLiteral -> visitIntegerLiteral(node) + is DoubleLiteral -> visitDoubleLiteral(node) is ListLiteral -> visitListLiteral(node) is Parentheses -> visitParentheses(node) is PrefixOperation -> visitPrefixOperation(node) diff --git a/buildext/src/main/kotlin/gay/pizza/pork/buildext/ast/AstPrimitive.kt b/buildext/src/main/kotlin/gay/pizza/pork/buildext/ast/AstPrimitive.kt index 973f1a7..331fad6 100644 --- a/buildext/src/main/kotlin/gay/pizza/pork/buildext/ast/AstPrimitive.kt +++ b/buildext/src/main/kotlin/gay/pizza/pork/buildext/ast/AstPrimitive.kt @@ -3,5 +3,6 @@ package gay.pizza.pork.buildext.ast enum class AstPrimitive(val id: kotlin.String) { Boolean("Boolean"), String("String"), - Int("Int") + Int("Int"), + Double("Double") } 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 f0f2a6e..2d569a4 100644 --- a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/EvaluationVisitor.kt +++ b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/EvaluationVisitor.kt @@ -5,7 +5,8 @@ import gay.pizza.pork.ast.* class EvaluationVisitor(root: Scope) : NodeVisitor { private var currentScope: Scope = root - override fun visitIntLiteral(node: IntLiteral): Any = node.value + override fun visitIntegerLiteral(node: IntegerLiteral): Any = node.value + override fun visitDoubleLiteral(node: DoubleLiteral): Any = node.value override fun visitStringLiteral(node: StringLiteral): Any = node.text override fun visitBooleanLiteral(node: BooleanLiteral): Any = node.value @@ -95,15 +96,77 @@ class EvaluationVisitor(root: Scope) : NodeVisitor { throw RuntimeException("Failed to evaluate infix operation, bad types.") } - val leftInt = left.toInt() - val rightInt = right.toInt() + if (left is Double || right is Double) { + return numericOperation( + node.op, + left, + right, + convert = { it.toDouble() }, + add = { a, b -> a + b }, + subtract = { a, b -> a - b }, + multiply = { a, b -> a * b }, + divide = { a, b -> a / b } + ) + } - return when (node.op) { - InfixOperator.Plus -> leftInt + rightInt - InfixOperator.Minus -> leftInt - rightInt - InfixOperator.Multiply -> leftInt * rightInt - InfixOperator.Divide -> leftInt / rightInt - else -> throw RuntimeException("Unable to handle operation ${node.op}") + if (left is Float || right is Float) { + return numericOperation( + node.op, + left, + right, + convert = { it.toFloat() }, + add = { a, b -> a + b }, + subtract = { a, b -> a - b }, + multiply = { a, b -> a * b }, + divide = { a, b -> a / b } + ) + } + + if (left is Long || right is Long) { + return numericOperation( + node.op, + left, + right, + convert = { it.toLong() }, + add = { a, b -> a + b }, + subtract = { a, b -> a - b }, + multiply = { a, b -> a * b }, + divide = { a, b -> a / b } + ) + } + + if (left is Int || right is Int) { + return numericOperation( + node.op, + left, + right, + convert = { it.toInt() }, + add = { a, b -> a + b }, + subtract = { a, b -> a - b }, + multiply = { a, b -> a * b }, + divide = { a, b -> a / b } + ) + } + + throw RuntimeException("Unknown numeric type: ${left.javaClass.name}") + } + + private inline fun numericOperation( + op: InfixOperator, + left: Number, + right: Number, + convert: (Number) -> T, + add: (T, T) -> T, + subtract: (T, T) -> T, + multiply: (T, T) -> T, + divide: (T, T) -> T + ): T { + return when (op) { + InfixOperator.Plus -> add(convert(left), convert(right)) + InfixOperator.Minus -> subtract(convert(left), convert(right)) + InfixOperator.Multiply -> multiply(convert(left), convert(right)) + InfixOperator.Divide -> divide(convert(left), convert(right)) + else -> throw RuntimeException("Unable to handle operation $op") } } diff --git a/examples/java.pork b/examples/java.pork index 1c3e54b..f99437b 100644 --- a/examples/java.pork +++ b/examples/java.pork @@ -1,7 +1,10 @@ +import java java.lang.Math import java java.lang.System import java java.io.PrintStream export func main() { let stream = java_lang_System_err_get() java_io_PrintStream_println_string(stream, "Hello World") + let pi = java_lang_Math_PI_get() + println(pi) } diff --git a/examples/numbers.pork b/examples/numbers.pork new file mode 100644 index 0000000..19f0834 --- /dev/null +++ b/examples/numbers.pork @@ -0,0 +1,4 @@ +export func main() { + let pi = 3.141592653589793 + println(pi) +} diff --git a/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiFunctionDefinition.kt b/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiFunctionDefinition.kt index be94144..2173f3d 100644 --- a/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiFunctionDefinition.kt +++ b/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiFunctionDefinition.kt @@ -11,8 +11,8 @@ class FfiFunctionDefinition( if (parts.size !in arrayOf(3, 4) || parts.any { it.trim().isEmpty() }) { throw RuntimeException( "FFI function definition is invalid, " + - "excepted format is 'library:function:return-type:(optional)parameters'" + - " but '${def}' was specified") + "accepted format is 'library:function:return-type:(optional)parameters' " + + "but '${def}' was specified") } val (library, function, returnType) = parts return FfiFunctionDefinition(library, function, returnType) diff --git a/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaFunctionDefinition.kt b/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaFunctionDefinition.kt index 010449e..68caef8 100644 --- a/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaFunctionDefinition.kt +++ b/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaFunctionDefinition.kt @@ -13,7 +13,8 @@ class JavaFunctionDefinition( if (!(parts.size == 4 || parts.size == 5) || parts.any { it.trim().isEmpty() }) { throw RuntimeException( "Java function definition is invalid, " + - "excepted format is 'type:kind:symbol:return-type:(optional)parameters' but '${def}' was specified") + "accepted format is 'type:kind:symbol:return-type:(optional)parameters' " + + "but '${def}' was specified") } val (type, kind, symbol, returnType) = parts val parameters = if (parts.size > 4) parts[4].split(",") else emptyList() diff --git a/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaNativeProvider.kt b/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaNativeProvider.kt index f0eed5c..27b77d7 100644 --- a/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaNativeProvider.kt +++ b/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaNativeProvider.kt @@ -32,6 +32,8 @@ class JavaNativeProvider : NativeFunctionProvider { "short" -> Short::class.java "int" -> Int::class.java "long" -> Long::class.java + "float" -> Float::class.java + "double" -> Double::class.java else -> lookup.findClass(name) } 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 8ee12ce..ee0b7ad 100644 --- a/parser/src/main/kotlin/gay/pizza/pork/parser/Parser.kt +++ b/parser/src/main/kotlin/gay/pizza/pork/parser/Parser.kt @@ -5,8 +5,14 @@ import gay.pizza.pork.ast.* class Parser(source: PeekableSource, val attribution: NodeAttribution) { private val unsanitizedSource = source - private fun readIntLiteral(): IntLiteral = within { - expect(TokenType.IntLiteral) { IntLiteral(it.text.toInt()) } + private fun readNumberLiteral(): Expression = within { + expect(TokenType.NumberLiteral) { + if (it.text.contains(".")) { + DoubleLiteral(it.text.toDouble()) + } else { + IntegerLiteral(it.text.toInt()) + } + } } private fun readStringLiteral(): StringLiteral = within { @@ -97,8 +103,8 @@ class Parser(source: PeekableSource, val attribution: NodeAttribution) { fun readExpression(): Expression { val token = peek() val expression = when (token.type) { - TokenType.IntLiteral -> { - readIntLiteral() + TokenType.NumberLiteral -> { + readNumberLiteral() } TokenType.StringLiteral -> { 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 d7d3d56..7a4836b 100644 --- a/parser/src/main/kotlin/gay/pizza/pork/parser/Printer.kt +++ b/parser/src/main/kotlin/gay/pizza/pork/parser/Printer.kt @@ -20,7 +20,11 @@ class Printer(buffer: StringBuilder) : NodeVisitor { autoIndentState = true } - override fun visitIntLiteral(node: IntLiteral) { + override fun visitIntegerLiteral(node: IntegerLiteral) { + append(node.value.toString()) + } + + override fun visitDoubleLiteral(node: DoubleLiteral) { append(node.value.toString()) } 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 72d9df9..7dadcaf 100644 --- a/parser/src/main/kotlin/gay/pizza/pork/parser/TokenType.kt +++ b/parser/src/main/kotlin/gay/pizza/pork/parser/TokenType.kt @@ -4,8 +4,13 @@ import gay.pizza.pork.parser.TokenTypeProperty.* import gay.pizza.pork.parser.TokenFamily.* enum class TokenType(vararg properties: TokenTypeProperty) { - Symbol(SymbolFamily, CharConsumer { (it in 'a'..'z') || (it in 'A'..'Z') || it == '_' }, KeywordUpgrader), - IntLiteral(NumericLiteralFamily, CharConsumer { it in '0'..'9' }), + NumberLiteral(NumericLiteralFamily, CharIndexConsumer { it, index -> + (it in '0'..'9') || (index > 0 && it == '.') }), + Symbol(SymbolFamily, CharConsumer { + (it in 'a'..'z') || + (it in 'A'..'Z') || + (it == '_') || + (it in '0' .. '9')}, KeywordUpgrader), StringLiteral(StringLiteralFamily), Equality(OperatorFamily), Inequality(OperatorFamily), @@ -40,17 +45,24 @@ enum class TokenType(vararg properties: TokenTypeProperty) { LineComment(CommentFamily), EndOfFile; - val promotions: List = properties.filterIsInstance() - val keyword: Keyword? = properties.filterIsInstance().singleOrNull() - val singleChar: SingleChar? = properties.filterIsInstance().singleOrNull() + val promotions: List = + properties.filterIsInstance() + val keyword: Keyword? = + properties.filterIsInstance().singleOrNull() + val singleChar: SingleChar? = + properties.filterIsInstance().singleOrNull() val family: TokenFamily = properties.filterIsInstance().singleOrNull() ?: OtherFamily val charConsumer: CharConsumer? = properties.filterIsInstance().singleOrNull() - val tokenUpgrader: TokenUpgrader? = properties.filterIsInstance().singleOrNull() + val charIndexConsumer: CharIndexConsumer? = + properties.filterIsInstance().singleOrNull() + val tokenUpgrader: TokenUpgrader? = + properties.filterIsInstance().singleOrNull() companion object { val Keywords = entries.filter { item -> item.keyword != null } val SingleChars = entries.filter { item -> item.singleChar != null } - val CharConsumers = entries.filter { item -> item.charConsumer != null } + val CharConsumers = entries.filter { item -> + item.charConsumer != null || item.charIndexConsumer != null } } } diff --git a/parser/src/main/kotlin/gay/pizza/pork/parser/TokenTypeProperty.kt b/parser/src/main/kotlin/gay/pizza/pork/parser/TokenTypeProperty.kt index f009921..a9c11b2 100644 --- a/parser/src/main/kotlin/gay/pizza/pork/parser/TokenTypeProperty.kt +++ b/parser/src/main/kotlin/gay/pizza/pork/parser/TokenTypeProperty.kt @@ -5,6 +5,7 @@ interface TokenTypeProperty { class Promotion(val nextChar: Char, val type: TokenType) : TokenTypeProperty class Keyword(val text: String) : TokenTypeProperty class CharConsumer(val isValid: (Char) -> Boolean) : TokenTypeProperty + class CharIndexConsumer(val isValid: (Char, Int) -> Boolean) : TokenTypeProperty open class TokenUpgrader(val maybeUpgrade: (Token) -> Token?) : TokenTypeProperty object KeywordUpgrader : TokenUpgrader({ token -> 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 bbf2c55..b4f3637 100644 --- a/parser/src/main/kotlin/gay/pizza/pork/parser/Tokenizer.kt +++ b/parser/src/main/kotlin/gay/pizza/pork/parser/Tokenizer.kt @@ -90,15 +90,29 @@ class Tokenizer(val source: CharSource) { return Token(type, tokenStart, text) } + var index = 0 for (item in TokenType.CharConsumers) { - val consumer = item.charConsumer ?: continue - if (!consumer.isValid(char)) { - continue + if (item.charConsumer != null) { + if (!item.charConsumer.isValid(char)) { + continue + } + } else if (item.charIndexConsumer != null) { + if (!item.charIndexConsumer.isValid(char, index)) { + continue + } + } else { + throw RuntimeException("Unknown Char Consumer") } val text = buildString { append(char) - while (consumer.isValid(source.peek())) { + + while ( + if (item.charConsumer != null) + item.charConsumer.isValid(source.peek()) + else + item.charIndexConsumer!!.isValid(source.peek(), ++index) + ) { append(source.next()) } }