Skip to content

Commit

Permalink
Merge pull request #4 from quephird/implement_functions
Browse files Browse the repository at this point in the history
Implement functions
  • Loading branch information
quephird committed Mar 3, 2024
2 parents 062881d + 314a63c commit 3c7dcbc
Show file tree
Hide file tree
Showing 15 changed files with 827 additions and 90 deletions.
26 changes: 25 additions & 1 deletion slox.xcodeproj/project.pbxproj
Expand Up @@ -7,6 +7,8 @@
objects = {

/* Begin PBXBuildFile section */
8732838F2B93F89300E49035 /* NativeFunction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8732838E2B93F89300E49035 /* NativeFunction.swift */; };
873283902B93FC0900E49035 /* NativeFunction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8732838E2B93F89300E49035 /* NativeFunction.swift */; };
873CCB212B8D5FAE00FC249A /* Interpreter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873CCB202B8D5FAE00FC249A /* Interpreter.swift */; };
873CCB232B8D617C00FC249A /* RuntimeError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873CCB222B8D617C00FC249A /* RuntimeError.swift */; };
873CCB252B8D765D00FC249A /* Lox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873CCB242B8D765D00FC249A /* Lox.swift */; };
Expand All @@ -21,6 +23,8 @@
873CCB312B8EBB7800FC249A /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873CCB2F2B8EAEC100FC249A /* Statement.swift */; };
873CCB332B8ED8B900FC249A /* Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873CCB322B8ED8B900FC249A /* Environment.swift */; };
873CCB342B8EE0FF00FC249A /* Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873CCB322B8ED8B900FC249A /* Environment.swift */; };
8755B8B42B91983F00530DC4 /* UserDefinedFunction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87BAFC4A2B918C520013E5FE /* UserDefinedFunction.swift */; };
8755B8B52B91984C00530DC4 /* LoxCallable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87BAFC482B9179CB0013E5FE /* LoxCallable.swift */; };
876560032B882259002BDE42 /* TokenType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 876560022B882259002BDE42 /* TokenType.swift */; };
876560052B8825AC002BDE42 /* Token.swift in Sources */ = {isa = PBXBuildFile; fileRef = 876560042B8825AC002BDE42 /* Token.swift */; };
876560072B8827F9002BDE42 /* Scanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 876560062B8827F9002BDE42 /* Scanner.swift */; };
Expand All @@ -35,6 +39,10 @@
876A31652B8C04990085A350 /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 876A31642B8C04990085A350 /* Expression.swift */; };
876A31672B8C11810085A350 /* Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 876A31662B8C11810085A350 /* Parser.swift */; };
876A31692B8C3AAB0085A350 /* ParseError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 876A31682B8C3AAB0085A350 /* ParseError.swift */; };
877168C22B91A9BD00723543 /* Return.swift in Sources */ = {isa = PBXBuildFile; fileRef = 877168C12B91A9BD00723543 /* Return.swift */; };
87BAFC492B9179CB0013E5FE /* LoxCallable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87BAFC482B9179CB0013E5FE /* LoxCallable.swift */; };
87BAFC4B2B918C520013E5FE /* UserDefinedFunction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87BAFC4A2B918C520013E5FE /* UserDefinedFunction.swift */; };
87C2F3742B91C2BA00126707 /* Return.swift in Sources */ = {isa = PBXBuildFile; fileRef = 877168C12B91A9BD00723543 /* Return.swift */; };
/* End PBXBuildFile section */

