Skip to content

Commit

Permalink
Merge pull request #36 from quephird/implement_dictionary
Browse files Browse the repository at this point in the history
Implement dictionary
  • Loading branch information
quephird committed Apr 1, 2024
2 parents de0a24b + 1577cd6 commit 2aa42b9
Show file tree
Hide file tree
Showing 19 changed files with 597 additions and 67 deletions.
3 changes: 3 additions & 0 deletions README.md
Expand Up @@ -29,6 +29,9 @@ So far, the following have been implemented in `slox`:
- Native functions for lists, `append()` and `deleteAt()`
- `break` and `continue` for flow control within loops
- Computed properties (inside classes, not at the top-level)
- Dictionary literals using square brackets and colons
- Native properties for dictionaries, `keys` and `values`
- Native functions for dictionaries, `merge()` and `removeValue()`

# Design

Expand Down
12 changes: 12 additions & 0 deletions slox.xcodeproj/project.pbxproj
Expand Up @@ -15,6 +15,7 @@
8702307C2B9575610056FE57 /* ResolvedStatement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873283932B95122800E49035 /* ResolvedStatement.swift */; };
8702307E2B95AA2A0056FE57 /* ResolverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8702307D2B95AA2A0056FE57 /* ResolverTests.swift */; };
870230802B96E9580056FE57 /* LoxClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8702307F2B96E9580056FE57 /* LoxClass.swift */; };
8730DDE92BB250CE00372548 /* LoxDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8730DDE82BB250CE00372548 /* LoxDictionary.swift */; };
8732838F2B93F89300E49035 /* NativeFunction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8732838E2B93F89300E49035 /* NativeFunction.swift */; };
873283902B93FC0900E49035 /* NativeFunction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8732838E2B93F89300E49035 /* NativeFunction.swift */; };
873283922B95118A00E49035 /* ResolvedExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 873283912B95118A00E49035 /* ResolvedExpression.swift */; };
Expand All @@ -37,6 +38,7 @@
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 */; };
8764AB632BB8E5A7006D4B9D /* StandardLibrary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8792A9982BB36C66009842D8 /* StandardLibrary.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 @@ -54,9 +56,11 @@
877168C22B91A9BD00723543 /* JumpType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 877168C12B91A9BD00723543 /* JumpType.swift */; };
87777E3C2BA0FF70002E38F2 /* LoxList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87777E3B2BA0FF70002E38F2 /* LoxList.swift */; };
87777E3D2BA1015F002E38F2 /* LoxList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87777E3B2BA0FF70002E38F2 /* LoxList.swift */; };
8792A9992BB36C66009842D8 /* StandardLibrary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8792A9982BB36C66009842D8 /* StandardLibrary.swift */; };
87BAFC492B9179CB0013E5FE /* LoxCallable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87BAFC482B9179CB0013E5FE /* LoxCallable.swift */; };
87BAFC4B2B918C520013E5FE /* UserDefinedFunction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87BAFC4A2B918C520013E5FE /* UserDefinedFunction.swift */; };
87C2F3742B91C2BA00126707 /* JumpType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 877168C12B91A9BD00723543 /* JumpType.swift */; };
87CB69762BB35EBA002A2E69 /* LoxDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8730DDE82BB250CE00372548 /* LoxDictionary.swift */; };
87EEDFBE2B96F52D00C7FE6D /* LoxInstance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87EEDFBD2B96F52D00C7FE6D /* LoxInstance.swift */; };
87EEDFBF2B9920F900C7FE6D /* LoxClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8702307F2B96E9580056FE57 /* LoxClass.swift */; };
87EEDFC02B9920FD00C7FE6D /* LoxInstance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87EEDFBD2B96F52D00C7FE6D /* LoxInstance.swift */; };
Expand All @@ -79,6 +83,7 @@
870230762B9574A90056FE57 /* TestPlan.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = TestPlan.xctestplan; sourceTree = "<group>"; };
8702307D2B95AA2A0056FE57 /* ResolverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResolverTests.swift; sourceTree = "<group>"; };
8702307F2B96E9580056FE57 /* LoxClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoxClass.swift; sourceTree = "<group>"; };
8730DDE82BB250CE00372548 /* LoxDictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoxDictionary.swift; sourceTree = "<group>"; };
8732838E2B93F89300E49035 /* NativeFunction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeFunction.swift; sourceTree = "<group>"; };
873283912B95118A00E49035 /* ResolvedExpression.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResolvedExpression.swift; sourceTree = "<group>"; };
873283932B95122800E49035 /* ResolvedStatement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResolvedStatement.swift; sourceTree = "<group>"; };
Expand All @@ -104,6 +109,7 @@
876A31682B8C3AAB0085A350 /* ParseError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseError.swift; sourceTree = "<group>"; };
877168C12B91A9BD00723543 /* JumpType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JumpType.swift; sourceTree = "<group>"; };
87777E3B2BA0FF70002E38F2 /* LoxList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoxList.swift; sourceTree = "<group>"; };
8792A9982BB36C66009842D8 /* StandardLibrary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StandardLibrary.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 @@ -156,6 +162,7 @@
873CCB242B8D765D00FC249A /* Lox.swift */,
87BAFC482B9179CB0013E5FE /* LoxCallable.swift */,
8702307F2B96E9580056FE57 /* LoxClass.swift */,
8730DDE82BB250CE00372548 /* LoxDictionary.swift */,
87EEDFBD2B96F52D00C7FE6D /* LoxInstance.swift */,
87777E3B2BA0FF70002E38F2 /* LoxList.swift */,
876A315E2B897EEB0085A350 /* LoxValue.swift */,
Expand All @@ -170,6 +177,7 @@
873CCB222B8D617C00FC249A /* RuntimeError.swift */,
876A31612B8986630085A350 /* ScanError.swift */,
876560062B8827F9002BDE42 /* Scanner.swift */,
8792A9982BB36C66009842D8 /* StandardLibrary.swift */,
873CCB2F2B8EAEC100FC249A /* Statement.swift */,
876560042B8825AC002BDE42 /* Token.swift */,
876560022B882259002BDE42 /* TokenType.swift */,
Expand Down Expand Up @@ -293,6 +301,7 @@
876A31672B8C11810085A350 /* Parser.swift in Sources */,
876560072B8827F9002BDE42 /* Scanner.swift in Sources */,
87BAFC4B2B918C520013E5FE /* UserDefinedFunction.swift in Sources */,
8792A9992BB36C66009842D8 /* StandardLibrary.swift in Sources */,
873CCB332B8ED8B900FC249A /* Environment.swift in Sources */,
876A31652B8C04990085A350 /* Expression.swift in Sources */,
876560032B882259002BDE42 /* TokenType.swift in Sources */,
Expand All @@ -301,6 +310,7 @@
8732838F2B93F89300E49035 /* NativeFunction.swift in Sources */,
873283922B95118A00E49035 /* ResolvedExpression.swift in Sources */,
876A31692B8C3AAB0085A350 /* ParseError.swift in Sources */,
8730DDE92BB250CE00372548 /* LoxDictionary.swift in Sources */,
870230752B9571490056FE57 /* MutableCollection+Extension.swift in Sources */,
873CCB252B8D765D00FC249A /* Lox.swift in Sources */,
);
Expand All @@ -310,6 +320,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
8764AB632BB8E5A7006D4B9D /* StandardLibrary.swift in Sources */,
8702307C2B9575610056FE57 /* ResolvedStatement.swift in Sources */,
873CCB2D2B8E88C900FC249A /* Interpreter.swift in Sources */,
876A31632B8987740085A350 /* ScanError.swift in Sources */,
Expand All @@ -325,6 +336,7 @@
876A31602B89827B0085A350 /* LoxValue.swift in Sources */,
873CCB312B8EBB7800FC249A /* Statement.swift in Sources */,
8755B8B52B91984C00530DC4 /* LoxCallable.swift in Sources */,
87CB69762BB35EBA002A2E69 /* LoxDictionary.swift in Sources */,
87777E3D2BA1015F002E38F2 /* LoxList.swift in Sources */,
873283902B93FC0900E49035 /* NativeFunction.swift in Sources */,
8702307B2B95755E0056FE57 /* ResolvedExpression.swift in Sources */,
Expand Down
52 changes: 52 additions & 0 deletions slox/Expression.swift
Expand Up @@ -22,4 +22,56 @@ indirect enum Expression: Equatable {
case list([Expression])
case subscriptGet(Expression, Expression)
case subscriptSet(Expression, Expression, Expression)
case dictionary([(Expression, Expression)])

static func == (lhs: Expression, rhs: Expression) -> Bool {
switch (lhs, rhs) {
case (.binary(let lhsExpr1, let lhsOper, let lhsExpr2), .binary(let rhsExpr1, let rhsOper, let rhsExpr2)):
return lhsExpr1 == rhsExpr1 && lhsOper == rhsOper && lhsExpr2 == rhsExpr2
case (.unary(let lhsOper, let lhsExpr), .unary(let rhsOper, let rhsExpr)):
return lhsOper == rhsOper && lhsExpr == rhsExpr
case (.literal(let lhsValue), .literal(let rhsValue)):
return lhsValue == rhsValue
case (.grouping(let lhsExpr), .grouping(let rhsExpr)):
return lhsExpr == rhsExpr
case (.variable(let lhsToken), .variable(let rhsToken)):
return lhsToken == rhsToken
case (.assignment(let lhsName, let lhsExpr), .assignment(let rhsName, let rhsExpr)):
return lhsName == rhsName && lhsExpr == rhsExpr
case (.logical(let lhsExpr1, let lhsOper, let lhsExpr2), .logical(let rhsExpr1, let rhsOper, let rhsExpr2)):
return lhsExpr1 == rhsExpr1 && lhsOper == rhsOper && lhsExpr2 == rhsExpr2
case (.call(let lhsCallee, let lhsToken, let lhsArgs), .call(let rhsCallee, let rhsToken, let rhsArgs)):
return lhsCallee == rhsCallee && lhsToken == rhsToken && lhsArgs == rhsArgs
case (.lambda(let lhsParams, let lhsBody), .lambda(let rhsParams, let rhsBody)):
return lhsParams == rhsParams && lhsBody == rhsBody
case (.get(let lhsExpr, let lhsName), .get(let rhsExpr, let rhsName)):
return lhsExpr == rhsExpr && lhsName == rhsName
case (.set(let lhsExpr1, let lhsName, let lhsExpr2), .set(let rhsExpr1, let rhsName, let rhsExpr2)):
return lhsExpr1 == rhsExpr1 && lhsName == rhsName && lhsExpr2 == rhsExpr2
case (.this(let lhsToken), .this(let rhsToken)):
return lhsToken == rhsToken
case (.super(let lhsSuper, let lhsMethod), .super(let rhsSuper, let rhsMethod)):
return lhsSuper == rhsSuper && lhsMethod == rhsMethod
case (.list(let lhsExprs), .list(let rhsExprs)):
return lhsExprs == rhsExprs
case (.subscriptGet(let lhsList, let lhsIdx), .subscriptGet(let rhsList, let rhsIdx)):
return lhsList == rhsList && lhsIdx == rhsIdx
case (.subscriptSet(let lhsList, let lhsIdx, let lhsExpr), .subscriptSet(let rhsList, let rhsIdx, let rhsExpr)):
return lhsList == rhsList && lhsIdx == rhsIdx && lhsExpr == rhsExpr
case (.dictionary(let lhsKVPairs), .dictionary(let rhsKVPairs)):
if lhsKVPairs.count != rhsKVPairs.count {
return false
}

for ((lhsKey, lhsValue), (rhsKey, rhsValue)) in zip(lhsKVPairs, rhsKVPairs) {
if lhsKey != rhsKey || lhsValue != rhsValue {
return false
}
}

return true
default:
return false
}
}
}
118 changes: 61 additions & 57 deletions slox/Interpreter.swift
Expand Up @@ -8,44 +8,6 @@
import Foundation

