mirror of
https://github.com/GayPizzaSpecifications/pork.git
synced 2025-08-03 05:10:55 +00:00
Exportable functions, common frontend, and scoped imports.
This commit is contained in:
parent
7a771980e0
commit
4f567eb287
@ -7,7 +7,7 @@ func fib(n) {
|
|||||||
else fib(n - 1) + fib(n - 2)
|
else fib(n - 1) + fib(n - 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
export func main() {
|
||||||
result = fib(20)
|
result = fib(20)
|
||||||
println(result)
|
println(result)
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import "module.pork"
|
import "module.pork"
|
||||||
|
|
||||||
func main() {
|
export func main() {
|
||||||
hello()
|
hello()
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
func hello() {
|
export func hello() {
|
||||||
println("Hello World")
|
println("Hello World")
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
func main() {
|
export func main() {
|
||||||
three = 3
|
three = 3
|
||||||
two = 2
|
two = 2
|
||||||
|
|
||||||
|
@ -3,4 +3,7 @@ package gay.pizza.pork.ast.nodes
|
|||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
sealed class Definition : Node()
|
sealed class Definition : Node() {
|
||||||
|
abstract val symbol: Symbol
|
||||||
|
abstract val modifiers: DefinitionModifiers
|
||||||
|
}
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
package gay.pizza.pork.ast.nodes
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class DefinitionModifiers(
|
||||||
|
var export: Boolean
|
||||||
|
)
|
@ -7,7 +7,12 @@ import kotlinx.serialization.Serializable
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@SerialName("functionDefinition")
|
@SerialName("functionDefinition")
|
||||||
class FunctionDefinition(val symbol: Symbol, val arguments: List<Symbol>, val block: Block) : Definition() {
|
class FunctionDefinition(
|
||||||
|
override val modifiers: DefinitionModifiers,
|
||||||
|
override val symbol: Symbol,
|
||||||
|
val arguments: List<Symbol>,
|
||||||
|
val block: Block
|
||||||
|
) : Definition() {
|
||||||
override val type: NodeType = NodeType.FunctionDeclaration
|
override val type: NodeType = NodeType.FunctionDeclaration
|
||||||
|
|
||||||
override fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> =
|
override fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package gay.pizza.pork.cli
|
package gay.pizza.pork.cli
|
||||||
|
|
||||||
|
import gay.pizza.pork.frontend.ContentSource
|
||||||
|
import gay.pizza.pork.frontend.FsContentSource
|
||||||
import gay.pizza.pork.parse.CharSource
|
import gay.pizza.pork.parse.CharSource
|
||||||
import gay.pizza.pork.parse.StringCharSource
|
import gay.pizza.pork.parse.StringCharSource
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
@ -7,6 +9,6 @@ import kotlin.io.path.readText
|
|||||||
|
|
||||||
class FileTool(val path: Path) : Tool() {
|
class FileTool(val path: Path) : Tool() {
|
||||||
override fun createCharSource(): CharSource = StringCharSource(path.readText())
|
override fun createCharSource(): CharSource = StringCharSource(path.readText())
|
||||||
override fun resolveImportSource(path: String): CharSource =
|
override fun createContentSource(): ContentSource = FsContentSource(path.parent)
|
||||||
StringCharSource(this.path.parent.resolve(path).readText())
|
override fun rootFilePath(): String = path.fileName.toString()
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ package gay.pizza.pork.cli
|
|||||||
import com.github.ajalt.clikt.core.CliktCommand
|
import com.github.ajalt.clikt.core.CliktCommand
|
||||||
import com.github.ajalt.clikt.parameters.arguments.argument
|
import com.github.ajalt.clikt.parameters.arguments.argument
|
||||||
import com.github.ajalt.clikt.parameters.types.path
|
import com.github.ajalt.clikt.parameters.types.path
|
||||||
import gay.pizza.pork.eval.Arguments
|
|
||||||
import gay.pizza.pork.eval.CallableFunction
|
import gay.pizza.pork.eval.CallableFunction
|
||||||
import gay.pizza.pork.eval.Scope
|
import gay.pizza.pork.eval.Scope
|
||||||
|
|
||||||
@ -19,6 +18,5 @@ class RunCommand : CliktCommand(help = "Run Program", name = "run") {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
tool.evaluate(scope)
|
tool.evaluate(scope)
|
||||||
scope.call("main", Arguments(emptyList()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,14 +3,15 @@ package gay.pizza.pork.cli
|
|||||||
import gay.pizza.pork.ast.NodeVisitor
|
import gay.pizza.pork.ast.NodeVisitor
|
||||||
import gay.pizza.pork.ast.Printer
|
import gay.pizza.pork.ast.Printer
|
||||||
import gay.pizza.pork.ast.nodes.CompilationUnit
|
import gay.pizza.pork.ast.nodes.CompilationUnit
|
||||||
import gay.pizza.pork.eval.Evaluator
|
import gay.pizza.pork.eval.*
|
||||||
import gay.pizza.pork.eval.ImportLoader
|
import gay.pizza.pork.frontend.ContentSource
|
||||||
import gay.pizza.pork.eval.Scope
|
import gay.pizza.pork.frontend.World
|
||||||
import gay.pizza.pork.parse.*
|
import gay.pizza.pork.parse.*
|
||||||
|
|
||||||
abstract class Tool {
|
abstract class Tool {
|
||||||
abstract fun createCharSource(): CharSource
|
abstract fun createCharSource(): CharSource
|
||||||
abstract fun resolveImportSource(path: String): CharSource
|
abstract fun createContentSource(): ContentSource
|
||||||
|
abstract fun rootFilePath(): String
|
||||||
|
|
||||||
fun tokenize(): TokenStream =
|
fun tokenize(): TokenStream =
|
||||||
Tokenizer(createCharSource()).tokenize()
|
Tokenizer(createCharSource()).tokenize()
|
||||||
@ -21,17 +22,15 @@ abstract class Tool {
|
|||||||
fun highlight(scheme: HighlightScheme): List<Highlight> =
|
fun highlight(scheme: HighlightScheme): List<Highlight> =
|
||||||
Highlighter(scheme).highlight(tokenize())
|
Highlighter(scheme).highlight(tokenize())
|
||||||
|
|
||||||
fun evaluate(scope: Scope = Scope()): Any =
|
|
||||||
visit(Evaluator(scope, FrontendImportLoader(this)))
|
|
||||||
|
|
||||||
fun reprint(): String = buildString { visit(Printer(this)) }
|
fun reprint(): String = buildString { visit(Printer(this)) }
|
||||||
|
|
||||||
fun <T> visit(visitor: NodeVisitor<T>): T = visitor.visit(parse())
|
fun <T> visit(visitor: NodeVisitor<T>): T = visitor.visit(parse())
|
||||||
|
|
||||||
private class FrontendImportLoader(val frontend: Tool) : ImportLoader {
|
fun evaluate(scope: Scope) {
|
||||||
override fun load(path: String): CompilationUnit {
|
val contentSource = createContentSource()
|
||||||
val tokenStream = Tokenizer(frontend.resolveImportSource(path)).tokenize()
|
val world = World(contentSource)
|
||||||
return Parser(TokenStreamSource(tokenStream), DiscardNodeAttribution).readCompilationUnit()
|
val evaluator = Evaluator(world, scope)
|
||||||
}
|
val resultingScope = evaluator.evaluate(rootFilePath())
|
||||||
|
resultingScope.call("main", Arguments(emptyList()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
30
src/main/kotlin/gay/pizza/pork/eval/EvaluationContext.kt
Normal file
30
src/main/kotlin/gay/pizza/pork/eval/EvaluationContext.kt
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package gay.pizza.pork.eval
|
||||||
|
|
||||||
|
import gay.pizza.pork.ast.nodes.CompilationUnit
|
||||||
|
import gay.pizza.pork.ast.nodes.ImportDeclaration
|
||||||
|
|
||||||
|
class EvaluationContext(
|
||||||
|
val compilationUnit: CompilationUnit,
|
||||||
|
val evaluationContextProvider: EvaluationContextProvider,
|
||||||
|
rootScope: Scope
|
||||||
|
) {
|
||||||
|
val internalScope = rootScope.fork()
|
||||||
|
val externalScope = rootScope.fork()
|
||||||
|
|
||||||
|
private val evaluationVisitor = EvaluationVisitor(internalScope)
|
||||||
|
|
||||||
|
fun setup() {
|
||||||
|
val imports = compilationUnit.declarations.filterIsInstance<ImportDeclaration>()
|
||||||
|
for (import in imports) {
|
||||||
|
val evaluationContext = evaluationContextProvider.provideEvaluationContext(import.path.text)
|
||||||
|
internalScope.inherit(evaluationContext.externalScope)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (definition in compilationUnit.definitions) {
|
||||||
|
evaluationVisitor.visit(definition)
|
||||||
|
if (definition.modifiers.export) {
|
||||||
|
externalScope.define(definition.symbol.id, internalScope.value(definition.symbol.id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package gay.pizza.pork.eval
|
||||||
|
|
||||||
|
interface EvaluationContextProvider {
|
||||||
|
fun provideEvaluationContext(path: String): EvaluationContext
|
||||||
|
}
|
143
src/main/kotlin/gay/pizza/pork/eval/EvaluationVisitor.kt
Normal file
143
src/main/kotlin/gay/pizza/pork/eval/EvaluationVisitor.kt
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
package gay.pizza.pork.eval
|
||||||
|
|
||||||
|
import gay.pizza.pork.ast.NodeVisitor
|
||||||
|
import gay.pizza.pork.ast.nodes.*
|
||||||
|
|
||||||
|
class EvaluationVisitor(val root: Scope) : NodeVisitor<Any> {
|
||||||
|
private var currentScope: Scope = root
|
||||||
|
|
||||||
|
override fun visitIntLiteral(node: IntLiteral): Any = node.value
|
||||||
|
override fun visitStringLiteral(node: StringLiteral): Any = node.text
|
||||||
|
override fun visitBooleanLiteral(node: BooleanLiteral): Any = node.value
|
||||||
|
override fun visitListLiteral(node: ListLiteral): Any = node.items.map { visit(it) }
|
||||||
|
|
||||||
|
override fun visitSymbol(node: Symbol): Any = None
|
||||||
|
|
||||||
|
override fun visitFunctionCall(node: FunctionCall): Any {
|
||||||
|
val arguments = node.arguments.map { visit(it) }
|
||||||
|
return currentScope.call(node.symbol.id, Arguments(arguments))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitDefine(node: Assignment): Any {
|
||||||
|
val value = visit(node.value)
|
||||||
|
currentScope.define(node.symbol.id, value)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitSymbolReference(node: SymbolReference): Any =
|
||||||
|
currentScope.value(node.symbol.id)
|
||||||
|
|
||||||
|
override fun visitLambda(node: Lambda): CallableFunction {
|
||||||
|
return CallableFunction { arguments ->
|
||||||
|
currentScope = currentScope.fork()
|
||||||
|
for ((index, argumentSymbol) in node.arguments.withIndex()) {
|
||||||
|
currentScope.define(argumentSymbol.id, arguments.values[index])
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
var value: Any? = null
|
||||||
|
for (expression in node.expressions) {
|
||||||
|
value = visit(expression)
|
||||||
|
}
|
||||||
|
value ?: None
|
||||||
|
} finally {
|
||||||
|
currentScope = currentScope.leave()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitParentheses(node: Parentheses): Any = visit(node.expression)
|
||||||
|
|
||||||
|
override fun visitPrefixOperation(node: PrefixOperation): Any {
|
||||||
|
val value = visit(node.expression)
|
||||||
|
return when (node.op) {
|
||||||
|
PrefixOperator.Negate -> {
|
||||||
|
if (value !is Boolean) {
|
||||||
|
throw RuntimeException("Cannot negate a value which is not a boolean.")
|
||||||
|
}
|
||||||
|
!value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitIf(node: If): Any {
|
||||||
|
val condition = visit(node.condition)
|
||||||
|
return if (condition == true) {
|
||||||
|
visit(node.thenExpression)
|
||||||
|
} else {
|
||||||
|
if (node.elseExpression != null) {
|
||||||
|
visit(node.elseExpression)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitInfixOperation(node: InfixOperation): Any {
|
||||||
|
val left = visit(node.left)
|
||||||
|
val right = visit(node.right)
|
||||||
|
|
||||||
|
when (node.op) {
|
||||||
|
InfixOperator.Equals -> {
|
||||||
|
return left == right
|
||||||
|
}
|
||||||
|
InfixOperator.NotEquals -> {
|
||||||
|
return left != right
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (left !is Number || right !is Number) {
|
||||||
|
throw RuntimeException("Failed to evaluate infix operation, bad types.")
|
||||||
|
}
|
||||||
|
|
||||||
|
val leftInt = left.toInt()
|
||||||
|
val rightInt = right.toInt()
|
||||||
|
|
||||||
|
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}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitFunctionDeclaration(node: FunctionDefinition): Any {
|
||||||
|
val blockFunction = visitBlock(node.block) as BlockFunction
|
||||||
|
val function = CallableFunction { arguments ->
|
||||||
|
currentScope = currentScope.fork()
|
||||||
|
for ((index, argumentSymbol) in node.arguments.withIndex()) {
|
||||||
|
currentScope.define(argumentSymbol.id, arguments.values[index])
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return@CallableFunction blockFunction.call()
|
||||||
|
} finally {
|
||||||
|
currentScope = currentScope.leave()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
currentScope.define(node.symbol.id, function)
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitBlock(node: Block): Any = BlockFunction {
|
||||||
|
var value: Any? = null
|
||||||
|
for (expression in node.expressions) {
|
||||||
|
value = visit(expression)
|
||||||
|
}
|
||||||
|
value ?: None
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitImportDeclaration(node: ImportDeclaration): Any {
|
||||||
|
throw RuntimeException(
|
||||||
|
"Import declarations cannot be visited in an EvaluationVisitor. " +
|
||||||
|
"Utilize an EvaluationContext."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitCompilationUnit(node: CompilationUnit): Any {
|
||||||
|
throw RuntimeException(
|
||||||
|
"Compilation units cannot be visited in an EvaluationVisitor. " +
|
||||||
|
"Utilize an EvaluationContext."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -1,147 +1,22 @@
|
|||||||
package gay.pizza.pork.eval
|
package gay.pizza.pork.eval
|
||||||
|
|
||||||
import gay.pizza.pork.ast.NodeVisitor
|
import gay.pizza.pork.frontend.World
|
||||||
import gay.pizza.pork.ast.nodes.*
|
|
||||||
|
|
||||||
class Evaluator(root: Scope, val importLoader: ImportLoader) : NodeVisitor<Any> {
|
class Evaluator(val world: World, val scope: Scope) : EvaluationContextProvider {
|
||||||
private var currentScope: Scope = root
|
private val contexts = mutableMapOf<String, EvaluationContext>()
|
||||||
|
|
||||||
override fun visitIntLiteral(node: IntLiteral): Any = node.value
|
fun evaluate(path: String): Scope {
|
||||||
override fun visitStringLiteral(node: StringLiteral): Any = node.text
|
val context = provideEvaluationContext(path)
|
||||||
override fun visitBooleanLiteral(node: BooleanLiteral): Any = node.value
|
return context.externalScope
|
||||||
override fun visitListLiteral(node: ListLiteral): Any = node.items.map { visit(it) }
|
|
||||||
|
|
||||||
override fun visitSymbol(node: Symbol): Any = None
|
|
||||||
|
|
||||||
override fun visitFunctionCall(node: FunctionCall): Any {
|
|
||||||
val arguments = node.arguments.map { visit(it) }
|
|
||||||
return currentScope.call(node.symbol.id, Arguments(arguments))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitDefine(node: Assignment): Any {
|
override fun provideEvaluationContext(path: String): EvaluationContext {
|
||||||
val value = visit(node.value)
|
val unit = world.load(path)
|
||||||
currentScope.define(node.symbol.id, value)
|
val identity = world.contentSource.stableContentIdentity(path)
|
||||||
return value
|
val context = contexts.computeIfAbsent(identity) {
|
||||||
}
|
EvaluationContext(unit, this, scope)
|
||||||
|
|
||||||
override fun visitSymbolReference(node: SymbolReference): Any =
|
|
||||||
currentScope.value(node.symbol.id)
|
|
||||||
|
|
||||||
override fun visitLambda(node: Lambda): CallableFunction {
|
|
||||||
return CallableFunction { arguments ->
|
|
||||||
currentScope = currentScope.fork()
|
|
||||||
for ((index, argumentSymbol) in node.arguments.withIndex()) {
|
|
||||||
currentScope.define(argumentSymbol.id, arguments.values[index])
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
var value: Any? = null
|
|
||||||
for (expression in node.expressions) {
|
|
||||||
value = visit(expression)
|
|
||||||
}
|
|
||||||
value ?: None
|
|
||||||
} finally {
|
|
||||||
currentScope = currentScope.leave()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
context.setup()
|
||||||
|
return context
|
||||||
override fun visitParentheses(node: Parentheses): Any = visit(node.expression)
|
|
||||||
|
|
||||||
override fun visitPrefixOperation(node: PrefixOperation): Any {
|
|
||||||
val value = visit(node.expression)
|
|
||||||
return when (node.op) {
|
|
||||||
PrefixOperator.Negate -> {
|
|
||||||
if (value !is Boolean) {
|
|
||||||
throw RuntimeException("Cannot negate a value which is not a boolean.")
|
|
||||||
}
|
|
||||||
!value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visitIf(node: If): Any {
|
|
||||||
val condition = visit(node.condition)
|
|
||||||
return if (condition == true) {
|
|
||||||
visit(node.thenExpression)
|
|
||||||
} else {
|
|
||||||
if (node.elseExpression != null) {
|
|
||||||
visit(node.elseExpression)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visitInfixOperation(node: InfixOperation): Any {
|
|
||||||
val left = visit(node.left)
|
|
||||||
val right = visit(node.right)
|
|
||||||
|
|
||||||
when (node.op) {
|
|
||||||
InfixOperator.Equals -> {
|
|
||||||
return left == right
|
|
||||||
}
|
|
||||||
InfixOperator.NotEquals -> {
|
|
||||||
return left != right
|
|
||||||
}
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (left !is Number || right !is Number) {
|
|
||||||
throw RuntimeException("Failed to evaluate infix operation, bad types.")
|
|
||||||
}
|
|
||||||
|
|
||||||
val leftInt = left.toInt()
|
|
||||||
val rightInt = right.toInt()
|
|
||||||
|
|
||||||
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}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visitFunctionDeclaration(node: FunctionDefinition): Any {
|
|
||||||
val blockFunction = visitBlock(node.block) as BlockFunction
|
|
||||||
val function = CallableFunction { arguments ->
|
|
||||||
currentScope = currentScope.fork()
|
|
||||||
for ((index, argumentSymbol) in node.arguments.withIndex()) {
|
|
||||||
currentScope.define(argumentSymbol.id, arguments.values[index])
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return@CallableFunction blockFunction.call()
|
|
||||||
} finally {
|
|
||||||
currentScope = currentScope.leave()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
currentScope.define(node.symbol.id, function)
|
|
||||||
return None
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visitBlock(node: Block): Any = BlockFunction {
|
|
||||||
var value: Any? = null
|
|
||||||
for (expression in node.expressions) {
|
|
||||||
value = visit(expression)
|
|
||||||
}
|
|
||||||
value ?: None
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visitImportDeclaration(node: ImportDeclaration): Any {
|
|
||||||
val importPath = node.path.text
|
|
||||||
val compilationUnit = importLoader.load(importPath)
|
|
||||||
visit(compilationUnit)
|
|
||||||
return None
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visitCompilationUnit(node: CompilationUnit): Any {
|
|
||||||
for (declaration in node.declarations) {
|
|
||||||
visit(declaration)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (definition in node.definitions) {
|
|
||||||
visit(definition)
|
|
||||||
}
|
|
||||||
return None
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,14 @@
|
|||||||
package gay.pizza.pork.eval
|
package gay.pizza.pork.eval
|
||||||
|
|
||||||
class Scope(val parent: Scope? = null) {
|
class Scope(val parent: Scope? = null, inherits: List<Scope> = emptyList()) {
|
||||||
|
private val inherited = inherits.toMutableList()
|
||||||
private val variables = mutableMapOf<String, Any>()
|
private val variables = mutableMapOf<String, Any>()
|
||||||
|
|
||||||
|
fun has(name: String): Boolean =
|
||||||
|
variables.containsKey(name) ||
|
||||||
|
(parent?.has(name) ?: false) ||
|
||||||
|
inherited.any { inherit -> inherit.has(name) }
|
||||||
|
|
||||||
fun define(name: String, value: Any) {
|
fun define(name: String, value: Any) {
|
||||||
if (variables.containsKey(name)) {
|
if (variables.containsKey(name)) {
|
||||||
throw RuntimeException("Variable '${name}' is already defined")
|
throw RuntimeException("Variable '${name}' is already defined")
|
||||||
@ -14,7 +20,15 @@ class Scope(val parent: Scope? = null) {
|
|||||||
val value = variables[name]
|
val value = variables[name]
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
if (parent != null) {
|
if (parent != null) {
|
||||||
return parent.value(name)
|
if (parent.has(name)) {
|
||||||
|
return parent.value(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (inherit in inherited) {
|
||||||
|
if (inherit.has(name)) {
|
||||||
|
return inherit.value(name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
throw RuntimeException("Variable '${name}' not defined.")
|
throw RuntimeException("Variable '${name}' not defined.")
|
||||||
}
|
}
|
||||||
@ -39,4 +53,8 @@ class Scope(val parent: Scope? = null) {
|
|||||||
}
|
}
|
||||||
return parent
|
return parent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun inherit(scope: Scope) {
|
||||||
|
inherited.add(scope)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
8
src/main/kotlin/gay/pizza/pork/frontend/ContentSource.kt
Normal file
8
src/main/kotlin/gay/pizza/pork/frontend/ContentSource.kt
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package gay.pizza.pork.frontend
|
||||||
|
|
||||||
|
import gay.pizza.pork.parse.CharSource
|
||||||
|
|
||||||
|
interface ContentSource {
|
||||||
|
fun loadAsCharSource(path: String): CharSource
|
||||||
|
fun stableContentIdentity(path: String): String
|
||||||
|
}
|
24
src/main/kotlin/gay/pizza/pork/frontend/FsContentSource.kt
Normal file
24
src/main/kotlin/gay/pizza/pork/frontend/FsContentSource.kt
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package gay.pizza.pork.frontend
|
||||||
|
|
||||||
|
import gay.pizza.pork.parse.CharSource
|
||||||
|
import gay.pizza.pork.parse.StringCharSource
|
||||||
|
import java.nio.file.Path
|
||||||
|
import kotlin.io.path.absolutePathString
|
||||||
|
import kotlin.io.path.readText
|
||||||
|
|
||||||
|
class FsContentSource(val root: Path) : ContentSource {
|
||||||
|
override fun loadAsCharSource(path: String): CharSource =
|
||||||
|
StringCharSource(asFsPath(path).readText())
|
||||||
|
|
||||||
|
override fun stableContentIdentity(path: String): String =
|
||||||
|
asFsPath(path).absolutePathString()
|
||||||
|
|
||||||
|
private fun asFsPath(path: String): Path {
|
||||||
|
val fsPath = root.resolve(path)
|
||||||
|
val absoluteRootPath = root.absolutePathString() + root.fileSystem.separator
|
||||||
|
if (!fsPath.absolutePathString().startsWith(absoluteRootPath)) {
|
||||||
|
throw RuntimeException("Unable to load path outside of the root: $fsPath (root is ${root})")
|
||||||
|
}
|
||||||
|
return fsPath
|
||||||
|
}
|
||||||
|
}
|
42
src/main/kotlin/gay/pizza/pork/frontend/World.kt
Normal file
42
src/main/kotlin/gay/pizza/pork/frontend/World.kt
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package gay.pizza.pork.frontend
|
||||||
|
|
||||||
|
import gay.pizza.pork.ast.nodes.CompilationUnit
|
||||||
|
import gay.pizza.pork.ast.nodes.ImportDeclaration
|
||||||
|
import gay.pizza.pork.parse.DiscardNodeAttribution
|
||||||
|
import gay.pizza.pork.parse.Parser
|
||||||
|
import gay.pizza.pork.parse.TokenStreamSource
|
||||||
|
import gay.pizza.pork.parse.Tokenizer
|
||||||
|
|
||||||
|
class World(val contentSource: ContentSource) {
|
||||||
|
private val units = mutableMapOf<String, CompilationUnit>()
|
||||||
|
|
||||||
|
private fun loadOneUnit(path: String): CompilationUnit {
|
||||||
|
val stableIdentity = contentSource.stableContentIdentity(path)
|
||||||
|
val cached = units[stableIdentity]
|
||||||
|
if (cached != null) {
|
||||||
|
return cached
|
||||||
|
}
|
||||||
|
val charSource = contentSource.loadAsCharSource(path)
|
||||||
|
val tokenizer = Tokenizer(charSource)
|
||||||
|
val tokenStream = tokenizer.tokenize()
|
||||||
|
val parser = Parser(TokenStreamSource(tokenStream), DiscardNodeAttribution)
|
||||||
|
return parser.readCompilationUnit()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resolveAllImports(unit: CompilationUnit): Set<CompilationUnit> {
|
||||||
|
val units = mutableSetOf<CompilationUnit>()
|
||||||
|
for (declaration in unit.declarations.filterIsInstance<ImportDeclaration>()) {
|
||||||
|
val importedUnit = loadOneUnit(declaration.path.text)
|
||||||
|
units.add(importedUnit)
|
||||||
|
}
|
||||||
|
return units
|
||||||
|
}
|
||||||
|
|
||||||
|
fun load(path: String): CompilationUnit {
|
||||||
|
val unit = loadOneUnit(path)
|
||||||
|
resolveAllImports(unit)
|
||||||
|
return unit
|
||||||
|
}
|
||||||
|
|
||||||
|
fun units(path: String): Set<CompilationUnit> = resolveAllImports(loadOneUnit(path))
|
||||||
|
}
|
@ -175,17 +175,29 @@ class Parser(source: PeekableSource<Token>, val attribution: NodeAttribution) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun readFunctionDeclaration(): FunctionDefinition = within {
|
private fun readFunctionDeclaration(): FunctionDefinition = within {
|
||||||
|
val modifiers = DefinitionModifiers(export = false)
|
||||||
|
while (true) {
|
||||||
|
val token = peek()
|
||||||
|
when (token.type) {
|
||||||
|
TokenType.Export -> {
|
||||||
|
expect(TokenType.Export)
|
||||||
|
modifiers.export = true
|
||||||
|
}
|
||||||
|
else -> break
|
||||||
|
}
|
||||||
|
}
|
||||||
expect(TokenType.Func)
|
expect(TokenType.Func)
|
||||||
val name = readSymbolRaw()
|
val name = readSymbolRaw()
|
||||||
expect(TokenType.LeftParentheses)
|
expect(TokenType.LeftParentheses)
|
||||||
val arguments = collect(TokenType.RightParentheses, TokenType.Comma) { readSymbolRaw() }
|
val arguments = collect(TokenType.RightParentheses, TokenType.Comma) { readSymbolRaw() }
|
||||||
expect(TokenType.RightParentheses)
|
expect(TokenType.RightParentheses)
|
||||||
FunctionDefinition(name, arguments, readBlock())
|
FunctionDefinition(modifiers, name, arguments, readBlock())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun maybeReadDefinition(): Definition? {
|
private fun maybeReadDefinition(): Definition? {
|
||||||
val token = peek()
|
val token = peek()
|
||||||
return when (token.type) {
|
return when (token.type) {
|
||||||
|
TokenType.Export,
|
||||||
TokenType.Func -> readFunctionDeclaration()
|
TokenType.Func -> readFunctionDeclaration()
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ enum class TokenType(vararg properties: TokenTypeProperty) {
|
|||||||
Then(Keyword("then"), KeywordFamily),
|
Then(Keyword("then"), KeywordFamily),
|
||||||
Else(Keyword("else"), KeywordFamily),
|
Else(Keyword("else"), KeywordFamily),
|
||||||
Import(Keyword("import"), KeywordFamily),
|
Import(Keyword("import"), KeywordFamily),
|
||||||
|
Export(Keyword("export"), KeywordFamily),
|
||||||
Func(Keyword("func"), KeywordFamily),
|
Func(Keyword("func"), KeywordFamily),
|
||||||
Whitespace(CharConsumer { it == ' ' || it == '\r' || it == '\n' || it == '\t' }),
|
Whitespace(CharConsumer { it == ' ' || it == '\r' || it == '\n' || it == '\t' }),
|
||||||
BlockComment(CommentFamily),
|
BlockComment(CommentFamily),
|
||||||
|
Loading…
Reference in New Issue
Block a user