language: prelude and internal functions, and varargs support

This commit is contained in:
Alex Zenla 2023-09-10 19:27:59 -04:00
parent 1cfb197a7f
commit e8c984f2dc
Signed by: alex
GPG Key ID: C0780728420EBFE5
24 changed files with 166 additions and 104 deletions

View File

@ -5,11 +5,11 @@ A work-in-progress programming language.
```pork
/* fibonacci sequence */
func fib(n) {
if n == 0
then 0
else if n == 1
then 1
else fib(n - 1) + fib(n - 2)
if n < 2 {
n
} else {
fib(n - 1) + fib(n - 2)
}
}
func main() {

View File

@ -117,6 +117,12 @@ types:
type: Symbol
- name: arguments
type: List<Expression>
ArgumentSpec:
values:
- name: symbol
type: Symbol
- name: multiple
type: Boolean
FunctionDefinition:
parent: Definition
values:
@ -125,7 +131,7 @@ types:
- name: symbol
type: Symbol
- name: arguments
type: List<Symbol>
type: List<ArgumentSpec>
- name: block
type: Block?
- name: native

View File

@ -14,6 +14,7 @@ digraph A {
type_InfixOperation [shape=box,label="InfixOperation"]
type_BooleanLiteral [shape=box,label="BooleanLiteral"]
type_FunctionCall [shape=box,label="FunctionCall"]
type_ArgumentSpec [shape=box,label="ArgumentSpec"]
type_FunctionDefinition [shape=box,label="FunctionDefinition"]
type_If [shape=box,label="If"]
type_ImportDeclaration [shape=box,label="ImportDeclaration"]
@ -70,8 +71,10 @@ digraph A {
type_InfixOperation -> type_InfixOperator [style=dotted]
type_FunctionCall -> type_Symbol [style=dotted]
type_FunctionCall -> type_Expression [style=dotted]
type_ArgumentSpec -> type_Symbol [style=dotted]
type_FunctionDefinition -> type_DefinitionModifiers [style=dotted]
type_FunctionDefinition -> type_Symbol [style=dotted]
type_FunctionDefinition -> type_ArgumentSpec [style=dotted]
type_FunctionDefinition -> type_Block [style=dotted]
type_FunctionDefinition -> type_Native [style=dotted]
type_If -> type_Expression [style=dotted]

View File

@ -0,0 +1,9 @@
// GENERATED CODE FROM PORK AST CODEGEN
package gay.pizza.pork.ast
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
@SerialName("argumentSpec")
class ArgumentSpec(var symbol: Symbol, var multiple: Boolean)

View File

@ -6,11 +6,11 @@ import kotlinx.serialization.Serializable
@Serializable
@SerialName("functionDefinition")
class FunctionDefinition(override val modifiers: DefinitionModifiers, override val symbol: Symbol, val arguments: List<Symbol>, val block: Block?, val native: Native?) : Definition() {
class FunctionDefinition(override val modifiers: DefinitionModifiers, override val symbol: Symbol, val arguments: List<ArgumentSpec>, val block: Block?, val native: Native?) : Definition() {
override val type: NodeType = NodeType.FunctionDefinition
override fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> =
visitor.visitAll(listOf(symbol), arguments, listOf(block), listOf(native))
visitor.visitAll(listOf(symbol), listOf(block), listOf(native))
override fun <T> visit(visitor: NodeVisitor<T>): T =
visitor.visitFunctionDefinition(this)

View File

@ -1,9 +1,6 @@
package gay.pizza.pork.evaluator
import gay.pizza.pork.ast.CompilationUnit
import gay.pizza.pork.ast.Definition
import gay.pizza.pork.ast.FunctionDefinition
import gay.pizza.pork.ast.ImportDeclaration
import gay.pizza.pork.ast.*
import gay.pizza.pork.frontend.ImportLocator
class CompilationUnitContext(
@ -45,6 +42,7 @@ class CompilationUnitContext(
}
private fun processAllImports() {
processPreludeImport()
val imports = compilationUnit.declarations.filterIsInstance<ImportDeclaration>()
for (import in imports) {
processImport(import)
@ -57,4 +55,18 @@ class CompilationUnitContext(
val evaluationContext = evaluator.context(importLocator)
internalScope.inherit(evaluationContext.externalScope)
}
private fun processPreludeImport() {
processImport(preludeImport)
}
companion object {
private val preludeImport = ImportDeclaration(
Symbol("std"),
listOf(
Symbol("lang"),
Symbol("prelude")
)
)
}
}

View File

@ -119,8 +119,8 @@ class EvaluationVisitor(root: Scope) : NodeVisitor<Any> {
subtract = { a, b -> a - b },
multiply = { a, b -> a * b },
divide = { a, b -> a / b },
euclideanModulo = { _, _ -> throw RuntimeException("Can't perform integer modulo between floating point types") },
remainder = { _, _ -> throw RuntimeException("Can't perform integer remainder between floating point types") },
euclideanModulo = { _, _ -> floatingPointTypeError("integer modulo") },
remainder = { _, _ -> floatingPointTypeError("integer remainder") },
lesser = { a, b -> a < b },
greater = { a, b -> a > b },
lesserEqual = { a, b -> a <= b },
@ -138,8 +138,8 @@ class EvaluationVisitor(root: Scope) : NodeVisitor<Any> {
subtract = { a, b -> a - b },
multiply = { a, b -> a * b },
divide = { a, b -> a / b },
euclideanModulo = { _, _ -> throw RuntimeException("Can't perform integer modulo between floating point types") },
remainder = { _, _ -> throw RuntimeException("Can't perform integer remainder between floating point types") },
euclideanModulo = { _, _ -> floatingPointTypeError("integer modulo") },
remainder = { _, _ -> floatingPointTypeError("integer remainder") },
lesser = { a, b -> a < b },
greater = { a, b -> a > b },
lesserEqual = { a, b -> a <= b },
@ -228,31 +228,19 @@ class EvaluationVisitor(root: Scope) : NodeVisitor<Any> {
}
override fun visitFunctionDefinition(node: FunctionDefinition): Any {
throw RuntimeException(
"Function declarations cannot be visited in an EvaluationVisitor. " +
"Utilize a FunctionContext."
)
topLevelUsedError("FunctionDefinition", "FunctionContext")
}
override fun visitImportDeclaration(node: ImportDeclaration): Any {
throw RuntimeException(
"Import declarations cannot be visited in an EvaluationVisitor. " +
"Utilize an CompilationUnitContext."
)
topLevelUsedError("ImportDeclaration", "CompilationUnitContext")
}
override fun visitCompilationUnit(node: CompilationUnit): Any {
throw RuntimeException(
"Compilation units cannot be visited in an EvaluationVisitor. " +
"Utilize an CompilationUnitContext."
)
topLevelUsedError("CompilationUnit", "CompilationUnitContext")
}
override fun visitNative(node: Native): Any {
throw RuntimeException(
"Native definition cannot be visited in an EvaluationVisitor. " +
"Utilize an FunctionContext."
)
topLevelUsedError("Native", "FunctionContext")
}
override fun visitContinue(node: Continue): Any = ContinueMarker
@ -266,6 +254,17 @@ class EvaluationVisitor(root: Scope) : NodeVisitor<Any> {
}
}
private fun floatingPointTypeError(operation: String): Nothing {
throw RuntimeException("Can't perform $operation between floating point types")
}
private fun topLevelUsedError(name: String, alternative: String): Nothing {
throw RuntimeException(
"$name cannot be visited in an EvaluationVisitor. " +
"Utilize an $alternative instead."
)
}
private object BreakMarker : RuntimeException("Break Marker")
private object ContinueMarker: RuntimeException("Continue Marker")
}

View File

@ -5,7 +5,7 @@ import gay.pizza.pork.frontend.World
class Evaluator(val world: World, val scope: Scope) {
private val contexts = mutableMapOf<String, CompilationUnitContext>()
private val nativeFunctionProviders = mutableMapOf<String, NativeFunctionProvider>()
private val nativeProviders = mutableMapOf<String, NativeProvider>()
fun evaluate(locator: ImportLocator): Scope =
context(locator).externalScope
@ -20,12 +20,12 @@ class Evaluator(val world: World, val scope: Scope) {
return context
}
fun nativeFunctionProvider(form: String): NativeFunctionProvider {
return nativeFunctionProviders[form] ?:
fun nativeFunctionProvider(form: String): NativeProvider {
return nativeProviders[form] ?:
throw RuntimeException("Unknown native function form: $form")
}
fun addNativeFunctionProvider(form: String, nativeFunctionProvider: NativeFunctionProvider) {
nativeFunctionProviders[form] = nativeFunctionProvider
fun addNativeProvider(form: String, nativeProvider: NativeProvider) {
nativeProviders[form] = nativeProvider
}
}

View File

@ -20,8 +20,14 @@ class FunctionContext(val compilationUnitContext: CompilationUnitContext, val no
}
val scope = compilationUnitContext.internalScope.fork()
for ((index, argumentSymbol) in node.arguments.withIndex()) {
scope.define(argumentSymbol.id, arguments.values[index])
for ((index, spec) in node.arguments.withIndex()) {
if (spec.multiple) {
val list = arguments.values.subList(index, arguments.values.size - 1)
scope.define(spec.symbol.id, list)
break
} else {
scope.define(spec.symbol.id, arguments.values[index])
}
}
if (node.block == null) {

View File

@ -0,0 +1,23 @@
package gay.pizza.pork.evaluator
class InternalNativeProvider(val quiet: Boolean = false) : NativeProvider {
private val functions = mutableMapOf(
"println" to CallableFunction(::printLine)
)
override fun provideNativeFunction(definition: String): CallableFunction {
return functions[definition] ?: throw RuntimeException("Unknown Internal Function: $definition")
}
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(" "))
}
return None
}
}

View File

@ -1,5 +1,5 @@
package gay.pizza.pork.evaluator
interface NativeFunctionProvider {
interface NativeProvider {
fun provideNativeFunction(definition: String): CallableFunction
}

View File

@ -1,17 +1,13 @@
/* fibonacci sequence */
func fib(n) {
if n == 0 {
0
} else {
if n == 1 {
1
if n < 2 {
n
} else {
fib(n - 1) + fib(n - 2)
}
}
}
export func main() {
let result = fib(20)
let result = fib(30)
println(result)
}

View File

@ -1,11 +1,6 @@
package gay.pizza.pork.ffi
import gay.pizza.pork.ast.CompilationUnit
import gay.pizza.pork.ast.DefinitionModifiers
import gay.pizza.pork.ast.FunctionDefinition
import gay.pizza.pork.ast.Native
import gay.pizza.pork.ast.StringLiteral
import gay.pizza.pork.ast.Symbol
import gay.pizza.pork.ast.*
import java.lang.reflect.Method
import java.lang.reflect.Modifier
import java.lang.reflect.Parameter
@ -149,7 +144,12 @@ class JavaAutogen(val javaClass: Class<*>) {
FunctionDefinition(
modifiers = DefinitionModifiers(true),
symbol = Symbol("${prefix}_${name}"),
arguments = parameterNames.map { Symbol(it) },
arguments = parameterNames.map {
ArgumentSpec(
symbol = Symbol(it),
multiple = false
)
},
native = asNative(functionDefinition),
block = null
)

View File

@ -1,12 +1,12 @@
package gay.pizza.pork.ffi
import gay.pizza.pork.evaluator.CallableFunction
import gay.pizza.pork.evaluator.NativeFunctionProvider
import gay.pizza.pork.evaluator.NativeProvider
import gay.pizza.pork.evaluator.None
import java.lang.invoke.MethodHandles
import java.lang.invoke.MethodType
class JavaNativeProvider : NativeFunctionProvider {
class JavaNativeProvider : NativeProvider {
private val lookup = MethodHandles.lookup()
override fun provideNativeFunction(definition: String): CallableFunction {

View File

@ -2,9 +2,9 @@ package gay.pizza.pork.ffi
import com.sun.jna.Function
import gay.pizza.pork.evaluator.CallableFunction
import gay.pizza.pork.evaluator.NativeFunctionProvider
import gay.pizza.pork.evaluator.NativeProvider
class JnaNativeProvider : NativeFunctionProvider {
class JnaNativeProvider : NativeProvider {
override fun provideNativeFunction(definition: String): CallableFunction {
val functionDefinition = FfiFunctionDefinition.parse(definition)
val function = Function.getFunction(functionDefinition.library, functionDefinition.function)

View File

@ -217,7 +217,7 @@ class Parser(source: PeekableSource<Token>, val attribution: NodeAttribution) {
private fun readImportDeclaration(): ImportDeclaration = within {
expect(TokenType.Import)
val form = readSymbolRaw()
val components = oneAndContinuedBy(TokenType.Period) { readSymbolRaw() }
val components = oneAndContinuedBy(TokenType.Dot) { readSymbolRaw() }
ImportDeclaration(form, components)
}
@ -237,7 +237,12 @@ class Parser(source: PeekableSource<Token>, val attribution: NodeAttribution) {
val name = readSymbolRaw()
expect(TokenType.LeftParentheses)
val arguments = collect(TokenType.RightParentheses, TokenType.Comma) {
readSymbolRaw()
val symbol = readSymbolRaw()
var multiple: Boolean = false
if (next(TokenType.DotDotDot)) {
multiple = true
}
ArgumentSpec(symbol, multiple)
}
expect(TokenType.RightParentheses)

View File

@ -155,7 +155,10 @@ class Printer(buffer: StringBuilder) : NodeVisitor<Unit> {
visit(node.symbol)
append("(")
for ((index, argument) in node.arguments.withIndex()) {
visit(argument)
visit(argument.symbol)
if (argument.multiple) {
append("...")
}
if (index + 1 != node.arguments.size) {
append(", ")
}

View File

@ -30,23 +30,25 @@ enum class TokenType(vararg properties: TokenTypeProperty) {
LeftParentheses(SingleChar('(')),
RightParentheses(SingleChar(')')),
Negation(SingleChar('!'), Promotion('=', Inequality), OperatorFamily),
Mod(Keyword("mod"), OperatorFamily),
Rem(Keyword("rem"), OperatorFamily),
Mod(ManyChars("mod"), OperatorFamily),
Rem(ManyChars("rem"), OperatorFamily),
Comma(SingleChar(',')),
Period(SingleChar('.')),
False(Keyword("false"), KeywordFamily),
True(Keyword("true"), KeywordFamily),
If(Keyword("if"), KeywordFamily),
Else(Keyword("else"), KeywordFamily),
While(Keyword("while"), KeywordFamily),
Continue(Keyword("continue"), KeywordFamily),
Break(Keyword("break"), KeywordFamily),
Import(Keyword("import"), KeywordFamily),
Export(Keyword("export"), KeywordFamily),
Func(Keyword("func"), KeywordFamily),
Native(Keyword("native"), KeywordFamily),
Let(Keyword("let"), KeywordFamily),
Var(Keyword("var"), KeywordFamily),
DotDotDot(ManyChars("...")),
DotDot(ManyChars(".."), Promotion('.', DotDotDot)),
Dot(SingleChar('.'), Promotion('.', DotDot)),
False(ManyChars("false"), KeywordFamily),
True(ManyChars("true"), KeywordFamily),
If(ManyChars("if"), KeywordFamily),
Else(ManyChars("else"), KeywordFamily),
While(ManyChars("while"), KeywordFamily),
Continue(ManyChars("continue"), KeywordFamily),
Break(ManyChars("break"), KeywordFamily),
Import(ManyChars("import"), KeywordFamily),
Export(ManyChars("export"), KeywordFamily),
Func(ManyChars("func"), KeywordFamily),
Native(ManyChars("native"), KeywordFamily),
Let(ManyChars("let"), KeywordFamily),
Var(ManyChars("var"), KeywordFamily),
Whitespace(CharConsumer { it == ' ' || it == '\r' || it == '\n' || it == '\t' }),
BlockComment(CommentFamily),
LineComment(CommentFamily),
@ -54,8 +56,8 @@ enum class TokenType(vararg properties: TokenTypeProperty) {
val promotions: List<Promotion> =
properties.filterIsInstance<Promotion>()
val keyword: Keyword? =
properties.filterIsInstance<Keyword>().singleOrNull()
val manyChars: ManyChars? =
properties.filterIsInstance<ManyChars>().singleOrNull()
val singleChar: SingleChar? =
properties.filterIsInstance<SingleChar>().singleOrNull()
val family: TokenFamily =
@ -67,7 +69,7 @@ enum class TokenType(vararg properties: TokenTypeProperty) {
properties.filterIsInstance<TokenUpgrader>().singleOrNull()
companion object {
val Keywords = entries.filter { item -> item.keyword != null }
val ManyChars = entries.filter { item -> item.manyChars != null }
val SingleChars = entries.filter { item -> item.singleChar != null }
val CharConsumers = entries.filter { item ->
item.charConsumer != null || item.charIndexConsumer != null }

View File

@ -3,15 +3,15 @@ package gay.pizza.pork.parser
interface TokenTypeProperty {
class SingleChar(val char: Char) : TokenTypeProperty
class Promotion(val nextChar: Char, val type: TokenType) : TokenTypeProperty
class Keyword(val text: String) : TokenTypeProperty
class ManyChars(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 ->
var upgraded: Token? = null
for (item in TokenType.Keywords) {
if (item.keyword != null && token.text == item.keyword.text) {
for (item in TokenType.ManyChars) {
if (item.manyChars != null && token.text == item.manyChars.text) {
upgraded = Token(item, token.start, token.text)
break
}

View File

@ -79,13 +79,18 @@ class Tokenizer(val source: CharSource) {
var type = item
var text = itemChar.toString()
for (promotion in item.promotions) {
var promoted = true
while (promoted) {
promoted = false
for (promotion in type.promotions) {
if (source.peek() != promotion.nextChar) {
continue
}
val nextChar = source.next()
type = promotion.type
text += nextChar
promoted = true
}
}
return Token(type, tokenStart, text)
}

View File

@ -0,0 +1,2 @@
export func println(messages...)
native internal "println"

View File

@ -1,2 +1,3 @@
lang/prelude.pork
ffi/malloc.pork
numerics/operator.pork

View File

@ -19,21 +19,10 @@ class RunCommand : CliktCommand(help = "Run Program", name = "run") {
override fun run() {
val tool = FileTool(PlatformFsProvider.resolve(path))
val scope = Scope()
scope.define("println", CallableFunction { arguments ->
if (quiet) {
return@CallableFunction None
}
when (arguments.values.count()) {
0 -> println()
1 -> println(arguments.values[0])
else -> println(arguments.values.joinToString(" "))
}
None
})
val main = tool.loadMainFunction(scope, setupEvaluator = {
addNativeFunctionProvider("ffi", JnaNativeProvider())
addNativeFunctionProvider("java", JavaNativeProvider())
addNativeProvider("internal", InternalNativeProvider(quiet = quiet))
addNativeProvider("ffi", JnaNativeProvider())
addNativeProvider("java", JavaNativeProvider())
})
if (dumpScope) {

View File

@ -5,6 +5,7 @@ import gay.pizza.pork.ast.NodeVisitor
import gay.pizza.pork.ast.visit
import gay.pizza.pork.evaluator.CallableFunction
import gay.pizza.pork.evaluator.Evaluator
import gay.pizza.pork.evaluator.InternalNativeProvider
import gay.pizza.pork.evaluator.Scope
import gay.pizza.pork.ffi.JavaAutogenContentSource
import gay.pizza.pork.frontend.ContentSource