From bfd9dc876ae23f570c4b66e697ffba9eb3f217c1 Mon Sep 17 00:00:00 2001 From: Ter Date: Tue, 15 Jan 2019 22:41:17 +0700 Subject: [PATCH] Evaluator - Function call. --- Sources/swiftmonkey/Environment.swift | 7 +++ Sources/swiftmonkey/Evaluator.swift | 54 +++++++++++++++++++++ Sources/swiftmonkey/Object.swift | 23 +++++++++ Tests/swiftmonkeyTests/EvaluatorTests.swift | 42 +++++++++++++++- 4 files changed, 124 insertions(+), 2 deletions(-) diff --git a/Sources/swiftmonkey/Environment.swift b/Sources/swiftmonkey/Environment.swift index 756b620..0006709 100644 --- a/Sources/swiftmonkey/Environment.swift +++ b/Sources/swiftmonkey/Environment.swift @@ -9,12 +9,19 @@ import Foundation public class Environment { var store:[String: Object] = [:] + var outer:Environment? = nil + func get(name:String) -> (Object, Bool) { if let obj = store[name] { return (obj, true) } + if let obj = outer { + return obj.get(name: name) + } return (Evaluator.NULL, false) } + + @discardableResult func set(name:String, object:Object) -> Object { store[name] = object return object diff --git a/Sources/swiftmonkey/Evaluator.swift b/Sources/swiftmonkey/Evaluator.swift index 49bd052..0200932 100644 --- a/Sources/swiftmonkey/Evaluator.swift +++ b/Sources/swiftmonkey/Evaluator.swift @@ -57,11 +57,65 @@ public struct Evaluator { case is Identifier: let iden = node as! Identifier return evalIdentifier(node: iden, environment: env) + case is FunctionLiteral: + let funcLit = node as! FunctionLiteral + let params = funcLit.parameters + let body = funcLit.body + return FunctionObj(parameters: params, body: body, env: env) + case is CallExpression: + let call = node as! CallExpression + let function = eval(node: call.function, environment: env) + if isError(obj: function) { + return function + } + let args = evalExpression(args: call.arguments, environment: env) + if args.count == 1 && isError(obj: args[0]) { + return args[0] + } + return applyFunction(fn: function, args: args) default: return Evaluator.NULL } } + func applyFunction(fn: Object, args: [Object]) -> Object { + if let function = fn as? FunctionObj { + let extendedEnv = extendedFunctionEnv(fn: function, args: args) + let evaluated = eval(node: function.body, environment: extendedEnv) + return unwrapReturnValue(obj: evaluated) + } + return ErrorObj(message:"not a function: \(fn.type())") + } + + func unwrapReturnValue(obj: Object) -> Object { + if let returnValue = obj as? ReturnValueObj { + return returnValue.value + } + return obj + } + + func extendedFunctionEnv(fn: FunctionObj, args: [Object]) -> Environment { + let env = Environment() + env.outer = fn.env + let param = fn.parameters + for i in 0 ..< param.count { + env.set(name: param[i].value, object: args[i]) + } + return env + } + + func evalExpression(args: [Expression], environment env: Environment) -> [Object] { + var result:[Object] = [] + for a in args { + let evaluated = eval(node: a, environment: env) + if isError(obj: evaluated) { + return [evaluated] + } + result.append(evaluated) + } + return result + } + func evalIdentifier(node: Identifier, environment env: Environment) -> Object { let (iden,ok) = env.get(name: node.value) if !ok { diff --git a/Sources/swiftmonkey/Object.swift b/Sources/swiftmonkey/Object.swift index 630e78c..a73a306 100644 --- a/Sources/swiftmonkey/Object.swift +++ b/Sources/swiftmonkey/Object.swift @@ -13,6 +13,7 @@ public enum ObjectType { case RETURN_VALUE case NULL case ERROR + case FUNCTION } public protocol Object { @@ -76,3 +77,25 @@ struct ErrorObj:Object { return "\(message)" } } + +struct FunctionObj:Object { + var parameters:[Identifier] + var body:BlockStatement + var env:Environment + + func type() -> ObjectType { + return ObjectType.FUNCTION + } + + func inspect() -> String { + let param = parameters.map { (iden) -> String in + return iden.value + }.joined(separator: ",") + + return """ + fn(\(param)) { + \(body.string()) + } + """ + } +} diff --git a/Tests/swiftmonkeyTests/EvaluatorTests.swift b/Tests/swiftmonkeyTests/EvaluatorTests.swift index 540a337..ee39036 100644 --- a/Tests/swiftmonkeyTests/EvaluatorTests.swift +++ b/Tests/swiftmonkeyTests/EvaluatorTests.swift @@ -16,8 +16,8 @@ class EvaluatorTests: XCTestCase { let program = parser.parseProgram() let evaluated = Evaluator() - var env = Environment() - return evaluated.eval(program: program, environment: &env) + let env = Environment() + return evaluated.eval(program: program, environment: env) } func validateIntegerObject(obj:Object, expect: Int) { @@ -186,5 +186,43 @@ class EvaluatorTests: XCTestCase { validateIntegerObject(obj: resultObj, expect: test.expectedValue) } } + + func testFunctionObject () { + let code = "fn(x) { x + 2; };" + + let obj = evaluate(input: code) + let funcObj = obj as! FunctionObj + XCTAssertTrue(funcObj.parameters.count == 1) + XCTAssertTrue(funcObj.parameters[0].string() == "x", "got \(funcObj.parameters[0].string())") + XCTAssertTrue(funcObj.body.string() == "(x + 2)", "got \(funcObj.body.string())") + } + + func testFunctionApplication () { + + let tests = [ + (code:"let identity = fn(x) { x; }; identity(5);", expectedValue: 5), + (code:"let double = fn(x) { return x * 2; }; double(5);", expectedValue: 10), + (code:"let add = fn(x, y) { x + y; }; add(5, 5);", expectedValue: 10), + (code:"let add = fn(x, y) { x + y; }; add(5 + 5, add(5, 5));", expectedValue: 20), + ] + for test in tests { + let resultObj = evaluate(input: test.code) + validateIntegerObject(obj: resultObj, expect: test.expectedValue) + } + + } + + func testClosure() { + let code = """ + let newAdder = fn(x) { + fn(y) { x + y }; + }; + let addTwo = newAdder(2) + addTwo(2); + """ + + let resultObj = evaluate(input: code) + validateIntegerObject(obj: resultObj, expect: 4) + } }