If Statement Support

This commit is contained in:
Alex Zenla 2023-08-19 20:40:31 -07:00
parent f1645d0924
commit cccea9c2ca
Signed by: alex
GPG Key ID: C0780728420EBFE5
25 changed files with 176 additions and 32 deletions

8
examples/fib.pork Normal file
View File

@ -0,0 +1,8 @@
fib = { n in
if n == 0
then 0
else if n == 1
then 1
else fib(n - 1) + fib(n - 2)
}
main = { in fib(20) }

View File

@ -1,14 +1,18 @@
main = { main = { in
three = 3 three = 3
two = 2 two = 2
calculateSimple = { calculateSimple = { in
(50 + three) * two (50 + three) * two
} }
calculateComplex = { calculateComplex = { in
three + two + 50 three + two + 50
} }
multiply = { a, b in
a * b
}
calculateSimpleResult = calculateSimple() calculateSimpleResult = calculateSimple()
calculateComplexResult = calculateComplex() calculateComplexResult = calculateComplex()
multiplyResult = multiply(50, 50)
list = [10, 20, 30] list = [10, 20, 30]
trueValue = true trueValue = true
@ -17,6 +21,7 @@ main = {
[ [
calculateSimpleResult, calculateSimpleResult,
calculateComplexResult, calculateComplexResult,
multiplyResult,
list, list,
trueValue, trueValue,
falseValue falseValue

View File

@ -4,5 +4,5 @@ class Define(val symbol: Symbol, val value: Expression) : Expression {
override val type: NodeType = NodeType.Define override val type: NodeType = NodeType.Define
override fun <T> visitChildren(visitor: Visitor<T>): List<T> = override fun <T> visitChildren(visitor: Visitor<T>): List<T> =
listOf(visitor.visit(symbol), visitor.visit(value)) visitor.visitNodes(symbol, value)
} }

View File

@ -1,8 +1,8 @@
package gay.pizza.pork.ast package gay.pizza.pork.ast
class FunctionCall(val symbol: Symbol) : Expression { class FunctionCall(val symbol: Symbol, val arguments: List<Expression>) : Expression {
override val type: NodeType = NodeType.FunctionCall override val type: NodeType = NodeType.FunctionCall
override fun <T> visitChildren(visitor: Visitor<T>): List<T> = override fun <T> visitChildren(visitor: Visitor<T>): List<T> =
listOf(visitor.visit(symbol)) visitor.visitAll(listOf(symbol), arguments)
} }

View File

@ -0,0 +1,9 @@
package gay.pizza.pork.ast
class If(
val condition: Expression,
val thenExpression: Expression,
val elseExpression: Expression
) : Expression {
override val type: NodeType = NodeType.If
}

View File

@ -4,5 +4,5 @@ class InfixOperation(val left: Expression, val op: InfixOperator, val right: Exp
override val type: NodeType = NodeType.InfixOperation override val type: NodeType = NodeType.InfixOperation
override fun <T> visitChildren(visitor: Visitor<T>): List<T> = override fun <T> visitChildren(visitor: Visitor<T>): List<T> =
listOf(visitor.visit(left), visitor.visit(right)) visitor.visitNodes(left, right)
} }

View File

@ -4,5 +4,6 @@ enum class InfixOperator(val token: String) {
Plus("+"), Plus("+"),
Minus("-"), Minus("-"),
Multiply("*"), Multiply("*"),
Divide("/") Divide("/"),
Equals("==")
} }

View File

@ -1,10 +1,8 @@
package gay.pizza.pork.ast package gay.pizza.pork.ast
class Lambda(val expressions: List<Expression>) : Expression { class Lambda(val arguments: List<Symbol>, val expressions: List<Expression>) : Expression {
constructor(vararg expressions: Expression) : this(listOf(*expressions))
override val type: NodeType = NodeType.Lambda override val type: NodeType = NodeType.Lambda
override fun <T> visitChildren(visitor: Visitor<T>): List<T> = override fun <T> visitChildren(visitor: Visitor<T>): List<T> =
expressions.map { expression -> visitor.visit(expression) } visitor.visitAll(arguments, expressions)
} }

View File

@ -1,10 +1,8 @@
package gay.pizza.pork.ast package gay.pizza.pork.ast
class ListLiteral(val items: List<Expression>) : Expression { class ListLiteral(val items: List<Expression>) : Expression {
constructor(vararg items: Expression) : this(listOf(*items))
override val type: NodeType = NodeType.ListLiteral override val type: NodeType = NodeType.ListLiteral
override fun <T> visitChildren(visitor: Visitor<T>): List<T> = override fun <T> visitChildren(visitor: Visitor<T>): List<T> =
items.map { visitor.visit(it) } visitor.visitAll(items)
} }

View File

@ -15,7 +15,8 @@ enum class NodeType(val parent: NodeType? = null, vararg traits: NodeTypeTrait)
Lambda(Expression), Lambda(Expression),
InfixOperation(Expression), InfixOperation(Expression),
SymbolReference(Expression), SymbolReference(Expression),
FunctionCall(Expression); FunctionCall(Expression),
If(Expression);
val parents: Set<NodeType> val parents: Set<NodeType>

View File

@ -4,5 +4,5 @@ class Parentheses(val expression: Expression) : Expression {
override val type: NodeType = NodeType.Parentheses override val type: NodeType = NodeType.Parentheses
override fun <T> visitChildren(visitor: Visitor<T>): List<T> = override fun <T> visitChildren(visitor: Visitor<T>): List<T> =
listOf(visitor.visit(expression)) visitor.visitNodes(expression)
} }

View File

@ -25,19 +25,46 @@ class Printer(private val buffer: StringBuilder) : Visitor<Unit> {
override fun visitFunctionCall(node: FunctionCall) { override fun visitFunctionCall(node: FunctionCall) {
visit(node.symbol) visit(node.symbol)
append("()") append("(")
for ((index, argument) in node.arguments.withIndex()) {
visit(argument)
if (index + 1 != node.arguments.size) {
append(", ")
}
}
append(")")
} }
override fun visitReference(node: SymbolReference) { override fun visitReference(node: SymbolReference) {
visit(node.symbol) visit(node.symbol)
} }
override fun visitIf(node: If) {
append("if ")
visit(node.condition)
append(" then ")
visit(node.thenExpression)
append(" else ")
visit(node.elseExpression)
}
override fun visitSymbol(node: Symbol) { override fun visitSymbol(node: Symbol) {
append(node.id) append(node.id)
} }
override fun visitLambda(node: Lambda) { override fun visitLambda(node: Lambda) {
append("{") append("{")
if (node.arguments.isNotEmpty()) {
append(" ")
for ((index, argument) in node.arguments.withIndex()) {
visit(argument)
if (index + 1 != node.arguments.size) {
append(",")
}
append(" ")
}
}
append("in")
indent++ indent++
for (expression in node.expressions) { for (expression in node.expressions) {
appendLine() appendLine()

View File

@ -1,10 +1,8 @@
package gay.pizza.pork.ast package gay.pizza.pork.ast
class Program(val expressions: List<Expression>) : Node { class Program(val expressions: List<Expression>) : Node {
constructor(vararg expressions: Expression) : this(listOf(*expressions))
override val type: NodeType = NodeType.Program override val type: NodeType = NodeType.Program
override fun <T> visitChildren(visitor: Visitor<T>): List<T> = override fun <T> visitChildren(visitor: Visitor<T>): List<T> =
expressions.map { visitor.visit(it) } visitor.visitAll(expressions)
} }

View File

@ -4,5 +4,5 @@ class SymbolReference(val symbol: Symbol) : Expression {
override val type: NodeType = NodeType.SymbolReference override val type: NodeType = NodeType.SymbolReference
override fun <T> visitChildren(visitor: Visitor<T>): List<T> = override fun <T> visitChildren(visitor: Visitor<T>): List<T> =
listOf(visitor.visit(symbol)) visitor.visitNodes(symbol)
} }

View File

@ -4,6 +4,7 @@ interface Visitor<T> {
fun visitDefine(node: Define): T fun visitDefine(node: Define): T
fun visitFunctionCall(node: FunctionCall): T fun visitFunctionCall(node: FunctionCall): T
fun visitReference(node: SymbolReference): T fun visitReference(node: SymbolReference): T
fun visitIf(node: If): T
fun visitSymbol(node: Symbol): T fun visitSymbol(node: Symbol): T
fun visitLambda(node: Lambda): T fun visitLambda(node: Lambda): T
@ -26,6 +27,7 @@ interface Visitor<T> {
is Lambda -> visitLambda(node) is Lambda -> visitLambda(node)
is FunctionCall -> visitFunctionCall(node) is FunctionCall -> visitFunctionCall(node)
is SymbolReference -> visitReference(node) is SymbolReference -> visitReference(node)
is If -> visitIf(node)
else -> throw RuntimeException("Unknown Expression") else -> throw RuntimeException("Unknown Expression")
} }
@ -35,4 +37,10 @@ interface Visitor<T> {
is Program -> visitProgram(node) is Program -> visitProgram(node)
else -> throw RuntimeException("Unknown Node") else -> throw RuntimeException("Unknown Node")
} }
fun visitNodes(vararg nodes: Node): List<T> =
nodes.map { visit(it) }
fun visitAll(vararg nodeLists: List<Node>): List<T> =
nodeLists.asSequence().flatten().map { visit(it) }.toList()
} }

View File

@ -0,0 +1,7 @@
package gay.pizza.pork.eval
class Arguments(val values: List<Any>) {
companion object {
val Zero = Arguments(emptyList())
}
}

View File

@ -1,5 +1,5 @@
package gay.pizza.pork.eval package gay.pizza.pork.eval
fun interface CallableFunction { fun interface CallableFunction {
fun call(argument: Any): Any fun call(arguments: Arguments): Any
} }

View File

@ -11,18 +11,33 @@ class PorkEvaluator(root: Scope) : Visitor<Any> {
return value return value
} }
override fun visitFunctionCall(node: FunctionCall): Any = currentScope.call(node.symbol.id) override fun visitFunctionCall(node: FunctionCall): Any {
val arguments = node.arguments.map { visit(it) }
return currentScope.call(node.symbol.id, Arguments(arguments))
}
override fun visitReference(node: SymbolReference): Any = override fun visitReference(node: SymbolReference): Any =
currentScope.value(node.symbol.id) currentScope.value(node.symbol.id)
override fun visitIf(node: If): Any {
val condition = visit(node.condition)
return if (condition == true) {
visit(node.thenExpression)
} else {
visit(node.elseExpression)
}
}
override fun visitSymbol(node: Symbol): Any { override fun visitSymbol(node: Symbol): Any {
return Unit return Unit
} }
override fun visitLambda(node: Lambda): CallableFunction { override fun visitLambda(node: Lambda): CallableFunction {
return CallableFunction { _ -> return CallableFunction { arguments ->
currentScope = currentScope.fork() currentScope = currentScope.fork()
for ((index, argumentSymbol) in node.arguments.withIndex()) {
currentScope.define(argumentSymbol.id, arguments.values[index])
}
try { try {
var value: Any? = null var value: Any? = null
for (expression in node.expressions) { for (expression in node.expressions) {
@ -45,6 +60,13 @@ class PorkEvaluator(root: Scope) : Visitor<Any> {
val left = visit(node.left) val left = visit(node.left)
val right = visit(node.right) val right = visit(node.right)
when (node.op) {
InfixOperator.Equals -> {
return left == right
}
else -> {}
}
if (left !is Number || right !is Number) { if (left !is Number || right !is Number) {
throw RuntimeException("Failed to evaluate infix operation, bad types.") throw RuntimeException("Failed to evaluate infix operation, bad types.")
} }
@ -57,6 +79,7 @@ class PorkEvaluator(root: Scope) : Visitor<Any> {
InfixOperator.Minus -> leftInt - rightInt InfixOperator.Minus -> leftInt - rightInt
InfixOperator.Multiply -> leftInt * rightInt InfixOperator.Multiply -> leftInt * rightInt
InfixOperator.Divide -> leftInt / rightInt InfixOperator.Divide -> leftInt / rightInt
else -> throw RuntimeException("Unable to handle operation ${node.op}")
} }
} }

View File

@ -21,13 +21,12 @@ class Scope(val parent: Scope? = null) {
return value return value
} }
fun call(name: String, argument: Any = Unit): Any { fun call(name: String, arguments: Arguments): Any {
val value = value(name) val value = value(name)
if (value !is CallableFunction) { if (value !is CallableFunction) {
throw RuntimeException("$value is not callable.") throw RuntimeException("$value is not callable.")
} }
val function = value as CallableFunction return value.call(arguments)
return function.call(argument)
} }
fun fork(): Scope { fun fork(): Scope {

View File

@ -1,6 +1,7 @@
package gay.pizza.pork package gay.pizza.pork
import gay.pizza.pork.ast.* import gay.pizza.pork.ast.*
import gay.pizza.pork.eval.Arguments
import gay.pizza.pork.eval.Scope import gay.pizza.pork.eval.Scope
import gay.pizza.pork.eval.PorkEvaluator import gay.pizza.pork.eval.PorkEvaluator
import gay.pizza.pork.parse.* import gay.pizza.pork.parse.*
@ -12,7 +13,7 @@ fun main(args: Array<String>) {
val scope = Scope() val scope = Scope()
val evaluator = PorkEvaluator(scope) val evaluator = PorkEvaluator(scope)
evaluator.visit(ast) evaluator.visit(ast)
println("> ${scope.call("main")}") println("> ${scope.call("main", Arguments.Zero)}")
} }
val code = Path(args[0]).readText() val code = Path(args[0]).readText()

View File

@ -2,6 +2,7 @@ package gay.pizza.pork.parse
interface PeekableSource<T> { interface PeekableSource<T> {
val currentIndex: Int val currentIndex: Int
fun back()
fun next(): T fun next(): T
fun peek(): T fun peek(): T
} }

View File

@ -13,14 +13,29 @@ class PorkParser(val source: PeekableSource<Token>) {
return Symbol(token.text) return Symbol(token.text)
} }
private fun readIf(): If {
expect(TokenType.If)
val condition = readExpression()
expect(TokenType.Then)
val thenExpression = readExpression()
expect(TokenType.Else)
val elseExpression = readExpression()
return If(condition, thenExpression, elseExpression)
}
private fun readSymbolCases(): Expression { private fun readSymbolCases(): Expression {
val symbol = readSymbol() val symbol = readSymbol()
return if (peekType(TokenType.LeftParentheses)) { return if (peekType(TokenType.LeftParentheses)) {
expect(TokenType.LeftParentheses) expect(TokenType.LeftParentheses)
val arguments = collectExpressions(TokenType.RightParentheses, TokenType.Comma)
expect(TokenType.RightParentheses) expect(TokenType.RightParentheses)
FunctionCall(symbol) FunctionCall(symbol, arguments)
} else if (peekType(TokenType.Equals)) { } else if (peekType(TokenType.Equals)) {
expect(TokenType.Equals) expect(TokenType.Equals)
if (peekType(TokenType.Equals)) {
source.back()
return SymbolReference(symbol)
}
Define(symbol, readExpression()) Define(symbol, readExpression())
} else { } else {
SymbolReference(symbol) SymbolReference(symbol)
@ -29,9 +44,21 @@ class PorkParser(val source: PeekableSource<Token>) {
fun readLambda(): Lambda { fun readLambda(): Lambda {
expect(TokenType.LeftCurly) expect(TokenType.LeftCurly)
val arguments = mutableListOf<Symbol>()
while (!peekType(TokenType.In)) {
val symbol = readSymbol()
arguments.add(symbol)
if (peekType(TokenType.Comma)) {
expect(TokenType.Comma)
continue
} else {
break
}
}
expect(TokenType.In)
val items = collectExpressions(TokenType.RightCurly) val items = collectExpressions(TokenType.RightCurly)
expect(TokenType.RightCurly) expect(TokenType.RightCurly)
return Lambda(items) return Lambda(arguments, items)
} }
fun readExpression(): Expression { fun readExpression(): Expression {
@ -40,31 +67,42 @@ class PorkParser(val source: PeekableSource<Token>) {
TokenType.IntLiteral -> { TokenType.IntLiteral -> {
readIntLiteral() readIntLiteral()
} }
TokenType.LeftBracket -> { TokenType.LeftBracket -> {
readListLiteral() readListLiteral()
} }
TokenType.Symbol -> { TokenType.Symbol -> {
readSymbolCases() readSymbolCases()
} }
TokenType.LeftCurly -> { TokenType.LeftCurly -> {
readLambda() readLambda()
} }
TokenType.LeftParentheses -> { TokenType.LeftParentheses -> {
expect(TokenType.LeftParentheses) expect(TokenType.LeftParentheses)
val expression = readExpression() val expression = readExpression()
expect(TokenType.RightParentheses) expect(TokenType.RightParentheses)
Parentheses(expression) Parentheses(expression)
} }
TokenType.True -> { TokenType.True -> {
expect(TokenType.True) expect(TokenType.True)
return BooleanLiteral(true) return BooleanLiteral(true)
} }
TokenType.False -> { TokenType.False -> {
expect(TokenType.False) expect(TokenType.False)
return BooleanLiteral(false) return BooleanLiteral(false)
} }
TokenType.If -> {
return readIf()
}
else -> { else -> {
throw RuntimeException("Failed to parse token: ${token.type} '${token.text}' as expression.") throw RuntimeException("Failed to parse token: ${token.type} '${token.text}' as expression")
} }
} }
@ -73,6 +111,13 @@ class PorkParser(val source: PeekableSource<Token>) {
val infixOperator = convertInfixOperator(infixToken) val infixOperator = convertInfixOperator(infixToken)
return InfixOperation(expression, infixOperator, readExpression()) return InfixOperation(expression, infixOperator, readExpression())
} }
if (peekType(TokenType.Equals)) {
val twoWideInfix = source.next()
val secondToken = expect(twoWideInfix.type)
return InfixOperation(expression, convertWideInfixOperator(twoWideInfix, secondToken), readExpression())
}
return expression return expression
} }
@ -85,6 +130,12 @@ class PorkParser(val source: PeekableSource<Token>) {
else -> throw RuntimeException("Unknown Infix Operator") else -> throw RuntimeException("Unknown Infix Operator")
} }
private fun convertWideInfixOperator(firstToken: Token, secondToken: Token): InfixOperator =
when (firstToken.type to secondToken.type) {
TokenType.Equals to TokenType.Equals -> InfixOperator.Equals
else -> throw RuntimeException("Unknown Infix Operator")
}
fun readListLiteral(): ListLiteral { fun readListLiteral(): ListLiteral {
expect(TokenType.LeftBracket) expect(TokenType.LeftBracket)
val items = collectExpressions(TokenType.RightBracket, TokenType.Comma) val items = collectExpressions(TokenType.RightBracket, TokenType.Comma)

View File

@ -3,6 +3,9 @@ package gay.pizza.pork.parse
class StringCharSource(val input: String) : CharSource { class StringCharSource(val input: String) : CharSource {
private var index = 0 private var index = 0
override val currentIndex: Int = index override val currentIndex: Int = index
override fun back() {
index--
}
override fun next(): Char { override fun next(): Char {
if (index == input.length) { if (index == input.length) {

View File

@ -3,6 +3,9 @@ package gay.pizza.pork.parse
class TokenStreamSource(val stream: TokenStream) : TokenSource { class TokenStreamSource(val stream: TokenStream) : TokenSource {
private var index = 0 private var index = 0
override val currentIndex: Int = index override val currentIndex: Int = index
override fun back() {
index--
}
override fun next(): Token { override fun next(): Token {
if (index == stream.tokens.size) { if (index == stream.tokens.size) {

View File

@ -18,6 +18,9 @@ enum class TokenType(val char: Char? = null, val keyword: String? = null) {
False(keyword = "false"), False(keyword = "false"),
True(keyword = "true"), True(keyword = "true"),
In(keyword = "in"), In(keyword = "in"),
If(keyword = "if"),
Then(keyword = "then"),
Else(keyword = "else"),
EndOfFile; EndOfFile;
companion object { companion object {