Skip to content

Commit ab08323

Browse files
committed
- added binary mathematical functions to kalk
- included tests
1 parent c4c5576 commit ab08323

File tree

8 files changed

+166
-18
lines changed

8 files changed

+166
-18
lines changed

Common/Analyzer/CyclicReferenceIdentifier.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,7 @@ fileprivate struct JohnsonCircuitFindingAlgorithm {
293293
var blocked = [Bool](repeatElement(false, count: graph.vertices.count))
294294
var s = 0
295295
var stack = [Int]()
296+
var sanity = 0
296297
func circuit(_ v: Int) -> Bool {
297298
func unblock(_ u: Int) {
298299
blocked[u] = false
@@ -303,7 +304,12 @@ fileprivate struct JohnsonCircuitFindingAlgorithm {
303304
}
304305
}
305306
}
306-
var f = false
307+
// added a sanity check because this thing would just run in circles ...
308+
sanity = sanity + 1
309+
if sanity > Tracery.maxStackDepth {
310+
return true
311+
}
312+
var f = false
307313
stack.append(v)
308314
blocked[v] = true
309315
for w in graph.successors(of: graph.getVertex(index: v)!) {

Common/Kalk/Kalk.Interpreter.swift

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,31 +15,64 @@ public class KalkInterpreter {
1515
public static func getDouble(value: Double) -> KalkValue {
1616
return KalkValue.double(value: value)
1717
}
18-
public static func getFunction(name: String) -> KalkFunction? {
19-
if let f = functions[name] {
18+
public static func getInt(value: Int) -> KalkValue {
19+
return KalkValue.int(value: value)
20+
}
21+
public static func getUnaryFunction(name: String) -> KalkUnaryFunction? {
22+
if let f = unaryFunctions[name] {
23+
return f
24+
}
25+
return nil
26+
}
27+
public static func getBinaryFunction(name: String) -> KalkBinaryFunction? {
28+
if let f = binaryFunctions[name] {
2029
return f
2130
}
2231
return nil
2332
}
2433
static func registerUnaryFunction(_ name: String, _ function: @escaping (KalkValue) -> KalkValue) {
25-
functions[name] = KalkFunction(name: name, callback: function)
34+
unaryFunctions[name] = KalkUnaryFunction(name: name, callback: function)
35+
}
36+
static func registerBinaryFunction(_ name: String, _ function: @escaping (KalkValue, KalkValue) -> KalkValue) {
37+
binaryFunctions[name] = KalkBinaryFunction(name: name, callback: function)
2638
}
2739

28-
public static var functions : [String : KalkFunction] = [:]
40+
public static var unaryFunctions : [String : KalkUnaryFunction] = [:]
41+
public static var binaryFunctions : [String : KalkBinaryFunction] = [:]
2942

30-
public static func KalkSin(_ value: KalkValue) -> KalkValue { return getDouble(value: sin(value.getDouble()!)) }
31-
public static func KalkCos(_ value: KalkValue) -> KalkValue { return getDouble(value: cos(value.getDouble()!)) }
32-
public static func KalkRand(_ value: KalkValue) -> KalkValue { return getDouble(value:
33-
(Double(arc4random()) / Double(UINT32_MAX)) * value.getDouble()!) }
43+
public static func KalkSin(_ value: KalkValue) -> KalkValue {
44+
return getDouble(value: sin(value.getDouble()!))
45+
}
46+
public static func KalkCos(_ value: KalkValue) -> KalkValue {
47+
return getDouble(value: cos(value.getDouble()!))
48+
}
49+
public static func KalkRand(_ value: KalkValue) -> KalkValue {
50+
return getDouble(value:
51+
(Double(arc4random()) / Double(UINT32_MAX)) * value.getDouble()!)
52+
}
53+
public static func KalkInt(_ value: KalkValue) -> KalkValue {
54+
return getInt(value: value.getInt()!)
55+
}
3456

3557
public static func registerBuiltins() {
3658
registerUnaryFunction("sin", KalkSin)
3759
registerUnaryFunction("cos", KalkCos)
3860
registerUnaryFunction("rand", KalkRand)
61+
registerUnaryFunction("int", KalkInt)
3962

4063
registerUnaryFunction("sqrt") { value -> KalkValue in
4164
return getDouble(value:sqrt(value.getDouble()!))
4265
}
66+
registerUnaryFunction("sqr") { value -> KalkValue in
67+
return getDouble(value:pow(value.getDouble()!, 2.0))
68+
}
69+
registerBinaryFunction("pow") { (value,value2) -> KalkValue in
70+
return getDouble(value:pow(value.getDouble()!, value2.getDouble()!))
71+
}
72+
registerBinaryFunction("rand") { (value,value2) -> KalkValue in
73+
return getDouble(value: value.getDouble()! +
74+
(Double(arc4random()) / Double(UINT32_MAX)) * (value2.getDouble()!-value.getDouble()!))
75+
}
4376
}
4477

4578
public static func interpret(_ string: String) -> String {
@@ -52,7 +85,8 @@ public class KalkInterpreter {
5285
let nodes = try parser.parse()
5386
nodes.forEach {
5487
if let n = $0 as? KalkExprNode {
55-
expandedString += String(n.evaluate.getDouble()!)
88+
// expandedString += String(n.evaluate.getDouble()!)
89+
expandedString += String(n.evaluate.getString()!)
5690
}
5791
}
5892
} catch {

Common/Kalk/Kalk.Nodes.swift

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,12 @@ public struct KalkNumberNode: KalkExprNode {
2727
public var description: String {
2828
return "NumberNode(\(value))"
2929
}
30-
public var evaluate: KalkValue { return KalkInterpreter.getDouble(value:value) }
30+
public var evaluate: KalkValue {
31+
if value == floor(value) {
32+
return KalkInterpreter.getInt(value: Int(value))
33+
}
34+
return KalkInterpreter.getDouble(value:value)
35+
}
3136
}
3237

3338
//public struct KalkColourNode: KalkBaseNode {
@@ -84,7 +89,17 @@ public struct KalkCallNode: KalkExprNode {
8489
return "CallNode(name: \(callee), argument: \(arguments))"
8590
}
8691
public var evaluate: KalkValue {
87-
return KalkInterpreter.getFunction(name: callee)?.eval(arguments[0].evaluate) ?? KalkInterpreter.getDouble(value:-1.0)
92+
93+
if let binary = KalkInterpreter.getBinaryFunction(name: callee) {
94+
if arguments.count == 2 {
95+
return binary.eval(arguments[0].evaluate, arguments[1].evaluate)
96+
}
97+
}
98+
if let unary = KalkInterpreter.getUnaryFunction(name: callee) {
99+
return unary.eval(arguments[0].evaluate)
100+
}
101+
102+
return KalkInterpreter.getDouble(value:-1.0)
88103
}
89104
}
90105

@@ -105,3 +120,4 @@ public struct KalkFunctionNode: KalkExprNode {
105120
}
106121
public var evaluate: KalkValue { return KalkInterpreter.getDouble(value:0.0) }
107122
}
123+

Common/Kalk/Kalk.SSA.swift

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88

99
import Foundation
1010

11-
public class KalkFunction {
11+
public class KalkUnaryFunction {
1212
public var name: String
1313
public var callback: (KalkValue) -> KalkValue
14-
14+
1515
init (name: String, callback: @escaping (KalkValue) -> KalkValue) {
1616
self.name = name
1717
self.callback = callback
@@ -22,20 +22,58 @@ public class KalkFunction {
2222
}
2323
}
2424

25+
public class KalkBinaryFunction {
26+
public var name: String
27+
public var callbackBinary: (KalkValue, KalkValue) -> KalkValue
28+
29+
init (name: String, callback: @escaping (KalkValue, KalkValue) -> KalkValue) {
30+
self.name = name
31+
self.callbackBinary = callback
32+
}
33+
34+
public func eval(_ value:KalkValue, _ value2: KalkValue) -> KalkValue {
35+
return callbackBinary(value, value2)
36+
}
37+
}
38+
39+
2540
public enum KalkValue {
2641
case double(value: Double)
2742
case int(value: Int)
2843
case string(value: String)
29-
case function(func: KalkFunction)
44+
case function(func: KalkUnaryFunction)
3045

3146
public func getDouble() -> Double? {
3247
switch self {
3348
case let .double(value):
3449
return value
50+
case let .int(value):
51+
return Double(value)
52+
default:
53+
return nil
54+
}
55+
}
56+
public func getInt() -> Int? {
57+
switch self {
58+
case let .int(value):
59+
return value
60+
case let .double(value):
61+
return Int(value)
62+
default:
63+
return nil
64+
}
65+
}
66+
public func getString() -> String? {
67+
switch self {
68+
case let .int(value):
69+
return String(value)
70+
case let .double(value):
71+
return String(value)
72+
case let .string(value):
73+
return value
3574
default:
3675
return nil
3776
}
3877
}
39-
4078

4179
}

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ t.expand("There once was a man named #city.reverse.title#, who came from the cit
285285
- brackets for grouping: `()`
286286
- complex unary oprations: `sin(rad)`, `cos(rad)`, `rand(range)`, `sqrt(value)`
287287

288-
Due to how the parsing consumes brackets only bracket-less operations can be performed directly. Otherwise the syntax is as follows:
288+
Bracket-less operations can be performed directly, e.g. `#.k(1+3)#` works. In case of calling a function like `sin` and `rand`, due to the parsing method, the syntax is as follows:
289289

290290
```
291291
[origin]

Testing/Kalk.swift

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//
2+
// Kalk.swift
3+
// Tracery
4+
//
5+
// Created by Martin Pichlmair on 07/02/2019.
6+
// Copyright © 2019 Benzi Ahamed. All rights reserved.
7+
//
8+
9+
import XCTest
10+
@testable import Tracery
11+
12+
class Kalk: XCTestCase {
13+
14+
override func setUp() {
15+
// Put setup code here. This method is called before the invocation of each test method in the class.
16+
}
17+
18+
override func tearDown() {
19+
// Put teardown code here. This method is called after the invocation of each test method in the class.
20+
}
21+
22+
func testKalk() {
23+
let t = Tracery()
24+
t.add(object: "2+2", named: "kalk")
25+
t.add(object: "int(2.0)", named: "kalk2")
26+
t.add(object: "int(sqr(2.0))", named: "kalkSqr")
27+
t.add(object: "int(pow(2.0,3.0))", named: "kalkPow")
28+
t.add(object: "int(rand(3.0,10.0))", named: "kalkRand2")
29+
t.add(object: "int(rand(10,30))", named: "kalkRand3")
30+
XCTAssertEqual(t.expand("#.k(#kalk#)#"), "4.0")
31+
XCTAssertEqual(t.expand("#.k(#kalk2#)#"), "2")
32+
XCTAssertEqual(t.expand("#.k(#kalkSqr#)#"), "4")
33+
XCTAssertEqual(t.expand("#.k(#kalkPow#)#"), "8")
34+
XCTAssertLessThan(Double(t.expand("#.k(#kalkRand2#)#"))!, 10.0)
35+
XCTAssertGreaterThan(Double(t.expand("#.k(#kalkRand2#)#"))!, 2.99)
36+
XCTAssertLessThan(Double(t.expand("#.k(#kalkRand3#)#"))!, 30.0)
37+
XCTAssertGreaterThan(Double(t.expand("#.k(#kalkRand3#)#"))!, 9.99)
38+
}
39+
}

Testing/RecursiveRules.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ class RecursiveRules: XCTestCase {
1515

1616
override func setUp() {
1717
limit = Tracery.maxStackDepth
18-
Tracery.maxStackDepth = 20
18+
19+
// disable so we can test actual case instead of fake:
20+
// Tracery.maxStackDepth = 20
1921
}
2022

2123
override func tearDown() {
@@ -29,4 +31,11 @@ class RecursiveRules: XCTestCase {
2931
]}
3032
XCTAssertTrue(t.expand("#a#").contains("stack overflow"))
3133
}
34+
35+
func testStackOverflow2() {
36+
let t = Tracery {[
37+
"a": "#a# -- #a#",
38+
]}
39+
XCTAssertTrue(t.expand("#a#").contains("stack overflow"))
40+
}
3241
}

Tracery/Tracery.xcodeproj/project.pbxproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
0E48C452220C424C00858E44 /* Kalk.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E48C451220C424C00858E44 /* Kalk.swift */; };
11+
0E48C453220C424C00858E44 /* Kalk.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E48C451220C424C00858E44 /* Kalk.swift */; };
1012
0E71B8372181CF70008F829A /* RandomSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E71B8362181CF70008F829A /* RandomSource.swift */; };
1113
0E71B8382181D220008F829A /* RandomSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E71B8362181CF70008F829A /* RandomSource.swift */; };
1214
0E77FD3921E4F893002D45C8 /* Tracery.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E77FD3721E4F877002D45C8 /* Tracery.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -173,6 +175,7 @@
173175
/* End PBXCopyFilesBuildPhase section */
174176

175177
/* Begin PBXFileReference section */
178+
0E48C451220C424C00858E44 /* Kalk.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Kalk.swift; sourceTree = "<group>"; };
176179
0E71B8362181CF70008F829A /* RandomSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RandomSource.swift; sourceTree = "<group>"; };
177180
0E77FD3721E4F877002D45C8 /* Tracery.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Tracery.h; sourceTree = "<group>"; };
178181
0E79B9E6216CA65000359AB3 /* Tracery.JSON.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Tracery.JSON.swift; sourceTree = "<group>"; };
@@ -355,6 +358,7 @@
355358
B9B9CC411E882E260080881D /* Objects.swift */,
356359
0E79B9EE216CA6B500359AB3 /* Modifiers.swift */,
357360
0E9D3C3C21875B9900C77E64 /* RandomSources.swift */,
361+
0E48C451220C424C00858E44 /* Kalk.swift */,
358362
);
359363
name = Testing;
360364
path = ../Testing;
@@ -689,6 +693,7 @@
689693
B912804E1E77B0530063D5A8 /* TraceryioSamples.swift in Sources */,
690694
B9B9CC431E882E260080881D /* Objects.swift in Sources */,
691695
B912804D1E77B0530063D5A8 /* Tags.swift in Sources */,
696+
0E48C453220C424C00858E44 /* Kalk.swift in Sources */,
692697
B91280451E77B0530063D5A8 /* ExtensionMethod.swift in Sources */,
693698
B91280431E77B0530063D5A8 /* ErrorMessages.swift in Sources */,
694699
B91280461E77B0530063D5A8 /* ExtensionModifier.swift in Sources */,
@@ -787,6 +792,7 @@
787792
B912803E1E77B0520063D5A8 /* TraceryioSamples.swift in Sources */,
788793
B9B9CC421E882E260080881D /* Objects.swift in Sources */,
789794
B912803D1E77B0520063D5A8 /* Tags.swift in Sources */,
795+
0E48C452220C424C00858E44 /* Kalk.swift in Sources */,
790796
B91280351E77B0520063D5A8 /* ExtensionMethod.swift in Sources */,
791797
B91280331E77B0520063D5A8 /* ErrorMessages.swift in Sources */,
792798
B91280361E77B0520063D5A8 /* ExtensionModifier.swift in Sources */,

0 commit comments

Comments
 (0)