Declaration based compilation units.

This commit is contained in:
Alex Zenla 2023-09-02 20:22:08 -07:00
parent 04c78c35e0
commit b1f9e02253
Signed by: alex
GPG Key ID: C0780728420EBFE5
26 changed files with 254 additions and 305 deletions

View File

@ -6,7 +6,7 @@ Very WIP. Like VERY.
```pork
/* fibonacci sequence */
fib = { n in
fn fib(n) {
if n == 0
then 0
else if n == 1
@ -14,8 +14,10 @@ fib = { n in
else fib(n - 1) + fib(n - 2)
}
result = fib(20)
println(result)
fn main() {
result = fib(20)
println(result)
}
```
## Usage

View File

@ -1,17 +0,0 @@
/* fibonacci sequence */
/**
* fib(n): calculate the fibonacci sequence.
* @input n the number to calculate fibonacci for
* @result the value of the fibonacci sequence for the number
*/
fib = { n in
if n == 0 // if n is zero, return zero
then 0
else if n == 1 // if n is one, return one
then 1
else fib(n - 1) + fib(n - 2)
}
// result of fib(20)
result = fib(20)
println(result)

View File

@ -1,5 +1,5 @@
/* fibonacci sequence */
fib = { n in
fn fib(n) {
if n == 0
then 0
else if n == 1
@ -7,5 +7,7 @@ fib = { n in
else fib(n - 1) + fib(n - 2)
}
result = fib(20)
println(result)
fn main() {
result = fib(20)
println(result)
}

View File

@ -1,53 +1,55 @@
three = 3
two = 2
fn main() {
three = 3
two = 2
calculateSimple = { in
(50 + three) * two
calculateSimple = { in
(50 + three) * two
}
calculateComplex = { in
three + two + 50
}
multiply = { a, b in
a * b
}
// calculates the result
calculateSimpleResult = calculateSimple()
calculateComplexResult = calculateComplex()
multiplyResult = multiply(50, 50)
list = [10, 20, 30]
trueValue = true
falseValue = false
invert = { value in
!value
}
notEqual = { a, b in
a != b
}
equal = { a, b in
a == b
}
results = [
calculateSimpleResult,
calculateComplexResult,
multiplyResult,
list,
trueValue,
falseValue,
invert(true),
invert(false),
equal(5, 5),
equal(5, 6),
notEqual(5, 5),
notEqual(5, 6)
]
println("results:")
println(results)
}
calculateComplex = { in
three + two + 50
}
multiply = { a, b in
a * b
}
// calculates the result
calculateSimpleResult = calculateSimple()
calculateComplexResult = calculateComplex()
multiplyResult = multiply(50, 50)
list = [10, 20, 30]
trueValue = true
falseValue = false
invert = { value in
!value
}
notEqual = { a, b in
a != b
}
equal = { a, b in
a == b
}
results = [
calculateSimpleResult,
calculateComplexResult,
multiplyResult,
list,
trueValue,
falseValue,
invert(true),
invert(false),
equal(5, 5),
equal(5, 6),
notEqual(5, 5),
notEqual(5, 6)
]
println("results:")
println(results)

View File

@ -16,7 +16,10 @@ class NodeCoalescer(val handler: (Node) -> Unit) : NodeVisitor<Unit> {
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 visitProgram(node: Program): Unit = handle(node)
override fun visitFunctionDeclaration(node: FunctionDeclaration): Unit = handle(node)
override fun visitBlock(node: Block): Unit = handle(node)
override fun visitCompilationUnit(node: CompilationUnit): Unit = handle(node)
private fun handle(node: Node) {
handler(node)

View File

@ -4,7 +4,9 @@ enum class NodeType(val parent: NodeType? = null) {
Node,
Symbol(Node),
Expression(Node),
Program(Node),
Declaration(Node),
Block(Node),
CompilationUnit(Node),
IntLiteral(Expression),
BooleanLiteral(Expression),
ListLiteral(Expression),
@ -16,7 +18,8 @@ enum class NodeType(val parent: NodeType? = null) {
InfixOperation(Expression),
SymbolReference(Expression),
FunctionCall(Expression),
If(Expression);
If(Expression),
FunctionDeclaration(Declaration);
val parents: Set<NodeType>

View File

@ -16,7 +16,10 @@ interface NodeVisitor<T> {
fun visitPrefixOperation(node: PrefixOperation): T
fun visitIf(node: If): T
fun visitInfixOperation(node: InfixOperation): T
fun visitProgram(node: Program): T
fun visitFunctionDeclaration(node: FunctionDeclaration): T
fun visitBlock(node: Block): T
fun visitCompilationUnit(node: CompilationUnit): T
fun visitExpression(node: Expression): T = when (node) {
is IntLiteral -> visitIntLiteral(node)
@ -33,10 +36,16 @@ interface NodeVisitor<T> {
is InfixOperation -> visitInfixOperation(node)
}
fun visitDeclaration(node: Declaration): T = when (node) {
is FunctionDeclaration -> visitFunctionDeclaration(node)
}
fun visit(node: Node): T = when (node) {
is Symbol -> visitSymbol(node)
is Expression -> visitExpression(node)
is Program -> visitProgram(node)
is CompilationUnit -> visitCompilationUnit(node)
is Block -> visitBlock(node)
is Declaration -> visitDeclaration(node)
}
fun visitNodes(vararg nodes: Node?): List<T> =

View File

@ -82,7 +82,6 @@ class Printer(buffer: StringBuilder) : NodeVisitor<Unit> {
visit(node.symbol)
}
override fun visitLambda(node: Lambda) {
append("{")
if (node.arguments.isNotEmpty()) {
@ -148,10 +147,37 @@ class Printer(buffer: StringBuilder) : NodeVisitor<Unit> {
visit(node.right)
}
override fun visitProgram(node: Program) {
for (expression in node.expressions) {
out.emitIndent()
visit(expression)
override fun visitFunctionDeclaration(node: FunctionDeclaration) {
append("fn ")
visit(node.symbol)
append("(")
for ((index, argument) in node.arguments.withIndex()) {
visit(argument)
if (index + 1 != node.arguments.size) {
append(", ")
}
}
append(") ")
visit(node.block)
}
override fun visitBlock(node: Block) {
append("{")
if (node.expressions.isNotEmpty()) {
out.increaseIndent()
for (expression in node.expressions) {
appendLine()
visit(expression)
}
out.decreaseIndent()
appendLine()
}
append("}")
}
override fun visitCompilationUnit(node: CompilationUnit) {
for (declaration in node.declarations) {
visit(declaration)
appendLine()
}
}

View File

@ -6,15 +6,15 @@ import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
@SerialName("program")
class Program(val expressions: List<Expression>) : Node() {
override val type: NodeType = NodeType.Program
@SerialName("block")
class Block(val expressions: List<Expression>) : Node() {
override val type: NodeType = NodeType.Block
override fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> =
visitor.visitAll(expressions)
override fun equals(other: Any?): Boolean {
if (other !is Program) return false
if (other !is Block) return false
return other.expressions == expressions
}

View File

@ -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("compilationUnit")
class CompilationUnit(val declarations: List<Declaration>) : Node() {
override val type: NodeType = NodeType.CompilationUnit
override fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> =
visitor.visitAll(declarations)
override fun equals(other: Any?): Boolean {
if (other !is CompilationUnit) return false
return other.declarations == declarations
}
override fun hashCode(): Int {
var result = declarations.hashCode()
result = 31 * result + type.hashCode()
return result
}
}

View File

@ -0,0 +1,6 @@
package gay.pizza.pork.ast.nodes
import kotlinx.serialization.Serializable
@Serializable
sealed class Declaration : Node()

View File

@ -0,0 +1,28 @@
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("functionDeclaration")
class FunctionDeclaration(val symbol: Symbol, val arguments: List<Symbol>, val block: Block) : Declaration() {
override val type: NodeType = NodeType.FunctionDeclaration
override fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> =
visitor.visitNodes(symbol)
override fun equals(other: Any?): Boolean {
if (other !is FunctionDeclaration) 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()
return result
}
}

View File

@ -13,7 +13,7 @@ class AttributeCommand : CliktCommand(help = "Attribute AST", name = "attribute"
override fun run() {
val frontend = FileFrontend(path)
val attribution = TokenNodeAttribution()
val program = frontend.parse(attribution)
val compilationUnit = frontend.parse(attribution)
val coalescer = NodeCoalescer { node ->
val tokens = attribution.assembleTokens(node)
@ -22,6 +22,6 @@ class AttributeCommand : CliktCommand(help = "Attribute AST", name = "attribute"
println("token $token")
}
}
coalescer.visit(program)
coalescer.visit(compilationUnit)
}
}

View File

@ -1,16 +0,0 @@
package gay.pizza.pork.cli
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.types.path
import gay.pizza.pork.compiler.DartCompiler
import gay.pizza.pork.frontend.FileFrontend
class GenerateDartCommand : CliktCommand(help = "Generate Dart Code", name = "generate-dart") {
val path by argument("file").path(mustExist = true, canBeDir = false)
override fun run() {
val frontend = FileFrontend(path)
println(frontend.visit(DartCompiler()))
}
}

View File

@ -1,16 +0,0 @@
package gay.pizza.pork.cli
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.types.path
import gay.pizza.pork.compiler.KotlinCompiler
import gay.pizza.pork.frontend.FileFrontend
class GenerateKotlinCommand : CliktCommand(help = "Generate Kotlin Code", name = "generate-kotlin") {
val path by argument("file").path(mustExist = true, canBeDir = false)
override fun run() {
val frontend = FileFrontend(path)
println(frontend.visit(KotlinCompiler()))
}
}

View File

@ -5,7 +5,7 @@ import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.types.path
import gay.pizza.pork.frontend.FileFrontend
class ReprintCommand : CliktCommand(help = "Reprint Parsed Program", name = "reprint") {
class ReprintCommand : CliktCommand(help = "Reprint Parsed Compilation Unit", name = "reprint") {
val path by argument("file").path(mustExist = true, canBeDir = false)
override fun run() {

View File

@ -14,9 +14,7 @@ class RootCommand : CliktCommand(
TokenizeCommand(),
ReprintCommand(),
AstCommand(),
AttributeCommand(),
GenerateKotlinCommand(),
GenerateDartCommand()
AttributeCommand()
)
}

View File

@ -3,6 +3,7 @@ package gay.pizza.pork.cli
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument
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.Scope
import gay.pizza.pork.frontend.FileFrontend
@ -19,5 +20,6 @@ class RunCommand : CliktCommand(help = "Run Program", name = "run") {
}
})
frontend.evaluate(scope)
scope.call("main", Arguments(emptyList()))
}
}

View File

@ -5,7 +5,7 @@ import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.types.path
import gay.pizza.pork.frontend.FileFrontend
class TokenizeCommand : CliktCommand(help = "Tokenize Program", name = "tokenize") {
class TokenizeCommand : CliktCommand(help = "Tokenize Compilation Unit", name = "tokenize") {
val path by argument("file").path(mustExist = true, canBeDir = false)
override fun run() {

View File

@ -1,90 +0,0 @@
package gay.pizza.pork.compiler
import gay.pizza.pork.ast.NodeVisitor
import gay.pizza.pork.ast.nodes.*
import gay.pizza.pork.util.StringEscape
class DartCompiler : NodeVisitor<String> {
override fun visitIntLiteral(node: IntLiteral): String =
node.value.toString()
override fun visitStringLiteral(node: StringLiteral): String =
"\"" + StringEscape.escape(node.text) + "\""
override fun visitBooleanLiteral(node: BooleanLiteral): String =
node.value.toString()
override fun visitListLiteral(node: ListLiteral): String = buildString {
append("[")
for ((index, item) in node.items.withIndex()) {
appendLine()
append(visit(item))
if (index + 1 != node.items.size) {
append(",")
}
}
append("]")
}
override fun visitSymbol(node: Symbol): String =
node.id
override fun visitFunctionCall(node: FunctionCall): String =
"${visit(node.symbol)}(${node.arguments.joinToString(", ") { visit(it) }})"
override fun visitDefine(node: Define): String =
"final ${visit(node.symbol)} = ${visit(node.value)};"
override fun visitSymbolReference(node: SymbolReference): String =
visit(node.symbol)
override fun visitLambda(node: Lambda): String = buildString {
append("(${node.arguments.joinToString(", ") { visit(it) }}) {")
appendLine()
for ((index, expression) in node.expressions.withIndex()) {
val code = visit(expression)
if (index == node.expressions.size - 1) {
append("return ");
}
append(code)
append(";")
}
appendLine()
append("}")
}
override fun visitParentheses(node: Parentheses): String =
"(${visit(node.expression)})"
override fun visitPrefixOperation(node: PrefixOperation): String =
"${node.op.token}${visit(node.expression)}"
override fun visitIf(node: If): String = buildString {
append("if (")
append(visit(node.condition))
append(") {")
append(visit(node.thenExpression))
append("}")
if (node.elseExpression != null) {
append(" else {")
append(visit(node.elseExpression))
append("}")
}
}
override fun visitInfixOperation(node: InfixOperation): String =
"${visit(node.left)} ${node.op.token} ${visit(node.right)}"
override fun visitProgram(node: Program): String = buildString {
appendLine("void main() {")
for (item in node.expressions) {
append(visit(item))
if (!endsWith(";")) {
append(";")
}
append(";")
appendLine()
}
appendLine("}")
}
}

View File

@ -1,78 +0,0 @@
package gay.pizza.pork.compiler
import gay.pizza.pork.ast.*
import gay.pizza.pork.ast.nodes.*
import gay.pizza.pork.util.StringEscape
class KotlinCompiler : NodeVisitor<String> {
override fun visitIntLiteral(node: IntLiteral): String =
node.value.toString()
override fun visitStringLiteral(node: StringLiteral): String =
"\"" + StringEscape.escape(node.text) + "\""
override fun visitBooleanLiteral(node: BooleanLiteral): String =
node.value.toString()
override fun visitListLiteral(node: ListLiteral): String = buildString {
append("listOf(")
for ((index, item) in node.items.withIndex()) {
appendLine()
append(visit(item))
if (index + 1 != node.items.size) {
append(",")
}
}
append(")")
}
override fun visitSymbol(node: Symbol): String =
node.id
override fun visitFunctionCall(node: FunctionCall): String =
"${visit(node.symbol)}(${node.arguments.joinToString(", ") { visit(it) }})"
override fun visitDefine(node: Define): String =
"val ${visit(node.symbol)} = ${visit(node.value)}"
override fun visitSymbolReference(node: SymbolReference): String =
visit(node.symbol)
override fun visitLambda(node: Lambda): String = buildString {
append("{ ${node.arguments.joinToString(", ") { visit(it) }} ->")
appendLine()
append(visitAll(node.expressions).joinToString("\n"))
appendLine()
append("}")
}
override fun visitParentheses(node: Parentheses): String =
"(${visit(node.expression)})"
override fun visitPrefixOperation(node: PrefixOperation): String =
"${node.op.token}${visit(node.expression)}"
override fun visitIf(node: If): String = buildString {
append("if (")
append(visit(node.condition))
append(") {")
append(visit(node.thenExpression))
append("}")
if (node.elseExpression != null) {
append(" else {")
append(visit(node.elseExpression))
append("}")
}
}
override fun visitInfixOperation(node: InfixOperation): String =
"${visit(node.left)} ${node.op.token} ${visit(node.right)}"
override fun visitProgram(node: Program): String = buildString {
appendLine("fun main() {")
for (item in node.expressions) {
appendLine(visit(item))
}
appendLine("}")
}
}

View File

@ -0,0 +1,5 @@
package gay.pizza.pork.eval
fun interface BlockFunction {
fun call(): Any
}

View File

@ -1,6 +1,6 @@
package gay.pizza.pork.eval
import gay.pizza.pork.ast.*
import gay.pizza.pork.ast.NodeVisitor
import gay.pizza.pork.ast.nodes.*
class Evaluator(root: Scope) : NodeVisitor<Any> {
@ -102,11 +102,35 @@ class Evaluator(root: Scope) : NodeVisitor<Any> {
}
}
override fun visitProgram(node: Program): Any {
override fun visitFunctionDeclaration(node: FunctionDeclaration): 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)
}
return value ?: None
value ?: None
}
override fun visitCompilationUnit(node: CompilationUnit): Any {
for (declaration in node.declarations) {
visit(declaration)
}
return None
}
}

View File

@ -2,7 +2,7 @@ package gay.pizza.pork.frontend
import gay.pizza.pork.ast.NodeVisitor
import gay.pizza.pork.ast.Printer
import gay.pizza.pork.ast.nodes.Program
import gay.pizza.pork.ast.nodes.CompilationUnit
import gay.pizza.pork.eval.Evaluator
import gay.pizza.pork.eval.Scope
import gay.pizza.pork.parse.*
@ -13,8 +13,8 @@ abstract class Frontend {
fun tokenize(): TokenStream =
Tokenizer(createCharSource()).tokenize()
fun parse(attribution: NodeAttribution = DiscardNodeAttribution): Program =
Parser(TokenStreamSource(tokenize()), attribution).readProgram()
fun parse(attribution: NodeAttribution = DiscardNodeAttribution): CompilationUnit =
Parser(TokenStreamSource(tokenize()), attribution).readCompilationUnit()
fun highlight(scheme: HighlightScheme): List<Highlight> =
Highlighter(scheme).highlight(tokenize())

View File

@ -160,6 +160,35 @@ class Parser(source: PeekableSource<Token>, val attribution: NodeAttribution) {
} else expression
}
private fun readBlock(): Block = within {
expect(TokenType.LeftCurly)
val items = collect(TokenType.RightCurly) {
readExpression()
}
expect(TokenType.RightCurly)
Block(items)
}
private fun readFunctionDeclaration(): FunctionDeclaration = within {
expect(TokenType.Fn)
val name = readSymbolRaw()
expect(TokenType.LeftParentheses)
val arguments = collect(TokenType.RightParentheses, TokenType.Comma) { readSymbolRaw() }
expect(TokenType.RightParentheses)
FunctionDeclaration(name, arguments, readBlock())
}
fun readDeclaration(): Declaration {
val token = peek()
return when (token.type) {
TokenType.Fn -> readFunctionDeclaration()
else -> throw RuntimeException(
"Failed to parse token: ${token.type} '${token.text}' as" +
" declaration (index ${unsanitizedSource.currentIndex})"
)
}
}
private fun convertInfixOperator(token: Token): InfixOperator =
when (token.type) {
TokenType.Plus -> InfixOperator.Plus
@ -171,10 +200,10 @@ class Parser(source: PeekableSource<Token>, val attribution: NodeAttribution) {
else -> throw RuntimeException("Unknown Infix Operator")
}
fun readProgram(): Program = within {
val items = collect(TokenType.EndOfFile) { readExpression() }
fun readCompilationUnit(): CompilationUnit = within {
val declarations = collect(TokenType.EndOfFile) { readDeclaration() }
expect(TokenType.EndOfFile)
Program(items)
CompilationUnit(declarations)
}
private fun <T> collect(
@ -209,7 +238,7 @@ class Parser(source: PeekableSource<Token>, val attribution: NodeAttribution) {
val token = next()
if (!types.contains(token.type)) {
throw RuntimeException(
"Expected one of ${types.joinToString(", ")} " +
"Expected one of ${types.joinToString(", ")}" +
" but got type ${token.type} '${token.text}'"
)
}

View File

@ -28,6 +28,7 @@ enum class TokenType(vararg properties: TokenTypeProperty) {
If(Keyword("if"), KeywordFamily),
Then(Keyword("then"), KeywordFamily),
Else(Keyword("else"), KeywordFamily),
Fn(Keyword("fn"), KeywordFamily),
Whitespace(CharConsumer { it == ' ' || it == '\r' || it == '\n' || it == '\t' }),
BlockComment(CommentFamily),
LineComment(CommentFamily),