mirror of
https://github.com/GayPizzaSpecifications/pork.git
synced 2025-08-02 12:50:55 +00:00
If Statement Support
This commit is contained in:
parent
f1645d0924
commit
cccea9c2ca
8
examples/fib.pork
Normal file
8
examples/fib.pork
Normal 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) }
|
@ -1,14 +1,18 @@
|
||||
main = {
|
||||
main = { in
|
||||
three = 3
|
||||
two = 2
|
||||
calculateSimple = {
|
||||
calculateSimple = { in
|
||||
(50 + three) * two
|
||||
}
|
||||
calculateComplex = {
|
||||
calculateComplex = { in
|
||||
three + two + 50
|
||||
}
|
||||
multiply = { a, b in
|
||||
a * b
|
||||
}
|
||||
calculateSimpleResult = calculateSimple()
|
||||
calculateComplexResult = calculateComplex()
|
||||
multiplyResult = multiply(50, 50)
|
||||
|
||||
list = [10, 20, 30]
|
||||
trueValue = true
|
||||
@ -17,6 +21,7 @@ main = {
|
||||
[
|
||||
calculateSimpleResult,
|
||||
calculateComplexResult,
|
||||
multiplyResult,
|
||||
list,
|
||||
trueValue,
|
||||
falseValue
|
||||
|
@ -4,5 +4,5 @@ class Define(val symbol: Symbol, val value: Expression) : Expression {
|
||||
override val type: NodeType = NodeType.Define
|
||||
|
||||
override fun <T> visitChildren(visitor: Visitor<T>): List<T> =
|
||||
listOf(visitor.visit(symbol), visitor.visit(value))
|
||||
visitor.visitNodes(symbol, value)
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
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 fun <T> visitChildren(visitor: Visitor<T>): List<T> =
|
||||
listOf(visitor.visit(symbol))
|
||||
visitor.visitAll(listOf(symbol), arguments)
|
||||
}
|
||||
|
9
src/main/kotlin/gay/pizza/pork/ast/If.kt
Normal file
9
src/main/kotlin/gay/pizza/pork/ast/If.kt
Normal 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
|
||||
}
|
@ -4,5 +4,5 @@ class InfixOperation(val left: Expression, val op: InfixOperator, val right: Exp
|
||||
override val type: NodeType = NodeType.InfixOperation
|
||||
|
||||
override fun <T> visitChildren(visitor: Visitor<T>): List<T> =
|
||||
listOf(visitor.visit(left), visitor.visit(right))
|
||||
visitor.visitNodes(left, right)
|
||||
}
|
||||
|
@ -4,5 +4,6 @@ enum class InfixOperator(val token: String) {
|
||||
Plus("+"),
|
||||
Minus("-"),
|
||||
Multiply("*"),
|
||||
Divide("/")
|
||||
Divide("/"),
|
||||
Equals("==")
|
||||
}
|
||||
|
@ -1,10 +1,8 @@
|
||||
package gay.pizza.pork.ast
|
||||
|
||||
class Lambda(val expressions: List<Expression>) : Expression {
|
||||
constructor(vararg expressions: Expression) : this(listOf(*expressions))
|
||||
|
||||
class Lambda(val arguments: List<Symbol>, val expressions: List<Expression>) : Expression {
|
||||
override val type: NodeType = NodeType.Lambda
|
||||
|
||||
override fun <T> visitChildren(visitor: Visitor<T>): List<T> =
|
||||
expressions.map { expression -> visitor.visit(expression) }
|
||||
visitor.visitAll(arguments, expressions)
|
||||
}
|
||||
|
@ -1,10 +1,8 @@
|
||||
package gay.pizza.pork.ast
|
||||
|
||||
class ListLiteral(val items: List<Expression>) : Expression {
|
||||
constructor(vararg items: Expression) : this(listOf(*items))
|
||||
|
||||
override val type: NodeType = NodeType.ListLiteral
|
||||
|
||||
override fun <T> visitChildren(visitor: Visitor<T>): List<T> =
|
||||
items.map { visitor.visit(it) }
|
||||
visitor.visitAll(items)
|
||||
}
|
||||
|
@ -15,7 +15,8 @@ enum class NodeType(val parent: NodeType? = null, vararg traits: NodeTypeTrait)
|
||||
Lambda(Expression),
|
||||
InfixOperation(Expression),
|
||||
SymbolReference(Expression),
|
||||
FunctionCall(Expression);
|
||||
FunctionCall(Expression),
|
||||
If(Expression);
|
||||
|
||||
val parents: Set<NodeType>
|
||||
|
||||
|
@ -4,5 +4,5 @@ class Parentheses(val expression: Expression) : Expression {
|
||||
override val type: NodeType = NodeType.Parentheses
|
||||
|
||||
override fun <T> visitChildren(visitor: Visitor<T>): List<T> =
|
||||
listOf(visitor.visit(expression))
|
||||
visitor.visitNodes(expression)
|
||||
}
|
||||
|
@ -25,19 +25,46 @@ class Printer(private val buffer: StringBuilder) : Visitor<Unit> {
|
||||
|
||||
override fun visitFunctionCall(node: FunctionCall) {
|
||||
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) {
|
||||
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) {
|
||||
append(node.id)
|
||||
}
|
||||
|
||||
override fun visitLambda(node: Lambda) {
|
||||
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++
|
||||
for (expression in node.expressions) {
|
||||
appendLine()
|
||||
|
@ -1,10 +1,8 @@
|
||||
package gay.pizza.pork.ast
|
||||
|
||||
class Program(val expressions: List<Expression>) : Node {
|
||||
constructor(vararg expressions: Expression) : this(listOf(*expressions))
|
||||
|
||||
override val type: NodeType = NodeType.Program
|
||||
|
||||
override fun <T> visitChildren(visitor: Visitor<T>): List<T> =
|
||||
expressions.map { visitor.visit(it) }
|
||||
visitor.visitAll(expressions)
|
||||
}
|
||||
|
@ -4,5 +4,5 @@ class SymbolReference(val symbol: Symbol) : Expression {
|
||||
override val type: NodeType = NodeType.SymbolReference
|
||||
|
||||
override fun <T> visitChildren(visitor: Visitor<T>): List<T> =
|
||||
listOf(visitor.visit(symbol))
|
||||
visitor.visitNodes(symbol)
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ interface Visitor<T> {
|
||||
fun visitDefine(node: Define): T
|
||||
fun visitFunctionCall(node: FunctionCall): T
|
||||
fun visitReference(node: SymbolReference): T
|
||||
fun visitIf(node: If): T
|
||||
fun visitSymbol(node: Symbol): T
|
||||
fun visitLambda(node: Lambda): T
|
||||
|
||||
@ -26,6 +27,7 @@ interface Visitor<T> {
|
||||
is Lambda -> visitLambda(node)
|
||||
is FunctionCall -> visitFunctionCall(node)
|
||||
is SymbolReference -> visitReference(node)
|
||||
is If -> visitIf(node)
|
||||
else -> throw RuntimeException("Unknown Expression")
|
||||
}
|
||||
|
||||
@ -35,4 +37,10 @@ interface Visitor<T> {
|
||||
is Program -> visitProgram(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()
|
||||
}
|
||||
|
7
src/main/kotlin/gay/pizza/pork/eval/Arguments.kt
Normal file
7
src/main/kotlin/gay/pizza/pork/eval/Arguments.kt
Normal file
@ -0,0 +1,7 @@
|
||||
package gay.pizza.pork.eval
|
||||
|
||||
class Arguments(val values: List<Any>) {
|
||||
companion object {
|
||||
val Zero = Arguments(emptyList())
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
package gay.pizza.pork.eval
|
||||
|
||||
fun interface CallableFunction {
|
||||
fun call(argument: Any): Any
|
||||
fun call(arguments: Arguments): Any
|
||||
}
|
||||
|
@ -11,18 +11,33 @@ class PorkEvaluator(root: Scope) : Visitor<Any> {
|
||||
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 =
|
||||
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 {
|
||||
return Unit
|
||||
}
|
||||
|
||||
override fun visitLambda(node: Lambda): CallableFunction {
|
||||
return 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) {
|
||||
@ -45,6 +60,13 @@ class PorkEvaluator(root: Scope) : Visitor<Any> {
|
||||
val left = visit(node.left)
|
||||
val right = visit(node.right)
|
||||
|
||||
when (node.op) {
|
||||
InfixOperator.Equals -> {
|
||||
return left == right
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
|
||||
if (left !is Number || right !is Number) {
|
||||
throw RuntimeException("Failed to evaluate infix operation, bad types.")
|
||||
}
|
||||
@ -57,6 +79,7 @@ class PorkEvaluator(root: Scope) : Visitor<Any> {
|
||||
InfixOperator.Minus -> leftInt - rightInt
|
||||
InfixOperator.Multiply -> leftInt * rightInt
|
||||
InfixOperator.Divide -> leftInt / rightInt
|
||||
else -> throw RuntimeException("Unable to handle operation ${node.op}")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -21,13 +21,12 @@ class Scope(val parent: Scope? = null) {
|
||||
return value
|
||||
}
|
||||
|
||||
fun call(name: String, argument: Any = Unit): Any {
|
||||
fun call(name: String, arguments: Arguments): Any {
|
||||
val value = value(name)
|
||||
if (value !is CallableFunction) {
|
||||
throw RuntimeException("$value is not callable.")
|
||||
}
|
||||
val function = value as CallableFunction
|
||||
return function.call(argument)
|
||||
return value.call(arguments)
|
||||
}
|
||||
|
||||
fun fork(): Scope {
|
||||
|
@ -1,6 +1,7 @@
|
||||
package gay.pizza.pork
|
||||
|
||||
import gay.pizza.pork.ast.*
|
||||
import gay.pizza.pork.eval.Arguments
|
||||
import gay.pizza.pork.eval.Scope
|
||||
import gay.pizza.pork.eval.PorkEvaluator
|
||||
import gay.pizza.pork.parse.*
|
||||
@ -12,7 +13,7 @@ fun main(args: Array<String>) {
|
||||
val scope = Scope()
|
||||
val evaluator = PorkEvaluator(scope)
|
||||
evaluator.visit(ast)
|
||||
println("> ${scope.call("main")}")
|
||||
println("> ${scope.call("main", Arguments.Zero)}")
|
||||
}
|
||||
|
||||
val code = Path(args[0]).readText()
|
||||
|
@ -2,6 +2,7 @@ package gay.pizza.pork.parse
|
||||
|
||||
interface PeekableSource<T> {
|
||||
val currentIndex: Int
|
||||
fun back()
|
||||
fun next(): T
|
||||
fun peek(): T
|
||||
}
|
||||
|
@ -13,14 +13,29 @@ class PorkParser(val source: PeekableSource<Token>) {
|
||||
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 {
|
||||
val symbol = readSymbol()
|
||||
return if (peekType(TokenType.LeftParentheses)) {
|
||||
expect(TokenType.LeftParentheses)
|
||||
val arguments = collectExpressions(TokenType.RightParentheses, TokenType.Comma)
|
||||
expect(TokenType.RightParentheses)
|
||||
FunctionCall(symbol)
|
||||
FunctionCall(symbol, arguments)
|
||||
} else if (peekType(TokenType.Equals)) {
|
||||
expect(TokenType.Equals)
|
||||
if (peekType(TokenType.Equals)) {
|
||||
source.back()
|
||||
return SymbolReference(symbol)
|
||||
}
|
||||
Define(symbol, readExpression())
|
||||
} else {
|
||||
SymbolReference(symbol)
|
||||
@ -29,9 +44,21 @@ class PorkParser(val source: PeekableSource<Token>) {
|
||||
|
||||
fun readLambda(): Lambda {
|
||||
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)
|
||||
expect(TokenType.RightCurly)
|
||||
return Lambda(items)
|
||||
return Lambda(arguments, items)
|
||||
}
|
||||
|
||||
fun readExpression(): Expression {
|
||||
@ -40,31 +67,42 @@ class PorkParser(val source: PeekableSource<Token>) {
|
||||
TokenType.IntLiteral -> {
|
||||
readIntLiteral()
|
||||
}
|
||||
|
||||
TokenType.LeftBracket -> {
|
||||
readListLiteral()
|
||||
}
|
||||
|
||||
TokenType.Symbol -> {
|
||||
readSymbolCases()
|
||||
}
|
||||
|
||||
TokenType.LeftCurly -> {
|
||||
readLambda()
|
||||
}
|
||||
|
||||
TokenType.LeftParentheses -> {
|
||||
expect(TokenType.LeftParentheses)
|
||||
val expression = readExpression()
|
||||
expect(TokenType.RightParentheses)
|
||||
Parentheses(expression)
|
||||
}
|
||||
|
||||
TokenType.True -> {
|
||||
expect(TokenType.True)
|
||||
return BooleanLiteral(true)
|
||||
}
|
||||
|
||||
TokenType.False -> {
|
||||
expect(TokenType.False)
|
||||
return BooleanLiteral(false)
|
||||
}
|
||||
|
||||
TokenType.If -> {
|
||||
return readIf()
|
||||
}
|
||||
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
@ -85,6 +130,12 @@ class PorkParser(val source: PeekableSource<Token>) {
|
||||
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 {
|
||||
expect(TokenType.LeftBracket)
|
||||
val items = collectExpressions(TokenType.RightBracket, TokenType.Comma)
|
||||
|
@ -3,6 +3,9 @@ package gay.pizza.pork.parse
|
||||
class StringCharSource(val input: String) : CharSource {
|
||||
private var index = 0
|
||||
override val currentIndex: Int = index
|
||||
override fun back() {
|
||||
index--
|
||||
}
|
||||
|
||||
override fun next(): Char {
|
||||
if (index == input.length) {
|
||||
|
@ -3,6 +3,9 @@ package gay.pizza.pork.parse
|
||||
class TokenStreamSource(val stream: TokenStream) : TokenSource {
|
||||
private var index = 0
|
||||
override val currentIndex: Int = index
|
||||
override fun back() {
|
||||
index--
|
||||
}
|
||||
|
||||
override fun next(): Token {
|
||||
if (index == stream.tokens.size) {
|
||||
|
@ -18,6 +18,9 @@ enum class TokenType(val char: Char? = null, val keyword: String? = null) {
|
||||
False(keyword = "false"),
|
||||
True(keyword = "true"),
|
||||
In(keyword = "in"),
|
||||
If(keyword = "if"),
|
||||
Then(keyword = "then"),
|
||||
Else(keyword = "else"),
|
||||
EndOfFile;
|
||||
|
||||
companion object {
|
||||
|
Loading…
Reference in New Issue
Block a user