diff --git a/examples/fib.pork b/examples/fib.pork new file mode 100644 index 0000000..5d71a9a --- /dev/null +++ b/examples/fib.pork @@ -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) } diff --git a/examples/syntax.pork b/examples/syntax.pork index a33cc40..17a264c 100644 --- a/examples/syntax.pork +++ b/examples/syntax.pork @@ -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 diff --git a/src/main/kotlin/gay/pizza/pork/ast/Define.kt b/src/main/kotlin/gay/pizza/pork/ast/Define.kt index 44a4109..0975e68 100644 --- a/src/main/kotlin/gay/pizza/pork/ast/Define.kt +++ b/src/main/kotlin/gay/pizza/pork/ast/Define.kt @@ -4,5 +4,5 @@ class Define(val symbol: Symbol, val value: Expression) : Expression { override val type: NodeType = NodeType.Define override fun visitChildren(visitor: Visitor): List = - listOf(visitor.visit(symbol), visitor.visit(value)) + visitor.visitNodes(symbol, value) } diff --git a/src/main/kotlin/gay/pizza/pork/ast/FunctionCall.kt b/src/main/kotlin/gay/pizza/pork/ast/FunctionCall.kt index a2c699e..a2f0b90 100644 --- a/src/main/kotlin/gay/pizza/pork/ast/FunctionCall.kt +++ b/src/main/kotlin/gay/pizza/pork/ast/FunctionCall.kt @@ -1,8 +1,8 @@ package gay.pizza.pork.ast -class FunctionCall(val symbol: Symbol) : Expression { +class FunctionCall(val symbol: Symbol, val arguments: List) : Expression { override val type: NodeType = NodeType.FunctionCall override fun visitChildren(visitor: Visitor): List = - listOf(visitor.visit(symbol)) + visitor.visitAll(listOf(symbol), arguments) } diff --git a/src/main/kotlin/gay/pizza/pork/ast/If.kt b/src/main/kotlin/gay/pizza/pork/ast/If.kt new file mode 100644 index 0000000..3ecaf27 --- /dev/null +++ b/src/main/kotlin/gay/pizza/pork/ast/If.kt @@ -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 +} diff --git a/src/main/kotlin/gay/pizza/pork/ast/InfixOperation.kt b/src/main/kotlin/gay/pizza/pork/ast/InfixOperation.kt index dc07ce4..d35f05a 100644 --- a/src/main/kotlin/gay/pizza/pork/ast/InfixOperation.kt +++ b/src/main/kotlin/gay/pizza/pork/ast/InfixOperation.kt @@ -4,5 +4,5 @@ class InfixOperation(val left: Expression, val op: InfixOperator, val right: Exp override val type: NodeType = NodeType.InfixOperation override fun visitChildren(visitor: Visitor): List = - listOf(visitor.visit(left), visitor.visit(right)) + visitor.visitNodes(left, right) } diff --git a/src/main/kotlin/gay/pizza/pork/ast/InfixOperator.kt b/src/main/kotlin/gay/pizza/pork/ast/InfixOperator.kt index b2637ac..0d015c2 100644 --- a/src/main/kotlin/gay/pizza/pork/ast/InfixOperator.kt +++ b/src/main/kotlin/gay/pizza/pork/ast/InfixOperator.kt @@ -4,5 +4,6 @@ enum class InfixOperator(val token: String) { Plus("+"), Minus("-"), Multiply("*"), - Divide("/") + Divide("/"), + Equals("==") } diff --git a/src/main/kotlin/gay/pizza/pork/ast/Lambda.kt b/src/main/kotlin/gay/pizza/pork/ast/Lambda.kt index 653c8c0..aa0be45 100644 --- a/src/main/kotlin/gay/pizza/pork/ast/Lambda.kt +++ b/src/main/kotlin/gay/pizza/pork/ast/Lambda.kt @@ -1,10 +1,8 @@ package gay.pizza.pork.ast -class Lambda(val expressions: List) : Expression { - constructor(vararg expressions: Expression) : this(listOf(*expressions)) - +class Lambda(val arguments: List, val expressions: List) : Expression { override val type: NodeType = NodeType.Lambda override fun visitChildren(visitor: Visitor): List = - expressions.map { expression -> visitor.visit(expression) } + visitor.visitAll(arguments, expressions) } diff --git a/src/main/kotlin/gay/pizza/pork/ast/ListLiteral.kt b/src/main/kotlin/gay/pizza/pork/ast/ListLiteral.kt index 0041afd..b8c467b 100644 --- a/src/main/kotlin/gay/pizza/pork/ast/ListLiteral.kt +++ b/src/main/kotlin/gay/pizza/pork/ast/ListLiteral.kt @@ -1,10 +1,8 @@ package gay.pizza.pork.ast class ListLiteral(val items: List) : Expression { - constructor(vararg items: Expression) : this(listOf(*items)) - override val type: NodeType = NodeType.ListLiteral override fun visitChildren(visitor: Visitor): List = - items.map { visitor.visit(it) } + visitor.visitAll(items) } diff --git a/src/main/kotlin/gay/pizza/pork/ast/NodeType.kt b/src/main/kotlin/gay/pizza/pork/ast/NodeType.kt index 73b9713..ff8f282 100644 --- a/src/main/kotlin/gay/pizza/pork/ast/NodeType.kt +++ b/src/main/kotlin/gay/pizza/pork/ast/NodeType.kt @@ -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 diff --git a/src/main/kotlin/gay/pizza/pork/ast/Parentheses.kt b/src/main/kotlin/gay/pizza/pork/ast/Parentheses.kt index 931ff5e..b2cf606 100644 --- a/src/main/kotlin/gay/pizza/pork/ast/Parentheses.kt +++ b/src/main/kotlin/gay/pizza/pork/ast/Parentheses.kt @@ -4,5 +4,5 @@ class Parentheses(val expression: Expression) : Expression { override val type: NodeType = NodeType.Parentheses override fun visitChildren(visitor: Visitor): List = - listOf(visitor.visit(expression)) + visitor.visitNodes(expression) } diff --git a/src/main/kotlin/gay/pizza/pork/ast/Printer.kt b/src/main/kotlin/gay/pizza/pork/ast/Printer.kt index 6778996..ffe96a4 100644 --- a/src/main/kotlin/gay/pizza/pork/ast/Printer.kt +++ b/src/main/kotlin/gay/pizza/pork/ast/Printer.kt @@ -25,19 +25,46 @@ class Printer(private val buffer: StringBuilder) : Visitor { 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() diff --git a/src/main/kotlin/gay/pizza/pork/ast/Program.kt b/src/main/kotlin/gay/pizza/pork/ast/Program.kt index 6eca752..5045708 100644 --- a/src/main/kotlin/gay/pizza/pork/ast/Program.kt +++ b/src/main/kotlin/gay/pizza/pork/ast/Program.kt @@ -1,10 +1,8 @@ package gay.pizza.pork.ast class Program(val expressions: List) : Node { - constructor(vararg expressions: Expression) : this(listOf(*expressions)) - override val type: NodeType = NodeType.Program override fun visitChildren(visitor: Visitor): List = - expressions.map { visitor.visit(it) } + visitor.visitAll(expressions) } diff --git a/src/main/kotlin/gay/pizza/pork/ast/SymbolReference.kt b/src/main/kotlin/gay/pizza/pork/ast/SymbolReference.kt index 2839570..714ad5e 100644 --- a/src/main/kotlin/gay/pizza/pork/ast/SymbolReference.kt +++ b/src/main/kotlin/gay/pizza/pork/ast/SymbolReference.kt @@ -4,5 +4,5 @@ class SymbolReference(val symbol: Symbol) : Expression { override val type: NodeType = NodeType.SymbolReference override fun visitChildren(visitor: Visitor): List = - listOf(visitor.visit(symbol)) + visitor.visitNodes(symbol) } diff --git a/src/main/kotlin/gay/pizza/pork/ast/Visitor.kt b/src/main/kotlin/gay/pizza/pork/ast/Visitor.kt index 22cbcee..d2e166f 100644 --- a/src/main/kotlin/gay/pizza/pork/ast/Visitor.kt +++ b/src/main/kotlin/gay/pizza/pork/ast/Visitor.kt @@ -4,6 +4,7 @@ interface Visitor { 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 { 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 { is Program -> visitProgram(node) else -> throw RuntimeException("Unknown Node") } + + fun visitNodes(vararg nodes: Node): List = + nodes.map { visit(it) } + + fun visitAll(vararg nodeLists: List): List = + nodeLists.asSequence().flatten().map { visit(it) }.toList() } diff --git a/src/main/kotlin/gay/pizza/pork/eval/Arguments.kt b/src/main/kotlin/gay/pizza/pork/eval/Arguments.kt new file mode 100644 index 0000000..b9cc82c --- /dev/null +++ b/src/main/kotlin/gay/pizza/pork/eval/Arguments.kt @@ -0,0 +1,7 @@ +package gay.pizza.pork.eval + +class Arguments(val values: List) { + companion object { + val Zero = Arguments(emptyList()) + } +} diff --git a/src/main/kotlin/gay/pizza/pork/eval/CallableFunction.kt b/src/main/kotlin/gay/pizza/pork/eval/CallableFunction.kt index 7b310fd..d06ef7b 100644 --- a/src/main/kotlin/gay/pizza/pork/eval/CallableFunction.kt +++ b/src/main/kotlin/gay/pizza/pork/eval/CallableFunction.kt @@ -1,5 +1,5 @@ package gay.pizza.pork.eval fun interface CallableFunction { - fun call(argument: Any): Any + fun call(arguments: Arguments): Any } diff --git a/src/main/kotlin/gay/pizza/pork/eval/PorkEvaluator.kt b/src/main/kotlin/gay/pizza/pork/eval/PorkEvaluator.kt index fcedcd1..00c9893 100644 --- a/src/main/kotlin/gay/pizza/pork/eval/PorkEvaluator.kt +++ b/src/main/kotlin/gay/pizza/pork/eval/PorkEvaluator.kt @@ -11,18 +11,33 @@ class PorkEvaluator(root: Scope) : Visitor { 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 { 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 { InfixOperator.Minus -> leftInt - rightInt InfixOperator.Multiply -> leftInt * rightInt InfixOperator.Divide -> leftInt / rightInt + else -> throw RuntimeException("Unable to handle operation ${node.op}") } } diff --git a/src/main/kotlin/gay/pizza/pork/eval/Scope.kt b/src/main/kotlin/gay/pizza/pork/eval/Scope.kt index dcaf64b..78e7a6a 100644 --- a/src/main/kotlin/gay/pizza/pork/eval/Scope.kt +++ b/src/main/kotlin/gay/pizza/pork/eval/Scope.kt @@ -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 { diff --git a/src/main/kotlin/gay/pizza/pork/main.kt b/src/main/kotlin/gay/pizza/pork/main.kt index fbdc4ac..fa93067 100644 --- a/src/main/kotlin/gay/pizza/pork/main.kt +++ b/src/main/kotlin/gay/pizza/pork/main.kt @@ -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) { 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() diff --git a/src/main/kotlin/gay/pizza/pork/parse/PeekableSource.kt b/src/main/kotlin/gay/pizza/pork/parse/PeekableSource.kt index 6335c4c..c8c2ece 100644 --- a/src/main/kotlin/gay/pizza/pork/parse/PeekableSource.kt +++ b/src/main/kotlin/gay/pizza/pork/parse/PeekableSource.kt @@ -2,6 +2,7 @@ package gay.pizza.pork.parse interface PeekableSource { val currentIndex: Int + fun back() fun next(): T fun peek(): T } diff --git a/src/main/kotlin/gay/pizza/pork/parse/PorkParser.kt b/src/main/kotlin/gay/pizza/pork/parse/PorkParser.kt index a52ac99..c90b0d9 100644 --- a/src/main/kotlin/gay/pizza/pork/parse/PorkParser.kt +++ b/src/main/kotlin/gay/pizza/pork/parse/PorkParser.kt @@ -13,14 +13,29 @@ class PorkParser(val source: PeekableSource) { 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) { fun readLambda(): Lambda { expect(TokenType.LeftCurly) + val arguments = mutableListOf() + 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) { 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) { 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) { 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) diff --git a/src/main/kotlin/gay/pizza/pork/parse/StringCharSource.kt b/src/main/kotlin/gay/pizza/pork/parse/StringCharSource.kt index 6d7d996..540c57d 100644 --- a/src/main/kotlin/gay/pizza/pork/parse/StringCharSource.kt +++ b/src/main/kotlin/gay/pizza/pork/parse/StringCharSource.kt @@ -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) { diff --git a/src/main/kotlin/gay/pizza/pork/parse/TokenStreamSource.kt b/src/main/kotlin/gay/pizza/pork/parse/TokenStreamSource.kt index 3719415..b2ea3a7 100644 --- a/src/main/kotlin/gay/pizza/pork/parse/TokenStreamSource.kt +++ b/src/main/kotlin/gay/pizza/pork/parse/TokenStreamSource.kt @@ -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) { diff --git a/src/main/kotlin/gay/pizza/pork/parse/TokenType.kt b/src/main/kotlin/gay/pizza/pork/parse/TokenType.kt index d586677..83d7c75 100644 --- a/src/main/kotlin/gay/pizza/pork/parse/TokenType.kt +++ b/src/main/kotlin/gay/pizza/pork/parse/TokenType.kt @@ -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 {