/* Begin PBXCopyFilesBuildPhase section */
Expand All @@ -50,6 +58,7 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
8732838E2B93F89300E49035 /* NativeFunction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeFunction.swift; sourceTree = "<group>"; };
873CCB202B8D5FAE00FC249A /* Interpreter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Interpreter.swift; sourceTree = "<group>"; };
873CCB222B8D617C00FC249A /* RuntimeError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeError.swift; sourceTree = "<group>"; };
873CCB242B8D765D00FC249A /* Lox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Lox.swift; sourceTree = "<group>"; };
Expand All @@ -68,6 +77,9 @@
876A31642B8C04990085A350 /* Expression.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Expression.swift; sourceTree = "<group>"; };
876A31662B8C11810085A350 /* Parser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Parser.swift; sourceTree = "<group>"; };
876A31682B8C3AAB0085A350 /* ParseError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseError.swift; sourceTree = "<group>"; };
877168C12B91A9BD00723543 /* Return.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Return.swift; sourceTree = "<group>"; };
87BAFC482B9179CB0013E5FE /* LoxCallable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoxCallable.swift; sourceTree = "<group>"; };
87BAFC4A2B918C520013E5FE /* UserDefinedFunction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefinedFunction.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -112,16 +124,20 @@
873CCB322B8ED8B900FC249A /* Environment.swift */,
876A31642B8C04990085A350 /* Expression.swift */,
873CCB202B8D5FAE00FC249A /* Interpreter.swift */,
876A315E2B897EEB0085A350 /* LoxValue.swift */,
873CCB242B8D765D00FC249A /* Lox.swift */,
87BAFC482B9179CB0013E5FE /* LoxCallable.swift */,
876A315E2B897EEB0085A350 /* LoxValue.swift */,
8732838E2B93F89300E49035 /* NativeFunction.swift */,
876A31682B8C3AAB0085A350 /* ParseError.swift */,
876A31662B8C11810085A350 /* Parser.swift */,
877168C12B91A9BD00723543 /* Return.swift */,
873CCB222B8D617C00FC249A /* RuntimeError.swift */,
876A31612B8986630085A350 /* ScanError.swift */,
876560062B8827F9002BDE42 /* Scanner.swift */,
873CCB2F2B8EAEC100FC249A /* Statement.swift */,
876560042B8825AC002BDE42 /* Token.swift */,
876560022B882259002BDE42 /* TokenType.swift */,
87BAFC4A2B918C520013E5FE /* UserDefinedFunction.swift */,
);
path = slox;
sourceTree = "<group>";
Expand Down Expand Up @@ -230,12 +246,16 @@
876A315F2B897EEB0085A350 /* LoxValue.swift in Sources */,
873CCB302B8EAEC100FC249A /* Statement.swift in Sources */,
873CCB232B8D617C00FC249A /* RuntimeError.swift in Sources */,
87BAFC492B9179CB0013E5FE /* LoxCallable.swift in Sources */,
873CCB212B8D5FAE00FC249A /* Interpreter.swift in Sources */,
877168C22B91A9BD00723543 /* Return.swift in Sources */,
876A31672B8C11810085A350 /* Parser.swift in Sources */,
876560072B8827F9002BDE42 /* Scanner.swift in Sources */,
87BAFC4B2B918C520013E5FE /* UserDefinedFunction.swift in Sources */,
873CCB332B8ED8B900FC249A /* Environment.swift in Sources */,
876A31652B8C04990085A350 /* Expression.swift in Sources */,
876560032B882259002BDE42 /* TokenType.swift in Sources */,
8732838F2B93F89300E49035 /* NativeFunction.swift in Sources */,
876A31692B8C3AAB0085A350 /* ParseError.swift in Sources */,
873CCB252B8D765D00FC249A /* Lox.swift in Sources */,
);
Expand All @@ -250,10 +270,14 @@
873CCB2C2B8E88A500FC249A /* InterpreterTests.swift in Sources */,
873CCB2E2B8E88D200FC249A /* RuntimeError.swift in Sources */,
873CCB342B8EE0FF00FC249A /* Environment.swift in Sources */,
8755B8B42B91983F00530DC4 /* UserDefinedFunction.swift in Sources */,
876A315C2B897C000085A350 /* Token.swift in Sources */,
876A31572B8979BD0085A350 /* ScannerTests.swift in Sources */,
876A31602B89827B0085A350 /* LoxValue.swift in Sources */,
873CCB312B8EBB7800FC249A /* Statement.swift in Sources */,
8755B8B52B91984C00530DC4 /* LoxCallable.swift in Sources */,
873283902B93FC0900E49035 /* NativeFunction.swift in Sources */,
87C2F3742B91C2BA00126707 /* Return.swift in Sources */,
873CCB282B8E7B6300FC249A /* Parser.swift in Sources */,
873CCB272B8E7AD100FC249A /* ParserTests.swift in Sources */,
876A315D2B897C020085A350 /* Scanner.swift in Sources */,
Expand Down
6 changes: 5 additions & 1 deletion slox/Environment.swift
Expand Up @@ -5,7 +5,7 @@
// Created by Danielle Kefford on 2/27/24.
//