class Interpreter {
static let standardLibrary = """
class List {
append(element) {
appendNative(this, element);
}
deleteAt(index) {
return deleteAtNative(this, index);
}
map(fn) {
var result = [];
for (var i = 0; i < this.count; i = i + 1) {
var newElement = fn(this[i]);
result.append(newElement);
}
return result;
}
filter(fn) {
var result = [];
for (var i = 0; i < this.count; i = i + 1) {
if (fn(this[i])) {
result.append(this[i]);
}
}
return result;
}
reduce(initial, fn) {
var result = initial;
for (var i = 0; i < this.count; i = i + 1) {
result = fn(result, this[i]);
}
return result;
}
}
"""
var environment: Environment = Environment()

init() {
Expand All @@ -68,7 +30,7 @@ class Interpreter {
value: .nativeFunction(nativeFunction))
}

try! interpret(source: Self.standardLibrary)
try! interpret(source: standardLibrary)
}

func interpret(source: String) throws {
Expand Down Expand Up @@ -350,11 +312,13 @@ class Interpreter {
case .list(let elements):
return try handleListExpression(elements: elements)
case .subscriptGet(let listExpr, let indexExpr):
return try handleSubscriptGetExpression(listExpr: listExpr, indexExpr: indexExpr)
return try handleSubscriptGetExpression(collectionExpr: listExpr, indexExpr: indexExpr)
case .subscriptSet(let listExpr, let indexExpr, let valueExpr):
return try handleSubscriptSetExpression(listExpr: listExpr,
return try handleSubscriptSetExpression(collectionExpr: listExpr,
indexExpr: indexExpr,
valueExpr: valueExpr)
case .dictionary(let kvPairs):
return try handleDictionary(kvExprPairs: kvPairs)
}
}

Expand Down Expand Up @@ -595,34 +559,65 @@ class Interpreter {
return try makeList(elements: elementValues)
}

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

guard case .int(let index) = try evaluate(expr: indexExpr) else {
throw RuntimeError.indexMustBeAnInteger
}
switch collection {
case .instance(let list as LoxList):
guard case .int(let index) = try evaluate(expr: indexExpr) else {
throw RuntimeError.indexMustBeAnInteger
}

return list[Int(index)]
case .instance(let dictionary as LoxDictionary):
let key = try evaluate(expr: indexExpr)

return list[Int(index)]
return dictionary[key]
default:
throw RuntimeError.notAListOrDictionary
}
}

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

