Skip to content

Commit

Permalink
Merge pull request #11 from quephird/implement_lists
Browse files Browse the repository at this point in the history
Implement lists
  • Loading branch information
quephird committed Mar 15, 2024
2 parents 0485f0e + cba5109 commit 1cc1447
Show file tree
Hide file tree
Showing 19 changed files with 636 additions and 954 deletions.
6 changes: 6 additions & 0 deletions slox.xcodeproj/project.pbxproj
Expand Up @@ -52,6 +52,8 @@
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 */; };
87777E3C2BA0FF70002E38F2 /* LoxList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87777E3B2BA0FF70002E38F2 /* LoxList.swift */; };
87777E3D2BA1015F002E38F2 /* LoxList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87777E3B2BA0FF70002E38F2 /* LoxList.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 */; };
Expand Down Expand Up @@ -101,6 +103,7 @@
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>"; };
87777E3B2BA0FF70002E38F2 /* LoxList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoxList.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>"; };
87EEDFBD2B96F52D00C7FE6D /* LoxInstance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoxInstance.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -153,6 +156,7 @@
87BAFC482B9179CB0013E5FE /* LoxCallable.swift */,
8702307F2B96E9580056FE57 /* LoxClass.swift */,
87EEDFBD2B96F52D00C7FE6D /* LoxInstance.swift */,
87777E3B2BA0FF70002E38F2 /* LoxList.swift */,
876A315E2B897EEB0085A350 /* LoxValue.swift */,
870230742B9571490056FE57 /* MutableCollection+Extension.swift */,
8732838E2B93F89300E49035 /* NativeFunction.swift */,
Expand Down Expand Up @@ -275,6 +279,7 @@
buildActionMask = 2147483647;
files = (
876A31622B8986630085A350 /* ScanError.swift in Sources */,
87777E3C2BA0FF70002E38F2 /* LoxList.swift in Sources */,
876560052B8825AC002BDE42 /* Token.swift in Sources */,
873283942B95122800E49035 /* ResolvedStatement.swift in Sources */,
876A315F2B897EEB0085A350 /* LoxValue.swift in Sources */,
Expand Down Expand Up @@ -320,6 +325,7 @@
876A31602B89827B0085A350 /* LoxValue.swift in Sources */,
873CCB312B8EBB7800FC249A /* Statement.swift in Sources */,
8755B8B52B91984C00530DC4 /* LoxCallable.swift in Sources */,
87777E3D2BA1015F002E38F2 /* LoxList.swift in Sources */,
873283902B93FC0900E49035 /* NativeFunction.swift in Sources */,
8702307B2B95755E0056FE57 /* ResolvedExpression.swift in Sources */,
87C2F3742B91C2BA00126707 /* Return.swift in Sources */,
Expand Down
12 changes: 12 additions & 0 deletions slox/Environment.swift
Expand Up @@ -38,6 +38,18 @@ class Environment: Equatable {
throw RuntimeError.undefinedVariable(name)
}

func getValue(name: String) throws -> LoxValue {
if let value = values[name] {
return value
}

if let enclosingEnvironment {
return try enclosingEnvironment.getValue(name: name)
}

throw RuntimeError.undefinedVariable(name)
}

private func ancestor(depth: Int) throws -> Environment {
var i = 0
var ancestor: Environment = self
Expand Down
3 changes: 3 additions & 0 deletions slox/Expression.swift
Expand Up @@ -19,4 +19,7 @@ indirect enum Expression: Equatable {
case set(Expression, Token, Expression)
case this(Token)
case `super`(Token, Token)
case list([Expression])
case subscriptGet(Expression, Expression)
case subscriptSet(Expression, Expression, Expression)
}
117 changes: 85 additions & 32 deletions slox/Interpreter.swift
Expand Up @@ -8,26 +8,53 @@
import Foundation

class Interpreter {
static let standardLibrary = """
class List {
append(element) {
appendNative(this, element);
}
deleteAt(index) {
return deleteAtNative(this, index);
}
}
"""
var environment: Environment = Environment()

init() {
setUpGlobals()
}

private func prepareCode(source: String) throws -> [ResolvedStatement] {
var scanner = Scanner(source: source)
let tokens = try scanner.scanTokens()
var parser = Parser(tokens: tokens)
let statements = try parser.parse()
var resolver = Resolver()

return try resolver.resolve(statements: statements)
}

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

try! interpret(source: Self.standardLibrary)
}

func interpret(statements: [ResolvedStatement]) throws {
func interpret(source: String) throws {
let statements = try prepareCode(source: source)

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

func interpretRepl(statements: [ResolvedStatement]) throws -> LoxValue? {
func interpretRepl(source: String) throws -> LoxValue? {
let statements = try prepareCode(source: source)

for (i, statement) in statements.enumerated() {
if i == statements.endIndex-1, case .expression(let expr) = statement {
return try evaluate(expr: expr)
Expand Down Expand Up @@ -71,7 +98,9 @@ class Interpreter {
private func handleIfStatement(testExpr: ResolvedExpression,
consequentStmt: ResolvedStatement,
alternativeStmt: ResolvedStatement?) throws {
if isTruthy(value: try evaluate(expr: testExpr)) {
let value = try evaluate(expr: testExpr)

if value.isTruthy {
try execute(statement: consequentStmt)
} else if let alternativeStmt {
try execute(statement: alternativeStmt)
Expand Down Expand Up @@ -207,7 +236,7 @@ class Interpreter {
}

private func handleWhileStatement(expr: ResolvedExpression, stmt: ResolvedStatement) throws {
while isTruthy(value: try evaluate(expr: expr)) {
while try evaluate(expr: expr).isTruthy {
try execute(statement: stmt)
}
}
Expand Down Expand Up @@ -242,6 +271,14 @@ class Interpreter {
return try handleLambdaExpression(params: params, statements: statements)
case .super(let superToken, let methodToken, let depth):
return try handleSuperExpression(superToken: superToken, methodToken: methodToken, depth: depth)
case .list(let elements):
return try handleListExpression(elements: elements)
case .subscriptGet(let listExpr, let indexExpr):
return try handleSubscriptGetExpression(listExpr: listExpr, indexExpr: indexExpr)
case .subscriptSet(let listExpr, let indexExpr, let valueExpr):
return try handleSubscriptSetExpression(listExpr: listExpr,
indexExpr: indexExpr,
valueExpr: valueExpr)
}
}

Expand All @@ -256,7 +293,7 @@ class Interpreter {

return .number(-number)
case .bang:
return .boolean(!isTruthy(value: value))
return .boolean(!value.isTruthy)
default:
throw RuntimeError.unsupportedUnaryOperator
}
Expand Down Expand Up @@ -300,9 +337,9 @@ class Interpreter {

switch oper.type {
case .bangEqual:
return .boolean(!isEqual(leftValue: leftValue, rightValue: rightValue))
return .boolean(!leftValue.isEqual(to: rightValue))
case .equalEqual:
return .boolean(isEqual(leftValue: leftValue, rightValue: rightValue))
return .boolean(leftValue.isEqual(to: rightValue))
case .plus:
throw RuntimeError.binaryOperandsMustBeNumbersOrStrings
case .minus, .star, .slash, .greater, .greaterEqual, .less, .lessEqual:
Expand Down Expand Up @@ -330,13 +367,13 @@ class Interpreter {
let leftValue = try evaluate(expr: leftExpr)

if case .and = oper.type {
if !isTruthy(value: leftValue) {
if !leftValue.isTruthy {
return leftValue
} else {
return try evaluate(expr: rightExpr)
}
} else {
if isTruthy(value: leftValue) {
if leftValue.isTruthy {
return leftValue
} else {
return try evaluate(expr: rightExpr)
Expand Down Expand Up @@ -391,7 +428,7 @@ class Interpreter {

let propertyValue = try evaluate(expr: valueExpr)

instance.set(propertyName: propertyNameToken.lexeme, propertyValue: propertyValue)
try instance.set(propertyName: propertyNameToken.lexeme, propertyValue: propertyValue)
return propertyValue
}

Expand Down Expand Up @@ -427,31 +464,47 @@ class Interpreter {
throw RuntimeError.undefinedProperty(methodToken.lexeme)
}

// Utility functions below
private func isEqual(leftValue: LoxValue, rightValue: LoxValue) -> Bool {
switch (leftValue, rightValue) {
case (.nil, .nil):
return true
case (.number(let leftNumber), .number(let rightNumber)):
return leftNumber == rightNumber
case (.string(let leftString), .string(let rightString)):
return leftString == rightString
case (.boolean(let leftBoolean), .boolean(let rightBoolean)):
return leftBoolean == rightBoolean
default:
return false
private func handleListExpression(elements: [ResolvedExpression]) throws -> LoxValue {
let elementValues = try elements.map { element in
return try evaluate(expr: element)
}

guard case .instance(let listClass as LoxClass) = try environment.getValue(name: "List") else {
// TODO: Do we need throw an exception here?
fatalError()
}

let list = LoxList(elements: elementValues, klass: listClass)
return .instance(list)
}

// In Lox, `false` and `nil` are false; everything else is true
private func isTruthy(value: LoxValue) -> Bool {
switch value {
case .nil:
return false
case .boolean(let boolean):
return boolean
default:
return true
private func handleSubscriptGetExpression(listExpr: ResolvedExpression,
indexExpr: ResolvedExpression) throws -> LoxValue {
guard case .instance(let list as LoxList) = try evaluate(expr: listExpr) else {
throw RuntimeError.notAList
}

guard case .number(let index) = try evaluate(expr: indexExpr) else {
throw RuntimeError.indexMustBeANumber
}

return list[Int(index)]
}

private func handleSubscriptSetExpression(listExpr: ResolvedExpression,
indexExpr: ResolvedExpression,
valueExpr: ResolvedExpression) throws -> LoxValue {
guard case .instance(let list as LoxList) = try evaluate(expr: listExpr) else {
throw RuntimeError.notAList
}

guard case .number(let index) = try evaluate(expr: indexExpr) else {
throw RuntimeError.indexMustBeANumber
}

let value = try evaluate(expr: valueExpr)

list[Int(index)] = value
return value
}
}
16 changes: 2 additions & 14 deletions slox/Lox.swift
Expand Up @@ -42,13 +42,7 @@ struct Lox {
print("slox>", terminator: " ")
while let input = readLine() {
do {
var scanner = Scanner(source: input)
let tokens = try scanner.scanTokens()
var parser = Parser(tokens: tokens)
let statements = try parser.parse()
var resolver = Resolver()
let resolvedStatements = try resolver.resolve(statements: statements)
if let result = try interpreter.interpretRepl(statements: resolvedStatements) {
if let result = try interpreter.interpretRepl(source: input) {
print(result)
}
} catch {
Expand All @@ -60,12 +54,6 @@ struct Lox {
}

private static func run(input: String) throws {
var scanner = Scanner(source: input)
let tokens = try scanner.scanTokens()
var parser = Parser(tokens: tokens)
let statements = try parser.parse()
var resolver = Resolver()
let resolvedStatements = try resolver.resolve(statements: statements)
try interpreter.interpret(statements: resolvedStatements)
try interpreter.interpret(source: input)
}
}
2 changes: 1 addition & 1 deletion slox/LoxInstance.swift
Expand Up @@ -45,7 +45,7 @@ class LoxInstance: Equatable {
throw RuntimeError.undefinedProperty(propertyName)
}

func set(propertyName: String, propertyValue: LoxValue) {
func set(propertyName: String, propertyValue: LoxValue) throws {
self.properties[propertyName] = propertyValue
}

Expand Down
45 changes: 45 additions & 0 deletions slox/LoxList.swift
@@ -0,0 +1,45 @@
//
// LoxList.swift
// slox
//
// Created by Danielle Kefford on 3/12/24.
//

class LoxList: LoxInstance {
var elements: [LoxValue]
var count: Int {
return elements.count
}

init(elements: [LoxValue], klass: LoxClass) {
self.elements = elements
super.init(klass: klass)
}

override func get(propertyName: String) throws -> LoxValue {
switch propertyName {
case "count":
return .number(Double(elements.count))
default:
return try super.get(propertyName: propertyName)
}
}

override func set(propertyName: String, propertyValue: LoxValue) throws {
throw RuntimeError.onlyInstancesHaveProperties
}

static func == (lhs: LoxList, rhs: LoxList) -> Bool {
return lhs.elements == rhs.elements
}

// TODO: Need to think about how to handle invalid indices!!!
subscript(index: Int) -> LoxValue {
get {
return elements[index]
}
set(newValue) {
elements[index] = newValue
}
}
}

0 comments on commit 1cc1447

Please sign in to comment.