diff --git a/examples/imports.pork b/examples/imports.pork new file mode 100644 index 0000000..e892222 --- /dev/null +++ b/examples/imports.pork @@ -0,0 +1,5 @@ +import "module.pork" + +func main() { + hello() +} diff --git a/examples/module.pork b/examples/module.pork new file mode 100644 index 0000000..1d5f3b8 --- /dev/null +++ b/examples/module.pork @@ -0,0 +1,3 @@ +func hello() { + println("Hello World") +} diff --git a/src/main/kotlin/gay/pizza/pork/ast/NodeCoalescer.kt b/src/main/kotlin/gay/pizza/pork/ast/NodeCoalescer.kt index 41a38b2..bfe1806 100644 --- a/src/main/kotlin/gay/pizza/pork/ast/NodeCoalescer.kt +++ b/src/main/kotlin/gay/pizza/pork/ast/NodeCoalescer.kt @@ -9,15 +9,16 @@ class NodeCoalescer(val handler: (Node) -> Unit) : NodeVisitor { override fun visitListLiteral(node: ListLiteral): Unit = handle(node) override fun visitSymbol(node: Symbol): Unit = handle(node) override fun visitFunctionCall(node: FunctionCall): Unit = handle(node) - override fun visitDefine(node: Define): Unit = handle(node) + override fun visitDefine(node: Assignment): Unit = handle(node) override fun visitSymbolReference(node: SymbolReference): Unit = handle(node) override fun visitLambda(node: Lambda): Unit = handle(node) override fun visitParentheses(node: Parentheses): Unit = handle(node) override fun visitPrefixOperation(node: PrefixOperation): Unit = handle(node) override fun visitIf(node: If): Unit = handle(node) override fun visitInfixOperation(node: InfixOperation): Unit = handle(node) - override fun visitFunctionDeclaration(node: FunctionDeclaration): Unit = handle(node) + override fun visitFunctionDeclaration(node: FunctionDefinition): Unit = handle(node) override fun visitBlock(node: Block): Unit = handle(node) + override fun visitImportDeclaration(node: ImportDeclaration): Unit = handle(node) override fun visitCompilationUnit(node: CompilationUnit): Unit = handle(node) diff --git a/src/main/kotlin/gay/pizza/pork/ast/NodeType.kt b/src/main/kotlin/gay/pizza/pork/ast/NodeType.kt index 7baef87..1e115c6 100644 --- a/src/main/kotlin/gay/pizza/pork/ast/NodeType.kt +++ b/src/main/kotlin/gay/pizza/pork/ast/NodeType.kt @@ -12,13 +12,14 @@ enum class NodeType(val parent: NodeType? = null) { ListLiteral(Expression), StringLiteral(Expression), Parentheses(Expression), - Define(Expression), + Assignment(Expression), Lambda(Expression), PrefixOperation(Expression), InfixOperation(Expression), SymbolReference(Expression), FunctionCall(Expression), If(Expression), + ImportDeclaration(Declaration), FunctionDeclaration(Declaration); val parents: Set diff --git a/src/main/kotlin/gay/pizza/pork/ast/NodeVisitor.kt b/src/main/kotlin/gay/pizza/pork/ast/NodeVisitor.kt index c97179b..eb4d3fe 100644 --- a/src/main/kotlin/gay/pizza/pork/ast/NodeVisitor.kt +++ b/src/main/kotlin/gay/pizza/pork/ast/NodeVisitor.kt @@ -9,16 +9,17 @@ interface NodeVisitor { fun visitListLiteral(node: ListLiteral): T fun visitSymbol(node: Symbol): T fun visitFunctionCall(node: FunctionCall): T - fun visitDefine(node: Define): T + fun visitDefine(node: Assignment): T fun visitSymbolReference(node: SymbolReference): T fun visitLambda(node: Lambda): T fun visitParentheses(node: Parentheses): T fun visitPrefixOperation(node: PrefixOperation): T fun visitIf(node: If): T fun visitInfixOperation(node: InfixOperation): T - fun visitFunctionDeclaration(node: FunctionDeclaration): T + fun visitFunctionDeclaration(node: FunctionDefinition): T fun visitBlock(node: Block): T + fun visitImportDeclaration(node: ImportDeclaration): T fun visitCompilationUnit(node: CompilationUnit): T fun visitExpression(node: Expression): T = when (node) { @@ -27,7 +28,7 @@ interface NodeVisitor { is BooleanLiteral -> visitBooleanLiteral(node) is ListLiteral -> visitListLiteral(node) is FunctionCall -> visitFunctionCall(node) - is Define -> visitDefine(node) + is Assignment -> visitDefine(node) is SymbolReference -> visitSymbolReference(node) is Lambda -> visitLambda(node) is Parentheses -> visitParentheses(node) @@ -37,7 +38,11 @@ interface NodeVisitor { } fun visitDeclaration(node: Declaration): T = when (node) { - is FunctionDeclaration -> visitFunctionDeclaration(node) + is ImportDeclaration -> visitImportDeclaration(node) + } + + fun visitDefinition(node: Definition): T = when (node) { + is FunctionDefinition -> visitFunctionDeclaration(node) } fun visit(node: Node): T = when (node) { @@ -46,6 +51,7 @@ interface NodeVisitor { is CompilationUnit -> visitCompilationUnit(node) is Block -> visitBlock(node) is Declaration -> visitDeclaration(node) + is Definition -> visitDefinition(node) } fun visitNodes(vararg nodes: Node?): List = diff --git a/src/main/kotlin/gay/pizza/pork/ast/Printer.kt b/src/main/kotlin/gay/pizza/pork/ast/Printer.kt index d432197..72d4492 100644 --- a/src/main/kotlin/gay/pizza/pork/ast/Printer.kt +++ b/src/main/kotlin/gay/pizza/pork/ast/Printer.kt @@ -72,7 +72,7 @@ class Printer(buffer: StringBuilder) : NodeVisitor { append(")") } - override fun visitDefine(node: Define) { + override fun visitDefine(node: Assignment) { visit(node.symbol) append(" = ") visit(node.value) @@ -147,7 +147,7 @@ class Printer(buffer: StringBuilder) : NodeVisitor { visit(node.right) } - override fun visitFunctionDeclaration(node: FunctionDeclaration) { + override fun visitFunctionDeclaration(node: FunctionDefinition) { append("fn ") visit(node.symbol) append("(") @@ -175,10 +175,24 @@ class Printer(buffer: StringBuilder) : NodeVisitor { append("}") } + override fun visitImportDeclaration(node: ImportDeclaration) { + append("import ") + visit(node.path) + } + override fun visitCompilationUnit(node: CompilationUnit) { for (declaration in node.declarations) { visit(declaration) appendLine() } + + if (node.declarations.isNotEmpty()) { + appendLine() + } + + for (definition in node.definitions) { + visit(definition) + appendLine() + } } } diff --git a/src/main/kotlin/gay/pizza/pork/ast/nodes/Define.kt b/src/main/kotlin/gay/pizza/pork/ast/nodes/Assignment.kt similarity index 75% rename from src/main/kotlin/gay/pizza/pork/ast/nodes/Define.kt rename to src/main/kotlin/gay/pizza/pork/ast/nodes/Assignment.kt index 334af6b..2ed8adc 100644 --- a/src/main/kotlin/gay/pizza/pork/ast/nodes/Define.kt +++ b/src/main/kotlin/gay/pizza/pork/ast/nodes/Assignment.kt @@ -6,15 +6,15 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -@SerialName("define") -class Define(val symbol: Symbol, val value: Expression) : Expression() { - override val type: NodeType = NodeType.Define +@SerialName("assignment") +class Assignment(val symbol: Symbol, val value: Expression) : Expression() { + override val type: NodeType = NodeType.Assignment override fun visitChildren(visitor: NodeVisitor): List = visitor.visitNodes(symbol, value) override fun equals(other: Any?): Boolean { - if (other !is Define) return false + if (other !is Assignment) return false return other.symbol == symbol && other.value == value } diff --git a/src/main/kotlin/gay/pizza/pork/ast/nodes/CompilationUnit.kt b/src/main/kotlin/gay/pizza/pork/ast/nodes/CompilationUnit.kt index 077d605..5903d5a 100644 --- a/src/main/kotlin/gay/pizza/pork/ast/nodes/CompilationUnit.kt +++ b/src/main/kotlin/gay/pizza/pork/ast/nodes/CompilationUnit.kt @@ -7,7 +7,7 @@ import kotlinx.serialization.Serializable @Serializable @SerialName("compilationUnit") -class CompilationUnit(val declarations: List) : Node() { +class CompilationUnit(val declarations: List, val definitions: List) : Node() { override val type: NodeType = NodeType.CompilationUnit override fun visitChildren(visitor: NodeVisitor): List = diff --git a/src/main/kotlin/gay/pizza/pork/ast/nodes/Definition.kt b/src/main/kotlin/gay/pizza/pork/ast/nodes/Definition.kt new file mode 100644 index 0000000..2a49069 --- /dev/null +++ b/src/main/kotlin/gay/pizza/pork/ast/nodes/Definition.kt @@ -0,0 +1,6 @@ +package gay.pizza.pork.ast.nodes + +import kotlinx.serialization.Serializable + +@Serializable +sealed class Definition : Node() diff --git a/src/main/kotlin/gay/pizza/pork/ast/nodes/FunctionDeclaration.kt b/src/main/kotlin/gay/pizza/pork/ast/nodes/FunctionDefinition.kt similarity index 74% rename from src/main/kotlin/gay/pizza/pork/ast/nodes/FunctionDeclaration.kt rename to src/main/kotlin/gay/pizza/pork/ast/nodes/FunctionDefinition.kt index 90dab09..1f9b4da 100644 --- a/src/main/kotlin/gay/pizza/pork/ast/nodes/FunctionDeclaration.kt +++ b/src/main/kotlin/gay/pizza/pork/ast/nodes/FunctionDefinition.kt @@ -6,23 +6,23 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -@SerialName("functionDeclaration") -class FunctionDeclaration(val symbol: Symbol, val arguments: List, val block: Block) : Declaration() { +@SerialName("functionDefinition") +class FunctionDefinition(val symbol: Symbol, val arguments: List, val block: Block) : Definition() { override val type: NodeType = NodeType.FunctionDeclaration override fun visitChildren(visitor: NodeVisitor): List = visitor.visitNodes(symbol) override fun equals(other: Any?): Boolean { - if (other !is FunctionDeclaration) return false + if (other !is FunctionDefinition) return false return other.symbol == symbol && other.arguments == arguments && other.block == block } override fun hashCode(): Int { var result = symbol.hashCode() - result = 31 * result + symbol.hashCode() result = 31 * result + arguments.hashCode() result = 31 * result + block.hashCode() + result = 31 * result + type.hashCode() return result } } diff --git a/src/main/kotlin/gay/pizza/pork/ast/nodes/ImportDeclaration.kt b/src/main/kotlin/gay/pizza/pork/ast/nodes/ImportDeclaration.kt new file mode 100644 index 0000000..f15bccb --- /dev/null +++ b/src/main/kotlin/gay/pizza/pork/ast/nodes/ImportDeclaration.kt @@ -0,0 +1,26 @@ +package gay.pizza.pork.ast.nodes + +import gay.pizza.pork.ast.NodeType +import gay.pizza.pork.ast.NodeVisitor +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +@SerialName("importDeclaration") +class ImportDeclaration(val path: StringLiteral) : Declaration() { + override val type: NodeType = NodeType.FunctionDeclaration + + override fun visitChildren(visitor: NodeVisitor): List = + visitor.visitNodes(path) + + override fun equals(other: Any?): Boolean { + if (other !is ImportDeclaration) return false + return other.path == path + } + + override fun hashCode(): Int { + var result = path.hashCode() + result = 31 * result + type.hashCode() + return result + } +} diff --git a/src/main/kotlin/gay/pizza/pork/eval/Evaluator.kt b/src/main/kotlin/gay/pizza/pork/eval/Evaluator.kt index 49f5eae..c17ad6e 100644 --- a/src/main/kotlin/gay/pizza/pork/eval/Evaluator.kt +++ b/src/main/kotlin/gay/pizza/pork/eval/Evaluator.kt @@ -3,7 +3,7 @@ package gay.pizza.pork.eval import gay.pizza.pork.ast.NodeVisitor import gay.pizza.pork.ast.nodes.* -class Evaluator(root: Scope) : NodeVisitor { +class Evaluator(root: Scope, val importLoader: ImportLoader) : NodeVisitor { private var currentScope: Scope = root override fun visitIntLiteral(node: IntLiteral): Any = node.value @@ -18,7 +18,7 @@ class Evaluator(root: Scope) : NodeVisitor { return currentScope.call(node.symbol.id, Arguments(arguments)) } - override fun visitDefine(node: Define): Any { + override fun visitDefine(node: Assignment): Any { val value = visit(node.value) currentScope.define(node.symbol.id, value) return value @@ -102,7 +102,7 @@ class Evaluator(root: Scope) : NodeVisitor { } } - override fun visitFunctionDeclaration(node: FunctionDeclaration): Any { + override fun visitFunctionDeclaration(node: FunctionDefinition): Any { val blockFunction = visitBlock(node.block) as BlockFunction val function = CallableFunction { arguments -> currentScope = currentScope.fork() @@ -127,10 +127,21 @@ class Evaluator(root: Scope) : NodeVisitor { 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 } } diff --git a/src/main/kotlin/gay/pizza/pork/eval/ImportLoader.kt b/src/main/kotlin/gay/pizza/pork/eval/ImportLoader.kt new file mode 100644 index 0000000..08f7d59 --- /dev/null +++ b/src/main/kotlin/gay/pizza/pork/eval/ImportLoader.kt @@ -0,0 +1,7 @@ +package gay.pizza.pork.eval + +import gay.pizza.pork.ast.nodes.CompilationUnit + +interface ImportLoader { + fun load(path: String): CompilationUnit +} diff --git a/src/main/kotlin/gay/pizza/pork/eval/NullImportLoader.kt b/src/main/kotlin/gay/pizza/pork/eval/NullImportLoader.kt new file mode 100644 index 0000000..48c02b8 --- /dev/null +++ b/src/main/kotlin/gay/pizza/pork/eval/NullImportLoader.kt @@ -0,0 +1,9 @@ +package gay.pizza.pork.eval + +import gay.pizza.pork.ast.nodes.CompilationUnit + +object NullImportLoader : ImportLoader { + override fun load(path: String): CompilationUnit { + throw RuntimeException("NullImportLoader cannot import compilation units.") + } +} diff --git a/src/main/kotlin/gay/pizza/pork/frontend/FileFrontend.kt b/src/main/kotlin/gay/pizza/pork/frontend/FileFrontend.kt index d3ecaac..9f831a2 100644 --- a/src/main/kotlin/gay/pizza/pork/frontend/FileFrontend.kt +++ b/src/main/kotlin/gay/pizza/pork/frontend/FileFrontend.kt @@ -7,4 +7,6 @@ import kotlin.io.path.readText class FileFrontend(val path: Path) : Frontend() { override fun createCharSource(): CharSource = StringCharSource(path.readText()) + override fun resolveImportSource(path: String): CharSource = + StringCharSource(this.path.parent.resolve(path).readText()) } diff --git a/src/main/kotlin/gay/pizza/pork/frontend/Frontend.kt b/src/main/kotlin/gay/pizza/pork/frontend/Frontend.kt index acdfd1d..29c10da 100644 --- a/src/main/kotlin/gay/pizza/pork/frontend/Frontend.kt +++ b/src/main/kotlin/gay/pizza/pork/frontend/Frontend.kt @@ -4,11 +4,13 @@ import gay.pizza.pork.ast.NodeVisitor import gay.pizza.pork.ast.Printer import gay.pizza.pork.ast.nodes.CompilationUnit import gay.pizza.pork.eval.Evaluator +import gay.pizza.pork.eval.ImportLoader import gay.pizza.pork.eval.Scope import gay.pizza.pork.parse.* abstract class Frontend { abstract fun createCharSource(): CharSource + abstract fun resolveImportSource(path: String): CharSource fun tokenize(): TokenStream = Tokenizer(createCharSource()).tokenize() @@ -20,9 +22,16 @@ abstract class Frontend { Highlighter(scheme).highlight(tokenize()) fun evaluate(scope: Scope = Scope()): Any = - visit(Evaluator(scope)) + visit(Evaluator(scope, FrontendImportLoader(this))) fun reprint(): String = buildString { visit(Printer(this)) } fun visit(visitor: NodeVisitor): T = visitor.visit(parse()) + + private class FrontendImportLoader(val frontend: Frontend) : ImportLoader { + override fun load(path: String): CompilationUnit { + val tokenStream = Tokenizer(frontend.resolveImportSource(path)).tokenize() + return Parser(TokenStreamSource(tokenStream), DiscardNodeAttribution).readCompilationUnit() + } + } } diff --git a/src/main/kotlin/gay/pizza/pork/parse/Parser.kt b/src/main/kotlin/gay/pizza/pork/parse/Parser.kt index 1238198..4a4c61b 100644 --- a/src/main/kotlin/gay/pizza/pork/parse/Parser.kt +++ b/src/main/kotlin/gay/pizza/pork/parse/Parser.kt @@ -45,7 +45,7 @@ class Parser(source: PeekableSource, val attribution: NodeAttribution) { expect(TokenType.RightParentheses) FunctionCall(symbol, arguments) } else if (next(TokenType.Equals)) { - Define(symbol, readExpression()) + Assignment(symbol, readExpression()) } else { SymbolReference(symbol) } @@ -169,19 +169,44 @@ class Parser(source: PeekableSource, val attribution: NodeAttribution) { Block(items) } - private fun readFunctionDeclaration(): FunctionDeclaration = within { + private fun readImportDeclaration(): ImportDeclaration = within { + expect(TokenType.Import) + ImportDeclaration(readStringLiteral()) + } + + private fun readFunctionDeclaration(): FunctionDefinition = within { expect(TokenType.Func) val name = readSymbolRaw() expect(TokenType.LeftParentheses) val arguments = collect(TokenType.RightParentheses, TokenType.Comma) { readSymbolRaw() } expect(TokenType.RightParentheses) - FunctionDeclaration(name, arguments, readBlock()) + FunctionDefinition(name, arguments, readBlock()) + } + + private fun maybeReadDefinition(): Definition? { + val token = peek() + return when (token.type) { + TokenType.Func -> readFunctionDeclaration() + else -> null + } + } + + private fun readDefinition(): Definition { + val definition = maybeReadDefinition() + if (definition != null) { + return definition + } + val token = peek() + throw RuntimeException( + "Failed to parse token: ${token.type} '${token.text}' as" + + " definition (index ${unsanitizedSource.currentIndex})" + ) } fun readDeclaration(): Declaration { val token = peek() return when (token.type) { - TokenType.Func -> readFunctionDeclaration() + TokenType.Import -> readImportDeclaration() else -> throw RuntimeException( "Failed to parse token: ${token.type} '${token.text}' as" + " declaration (index ${unsanitizedSource.currentIndex})" @@ -201,9 +226,25 @@ class Parser(source: PeekableSource, val attribution: NodeAttribution) { } fun readCompilationUnit(): CompilationUnit = within { - val declarations = collect(TokenType.EndOfFile) { readDeclaration() } - expect(TokenType.EndOfFile) - CompilationUnit(declarations) + val declarations = mutableListOf() + val definitions = mutableListOf() + var declarationAccepted = true + + while (!peek(TokenType.EndOfFile)) { + if (declarationAccepted) { + val definition = maybeReadDefinition() + if (definition != null) { + declarationAccepted = false + definitions.add(definition) + continue + } + declarations.add(readDeclaration()) + } else { + definitions.add(readDefinition()) + } + } + + CompilationUnit(declarations, definitions) } private fun collect( diff --git a/src/main/kotlin/gay/pizza/pork/parse/TokenType.kt b/src/main/kotlin/gay/pizza/pork/parse/TokenType.kt index c2177ee..291f8ec 100644 --- a/src/main/kotlin/gay/pizza/pork/parse/TokenType.kt +++ b/src/main/kotlin/gay/pizza/pork/parse/TokenType.kt @@ -28,6 +28,7 @@ enum class TokenType(vararg properties: TokenTypeProperty) { If(Keyword("if"), KeywordFamily), Then(Keyword("then"), KeywordFamily), Else(Keyword("else"), KeywordFamily), + Import(Keyword("import"), KeywordFamily), Func(Keyword("func"), KeywordFamily), Whitespace(CharConsumer { it == ' ' || it == '\r' || it == '\n' || it == '\t' }), BlockComment(CommentFamily),