switch collection {
case .instance(let list as LoxList):
guard case .int(let index) = try evaluate(expr: indexExpr) else {
throw RuntimeError.indexMustBeAnInteger
}

list[Int(index)] = value
case .instance(let dictionary as LoxDictionary):
let key = try evaluate(expr: indexExpr)

dictionary[key] = value
default:
throw RuntimeError.notAListOrDictionary
}

guard case .int(let index) = try evaluate(expr: indexExpr) else {
throw RuntimeError.indexMustBeAnInteger
return value
}

private func handleDictionary(kvExprPairs: [(ResolvedExpression, ResolvedExpression)]) throws -> LoxValue {
var kvPairs: [LoxValue: LoxValue] = [:]

for (keyExpr, valueExpr) in kvExprPairs {
let key = try evaluate(expr: keyExpr)
let value = try evaluate(expr: valueExpr)
kvPairs[key] = value
}

let value = try evaluate(expr: valueExpr)
guard case .instance(let dictionaryClass as LoxClass) = try environment.getValue(name: "Dictionary") else {
fatalError()
}

list[Int(index)] = value
return value
let dictionary = LoxDictionary(kvPairs: kvPairs, klass: dictionaryClass)
return .instance(dictionary)
}

func makeList(elements: [LoxValue]) throws -> LoxValue {
Expand All @@ -633,4 +628,13 @@ class Interpreter {
let list = LoxList(elements: elements, klass: listClass)
return .instance(list)
}

func makeDictionary(kvPairs: [LoxValue: LoxValue]) throws -> LoxValue {
guard case .instance(let dictionaryClass as LoxClass) = try environment.getValue(name: "Dictionary") else {
fatalError()
}

let dictionary = LoxDictionary(kvPairs: kvPairs, klass: dictionaryClass)
return .instance(dictionary)
}
}
2 changes: 2 additions & 0 deletions slox/LoxClass.swift
Expand Up @@ -19,6 +19,8 @@ class LoxClass: LoxInstance, LoxCallable {
var instanceType: LoxInstance.Type {
if self.name == "List" {
LoxList.self
} else if self.name == "Dictionary" {
LoxDictionary.self
} else {
LoxInstance.self
}
Expand Down
42 changes: 42 additions & 0 deletions slox/LoxDictionary.swift
@@ -0,0 +1,42 @@
//
// LoxDictionary.swift
// slox
//
// Created by Danielle Kefford on 3/25/24.
//

class LoxDictionary: LoxInstance {
var kvPairs: [LoxValue: LoxValue]

convenience init(kvPairs: [LoxValue: LoxValue], klass: LoxClass) {
self.init(klass: klass)
self.kvPairs = kvPairs
}

required init(klass: LoxClass?) {
self.kvPairs = [:]
super.init(klass: klass)
}

override func get(propertyName: String) throws -> LoxValue {
switch propertyName {
case "count":
return .int(self.kvPairs.count)
default:
return try super.get(propertyName: propertyName)
}
}

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

subscript(key: LoxValue) -> LoxValue {
get {
return kvPairs[key] ?? .nil
}
set(newValue) {
kvPairs[key] = newValue
}
}
}

0 comments on commit 2aa42b9

Please sign in to comment.