Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement functions and lambdas #4

Merged
merged 19 commits into from Mar 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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)
}
}
}