Skip to content

Commit

Permalink
Evaluator - Let statement and Environment
Browse files Browse the repository at this point in the history
  • Loading branch information
macfeteria committed Jan 15, 2019
1 parent be903b6 commit eff866c
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 20 deletions.
23 changes: 23 additions & 0 deletions Sources/swiftmonkey/Environment.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// Environment.swift
// swiftmonkey
//
// Created by Ter on 15/1/19.
//

import Foundation

public class Environment {
var store:[String: Object] = [:]
func get(name:String) -> (Object, Bool) {
if let obj = store[name] {
return (obj, true)
}
return (Evaluator.NULL, false)
}
func set(name:String, object:Object) -> Object {
store[name] = object
return object
}
}

50 changes: 33 additions & 17 deletions Sources/swiftmonkey/Evaluator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ public struct Evaluator {
static let FALSE = BooleanObj(value: false)
static let NULL = NullObj()

public func eval(node: Node) -> Object {
public func eval(node: Node, environment env: Environment) -> Object {
switch node {
case is Program:
let program = node as! Program
return eval(program: program)
return eval(program: program, environment: env)
case is ExpressionStatement:
let ex = node as! ExpressionStatement
return eval(node: ex.expression!)
return eval(node: ex.expression!, environment: env)
case is IntegerLiteral:
let int = node as! IntegerLiteral
return IntegerObj(value: int.value)
Expand All @@ -28,36 +28,52 @@ public struct Evaluator {
return boolean.value ? Evaluator.TRUE : Evaluator.FALSE
case is PrefixExpression:
let pre = node as! PrefixExpression
let right = eval(node: pre.right!)
let right = eval(node: pre.right!, environment: env)
if isError(obj: right) { return right }
return evalPrefixExpression(oper: pre.operatorLiteral, right: right)
case is InfixExpression:
let infix = node as! InfixExpression
let left = eval(node: infix.left)
let left = eval(node: infix.left, environment: env)
if isError(obj: left) { return left }
let right = eval(node: infix.right!)
let right = eval(node: infix.right!, environment: env)
if isError(obj: right) { return right }
return evalInfixExpression(oper: infix.operatorLiteral, left: left, right: right)
case is BlockStatement:
let block = node as! BlockStatement
return evalBlockStatement(block: block)
return evalBlockStatement(block: block, environment: env)
case is IfExpression:
let ifEx = node as! IfExpression
return evalIfExpression(expression: ifEx)
return evalIfExpression(expression: ifEx, environment: env)
case is ReturnStatement:
let returnStmt = node as! ReturnStatement
let value = eval(node:returnStmt.returnValue!)
let value = eval(node:returnStmt.returnValue!, environment: env)
if isError(obj: value) { return value }
return ReturnValueObj(value: value)
case is LetStatement:
let letStmt = node as! LetStatement
let value = eval(node: letStmt.value!, environment: env)
if isError(obj: value) { return value }
return env.set(name: letStmt.name.value, object: value)
case is Identifier:
let iden = node as! Identifier
return evalIdentifier(node: iden, environment: env)
default:
return Evaluator.NULL
}
}

func eval(program:Program) -> Object {
func evalIdentifier(node: Identifier, environment env: Environment) -> Object {
let (iden,ok) = env.get(name: node.value)
if !ok {
return ErrorObj(message: "identifier not found: \(node.value)")
}
return iden
}

func eval(program:Program, environment env: Environment) -> Object {
var result: Object = NullObj()
for s in program.statements {
result = eval(node: s)
result = eval(node: s, environment: env)
if let returnValue = result as? ReturnValueObj {
return returnValue.value
}
Expand All @@ -68,10 +84,10 @@ public struct Evaluator {
return result
}

func evalBlockStatement(block: BlockStatement) -> Object {
func evalBlockStatement(block: BlockStatement, environment env: Environment) -> Object {
var result: Object = NullObj()
for s in block.statements {
result = eval(node: s)
result = eval(node: s, environment: env)
let type = result.type()
if type == ObjectType.ERROR || type == ObjectType.RETURN_VALUE {
return result
Expand Down Expand Up @@ -158,13 +174,13 @@ public struct Evaluator {
}
}

func evalIfExpression(expression: IfExpression) -> Object {
let condition = eval(node: expression.condition)
func evalIfExpression(expression: IfExpression, environment env: Environment) -> Object {
let condition = eval(node: expression.condition, environment: env)
if isError(obj: condition) { return condition }
if isTruthy(obj: condition) {
return eval(node: expression.consequence)
return eval(node: expression.consequence, environment: env)
} else if let alter = expression.alternative {
return eval(node: alter)
return eval(node: alter, environment: env)
} else {
return Evaluator.NULL
}
Expand Down
15 changes: 13 additions & 2 deletions Sources/swiftmonkey/Repl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,17 @@ let PROMPT = ">> "
public func startRepl () {

var input:String?
let env = Environment()

repeat {
print(PROMPT,terminator:"")
input = readLine()
if let code = input , code.count > 0{
let lexer = Lexer(input: code)
// if code != code.alphanumeric {
// print("\t Code contains special character")
// continue
// }
let lexer = Lexer(input: code + "\0")
let parser = Parser(lexer: lexer)
let program = parser.parseProgram()
if parser.errors.count != 0 {
Expand All @@ -26,9 +31,15 @@ public func startRepl () {
}
} else {
let evaluated = Evaluator()
let result = evaluated.eval(node: program)
let result = evaluated.eval(program: program, environment: env)
print(result.inspect())
}
}
} while (input != nil)
}

extension String {
var alphanumeric: String {
return self.components(separatedBy: CharacterSet.alphanumerics.inverted).joined().lowercased()
}
}
17 changes: 16 additions & 1 deletion Tests/swiftmonkeyTests/EvaluatorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ class EvaluatorTests: XCTestCase {

let program = parser.parseProgram()
let evaluated = Evaluator()
return evaluated.eval(node: program)
var env = Environment()
return evaluated.eval(program: program, environment: &env)
}

func validateIntegerObject(obj:Object, expect: Int) {
Expand Down Expand Up @@ -165,11 +166,25 @@ class EvaluatorTests: XCTestCase {
return 1;
}
""", expectedValue:"unknow operator: BOOLEAN + BOOLEAN"),
(code:"foobar", expectedValue:"identifier not found: foobar"),
]
for test in tests {
let resultObj = evaluate(input: test.code)
XCTAssertTrue(resultObj.inspect() == test.expectedValue, "Expect \(test.expectedValue) Got \(resultObj.inspect())" )
}
}

func testLetStatement () {
let tests = [
(code:"let a = 5; a;", expectedValue: 5),
(code:"let a = 5 * 5; a;", expectedValue: 25),
(code:"let a = 5; let b = a; b;", expectedValue: 5),
(code:"let a = 5; let b = a; let c = a + b + 5; c;", expectedValue: 15),
]
for test in tests {
let resultObj = evaluate(input: test.code)
validateIntegerObject(obj: resultObj, expect: test.expectedValue)
}
}

}
4 changes: 4 additions & 0 deletions swiftmonkey.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
/* Begin PBXBuildFile section */
D758037621DE0EAD00CB65F6 /* ParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D758037521DE0EAD00CB65F6 /* ParserTests.swift */; };
D758037821DF5C6800CB65F6 /* AstTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D758037721DF5C6800CB65F6 /* AstTests.swift */; };
D76CF05521EDCA0A00D00C83 /* Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D76CF05421EDCA0A00D00C83 /* Environment.swift */; };
D772149021DCB627007BAA3B /* Repl.swift in Sources */ = {isa = PBXBuildFile; fileRef = D772148F21DCB627007BAA3B /* Repl.swift */; };
D772149221DDFB3F007BAA3B /* Ast.swift in Sources */ = {isa = PBXBuildFile; fileRef = D772149121DDFB3F007BAA3B /* Ast.swift */; };
D772149421DDFE0D007BAA3B /* Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = D772149321DDFE0D007BAA3B /* Parser.swift */; };
Expand Down Expand Up @@ -66,6 +67,7 @@
/* Begin PBXFileReference section */
D758037521DE0EAD00CB65F6 /* ParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParserTests.swift; sourceTree = "<group>"; };
D758037721DF5C6800CB65F6 /* AstTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AstTests.swift; sourceTree = "<group>"; };
D76CF05421EDCA0A00D00C83 /* Environment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Environment.swift; sourceTree = "<group>"; };
D772148F21DCB627007BAA3B /* Repl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Repl.swift; sourceTree = "<group>"; };
D772149121DDFB3F007BAA3B /* Ast.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ast.swift; sourceTree = "<group>"; };
D772149321DDFE0D007BAA3B /* Parser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Parser.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -120,6 +122,7 @@
D772149321DDFE0D007BAA3B /* Parser.swift */,
D7E3A04821E73272006D19CE /* Object.swift */,
D7E3A04A21E73831006D19CE /* Evaluator.swift */,
D76CF05421EDCA0A00D00C83 /* Environment.swift */,
);
name = swiftmonkey;
path = Sources/swiftmonkey;
Expand Down Expand Up @@ -286,6 +289,7 @@
buildActionMask = 0;
files = (
D772149221DDFB3F007BAA3B /* Ast.swift in Sources */,
D76CF05521EDCA0A00D00C83 /* Environment.swift in Sources */,
D7E3A04B21E73831006D19CE /* Evaluator.swift in Sources */,
D772149021DCB627007BAA3B /* Repl.swift in Sources */,
D772149421DDFE0D007BAA3B /* Parser.swift in Sources */,
Expand Down

0 comments on commit eff866c

Please sign in to comment.