add a standard library, and introduce formed imports (import std "myfile.pork")

This commit is contained in:
Alex Zenla 2023-09-06 21:39:57 -07:00
parent f31e12df89
commit 0e4362eefb
Signed by: alex
GPG Key ID: C0780728420EBFE5
18 changed files with 153 additions and 32 deletions

View File

@ -110,6 +110,8 @@ types:
ImportDeclaration: ImportDeclaration:
parent: Declaration parent: Declaration
values: values:
- name: form
type: Symbol?
- name: path - name: path
type: StringLiteral type: StringLiteral
IntLiteral: IntLiteral:

View File

@ -6,22 +6,23 @@ import kotlinx.serialization.Serializable
@Serializable @Serializable
@SerialName("importDeclaration") @SerialName("importDeclaration")
class ImportDeclaration(val path: StringLiteral) : Declaration() { class ImportDeclaration(val form: Symbol?, val path: StringLiteral) : Declaration() {
override val type: NodeType = NodeType.ImportDeclaration override val type: NodeType = NodeType.ImportDeclaration
override fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> = override fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> =
visitor.visitNodes(path) visitor.visitNodes(form, path)
override fun <T> visit(visitor: NodeVisitor<T>): T = override fun <T> visit(visitor: NodeVisitor<T>): T =
visitor.visitImportDeclaration(this) visitor.visitImportDeclaration(this)
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (other !is ImportDeclaration) return false if (other !is ImportDeclaration) return false
return other.path == path return other.form == form && other.path == path
} }
override fun hashCode(): Int { override fun hashCode(): Int {
var result = path.hashCode() var result = form.hashCode()
result = 31 * result + path.hashCode()
result = 31 * result + type.hashCode() result = 31 * result + type.hashCode()
return result return result
} }

View File

@ -4,6 +4,7 @@ import gay.pizza.pork.ast.CompilationUnit
import gay.pizza.pork.ast.Definition import gay.pizza.pork.ast.Definition
import gay.pizza.pork.ast.FunctionDefinition import gay.pizza.pork.ast.FunctionDefinition
import gay.pizza.pork.ast.ImportDeclaration import gay.pizza.pork.ast.ImportDeclaration
import gay.pizza.pork.frontend.ImportLocator
class CompilationUnitContext( class CompilationUnitContext(
val compilationUnit: CompilationUnit, val compilationUnit: CompilationUnit,
@ -50,8 +51,8 @@ class CompilationUnitContext(
} }
private fun processImport(import: ImportDeclaration) { private fun processImport(import: ImportDeclaration) {
val path = import.path.text val importLocator = ImportLocator(import.path.text, import.form?.id)
val evaluationContext = evaluator.context(path) val evaluationContext = evaluator.context(importLocator)
internalScope.inherit(evaluationContext.externalScope) internalScope.inherit(evaluationContext.externalScope)
} }
} }

View File

