diff --git a/Sources/SwispFramework/Environment/Environment.swift b/Sources/SwispFramework/Environment/Environment.swift index a6ebbd8..2bb13e4 100644 --- a/Sources/SwispFramework/Environment/Environment.swift +++ b/Sources/SwispFramework/Environment/Environment.swift @@ -76,8 +76,8 @@ public let standardEnv = Env([ "tan": Math.tan, // Angular conversion - "degrees": Math.degrees, - "radians": Math.radians, + "degrees": Math.degrees, + "radians": Math.radians, // Hyperbolic functions "acosh": Math.acosh, @@ -91,7 +91,7 @@ public let standardEnv = Env([ "erf": Math.erf, "erfc": Math.erfc, "gamma": Math.gamma, - "lgamma": Math.lgamma, + "lgamma": Math.lgamma, // // Misc. "abs": Library.abs, @@ -103,16 +103,16 @@ public let standardEnv = Env([ // "cons": { [$0] + $1 }, // "eq?": { $0 === $1 }, // "equal?": { $0 == $1 }, - // "length": { $0.count }, - // "list": { List($0) }, - // "list?": { $0 is List }, + "length": Library.length, + "list": Library.list, + "list?": Library.isList, // // "map": map, // [TODO](https://www.weheartswift.com/higher-order-functions-map-filter-reduce-and-more/) - // "max": max, - // "min": min, + "max": Library.max, + "min": Library.min, "not": Library.not, // "null?": { $0 == nil }, - // "number?": { $0 is Number }, + "number?": Library.isNumber, // "procedure?": { String(type(of: $0)).containsString("->") }, - // "round": round, + "round": Library.round, // "symbol?": { $0 is Symbol } ] as [Symbol: Any]) diff --git a/Sources/SwispFramework/Environment/Library.swift b/Sources/SwispFramework/Environment/Library.swift index 68b6624..c232ee9 100644 --- a/Sources/SwispFramework/Environment/Library.swift +++ b/Sources/SwispFramework/Environment/Library.swift @@ -36,17 +36,17 @@ import Foundation // "cons": { [$0] + $1 }, // "eq?": { $0 === $1 }, // "equal?": { $0 == $1 }, - // "length": { $0.count }, - // "list": { List($0) }, - // "list?": { $0 is List }, + - `length` + - `list` + - `list?` // // "map": map, // [TODO](https://www.weheartswift.com/higher-order-functions-map-filter-reduce-and-more/) - // "max": max, - // "min": min, + - `max` + - `min` - `not` // "null?": { $0 == nil }, - // "number?": { $0 is Number }, + - `number?` // "procedure?": { String(type(of: $0)).containsString("->") }, - // "round": round, + - `round` // "symbol?": { $0 is Symbol } */ internal struct Library { @@ -107,6 +107,109 @@ internal struct Library { return Array(lis.dropFirst()) } + /** + Static function for `length` operation + */ + static func length(_ args: [Any]) throws -> Any? { + guard args.count == 1 else { + throw SwispError.SyntaxError(message: "invalid procedure input") + } + guard let lis = args[safe: 0] as? [Any] else { + throw SwispError.SyntaxError(message: "invalid procedure input") + } + return lis.count + } + + /** + Static function for `list` operation + */ + static func list(_ args: [Any]) throws -> Any? { + return args + } + + /** + Static function for `list?` operation + */ + static func isList(_ args: [Any]) throws -> Any? { + guard args.count == 1 else { + throw SwispError.SyntaxError(message: "invalid procedure input") + } + if (args[safe: 0] as? [Any]) != nil { + return true + } + return false + } + + /** + Static function for `max` operation + */ + static func max(_ args: [Any]) throws -> Any? { + // Check that there are inputs + guard args.count > 0 else { + throw SwispError.SyntaxError(message: "invalid procedure input") + } + + // Calculate maximum double and integer inputs + var tempDouble: Double = -Double.infinity + var tempInt: Int = Int.min + for arg in args { + switch (arg) { + case let (val as Double): + if (val > tempDouble) { + tempDouble = val + } + case let (val as Int): + if (val > tempInt) { + tempInt = val + } + default: + throw SwispError.SyntaxError(message: "invalid procedure input") + } + } + + // Find and keep type of maximum between the two maximums + if (tempDouble > Double(tempInt)) { + return tempDouble + } else { + return tempInt + } + } + + /** + Static function for `min` operation + */ + static func min(_ args: [Any]) throws -> Any? { + // Check that there are inputs + guard args.count > 0 else { + throw SwispError.SyntaxError(message: "invalid procedure input") + } + + // Calculate maximum double and integer inputs + var tempDouble: Double = Double.infinity + var tempInt: Int = Int.max + for arg in args { + switch (arg) { + case let (val as Double): + if (val < tempDouble) { + tempDouble = val + } + case let (val as Int): + if (val < tempInt) { + tempInt = val + } + default: + throw SwispError.SyntaxError(message: "invalid procedure input") + } + } + + // Find and keep type of maximum between the two maximums + if (tempDouble < Double(tempInt)) { + return tempDouble + } else { + return tempInt + } + } + /** Static function for `not` operation */ @@ -117,8 +220,10 @@ internal struct Library { switch (args[safe: 0]) { case let (val as Bool): return !val - case let (val as NSNumber): - return !Bool(truncating: val) + case let (val as Int): + return val == 0 + case let (val as Double): + return val == 0 case let (val as String): if let bool = Bool(val) { return !bool @@ -130,4 +235,38 @@ internal struct Library { } } + /** + Static function for `number?` operation + */ + static func isNumber(_ args: [Any]) throws -> Any? { + guard args.count == 1 else { + throw SwispError.SyntaxError(message: "invalid procedure input") + } + switch (args[safe: 0]) { + case (_ as Int): + return true + case (_ as Double): + return true + default: + return false + } + } + + /** + Static function for `round` operation + */ + static func round(_ args: [Any]) throws -> Any? { + guard args.count == 1 else { + throw SwispError.SyntaxError(message: "invalid procedure input") + } + switch (args[safe: 0]) { + case let (val as Int): + return val + case let (val as Double): + return Foundation.round(val) + default: + throw SwispError.SyntaxError(message: "invalid procedure input") + } + } + } diff --git a/Tests/SwispTests/EnvironmentTests.swift b/Tests/SwispTests/EnvironmentTests.swift index fc29191..b8d5f59 100644 --- a/Tests/SwispTests/EnvironmentTests.swift +++ b/Tests/SwispTests/EnvironmentTests.swift @@ -827,6 +827,289 @@ public class EnvironmentTests: XCTestCase { } } + /** + Tests our `length` function + */ + func testLength() { + var parsed: Any + + do { + parsed = try Interpreter.parse("(length (quote (1 2 3 4 5)))") + let result = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTAssertEqual(result as? Int, 5) + } catch { + XCTFail() + } + + do { + parsed = try Interpreter.parse("(length (quote (1 2 3)))") + let result = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTAssertEqual(result as? Int, 3) + } catch { + XCTFail() + } + + do { + parsed = try Interpreter.parse("(length (quote ()))") + let result = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTAssertEqual(result as? Int, 0) + } catch { + XCTFail() + } + + do { + parsed = try Interpreter.parse("(length 3)") + let _ = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTFail() + } catch { + XCTPass() + } + + do { + parsed = try Interpreter.parse("(length (1 2 3 ))") + let _ = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTFail() + } catch { + XCTPass() + } + } + + /** + Tests our `list` function + */ + func testList() { + var parsed: Any + + do { + parsed = try Interpreter.parse("(list 1 2 3 4 5)") + let result = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTAssertEqual(result as? [Int], [1,2,3,4,5]) + } catch { + XCTFail() + } + + do { + parsed = try Interpreter.parse("(list 1010101)") + let result = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTAssertEqual(result as? [Int], [1010101]) + } catch { + XCTFail() + } + + do { + parsed = try Interpreter.parse("(list (list 1 3) (list 2 4))") + let result = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTAssertEqual(result as? [[Int]], [[1,3], [2,4]]) + } catch { + XCTFail() + } + + do { + parsed = try Interpreter.parse("(list 1.0 2.0 3.0 4.0 5.0)") + let result = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTAssertEqual(result as? [Double], [1,2,3,4,5]) + } catch { + XCTFail() + } + + do { + parsed = try Interpreter.parse("(list (list 1.0 3.0) (list 2.0 4.0))") + let result = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTAssertEqual(result as? [[Double]], [[1,3], [2,4]]) + } catch { + XCTFail() + } + } + + /** + Tests our `list?` function + */ + func testIsList() { + var parsed: Any + + do { + parsed = try Interpreter.parse("(list? (quote (1 2 3 4 5)))") + let result = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTAssertEqual(result as? Bool, true) + } catch { + XCTFail() + } + + do { + parsed = try Interpreter.parse("(list? 1010101)") + let result = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTAssertEqual(result as? Bool, false) + } catch { + XCTFail() + } + + do { + parsed = try Interpreter.parse("(list? (quote ()))") + let result = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTAssertEqual(result as? Bool, true) + } catch { + XCTFail() + } + + do { + parsed = try Interpreter.parse("(list? foo bar)") + let _ = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTFail() + } catch { + XCTPass() + } + + do { + parsed = try Interpreter.parse("(list? (1 2 3))") + let _ = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTFail() + } catch { + XCTPass() + } + } + + /** + Tests our `max` function + */ + func testMax() { + var parsed: Any + + do { + parsed = try Interpreter.parse("(max -100.0 -298.0 -390 -3909 -309498 -1.0 -39)") + let result = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTAssertEqual(result as? Double, -1) + } catch { + XCTFail() + } + + do { + parsed = try Interpreter.parse("(max 1.0 2.0 3.0 4.0 5.0)") + let result = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTAssertEqual(result as? Double, 5.0) + } catch { + XCTFail() + } + + do { + parsed = try Interpreter.parse("(max 100.0 298 390.0 3909 309498 1.0 39)") + let result = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTAssertEqual(result as? Int, 309498) + } catch { + XCTFail() + } + + do { + parsed = try Interpreter.parse("(max 100 -298 -390 -3909 -309498 1 39)") + let result = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTAssertEqual(result as? Int, 100) + } catch { + XCTFail() + } + + do { + parsed = try Interpreter.parse("(max -100 -298 -390 -3909 -309498 -1 -39)") + let result = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTAssertEqual(result as? Int, -1) + } catch { + XCTFail() + } + + do { + parsed = try Interpreter.parse("(max (> 2 1))") + let _ = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTFail() + } catch { + XCTPass() + } + + do { + parsed = try Interpreter.parse("(max true false)") + let _ = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTFail() + } catch { + XCTPass() + } + + do { + parsed = try Interpreter.parse("(max $$$)") + let _ = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTFail() + } catch { + XCTPass() + } + } + + /** + Tests our `min` function + */ + func testMin() { + var parsed: Any + + do { + parsed = try Interpreter.parse("(min -100.0 -298.0 -390 -3909 -309498 -1.0 -39)") + let result = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTAssertEqual(result as? Int, -309498) + } catch { + XCTFail() + } + + do { + parsed = try Interpreter.parse("(min 1.0 2.0 3.0 4.0 5.0)") + let result = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTAssertEqual(result as? Double, 1.0) + } catch { + XCTFail() + } + + do { + parsed = try Interpreter.parse("(min 100.0 298 390.0 3909 309498 1.0 39)") + let result = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTAssertEqual(result as? Double, 1.0) + } catch { + XCTFail() + } + + do { + parsed = try Interpreter.parse("(min 100 -298 -390 -3909 -309498 1 39)") + let result = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTAssertEqual(result as? Int, -309498) + } catch { + XCTFail() + } + + do { + parsed = try Interpreter.parse("(min 100 298 390 3909 309498 1 39)") + let result = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTAssertEqual(result as? Int, 1) + } catch { + XCTFail() + } + + do { + parsed = try Interpreter.parse("(min (> 2 1))") + let _ = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTFail() + } catch { + XCTPass() + } + + do { + parsed = try Interpreter.parse("(min true false)") + let _ = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTFail() + } catch { + XCTPass() + } + + do { + parsed = try Interpreter.parse("(min $$$)") + let _ = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTFail() + } catch { + XCTPass() + } + } + /** Tests our `not` function */ @@ -870,7 +1153,7 @@ public class EnvironmentTests: XCTestCase { let result = try Interpreter.eval(parsed, with: &interpreter.globalEnv) XCTAssertEqual(result as? Bool, false) } catch { - XCTPass() + XCTFail() } do { @@ -889,6 +1172,124 @@ public class EnvironmentTests: XCTestCase { XCTPass() } } + + /** + Tests our `number?` function + */ + func testIsNumber() { + var parsed: Any + + do { + parsed = try Interpreter.parse("(number? true)") + let result = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTAssertEqual(result as? Bool, false) + } catch { + XCTFail() + } + + do { + parsed = try Interpreter.parse("(number? 12.4)") + let result = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTAssertEqual(result as? Bool, true) + } catch { + XCTFail() + } + + do { + parsed = try Interpreter.parse("(number? pi)") + let result = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTAssertEqual(result as? Bool, true) + } catch { + XCTFail() + } + + do { + parsed = try Interpreter.parse("(number? 3)") + let result = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTAssertEqual(result as? Bool, true) + } catch { + XCTFail() + } + + do { + parsed = try Interpreter.parse("(number? (list 1 2 3))") + let result = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTAssertEqual(result as? Bool, false) + } catch { + XCTFail() + } + + do { + parsed = try Interpreter.parse("(number? 1 2)") + let _ = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTFail() + } catch { + XCTPass() + } + + do { + parsed = try Interpreter.parse("(number? 1.0 1)") + let _ = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTFail() + } catch { + XCTPass() + } + } + + /** + Tests our `round` function + */ + func testRound() { + var parsed: Any + + do { + parsed = try Interpreter.parse("(round 3)") + let result = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTAssertEqual(result as? Int, 3) + } catch { + XCTFail() + } + + do { + parsed = try Interpreter.parse("(round 2.5)") + let result = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTAssertEqual(result as? Double, 3) + } catch { + XCTFail() + } + + do { + parsed = try Interpreter.parse("(round 2.1)") + let result = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTAssertEqual(result as? Double, 2) + } catch { + XCTFail() + } + + do { + parsed = try Interpreter.parse("(round -0.5)") + let result = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTAssertEqual(result as? Double, -1) + } catch { + XCTFail() + } + + do { + parsed = try Interpreter.parse("(round true)") + let _ = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTFail() + } catch { + XCTPass() + } + + do { + parsed = try Interpreter.parse("(round -0.5 0.5)") + let _ = try Interpreter.eval(parsed, with: &interpreter.globalEnv) + XCTFail() + } catch { + XCTPass() + } + } // MARK: - Math Testing diff --git a/Tests/SwispTests/XCTestManifests.swift b/Tests/SwispTests/XCTestManifests.swift index 6ddef72..6666630 100644 --- a/Tests/SwispTests/XCTestManifests.swift +++ b/Tests/SwispTests/XCTestManifests.swift @@ -66,7 +66,14 @@ extension EnvironmentTests { ("testAppend", testAppend), ("testCar", testCar), ("testCdr", testCdr), + ("testLength", testLength), + ("testList", testList), + ("testIsList", testIsList), + ("testMax", testMax), + ("testMin", testMin), ("testNot", testNot), + ("testIsNumber", testIsNumber), + ("testRound", testRound), ("testCeil", testCeil), ("testCopySign", testCopySign), ("testFabs", testFabs),