Skip to content

Commit

Permalink
Evaluator - Function call.
Browse files Browse the repository at this point in the history
  • Loading branch information
macfeteria committed Jan 15, 2019
1 parent eff866c commit bfd9dc8
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 2 deletions.
7 changes: 7 additions & 0 deletions Sources/swiftmonkey/Environment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
54 changes: 54 additions & 0 deletions Sources/swiftmonkey/Evaluator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
23 changes: 23 additions & 0 deletions Sources/swiftmonkey/Object.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public enum ObjectType {
case RETURN_VALUE
case NULL
case ERROR
case FUNCTION
}

public protocol Object {
Expand Down Expand Up @@ -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())
}
"""
}
}
42 changes: 40 additions & 2 deletions Tests/swiftmonkeyTests/EvaluatorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
}

}

0 comments on commit bfd9dc8

Please sign in to comment.