class Environment {
class Environment: Equatable {
private var enclosingEnvironment: Environment?
private var values: [String: LoxValue] = [:]

Expand Down Expand Up @@ -42,4 +42,8 @@ class Environment {

throw RuntimeError.undefinedVariable(name)
}

static func == (lhs: Environment, rhs: Environment) -> Bool {
return lhs === rhs
}
}
2 changes: 2 additions & 0 deletions slox/Expression.swift
Expand Up @@ -13,4 +13,6 @@ indirect enum Expression: Equatable {
case variable(Token)
case assignment(Token, Expression)
case logical(Expression, Token, Expression)
case call(Expression, Token, [Expression])
case lambda([Token], [Statement])
}
114 changes: 99 additions & 15 deletions slox/Interpreter.swift
Expand Up @@ -5,30 +5,41 @@
// Created by Danielle Kefford on 2/26/24.
//

struct Interpreter {
import Foundation

class Interpreter {
var environment: Environment = Environment()

mutating func interpret(statements: [Statement]) throws {
init() {
setUpGlobals()
}

private func setUpGlobals() {
for nativeFunction in NativeFunction.allCases {
environment.define(name: String(describing: nativeFunction),
value: .nativeFunction(nativeFunction))
}
}

func interpret(statements: [Statement]) throws {
for statement in statements {
try execute(statement: statement)
}
}

mutating func interpretRepl(statements: [Statement]) throws -> LoxValue? {
var result: LoxValue? = nil

func interpretRepl(statements: [Statement]) throws -> LoxValue? {
for (i, statement) in statements.enumerated() {
if i == statements.endIndex-1, case .expression(let expr) = statement {
result = try evaluate(expr: expr)
return try evaluate(expr: expr)
} else {
try execute(statement: statement)
}
}

return result
return nil
}

mutating private func execute(statement: Statement) throws {
private func execute(statement: Statement) throws {
switch statement {
case .expression(let expr):
let _ = try evaluate(expr: expr)
Expand All @@ -45,10 +56,14 @@ struct Interpreter {
environment: Environment(enclosingEnvironment: environment))
case .while(let expr, let stmt):
try handleWhileStatement(expr: expr, stmt: stmt)
case .function(let name, let lambda):
try handleFunctionDeclaration(name: name, lambda: lambda)
case .return(let returnToken, let expr):
try handleReturnStatement(returnToken: returnToken, expr: expr)
}
}

mutating private func handleIfStatement(testExpr: Expression,
private func handleIfStatement(testExpr: Expression,
consequentStmt: Statement,
alternativeStmt: Statement?) throws {
if isTruthy(value: try evaluate(expr: testExpr)) {
Expand All @@ -63,6 +78,28 @@ struct Interpreter {
print(literal)
}

private func handleFunctionDeclaration(name: Token, lambda: Expression) throws {
guard case .lambda(let params, let body) = lambda else {
throw RuntimeError.notALambda
}

let environmentWhenDeclared = self.environment
let function = UserDefinedFunction(name: name.lexeme,
params: params,
enclosingEnvironment: environmentWhenDeclared,
body: body)
environment.define(name: name.lexeme, value: .userDefinedFunction(function))
}

private func handleReturnStatement(returnToken: Token, expr: Expression?) throws {
var value: LoxValue = .nil
if let expr {
value = try evaluate(expr: expr)
}

throw Return.return(value)
}

private func handleVariableDeclaration(name: Token, expr: Expression?) throws {
var value: LoxValue = .nil
if let expr = expr {
Expand All @@ -72,18 +109,23 @@ struct Interpreter {
environment.define(name: name.lexeme, value: value)
}

mutating private func handleBlock(statements: [Statement], environment: Environment) throws {
func handleBlock(statements: [Statement], environment: Environment) throws {
let environmentBeforeBlock = self.environment

self.environment = environment

// This ensures that the previous environment is restored
// if the try below throws, which is what will happen if
// there is a return statement.
defer {
self.environment = environmentBeforeBlock
}

for statement in statements {
try execute(statement: statement)
}

self.environment = environmentBeforeBlock
}

mutating private func handleWhileStatement(expr: Expression, stmt: Statement) throws {
private func handleWhileStatement(expr: Expression, stmt: Statement) throws {
while isTruthy(value: try evaluate(expr: expr)) {
try execute(statement: stmt)
}
Expand All @@ -105,6 +147,10 @@ struct Interpreter {
return try handleAssignmentExpression(name: varToken, expr: valueExpr)
case .logical(let leftExpr, let oper, let rightExpr):
return try handleLogicalExpression(leftExpr: leftExpr, oper: oper, rightExpr: rightExpr)
case .call(let calleeExpr, let rightParen, let args):
return try handleFunctionCallExpression(calleeExpr: calleeExpr, rightParen: rightParen, args: args)
case .lambda(let params, let statements):
return try handleLambdaExpression(params: params, statements: statements)
}
}

Expand Down Expand Up @@ -175,7 +221,7 @@ struct Interpreter {
}
}

func handleAssignmentExpression(name: Token, expr: Expression) throws -> LoxValue {
private func handleAssignmentExpression(name: Token, expr: Expression) throws -> LoxValue {
let value = try evaluate(expr: expr)
try environment.assign(name: name.lexeme, value: value)
return value
Expand All @@ -201,6 +247,44 @@ struct Interpreter {
}
}

private func handleFunctionCallExpression(calleeExpr: Expression,
rightParen: Token,
args: [Expression]) throws -> LoxValue {
let callee = try evaluate(expr: calleeExpr)

let actualFunction: LoxCallable = switch callee {
case .userDefinedFunction(let userDefinedFunction):
userDefinedFunction
case .nativeFunction(let nativeFunction):
nativeFunction
default:
throw RuntimeError.notAFunction
}

guard args.count == actualFunction.arity else {
throw RuntimeError.wrongArity(actualFunction.arity, args.count)
}

var argValues: [LoxValue] = []
for arg in args {
let argValue = try evaluate(expr: arg)
argValues.append(argValue)
}

return try actualFunction.call(interpreter: self, args: argValues)
}

private func handleLambdaExpression(params: [Token], statements: [Statement]) throws -> LoxValue {
let environmentWhenDeclared = self.environment

let function = UserDefinedFunction(name: "<lambda>",
params: params,
enclosingEnvironment: environmentWhenDeclared,
body: statements)

return .userDefinedFunction(function)
}

private func isEqual(leftValue: LoxValue, rightValue: LoxValue) -> Bool {
switch (leftValue, rightValue) {
case (.nil, .nil):
Expand Down
11 changes: 11 additions & 0 deletions slox/LoxCallable.swift
@@ -0,0 +1,11 @@
//
// Callable.swift
// slox
//
// Created by Danielle Kefford on 2/29/24.
//

protocol LoxCallable {
var arity: Int { get }
func call(interpreter: Interpreter, args: [LoxValue]) throws -> LoxValue
}
8 changes: 7 additions & 1 deletion slox/LoxValue.swift
@@ -1,5 +1,5 @@
//
// Literal.swift
// LoxValue.swift
// slox
//
// Created by Danielle Kefford on 2/23/24.
Expand All @@ -10,6 +10,8 @@ enum LoxValue: CustomStringConvertible, Equatable {
case number(Double)
case boolean(Bool)
case `nil`
case userDefinedFunction(UserDefinedFunction)
case nativeFunction(NativeFunction)

var description: String {
switch self {
Expand All @@ -21,6 +23,10 @@ enum LoxValue: CustomStringConvertible, Equatable {
return "\(boolean)"
case .nil:
return "nil"
case .userDefinedFunction(let function):
return "<function: \(function.name)>"
case .nativeFunction(let function):
return "<function: \(function)>"
}
}
}
26 changes: 26 additions & 0 deletions slox/NativeFunction.swift
@@ -0,0 +1,26 @@
//
// NativeFunction.swift
// slox
//
// Created by Danielle Kefford on 3/2/24.
//

import Foundation

enum NativeFunction: LoxCallable, Equatable, CaseIterable {
case clock

var arity: Int {
switch self {
case .clock:
return 0
}
}

func call(interpreter: Interpreter, args: [LoxValue]) throws -> LoxValue {
switch self {
case .clock:
return .number(Date().timeIntervalSince1970)
}
}
}

0 comments on commit 3c7dcbc

Please sign in to comment.