trackler 2.1.0.26 → 2.1.0.27
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/common/CONTRIBUTING.md +0 -15
- data/common/exercises/secret-handshake/canonical-data.json +14 -7
- data/lib/trackler/version.rb +1 -1
- data/tracks/dlang/config.json +18 -0
- data/tracks/dlang/exercises/leap/leap.d +8 -0
- data/tracks/dlang/exercises/leap/leap_example.d +15 -0
- data/tracks/dlang/exercises/react/react.d +211 -0
- data/tracks/dlang/exercises/react/react_example.d +291 -0
- data/tracks/factor/README.md +7 -7
- data/tracks/factor/config.json +13 -4
- data/tracks/factor/exercises/hello-world/hello-world-example.factor +2 -3
- data/tracks/factor/exercises/hello-world/hello-world-tests.factor +1 -5
- data/tracks/factor/exercises/hello-world/hello-world.factor +1 -1
- data/tracks/factor/exercises/two-fer/two-fer-example.factor +6 -0
- data/tracks/factor/exercises/two-fer/two-fer-tests.factor +8 -0
- data/tracks/fortran/Makefile +1 -2
- data/tracks/fortran/config.json +6 -0
- data/tracks/fortran/exercises/bob/bob.fun +18 -18
- data/tracks/fortran/exercises/hamming/example.f90 +25 -0
- data/tracks/fortran/exercises/hamming/hamming.fun +75 -0
- data/tracks/go/exercises/poker/.meta/gen.go +76 -0
- data/tracks/go/exercises/poker/cases_test.go +240 -0
- data/tracks/go/exercises/poker/example.go +10 -1
- data/tracks/go/exercises/poker/poker_test.go +1 -224
- data/tracks/go/exercises/sum-of-multiples/.meta/gen.go +43 -0
- data/tracks/go/exercises/sum-of-multiples/cases_test.go +24 -0
- data/tracks/go/exercises/sum-of-multiples/example.go +1 -1
- data/tracks/go/exercises/sum-of-multiples/sum_of_multiples_test.go +1 -18
- data/tracks/groovy/exercises/robot-name/RobotSpec.groovy +38 -0
- data/tracks/kotlin/config.json +10 -0
- data/tracks/kotlin/exercises/collatz-conjecture/build.gradle +28 -0
- data/tracks/kotlin/exercises/collatz-conjecture/src/example/kotlin/CollatzCalculator.kt +12 -0
- data/tracks/kotlin/exercises/collatz-conjecture/src/main/kotlin/.keep +0 -0
- data/tracks/kotlin/exercises/collatz-conjecture/src/test/kotlin/CollatzCalculatorTest.kt +51 -0
- data/tracks/kotlin/exercises/diamond/build.gradle +28 -0
- data/tracks/kotlin/exercises/diamond/src/example/kotlin/DiamondPrinter.kt +35 -0
- data/tracks/kotlin/exercises/diamond/src/main/kotlin/DiamondPrinter.kt +5 -0
- data/tracks/kotlin/exercises/diamond/src/test/kotlin/DiamondPrinterTest.kt +121 -0
- data/tracks/kotlin/exercises/settings.gradle +2 -0
- data/tracks/ocaml/.gitignore +1 -0
- data/tracks/ocaml/.vscode/launch.json +14 -0
- data/tracks/ocaml/tools/test-generator/src/controller.ml +15 -16
- data/tracks/ocaml/tools/test-generator/src/languages.ml +30 -0
- data/tracks/ocaml/tools/test-generator/src/ocaml_special_cases.ml +137 -0
- data/tracks/ocaml/tools/test-generator/src/purescript_special_cases.ml +13 -0
- data/tracks/ocaml/tools/test-generator/src/special_cases.ml +7 -132
- data/tracks/ocaml/tools/test-generator/src/template.ml +3 -3
- data/tracks/ocaml/tools/test-generator/src/test_gen.ml +13 -3
- data/tracks/ocaml/tools/test-generator/templates/{acronym → ocaml/acronym}/template.ml +0 -0
- data/tracks/ocaml/tools/test-generator/templates/{all-your-base → ocaml/all-your-base}/template.ml +0 -0
- data/tracks/ocaml/tools/test-generator/templates/{anagram → ocaml/anagram}/template.ml +0 -0
- data/tracks/ocaml/tools/test-generator/templates/{atbash-cipher → ocaml/atbash-cipher}/template.ml +0 -0
- data/tracks/ocaml/tools/test-generator/templates/{beer-song → ocaml/beer-song}/template.ml +0 -0
- data/tracks/ocaml/tools/test-generator/templates/{binary-search → ocaml/binary-search}/template.ml +0 -0
- data/tracks/ocaml/tools/test-generator/templates/{bob → ocaml/bob}/template.ml +0 -0
- data/tracks/ocaml/tools/test-generator/templates/{bowling → ocaml/bowling}/template.ml +0 -0
- data/tracks/ocaml/tools/test-generator/templates/{bracket-push → ocaml/bracket-push}/template.ml +0 -0
- data/tracks/ocaml/tools/test-generator/templates/{change → ocaml/change}/template.ml +0 -0
- data/tracks/ocaml/tools/test-generator/templates/{connect → ocaml/connect}/template.ml +0 -0
- data/tracks/ocaml/tools/test-generator/templates/{difference-of-squares → ocaml/difference-of-squares}/template.ml +0 -0
- data/tracks/ocaml/tools/test-generator/templates/{dominoes → ocaml/dominoes}/template.ml +0 -0
- data/tracks/ocaml/tools/test-generator/templates/{etl → ocaml/etl}/template.ml +0 -0
- data/tracks/ocaml/tools/test-generator/templates/{forth → ocaml/forth}/template.ml +0 -0
- data/tracks/ocaml/tools/test-generator/templates/{hamming → ocaml/hamming}/template.ml +0 -0
- data/tracks/ocaml/tools/test-generator/templates/{hello-world → ocaml/hello-world}/template.ml +0 -0
- data/tracks/ocaml/tools/test-generator/templates/{leap → ocaml/leap}/template.ml +0 -0
- data/tracks/ocaml/tools/test-generator/templates/{luhn → ocaml/luhn}/template.ml +0 -0
- data/tracks/ocaml/tools/test-generator/templates/{minesweeper → ocaml/minesweeper}/template.ml +0 -0
- data/tracks/ocaml/tools/test-generator/templates/{pangram → ocaml/pangram}/template.ml +0 -0
- data/tracks/ocaml/tools/test-generator/templates/{phone-number → ocaml/phone-number}/template.ml +0 -0
- data/tracks/ocaml/tools/test-generator/templates/{prime-factors → ocaml/prime-factors}/template.ml +0 -0
- data/tracks/ocaml/tools/test-generator/templates/{raindrops → ocaml/raindrops}/template.ml +0 -0
- data/tracks/ocaml/tools/test-generator/templates/{roman-numerals → ocaml/roman-numerals}/template.ml +0 -0
- data/tracks/ocaml/tools/test-generator/templates/{run-length-encoding → ocaml/run-length-encoding}/template.ml +0 -0
- data/tracks/ocaml/tools/test-generator/templates/{say → ocaml/say}/template.ml +0 -0
- data/tracks/ocaml/tools/test-generator/templates/{space-age → ocaml/space-age}/template.ml +0 -0
- data/tracks/ocaml/tools/test-generator/templates/{triangle → ocaml/triangle}/template.ml +0 -0
- data/tracks/ocaml/tools/test-generator/templates/{word-count → ocaml/word-count}/template.ml +0 -0
- data/tracks/ocaml/tools/test-generator/templates/purescript/hamming/Main.purs +18 -0
- data/tracks/ocaml/tools/test-generator/test/all_tests.ml +2 -2
- data/tracks/ocaml/tools/test-generator/test/{special_cases_test.ml → ocaml_special_cases_test.ml} +3 -3
- data/tracks/ocaml/tools/test-generator/test/template_test.ml +1 -0
- metadata +59 -34
- data/tracks/groovy/exercises/robot-name/RobotTest.groovy +0 -34
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e1cea3469c06413e200a4474c5867dc75dedf961
|
4
|
+
data.tar.gz: 66119d1d8e808237e971dcaeb737b52adcfc227a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6557449b11d4872505295d57268867e5f4cdcb2b6aa6e13be6ac28b3ad2bf97c20cc51bb39a076425ebc0f004ef70d4d26d57b9a738e389e9d070efe6547f1cc
|
7
|
+
data.tar.gz: 977e03371cbdeddb0307091430f425b039be3a52705353b27a0ad0bf272e13211d10fdd246ef576c7c56d5338551e2e3810c52608a29d2a829c78f29f2841df9
|
data/common/CONTRIBUTING.md
CHANGED
@@ -691,21 +691,6 @@ Optional keys:
|
|
691
691
|
* `ignore_pattern` - A (case insensitive) regex pattern that will cause files matching it to not be served to the student by `exercism fetch`. The default value used if this key is not present is `example`
|
692
692
|
* `solution_pattern` - A (case sensitive) regex pattern that matches solution files in the track repository. Used by [configlet](https://github.com/exercism/configlet) to check for the presence of an example solution for each problem implemented by the track. The default value used if this key is not present is `[Ee]xample`.
|
693
693
|
|
694
|
-
### Track-Level Linting With Configlet
|
695
|
-
|
696
|
-
If the `config.json` file is incomplete or broken, a lot of other things break.
|
697
|
-
To make things easier we made a small tool to help verify the config:
|
698
|
-
https://github.com/exercism/configlet#configlet
|
699
|
-
|
700
|
-
You can download the latest release from the releases page in the [configlet
|
701
|
-
repo](https://github.com/exercism/configlet/releases), or you can use the
|
702
|
-
`bin/fetch-configlet` command from the root of the language track repository,
|
703
|
-
which will make a guess at what operating system and architecture you have and
|
704
|
-
attempt to download the right one.
|
705
|
-
|
706
|
-
Verify the config by calling `bin/configlet .` (notice the dot). This says
|
707
|
-
_check the config of the language track that is stored right here).
|
708
|
-
|
709
694
|
### Git Basics
|
710
695
|
|
711
696
|
If you're concerned that you haven't done it right, don't worry. Submit your pull request, and we'll help you get the details sorted out.
|
@@ -1,6 +1,19 @@
|
|
1
1
|
{
|
2
2
|
"exercise": "secret-handshake",
|
3
|
-
"version": "1.
|
3
|
+
"version": "1.1.0",
|
4
|
+
"comments": [
|
5
|
+
" In a discussion in https://github.com/exercism/x-common/pull/794 and ",
|
6
|
+
" https://github.com/exercism/x-common/issues/335 it has been decided to ",
|
7
|
+
" only include numbers between 0 and 31 (00000 to 11111) in the canonical ",
|
8
|
+
" test data. ",
|
9
|
+
" ",
|
10
|
+
" This is to allow for different implementations in different tracks and ",
|
11
|
+
" not restrict solutions to bitwise or modulo-based algorithms. ",
|
12
|
+
" ",
|
13
|
+
" Tracks may include additional tests for numbers > 31 in their test ",
|
14
|
+
" suites. In this case, 32 (100000) should yield the same result as 0, ",
|
15
|
+
" 33 (100001) should yield the same result as 1, and so on. "
|
16
|
+
],
|
4
17
|
"cases": [
|
5
18
|
{
|
6
19
|
"description": "Create a handshake for a number",
|
@@ -70,12 +83,6 @@
|
|
70
83
|
"property": "commands",
|
71
84
|
"input": 0,
|
72
85
|
"expected": []
|
73
|
-
},
|
74
|
-
{
|
75
|
-
"description": "do nothing if lower 5 bits not set",
|
76
|
-
"property": "commands",
|
77
|
-
"input": 32,
|
78
|
-
"expected": []
|
79
86
|
}
|
80
87
|
]
|
81
88
|
}
|
data/lib/trackler/version.rb
CHANGED
data/tracks/dlang/config.json
CHANGED
@@ -12,6 +12,13 @@
|
|
12
12
|
"language basics"
|
13
13
|
]
|
14
14
|
},
|
15
|
+
{
|
16
|
+
"slug": "leap",
|
17
|
+
"difficulty": 1,
|
18
|
+
"topics": [
|
19
|
+
"language basics"
|
20
|
+
]
|
21
|
+
},
|
15
22
|
{
|
16
23
|
"slug": "gigasecond" ,
|
17
24
|
"difficulty": 1,
|
@@ -139,6 +146,17 @@
|
|
139
146
|
"string manipulation",
|
140
147
|
"control-flow"
|
141
148
|
]
|
149
|
+
},
|
150
|
+
{
|
151
|
+
"slug": "react" ,
|
152
|
+
"difficulty": 10,
|
153
|
+
"topics": [
|
154
|
+
"reactive programming",
|
155
|
+
"generics",
|
156
|
+
"nested classes",
|
157
|
+
"higher-order functions",
|
158
|
+
"delegates"
|
159
|
+
]
|
142
160
|
}
|
143
161
|
],
|
144
162
|
"deprecated": [
|
@@ -0,0 +1,15 @@
|
|
1
|
+
bool is_leap(uint year) {
|
2
|
+
bool div_by(uint n) {
|
3
|
+
return year % n == 0;
|
4
|
+
}
|
5
|
+
return div_by(4) && (!div_by(100) || div_by(400));
|
6
|
+
}
|
7
|
+
|
8
|
+
unittest {
|
9
|
+
assert(!is_leap(2015));
|
10
|
+
assert(is_leap(2016));
|
11
|
+
assert(!is_leap(2100));
|
12
|
+
assert(is_leap(2000));
|
13
|
+
}
|
14
|
+
|
15
|
+
void main() {}
|
@@ -0,0 +1,211 @@
|
|
1
|
+
module react;
|
2
|
+
|
3
|
+
unittest {
|
4
|
+
const int allTestsEnabled = 0;
|
5
|
+
|
6
|
+
{
|
7
|
+
// input cells have a value
|
8
|
+
Reactor!(int) r;
|
9
|
+
auto input = r.new InputCell(10);
|
10
|
+
|
11
|
+
assert(input.value == 10);
|
12
|
+
}
|
13
|
+
static if (allTestsEnabled) {
|
14
|
+
{
|
15
|
+
// an input cell's value can be set
|
16
|
+
Reactor!(int) r;
|
17
|
+
auto input = r.new InputCell(4);
|
18
|
+
|
19
|
+
input.value = 20;
|
20
|
+
assert(input.value == 20);
|
21
|
+
}
|
22
|
+
{
|
23
|
+
// compute cells calculate initial value
|
24
|
+
Reactor!(int) r;
|
25
|
+
auto input = r.new InputCell(1);
|
26
|
+
auto output = r.new ComputeCell(input, (x) => x + 1);
|
27
|
+
|
28
|
+
assert(output.value == 2);
|
29
|
+
}
|
30
|
+
{
|
31
|
+
// compute cells take inputs in the right order
|
32
|
+
Reactor!(int) r;
|
33
|
+
auto one = r.new InputCell(1);
|
34
|
+
auto two = r.new InputCell(2);
|
35
|
+
auto output = r.new ComputeCell(one, two, (x, y) => x + y * 10);
|
36
|
+
|
37
|
+
assert(output.value == 21);
|
38
|
+
}
|
39
|
+
{
|
40
|
+
// compute cells update value when dependencies are changed
|
41
|
+
Reactor!(int) r;
|
42
|
+
auto input = r.new InputCell(1);
|
43
|
+
auto output = r.new ComputeCell(input, (x) => x + 1);
|
44
|
+
|
45
|
+
input.value = 3;
|
46
|
+
assert(output.value == 4);
|
47
|
+
}
|
48
|
+
{
|
49
|
+
// compute cells can depend on other compute cells
|
50
|
+
Reactor!(int) r;
|
51
|
+
auto input = r.new InputCell(1);
|
52
|
+
auto timesTwo = r.new ComputeCell(input, (x) => x * 2);
|
53
|
+
auto timesThirty = r.new ComputeCell(input, (x) => x * 30);
|
54
|
+
auto output = r.new ComputeCell(timesTwo, timesThirty, (x, y) => x + y);
|
55
|
+
|
56
|
+
assert(output.value == 32);
|
57
|
+
input.value = 3;
|
58
|
+
assert(output.value == 96);
|
59
|
+
}
|
60
|
+
{
|
61
|
+
// compute cells fire callbacks
|
62
|
+
Reactor!(int) r;
|
63
|
+
auto input = r.new InputCell(1);
|
64
|
+
auto output = r.new ComputeCell(input, (x) => x + 1);
|
65
|
+
int[] vals;
|
66
|
+
|
67
|
+
output.addCallback((int x) { vals ~= [x]; return; });
|
68
|
+
|
69
|
+
input.value = 3;
|
70
|
+
assert(vals.length == 1);
|
71
|
+
assert(vals[0] == 4);
|
72
|
+
}
|
73
|
+
{
|
74
|
+
// compute cells only fire on change
|
75
|
+
Reactor!(int) r;
|
76
|
+
auto input = r.new InputCell(1);
|
77
|
+
auto output = r.new ComputeCell(input, (x) => x < 3 ? 111 : 222);
|
78
|
+
int[] vals;
|
79
|
+
|
80
|
+
output.addCallback((int x) { vals ~= [x]; return; });
|
81
|
+
|
82
|
+
input.value = 2;
|
83
|
+
assert(vals.length == 0);
|
84
|
+
input.value = 3;
|
85
|
+
assert(vals.length == 1);
|
86
|
+
assert(vals[0] == 222);
|
87
|
+
}
|
88
|
+
{
|
89
|
+
// callbacks can be added and removed
|
90
|
+
Reactor!(int) r;
|
91
|
+
auto input = r.new InputCell(11);
|
92
|
+
auto output = r.new ComputeCell(input, (x) => x + 1);
|
93
|
+
int[] vals1;
|
94
|
+
int[] vals2;
|
95
|
+
int[] vals3;
|
96
|
+
|
97
|
+
void delegate() cancel1 = output.addCallback((int x) { vals1 ~= [x]; return; });
|
98
|
+
output.addCallback((int x) { vals2 ~= [x]; return; });
|
99
|
+
|
100
|
+
input.value = 31;
|
101
|
+
|
102
|
+
cancel1();
|
103
|
+
output.addCallback((int x) { vals3 ~= [x]; return; });
|
104
|
+
|
105
|
+
input.value = 41;
|
106
|
+
|
107
|
+
assert(vals1.length == 1);
|
108
|
+
assert(vals1[0] == 32);
|
109
|
+
assert(vals2.length == 2);
|
110
|
+
assert(vals2[0] == 32);
|
111
|
+
assert(vals2[1] == 42);
|
112
|
+
assert(vals3.length == 1);
|
113
|
+
assert(vals3[0] == 42);
|
114
|
+
}
|
115
|
+
{
|
116
|
+
// removing a callback multiple times doesn't interfere with other callbacks
|
117
|
+
Reactor!(int) r;
|
118
|
+
auto input = r.new InputCell(1);
|
119
|
+
auto output = r.new ComputeCell(input, (x) => x + 1);
|
120
|
+
int[] vals1;
|
121
|
+
int[] vals2;
|
122
|
+
|
123
|
+
void delegate() cancel1 = output.addCallback((int x) { vals1 ~= [x]; return; });
|
124
|
+
output.addCallback((int x) { vals2 ~= [x]; return; });
|
125
|
+
|
126
|
+
foreach (i; 0 .. 10) {
|
127
|
+
cancel1();
|
128
|
+
}
|
129
|
+
|
130
|
+
input.value = 2;
|
131
|
+
|
132
|
+
assert(vals1.length == 0);
|
133
|
+
assert(vals2.length == 1);
|
134
|
+
assert(vals2[0] == 3);
|
135
|
+
}
|
136
|
+
{
|
137
|
+
// callbacks should only be called once even if multiple dependencies change
|
138
|
+
Reactor!(int) r;
|
139
|
+
auto input = r.new InputCell(1);
|
140
|
+
auto plusOne = r.new ComputeCell(input, (x) => x + 1);
|
141
|
+
auto minusOne1 = r.new ComputeCell(input, (x) => x - 1);
|
142
|
+
auto minusOne2 = r.new ComputeCell(minusOne1, (x) => x - 1);
|
143
|
+
auto output = r.new ComputeCell(plusOne, minusOne2, (x, y) => x * y);
|
144
|
+
int[] vals;
|
145
|
+
|
146
|
+
output.addCallback((int x) { vals ~= [x]; return; });
|
147
|
+
|
148
|
+
input.value = 4;
|
149
|
+
|
150
|
+
assert(vals.length == 1);
|
151
|
+
assert(vals[0] == 10);
|
152
|
+
}
|
153
|
+
{
|
154
|
+
// callbacks should not be called if dependencies change but output value doesn't change
|
155
|
+
Reactor!(int) r;
|
156
|
+
auto input = r.new InputCell(1);
|
157
|
+
auto plusOne = r.new ComputeCell(input, (x) => x + 1);
|
158
|
+
auto minusOne = r.new ComputeCell(input, (x) => x - 1);
|
159
|
+
auto alwaysTwo = r.new ComputeCell(plusOne, minusOne, (x, y) => x - y);
|
160
|
+
int[] vals;
|
161
|
+
|
162
|
+
alwaysTwo.addCallback((int x) { vals ~= [x]; return; });
|
163
|
+
|
164
|
+
foreach (i; 0 .. 10) {
|
165
|
+
input.value = i;
|
166
|
+
}
|
167
|
+
|
168
|
+
assert(vals.length == 0);
|
169
|
+
}
|
170
|
+
{
|
171
|
+
// This is a digital logic circuit called an adder:
|
172
|
+
// https://en.wikipedia.org/wiki/Adder_(electronics)
|
173
|
+
Reactor!(bool) r;
|
174
|
+
auto a = r.new InputCell(false);
|
175
|
+
auto b = r.new InputCell(false);
|
176
|
+
auto carryIn = r.new InputCell(false);
|
177
|
+
|
178
|
+
auto aXorB = r.new ComputeCell(a, b, (x, y) => x != y);
|
179
|
+
auto sum = r.new ComputeCell(aXorB, carryIn, (x, y) => x != y);
|
180
|
+
|
181
|
+
auto aXorBAndCin = r.new ComputeCell(aXorB, carryIn, (x, y) => x && y);
|
182
|
+
auto aAndB = r.new ComputeCell(a, b, (x, y) => x && y);
|
183
|
+
auto carryOut = r.new ComputeCell(aXorBAndCin, aAndB, (x, y) => x || y);
|
184
|
+
|
185
|
+
bool[5][] tests = [
|
186
|
+
// inputs, expected
|
187
|
+
// a, b, cin, cout, sum
|
188
|
+
[false, false, false, false, false],
|
189
|
+
[false, false, true, false, true],
|
190
|
+
[false, true, false, false, true],
|
191
|
+
[false, true, true, true, false],
|
192
|
+
[ true, false, false, false, true],
|
193
|
+
[ true, false, true, true, false],
|
194
|
+
[ true, true, false, true, false],
|
195
|
+
[ true, true, true, true, true],
|
196
|
+
];
|
197
|
+
|
198
|
+
foreach (test; tests) {
|
199
|
+
a.value = test[0];
|
200
|
+
b.value = test[1];
|
201
|
+
carryIn.value = test[2];
|
202
|
+
|
203
|
+
assert(carryOut.value == test[3]);
|
204
|
+
assert(sum.value == test[4]);
|
205
|
+
}
|
206
|
+
}
|
207
|
+
}
|
208
|
+
|
209
|
+
}
|
210
|
+
|
211
|
+
void main() {}
|
@@ -0,0 +1,291 @@
|
|
1
|
+
module react;
|
2
|
+
|
3
|
+
class Reactor(T) {
|
4
|
+
class Cell {
|
5
|
+
@property T value() { return m_value; }
|
6
|
+
private:
|
7
|
+
ComputeCell[] dependents;
|
8
|
+
T m_value;
|
9
|
+
}
|
10
|
+
|
11
|
+
class InputCell: Cell {
|
12
|
+
// If this override getter is not present,
|
13
|
+
// then value becomes write-only.
|
14
|
+
override @property T value() { return super.value; }
|
15
|
+
@property T value(T newValue) {
|
16
|
+
m_value = newValue;
|
17
|
+
foreach (dep; dependents) {
|
18
|
+
dep.propagate();
|
19
|
+
}
|
20
|
+
foreach (dep; dependents) {
|
21
|
+
dep.fireCallbacks();
|
22
|
+
}
|
23
|
+
return newValue;
|
24
|
+
}
|
25
|
+
|
26
|
+
this(T initialValue) {
|
27
|
+
m_value = initialValue;
|
28
|
+
}
|
29
|
+
}
|
30
|
+
|
31
|
+
class ComputeCell: Cell {
|
32
|
+
this(Cell c, T function(T) f) {
|
33
|
+
c.dependents ~= [this];
|
34
|
+
this(() => f(c.value));
|
35
|
+
}
|
36
|
+
|
37
|
+
this(Cell c1, Cell c2, T function(T, T) f) {
|
38
|
+
c1.dependents ~= [this];
|
39
|
+
c2.dependents ~= [this];
|
40
|
+
this(() => f(c1.value, c2.value));
|
41
|
+
}
|
42
|
+
|
43
|
+
void delegate() addCallback(void delegate(T) cb) {
|
44
|
+
int id = callbacksIssued++;
|
45
|
+
callbacks[id] = cb;
|
46
|
+
return () { callbacks.remove(id); };
|
47
|
+
}
|
48
|
+
|
49
|
+
private:
|
50
|
+
|
51
|
+
T delegate() compute;
|
52
|
+
T lastCallbackValue;
|
53
|
+
void delegate(T)[int] callbacks;
|
54
|
+
int callbacksIssued = 0;
|
55
|
+
|
56
|
+
this(T delegate() f) {
|
57
|
+
compute = f;
|
58
|
+
m_value = compute();
|
59
|
+
lastCallbackValue = m_value;
|
60
|
+
}
|
61
|
+
|
62
|
+
void propagate() {
|
63
|
+
T newValue = compute();
|
64
|
+
if (value == newValue) {
|
65
|
+
return;
|
66
|
+
}
|
67
|
+
m_value = newValue;
|
68
|
+
foreach (dep; dependents) {
|
69
|
+
dep.propagate();
|
70
|
+
}
|
71
|
+
}
|
72
|
+
|
73
|
+
void fireCallbacks() {
|
74
|
+
if (value == lastCallbackValue) {
|
75
|
+
return;
|
76
|
+
}
|
77
|
+
lastCallbackValue = value;
|
78
|
+
foreach (cb; callbacks) {
|
79
|
+
cb(value);
|
80
|
+
}
|
81
|
+
foreach (dep; dependents) {
|
82
|
+
dep.fireCallbacks();
|
83
|
+
}
|
84
|
+
}
|
85
|
+
}
|
86
|
+
}
|
87
|
+
|
88
|
+
unittest {
|
89
|
+
{
|
90
|
+
// input cells have a value
|
91
|
+
Reactor!(int) r;
|
92
|
+
auto input = r.new InputCell(10);
|
93
|
+
|
94
|
+
assert(input.value == 10);
|
95
|
+
}
|
96
|
+
{
|
97
|
+
// an input cell's value can be set
|
98
|
+
Reactor!(int) r;
|
99
|
+
auto input = r.new InputCell(4);
|
100
|
+
|
101
|
+
input.value = 20;
|
102
|
+
assert(input.value == 20);
|
103
|
+
}
|
104
|
+
{
|
105
|
+
// compute cells calculate initial value
|
106
|
+
Reactor!(int) r;
|
107
|
+
auto input = r.new InputCell(1);
|
108
|
+
auto output = r.new ComputeCell(input, (x) => x + 1);
|
109
|
+
|
110
|
+
assert(output.value == 2);
|
111
|
+
}
|
112
|
+
{
|
113
|
+
// compute cells take inputs in the right order
|
114
|
+
Reactor!(int) r;
|
115
|
+
auto one = r.new InputCell(1);
|
116
|
+
auto two = r.new InputCell(2);
|
117
|
+
auto output = r.new ComputeCell(one, two, (x, y) => x + y * 10);
|
118
|
+
|
119
|
+
assert(output.value == 21);
|
120
|
+
}
|
121
|
+
{
|
122
|
+
// compute cells update value when dependencies are changed
|
123
|
+
Reactor!(int) r;
|
124
|
+
auto input = r.new InputCell(1);
|
125
|
+
auto output = r.new ComputeCell(input, (x) => x + 1);
|
126
|
+
|
127
|
+
input.value = 3;
|
128
|
+
assert(output.value == 4);
|
129
|
+
}
|
130
|
+
{
|
131
|
+
// compute cells can depend on other compute cells
|
132
|
+
Reactor!(int) r;
|
133
|
+
auto input = r.new InputCell(1);
|
134
|
+
auto timesTwo = r.new ComputeCell(input, (x) => x * 2);
|
135
|
+
auto timesThirty = r.new ComputeCell(input, (x) => x * 30);
|
136
|
+
auto output = r.new ComputeCell(timesTwo, timesThirty, (x, y) => x + y);
|
137
|
+
|
138
|
+
assert(output.value == 32);
|
139
|
+
input.value = 3;
|
140
|
+
assert(output.value == 96);
|
141
|
+
}
|
142
|
+
{
|
143
|
+
// compute cells fire callbacks
|
144
|
+
Reactor!(int) r;
|
145
|
+
auto input = r.new InputCell(1);
|
146
|
+
auto output = r.new ComputeCell(input, (x) => x + 1);
|
147
|
+
int[] vals;
|
148
|
+
|
149
|
+
output.addCallback((int x) { vals ~= [x]; return; });
|
150
|
+
|
151
|
+
input.value = 3;
|
152
|
+
assert(vals.length == 1);
|
153
|
+
assert(vals[0] == 4);
|
154
|
+
}
|
155
|
+
{
|
156
|
+
// compute cells only fire on change
|
157
|
+
Reactor!(int) r;
|
158
|
+
auto input = r.new InputCell(1);
|
159
|
+
auto output = r.new ComputeCell(input, (x) => x < 3 ? 111 : 222);
|
160
|
+
int[] vals;
|
161
|
+
|
162
|
+
output.addCallback((int x) { vals ~= [x]; return; });
|
163
|
+
|
164
|
+
input.value = 2;
|
165
|
+
assert(vals.length == 0);
|
166
|
+
input.value = 3;
|
167
|
+
assert(vals.length == 1);
|
168
|
+
assert(vals[0] == 222);
|
169
|
+
}
|
170
|
+
{
|
171
|
+
// callbacks can be added and removed
|
172
|
+
Reactor!(int) r;
|
173
|
+
auto input = r.new InputCell(11);
|
174
|
+
auto output = r.new ComputeCell(input, (x) => x + 1);
|
175
|
+
int[] vals1;
|
176
|
+
int[] vals2;
|
177
|
+
int[] vals3;
|
178
|
+
|
179
|
+
void delegate() cancel1 = output.addCallback((int x) { vals1 ~= [x]; return; });
|
180
|
+
output.addCallback((int x) { vals2 ~= [x]; return; });
|
181
|
+
|
182
|
+
input.value = 31;
|
183
|
+
|
184
|
+
cancel1();
|
185
|
+
output.addCallback((int x) { vals3 ~= [x]; return; });
|
186
|
+
|
187
|
+
input.value = 41;
|
188
|
+
|
189
|
+
assert(vals1.length == 1);
|
190
|
+
assert(vals1[0] == 32);
|
191
|
+
assert(vals2.length == 2);
|
192
|
+
assert(vals2[0] == 32);
|
193
|
+
assert(vals2[1] == 42);
|
194
|
+
assert(vals3.length == 1);
|
195
|
+
assert(vals3[0] == 42);
|
196
|
+
}
|
197
|
+
{
|
198
|
+
// removing a callback multiple times doesn't interfere with other callbacks
|
199
|
+
Reactor!(int) r;
|
200
|
+
auto input = r.new InputCell(1);
|
201
|
+
auto output = r.new ComputeCell(input, (x) => x + 1);
|
202
|
+
int[] vals1;
|
203
|
+
int[] vals2;
|
204
|
+
|
205
|
+
void delegate() cancel1 = output.addCallback((int x) { vals1 ~= [x]; return; });
|
206
|
+
output.addCallback((int x) { vals2 ~= [x]; return; });
|
207
|
+
|
208
|
+
foreach (i; 0 .. 10) {
|
209
|
+
cancel1();
|
210
|
+
}
|
211
|
+
|
212
|
+
input.value = 2;
|
213
|
+
|
214
|
+
assert(vals1.length == 0);
|
215
|
+
assert(vals2.length == 1);
|
216
|
+
assert(vals2[0] == 3);
|
217
|
+
}
|
218
|
+
{
|
219
|
+
// callbacks should only be called once even if multiple dependencies change
|
220
|
+
Reactor!(int) r;
|
221
|
+
auto input = r.new InputCell(1);
|
222
|
+
auto plusOne = r.new ComputeCell(input, (x) => x + 1);
|
223
|
+
auto minusOne1 = r.new ComputeCell(input, (x) => x - 1);
|
224
|
+
auto minusOne2 = r.new ComputeCell(minusOne1, (x) => x - 1);
|
225
|
+
auto output = r.new ComputeCell(plusOne, minusOne2, (x, y) => x * y);
|
226
|
+
int[] vals;
|
227
|
+
|
228
|
+
output.addCallback((int x) { vals ~= [x]; return; });
|
229
|
+
|
230
|
+
input.value = 4;
|
231
|
+
|
232
|
+
assert(vals.length == 1);
|
233
|
+
assert(vals[0] == 10);
|
234
|
+
}
|
235
|
+
{
|
236
|
+
// callbacks should not be called if dependencies change but output value doesn't change
|
237
|
+
Reactor!(int) r;
|
238
|
+
auto input = r.new InputCell(1);
|
239
|
+
auto plusOne = r.new ComputeCell(input, (x) => x + 1);
|
240
|
+
auto minusOne = r.new ComputeCell(input, (x) => x - 1);
|
241
|
+
auto alwaysTwo = r.new ComputeCell(plusOne, minusOne, (x, y) => x - y);
|
242
|
+
int[] vals;
|
243
|
+
|
244
|
+
alwaysTwo.addCallback((int x) { vals ~= [x]; return; });
|
245
|
+
|
246
|
+
foreach (i; 0 .. 10) {
|
247
|
+
input.value = i;
|
248
|
+
}
|
249
|
+
|
250
|
+
assert(vals.length == 0);
|
251
|
+
}
|
252
|
+
{
|
253
|
+
// This is a digital logic circuit called an adder:
|
254
|
+
// https://en.wikipedia.org/wiki/Adder_(electronics)
|
255
|
+
Reactor!(bool) r;
|
256
|
+
auto a = r.new InputCell(false);
|
257
|
+
auto b = r.new InputCell(false);
|
258
|
+
auto carryIn = r.new InputCell(false);
|
259
|
+
|
260
|
+
auto aXorB = r.new ComputeCell(a, b, (x, y) => x != y);
|
261
|
+
auto sum = r.new ComputeCell(aXorB, carryIn, (x, y) => x != y);
|
262
|
+
|
263
|
+
auto aXorBAndCin = r.new ComputeCell(aXorB, carryIn, (x, y) => x && y);
|
264
|
+
auto aAndB = r.new ComputeCell(a, b, (x, y) => x && y);
|
265
|
+
auto carryOut = r.new ComputeCell(aXorBAndCin, aAndB, (x, y) => x || y);
|
266
|
+
|
267
|
+
bool[5][] tests = [
|
268
|
+
// inputs, expected
|
269
|
+
// a, b, cin, cout, sum
|
270
|
+
[false, false, false, false, false],
|
271
|
+
[false, false, true, false, true],
|
272
|
+
[false, true, false, false, true],
|
273
|
+
[false, true, true, true, false],
|
274
|
+
[ true, false, false, false, true],
|
275
|
+
[ true, false, true, true, false],
|
276
|
+
[ true, true, false, true, false],
|
277
|
+
[ true, true, true, true, true],
|
278
|
+
];
|
279
|
+
|
280
|
+
foreach (test; tests) {
|
281
|
+
a.value = test[0];
|
282
|
+
b.value = test[1];
|
283
|
+
carryIn.value = test[2];
|
284
|
+
|
285
|
+
assert(carryOut.value == test[3]);
|
286
|
+
assert(sum.value == test[4]);
|
287
|
+
}
|
288
|
+
}
|
289
|
+
}
|
290
|
+
|
291
|
+
void main() {}
|