@ -1,17 +1,18 @@
package gay.pizza.pork.evaluator package gay.pizza.pork.evaluator
import gay.pizza.pork.frontend.ImportLocator
import gay.pizza.pork.frontend.World import gay.pizza.pork.frontend.World
class Evaluator(val world: World, val scope: Scope) { class Evaluator(val world: World, val scope: Scope) {
private val contexts = mutableMapOf<String, CompilationUnitContext>() private val contexts = mutableMapOf<String, CompilationUnitContext>()
private val nativeFunctionProviders = mutableMapOf<String, NativeFunctionProvider>() private val nativeFunctionProviders = mutableMapOf<String, NativeFunctionProvider>()
fun evaluate(path: String): Scope = fun evaluate(locator: ImportLocator): Scope =
context(path).externalScope context(locator).externalScope
fun context(path: String): CompilationUnitContext { fun context(locator: ImportLocator): CompilationUnitContext {
val unit = world.load(path) val unit = world.load(locator)
val identity = world.contentSource.stableContentIdentity(path) val identity = world.stableIdentity(locator)
val context = contexts.computeIfAbsent(identity) { val context = contexts.computeIfAbsent(identity) {
CompilationUnitContext(unit, this, scope) CompilationUnitContext(unit, this, scope)
} }

View File

@ -1,8 +1,4 @@
func malloc(size) import std "ffi/malloc.pork"
native ffi "c:malloc:void*"
func free(pointer)
native ffi "c:free:void"
export func main() { export func main() {
while true { while true {

View File

@ -0,0 +1,3 @@
package gay.pizza.pork.frontend
data class ImportLocator(val path: String, val form: String? = null)

View File

@ -0,0 +1,7 @@
package gay.pizza.pork.frontend
interface ImportSource {
val fileContentSource: ContentSource
fun provideContentSource(form: String): ContentSource
}

View File

@ -0,0 +1,14 @@
package gay.pizza.pork.frontend
class StandardImportSource(override val fileContentSource: ContentSource) : ImportSource {
private val providers = mutableMapOf<String,ContentSource>()
override fun provideContentSource(form: String): ContentSource {
return providers[form] ?:
throw RuntimeException("Unknown import form: $form")
}
fun addContentSource(form: String, contentSource: ContentSource) {
providers[form] = contentSource
}
}

View File

@ -7,37 +7,57 @@ import gay.pizza.pork.parser.Parser
import gay.pizza.pork.parser.TokenStreamSource import gay.pizza.pork.parser.TokenStreamSource
import gay.pizza.pork.parser.Tokenizer import gay.pizza.pork.parser.Tokenizer
class World(val contentSource: ContentSource) { class World(val importSource: ImportSource) {
private val internalUnits = mutableMapOf<String, CompilationUnit>() private val internalUnits = mutableMapOf<String, CompilationUnit>()
val units: List<CompilationUnit> val units: List<CompilationUnit>
get() = internalUnits.values.toList() get() = internalUnits.values.toList()
private fun loadOneUnit(path: String): CompilationUnit { private fun loadOneUnit(importLocator: ImportLocator): CompilationUnit {
val stableIdentity = contentSource.stableContentIdentity(path) val contentSource = pickContentSource(importLocator.form)
val cached = internalUnits[stableIdentity] val stableKey = stableIdentity(importLocator, contentSource = contentSource)
val cached = internalUnits[stableKey]
if (cached != null) { if (cached != null) {
return cached return cached
} }
val charSource = contentSource.loadAsCharSource(path) val charSource = contentSource.loadAsCharSource(importLocator.path)
val tokenizer = Tokenizer(charSource) val tokenizer = Tokenizer(charSource)
val tokenStream = tokenizer.tokenize() val tokenStream = tokenizer.tokenize()
val parser = Parser(TokenStreamSource(tokenStream), DiscardNodeAttribution) val parser = Parser(TokenStreamSource(tokenStream), DiscardNodeAttribution)
return parser.readCompilationUnit() val unit = parser.readCompilationUnit()
internalUnits[stableKey] = unit
return unit
} }
private fun resolveAllImports(unit: CompilationUnit): Set<CompilationUnit> { private fun resolveAllImports(unit: CompilationUnit): Set<CompilationUnit> {
val units = mutableSetOf<CompilationUnit>() val units = mutableSetOf<CompilationUnit>()
for (declaration in unit.declarations.filterIsInstance<ImportDeclaration>()) { for (declaration in unit.declarations.filterIsInstance<ImportDeclaration>()) {
val importedUnit = loadOneUnit(declaration.path.text) val importLocator = ImportLocator(declaration.path.text, form = declaration.form?.id)
val importedUnit = loadOneUnit(importLocator)
units.add(importedUnit) units.add(importedUnit)
} }
return units return units
} }
fun load(path: String): CompilationUnit { fun load(importLocator: ImportLocator): CompilationUnit {
val unit = loadOneUnit(path) val unit = loadOneUnit(importLocator)
resolveAllImports(unit) resolveAllImports(unit)
return 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}]"
}
} }

View File

@ -183,7 +183,11 @@ class Parser(source: PeekableSource<Token>, val attribution: NodeAttribution) {
private fun readImportDeclaration(): ImportDeclaration = within { private fun readImportDeclaration(): ImportDeclaration = within {
expect(TokenType.Import) expect(TokenType.Import)
ImportDeclaration(readStringLiteral()) var form: Symbol? = null
if (peek(TokenType.Symbol)) {
form = readSymbolRaw()
}
ImportDeclaration(form, readStringLiteral())
} }
private fun readFunctionDeclaration(): FunctionDefinition = within { private fun readFunctionDeclaration(): FunctionDefinition = within {

View File

@ -8,6 +8,7 @@ include(
":parser", ":parser",
":frontend", ":frontend",
":evaluator", ":evaluator",
":stdlib",
":ffi", ":ffi",
":tool" ":tool"
) )

13
stdlib/build.gradle.kts Normal file
View File

@ -0,0 +1,13 @@
plugins {
id("gay.pizza.pork.module")
}
tasks.processResources {
from("src/main/pork") {
into("pork/stdlib")
}
}
dependencies {
implementation(project(":frontend"))
}

View File

@ -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<String> {
val manifestContent = read("stdlib.manifest")
return manifestContent.split("\n").filter { line ->
val trimmed = line.trim()
trimmed.isNotEmpty() && !trimmed.startsWith("#")
}
}
val files: List<String> = 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
}
}

View File

@ -0,0 +1,5 @@
export func malloc(size)
native ffi "c:malloc:void*"
export func free(pointer)
native ffi "c:free:void"

View File

@ -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
}

View File

@ -0,0 +1 @@
numbers.pork

View File

@ -10,6 +10,7 @@ dependencies {
api(project(":parser")) api(project(":parser"))
api(project(":frontend")) api(project(":frontend"))
api(project(":evaluator")) api(project(":evaluator"))
api(project(":stdlib"))
api(project(":ffi")) api(project(":ffi"))
api("com.github.ajalt.clikt:clikt:4.2.0") api("com.github.ajalt.clikt:clikt:4.2.0")
implementation(project(":common")) implementation(project(":common"))

View File

@ -1,16 +1,17 @@
package gay.pizza.pork.tool 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.CompilationUnit
import gay.pizza.pork.ast.NodeVisitor
import gay.pizza.pork.ast.visit import gay.pizza.pork.ast.visit
import gay.pizza.pork.evaluator.Arguments
import gay.pizza.pork.evaluator.CallableFunction import gay.pizza.pork.evaluator.CallableFunction
import gay.pizza.pork.evaluator.Evaluator import gay.pizza.pork.evaluator.Evaluator
import gay.pizza.pork.evaluator.Scope import gay.pizza.pork.evaluator.Scope
import gay.pizza.pork.frontend.ContentSource 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.frontend.World
import gay.pizza.pork.parser.* import gay.pizza.pork.parser.*
import gay.pizza.pork.stdlib.PorkStdlib
abstract class Tool { abstract class Tool {
abstract fun createCharSource(): CharSource abstract fun createCharSource(): CharSource
@ -31,11 +32,13 @@ abstract class Tool {
fun <T> visit(visitor: NodeVisitor<T>): T = visitor.visit(parse()) fun <T> visit(visitor: NodeVisitor<T>): T = visitor.visit(parse())
fun loadMainFunction(scope: Scope, setupEvaluator: Evaluator.() -> Unit = {}): CallableFunction { fun loadMainFunction(scope: Scope, setupEvaluator: Evaluator.() -> Unit = {}): CallableFunction {
val contentSource = createContentSource() val fileContentSource = createContentSource()
val world = World(contentSource) val standardImportSource = StandardImportSource(fileContentSource)
standardImportSource.addContentSource("std", PorkStdlib)
val world = World(standardImportSource)
val evaluator = Evaluator(world, scope) val evaluator = Evaluator(world, scope)
setupEvaluator(evaluator) setupEvaluator(evaluator)
val resultingScope = evaluator.evaluate(rootFilePath()) val resultingScope = evaluator.evaluate(ImportLocator(rootFilePath()))
return resultingScope.value("main") as CallableFunction return resultingScope.value("main") as CallableFunction
} }
} }