diff --git a/ast/src/main/ast/pork.yml b/ast/src/main/ast/pork.yml index 6af5e72..a8f6999 100644 --- a/ast/src/main/ast/pork.yml +++ b/ast/src/main/ast/pork.yml @@ -110,6 +110,8 @@ types: ImportDeclaration: parent: Declaration values: + - name: form + type: Symbol? - name: path type: StringLiteral IntLiteral: diff --git a/ast/src/main/kotlin/gay/pizza/pork/ast/ImportDeclaration.kt b/ast/src/main/kotlin/gay/pizza/pork/ast/ImportDeclaration.kt index 830128f..5f08de0 100644 --- a/ast/src/main/kotlin/gay/pizza/pork/ast/ImportDeclaration.kt +++ b/ast/src/main/kotlin/gay/pizza/pork/ast/ImportDeclaration.kt @@ -6,22 +6,23 @@ import kotlinx.serialization.Serializable @Serializable @SerialName("importDeclaration") -class ImportDeclaration(val path: StringLiteral) : Declaration() { +class ImportDeclaration(val form: Symbol?, val path: StringLiteral) : Declaration() { override val type: NodeType = NodeType.ImportDeclaration override fun visitChildren(visitor: NodeVisitor): List = - visitor.visitNodes(path) + visitor.visitNodes(form, path) override fun visit(visitor: NodeVisitor): T = visitor.visitImportDeclaration(this) override fun equals(other: Any?): Boolean { if (other !is ImportDeclaration) return false - return other.path == path + return other.form == form && other.path == path } override fun hashCode(): Int { - var result = path.hashCode() + var result = form.hashCode() + result = 31 * result + path.hashCode() result = 31 * result + type.hashCode() return result } diff --git a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/CompilationUnitContext.kt b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/CompilationUnitContext.kt index 669df6b..14d4705 100644 --- a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/CompilationUnitContext.kt +++ b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/CompilationUnitContext.kt @@ -4,6 +4,7 @@ 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.frontend.ImportLocator class CompilationUnitContext( val compilationUnit: CompilationUnit, @@ -50,8 +51,8 @@ class CompilationUnitContext( } private fun processImport(import: ImportDeclaration) { - val path = import.path.text - val evaluationContext = evaluator.context(path) + val importLocator = ImportLocator(import.path.text, import.form?.id) + val evaluationContext = evaluator.context(importLocator) internalScope.inherit(evaluationContext.externalScope) } } diff --git a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/Evaluator.kt b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/Evaluator.kt index a9eb203..affe5ef 100644 --- a/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/Evaluator.kt +++ b/evaluator/src/main/kotlin/gay/pizza/pork/evaluator/Evaluator.kt @@ -1,17 +1,18 @@ package gay.pizza.pork.evaluator +import gay.pizza.pork.frontend.ImportLocator import gay.pizza.pork.frontend.World class Evaluator(val world: World, val scope: Scope) { private val contexts = mutableMapOf() private val nativeFunctionProviders = mutableMapOf() - fun evaluate(path: String): Scope = - context(path).externalScope + fun evaluate(locator: ImportLocator): Scope = + context(locator).externalScope - fun context(path: String): CompilationUnitContext { - val unit = world.load(path) - val identity = world.contentSource.stableContentIdentity(path) + fun context(locator: ImportLocator): CompilationUnitContext { + val unit = world.load(locator) + val identity = world.stableIdentity(locator) val context = contexts.computeIfAbsent(identity) { CompilationUnitContext(unit, this, scope) } diff --git a/examples/ffi.pork b/examples/ffi.pork index cf6808d..367dc2c 100644 --- a/examples/ffi.pork +++ b/examples/ffi.pork @@ -1,8 +1,4 @@ -func malloc(size) - native ffi "c:malloc:void*" - -func free(pointer) - native ffi "c:free:void" +import std "ffi/malloc.pork" export func main() { while true { diff --git a/frontend/src/main/kotlin/gay/pizza/pork/frontend/ImportLocator.kt b/frontend/src/main/kotlin/gay/pizza/pork/frontend/ImportLocator.kt new file mode 100644 index 0000000..ee7e160 --- /dev/null +++ b/frontend/src/main/kotlin/gay/pizza/pork/frontend/ImportLocator.kt @@ -0,0 +1,3 @@ +package gay.pizza.pork.frontend + +data class ImportLocator(val path: String, val form: String? = null) diff --git a/frontend/src/main/kotlin/gay/pizza/pork/frontend/ImportSource.kt b/frontend/src/main/kotlin/gay/pizza/pork/frontend/ImportSource.kt new file mode 100644 index 0000000..4b93067 --- /dev/null +++ b/frontend/src/main/kotlin/gay/pizza/pork/frontend/ImportSource.kt @@ -0,0 +1,7 @@ +package gay.pizza.pork.frontend + +interface ImportSource { + val fileContentSource: ContentSource + + fun provideContentSource(form: String): ContentSource +} diff --git a/frontend/src/main/kotlin/gay/pizza/pork/frontend/StandardImportSource.kt b/frontend/src/main/kotlin/gay/pizza/pork/frontend/StandardImportSource.kt new file mode 100644 index 0000000..eeb20ad --- /dev/null +++ b/frontend/src/main/kotlin/gay/pizza/pork/frontend/StandardImportSource.kt @@ -0,0 +1,14 @@ +package gay.pizza.pork.frontend + +class StandardImportSource(override val fileContentSource: ContentSource) : ImportSource { + private val providers = mutableMapOf() + + override fun provideContentSource(form: String): ContentSource { + return providers[form] ?: + throw RuntimeException("Unknown import form: $form") + } + + fun addContentSource(form: String, contentSource: ContentSource) { + providers[form] = contentSource + } +} diff --git a/frontend/src/main/kotlin/gay/pizza/pork/frontend/World.kt b/frontend/src/main/kotlin/gay/pizza/pork/frontend/World.kt index b44bbb6..edf7328 100644 --- a/frontend/src/main/kotlin/gay/pizza/pork/frontend/World.kt +++ b/frontend/src/main/kotlin/gay/pizza/pork/frontend/World.kt @@ -7,37 +7,57 @@ import gay.pizza.pork.parser.Parser import gay.pizza.pork.parser.TokenStreamSource import gay.pizza.pork.parser.Tokenizer -class World(val contentSource: ContentSource) { +class World(val importSource: ImportSource) { private val internalUnits = mutableMapOf() val units: List get() = internalUnits.values.toList() - private fun loadOneUnit(path: String): CompilationUnit { - val stableIdentity = contentSource.stableContentIdentity(path) - val cached = internalUnits[stableIdentity] + private fun loadOneUnit(importLocator: ImportLocator): CompilationUnit { + val contentSource = pickContentSource(importLocator.form) + val stableKey = stableIdentity(importLocator, contentSource = contentSource) + val cached = internalUnits[stableKey] if (cached != null) { return cached } - val charSource = contentSource.loadAsCharSource(path) + val charSource = contentSource.loadAsCharSource(importLocator.path) val tokenizer = Tokenizer(charSource) val tokenStream = tokenizer.tokenize() val parser = Parser(TokenStreamSource(tokenStream), DiscardNodeAttribution) - return parser.readCompilationUnit() + val unit = parser.readCompilationUnit() + internalUnits[stableKey] = unit + return unit } private fun resolveAllImports(unit: CompilationUnit): Set { val units = mutableSetOf() for (declaration in unit.declarations.filterIsInstance()) { - val importedUnit = loadOneUnit(declaration.path.text) + val importLocator = ImportLocator(declaration.path.text, form = declaration.form?.id) + val importedUnit = loadOneUnit(importLocator) units.add(importedUnit) } return units } - fun load(path: String): CompilationUnit { - val unit = loadOneUnit(path) + fun load(importLocator: ImportLocator): CompilationUnit { + val unit = loadOneUnit(importLocator) resolveAllImports(unit) return unit } + + private fun pickContentSource(form: String? = null): ContentSource { + if (form != null) { + return importSource.provideContentSource(form) + } + return importSource.fileContentSource + } + + fun stableIdentity( + importLocator: ImportLocator, + contentSource: ContentSource = pickContentSource(importLocator.form) + ): String { + val formKey = importLocator.form ?: "file" + val stableIdentity = contentSource.stableContentIdentity(importLocator.path) + return "[${formKey}][${stableIdentity}]" + } } 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 eebc629..9650eb1 100644 --- a/parser/src/main/kotlin/gay/pizza/pork/parser/Parser.kt +++ b/parser/src/main/kotlin/gay/pizza/pork/parser/Parser.kt @@ -183,7 +183,11 @@ class Parser(source: PeekableSource, val attribution: NodeAttribution) { private fun readImportDeclaration(): ImportDeclaration = within { expect(TokenType.Import) - ImportDeclaration(readStringLiteral()) + var form: Symbol? = null + if (peek(TokenType.Symbol)) { + form = readSymbolRaw() + } + ImportDeclaration(form, readStringLiteral()) } private fun readFunctionDeclaration(): FunctionDefinition = within { diff --git a/settings.gradle.kts b/settings.gradle.kts index 9599a34..dc40cb8 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -8,6 +8,7 @@ include( ":parser", ":frontend", ":evaluator", + ":stdlib", ":ffi", ":tool" ) diff --git a/stdlib/build.gradle.kts b/stdlib/build.gradle.kts new file mode 100644 index 0000000..e3b1be3 --- /dev/null +++ b/stdlib/build.gradle.kts @@ -0,0 +1,13 @@ +plugins { + id("gay.pizza.pork.module") +} + +tasks.processResources { + from("src/main/pork") { + into("pork/stdlib") + } +} + +dependencies { + implementation(project(":frontend")) +} diff --git a/stdlib/src/main/kotlin/gay/pizza/pork/stdlib/PorkStdlib.kt b/stdlib/src/main/kotlin/gay/pizza/pork/stdlib/PorkStdlib.kt new file mode 100644 index 0000000..d0011dd --- /dev/null +++ b/stdlib/src/main/kotlin/gay/pizza/pork/stdlib/PorkStdlib.kt @@ -0,0 +1,33 @@ +package gay.pizza.pork.stdlib + +import gay.pizza.pork.frontend.ContentSource +import gay.pizza.pork.parser.CharSource +import gay.pizza.pork.parser.StringCharSource + +object PorkStdlib : ContentSource { + private val stdlibClass = PorkStdlib::class.java + + private fun readManifestFiles(): List { + val manifestContent = read("stdlib.manifest") + return manifestContent.split("\n").filter { line -> + val trimmed = line.trim() + trimmed.isNotEmpty() && !trimmed.startsWith("#") + } + } + + val files: List = readManifestFiles() + + private fun read(path: String): String { + val stream = stdlibClass.getResourceAsStream("/pork/stdlib/${path}") + ?: throw RuntimeException("Stdlib does not contain file '${path}'") + return String(stream.readAllBytes()) + } + + override fun loadAsCharSource(path: String): CharSource { + return StringCharSource(read(path)) + } + + override fun stableContentIdentity(path: String): String { + return path + } +} diff --git a/stdlib/src/main/pork/ffi/malloc.pork b/stdlib/src/main/pork/ffi/malloc.pork new file mode 100644 index 0000000..0854d36 --- /dev/null +++ b/stdlib/src/main/pork/ffi/malloc.pork @@ -0,0 +1,5 @@ +export func malloc(size) + native ffi "c:malloc:void*" + +export func free(pointer) + native ffi "c:free:void" diff --git a/stdlib/src/main/pork/numbers.pork b/stdlib/src/main/pork/numbers.pork new file mode 100644 index 0000000..72c88dd --- /dev/null +++ b/stdlib/src/main/pork/numbers.pork @@ -0,0 +1,15 @@ +export func add(a, b) { + a + b +} + +export func subtract(a, b) { + a - b +} + +export func multiply(a, b) { + a * b +} + +export func divide(a, b) { + a / b +} diff --git a/stdlib/src/main/pork/stdlib.manifest b/stdlib/src/main/pork/stdlib.manifest new file mode 100644 index 0000000..965bf61 --- /dev/null +++ b/stdlib/src/main/pork/stdlib.manifest @@ -0,0 +1 @@ +numbers.pork diff --git a/tool/build.gradle.kts b/tool/build.gradle.kts index 9d2d44c..9e623ea 100644 --- a/tool/build.gradle.kts +++ b/tool/build.gradle.kts @@ -10,6 +10,7 @@ dependencies { api(project(":parser")) api(project(":frontend")) api(project(":evaluator")) + api(project(":stdlib")) api(project(":ffi")) api("com.github.ajalt.clikt:clikt:4.2.0") implementation(project(":common")) diff --git a/tool/src/main/kotlin/gay/pizza/pork/tool/Tool.kt b/tool/src/main/kotlin/gay/pizza/pork/tool/Tool.kt index d086382..43bc8a4 100644 --- a/tool/src/main/kotlin/gay/pizza/pork/tool/Tool.kt +++ b/tool/src/main/kotlin/gay/pizza/pork/tool/Tool.kt @@ -1,16 +1,17 @@ package gay.pizza.pork.tool -import gay.pizza.pork.ast.NodeVisitor -import gay.pizza.pork.parser.Printer import gay.pizza.pork.ast.CompilationUnit +import gay.pizza.pork.ast.NodeVisitor import gay.pizza.pork.ast.visit -import gay.pizza.pork.evaluator.Arguments import gay.pizza.pork.evaluator.CallableFunction import gay.pizza.pork.evaluator.Evaluator import gay.pizza.pork.evaluator.Scope import gay.pizza.pork.frontend.ContentSource +import gay.pizza.pork.frontend.ImportLocator +import gay.pizza.pork.frontend.StandardImportSource import gay.pizza.pork.frontend.World import gay.pizza.pork.parser.* +import gay.pizza.pork.stdlib.PorkStdlib abstract class Tool { abstract fun createCharSource(): CharSource @@ -31,11 +32,13 @@ abstract class Tool { fun visit(visitor: NodeVisitor): T = visitor.visit(parse()) fun loadMainFunction(scope: Scope, setupEvaluator: Evaluator.() -> Unit = {}): CallableFunction { - val contentSource = createContentSource() - val world = World(contentSource) + val fileContentSource = createContentSource() + val standardImportSource = StandardImportSource(fileContentSource) + standardImportSource.addContentSource("std", PorkStdlib) + val world = World(standardImportSource) val evaluator = Evaluator(world, scope) setupEvaluator(evaluator) - val resultingScope = evaluator.evaluate(rootFilePath()) + val resultingScope = evaluator.evaluate(ImportLocator(rootFilePath())) return resultingScope.value("main") as CallableFunction } }