trackler 2.1.0.24 → 2.1.0.25

Sign up to get free protection for your applications and to get access to all the features.
Files changed (336) hide show
  1. checksums.yaml +4 -4
  2. data/.gitmodules +3 -0
  3. data/CONTRIBUTING.md +5 -8
  4. data/README.md +7 -7
  5. data/common/CONTRIBUTING.md +0 -5
  6. data/common/exercises/accumulate/description.md +4 -1
  7. data/common/exercises/acronym/description.md +1 -1
  8. data/common/exercises/bank-account/description.md +2 -1
  9. data/common/exercises/book-store/description.md +2 -5
  10. data/common/exercises/bracket-push/description.md +2 -4
  11. data/common/exercises/change/description.md +2 -5
  12. data/common/exercises/circular-buffer/description.md +0 -2
  13. data/common/exercises/clock/description.md +0 -2
  14. data/common/exercises/collatz-conjecture/canonical-data.json +36 -0
  15. data/common/exercises/collatz-conjecture/description.md +25 -0
  16. data/common/exercises/collatz-conjecture/metadata.yml +4 -0
  17. data/common/exercises/counter/description.md +1 -2
  18. data/common/exercises/diamond/description.md +0 -4
  19. data/common/exercises/flatten-array/description.md +1 -1
  20. data/common/exercises/grade-school/description.md +2 -1
  21. data/common/exercises/grains/description.md +2 -1
  22. data/common/exercises/grep/description.md +3 -2
  23. data/common/exercises/kindergarten-garden/description.md +2 -1
  24. data/common/exercises/largest-series-product/description.md +2 -1
  25. data/common/exercises/matrix/description.md +2 -1
  26. data/common/exercises/nucleotide-codons/description.md +2 -1
  27. data/common/exercises/ocr-numbers/description.md +2 -1
  28. data/common/exercises/octal/description.md +3 -1
  29. data/common/exercises/pangram/description.md +2 -1
  30. data/common/exercises/perfect-numbers/description.md +2 -1
  31. data/common/exercises/poker/description.md +0 -2
  32. data/common/exercises/protein-translation/description.md +3 -2
  33. data/common/exercises/proverb/description.md +2 -1
  34. data/common/exercises/pythagorean-triplet/description.md +4 -2
  35. data/common/exercises/queen-attack/description.md +2 -1
  36. data/common/exercises/rectangles/description.md +1 -3
  37. data/common/exercises/rotational-cipher/description.md +0 -4
  38. data/common/exercises/scale-generator/description.md +0 -2
  39. data/common/exercises/secret-handshake/description.md +2 -2
  40. data/common/exercises/series/description.md +2 -1
  41. data/common/exercises/sieve/description.md +2 -1
  42. data/common/exercises/space-age/description.md +0 -2
  43. data/common/exercises/strain/description.md +4 -5
  44. data/common/exercises/sublist/description.md +3 -6
  45. data/common/exercises/sum-of-multiples/description.md +2 -1
  46. data/common/exercises/tournament/description.md +4 -3
  47. data/common/exercises/transpose/description.md +1 -3
  48. data/common/exercises/trinary/description.md +2 -1
  49. data/common/exercises/two-bucket/description.md +1 -3
  50. data/common/exercises/two-fer/canonical-data.json +24 -0
  51. data/common/exercises/two-fer/description.md +40 -0
  52. data/common/exercises/two-fer/metadata.yml +4 -0
  53. data/common/exercises/word-search/description.md +2 -4
  54. data/common/exercises/zebra-puzzle/description.md +1 -1
  55. data/lib/trackler.rb +10 -5
  56. data/lib/trackler/description.rb +2 -2
  57. data/lib/trackler/guaranteed_file.rb +15 -15
  58. data/lib/trackler/implementation.rb +5 -5
  59. data/lib/trackler/implementations.rb +2 -2
  60. data/lib/trackler/metadata.rb +2 -2
  61. data/lib/trackler/problem.rb +2 -96
  62. data/lib/trackler/problems.rb +3 -48
  63. data/lib/trackler/specification.rb +101 -0
  64. data/lib/trackler/specifications.rb +52 -0
  65. data/lib/trackler/track.rb +6 -4
  66. data/lib/trackler/version.rb +1 -1
  67. data/tracks/bash/LICENSE +21 -0
  68. data/tracks/bash/README.md +0 -5
  69. data/tracks/c/LICENSE +21 -0
  70. data/tracks/c/README.md +0 -5
  71. data/tracks/clojure/LICENSE +2 -2
  72. data/tracks/clojure/README.org +0 -5
  73. data/tracks/clojure/config.json +5 -0
  74. data/tracks/clojure/exercises/sublist/project.clj +4 -0
  75. data/tracks/clojure/exercises/sublist/src/example.clj +19 -0
  76. data/tracks/clojure/exercises/sublist/sublist.mustache +9 -0
  77. data/tracks/clojure/exercises/sublist/test/sublist_test.clj +40 -0
  78. data/tracks/cpp/LICENSE +2 -2
  79. data/tracks/cpp/README.md +0 -5
  80. data/tracks/csharp/LICENSE +2 -2
  81. data/tracks/csharp/README.md +0 -5
  82. data/tracks/delphi/LICENSE +2 -2
  83. data/tracks/delphi/README.md +0 -5
  84. data/tracks/ecmascript/LICENSE +2 -2
  85. data/tracks/ecmascript/README.md +0 -5
  86. data/tracks/elisp/.travis.yml +7 -3
  87. data/tracks/elisp/LICENSE +2 -2
  88. data/tracks/elisp/README.org +3 -6
  89. data/tracks/elisp/bin/test-examples +27 -0
  90. data/tracks/elisp/config.json +10 -0
  91. data/tracks/elisp/docs/LEARNING.org +0 -3
  92. data/tracks/elisp/exercises/grains/example.el +18 -0
  93. data/tracks/elisp/exercises/grains/grains-test.el +38 -0
  94. data/tracks/elisp/exercises/grains/grains.el +10 -0
  95. data/tracks/elisp/exercises/phone-number/example.el +34 -0
  96. data/tracks/elisp/exercises/phone-number/phone-number-test.el +43 -0
  97. data/tracks/elisp/exercises/phone-number/phone-number.el +9 -0
  98. data/tracks/elixir/LICENSE +2 -2
  99. data/tracks/elixir/README.md +0 -5
  100. data/tracks/elm/LICENSE +2 -2
  101. data/tracks/elm/README.md +0 -5
  102. data/tracks/erlang/LICENSE +2 -2
  103. data/tracks/erlang/README.md +0 -5
  104. data/tracks/erlang/SETUP.md +7 -0
  105. data/tracks/erlang/exercises/rotational-cipher/rebar.config +5 -5
  106. data/tracks/erlang/exercises/rotational-cipher/test/rotational_cipher_tests.erl +18 -18
  107. data/tracks/factor/LICENSE +2 -2
  108. data/tracks/fortran/.github/PULL_REQUEST_TEMPLATE.md +23 -0
  109. data/tracks/fortran/.gitignore +4 -0
  110. data/tracks/fortran/.travis.yml +17 -0
  111. data/tracks/fortran/LICENSE +21 -0
  112. data/tracks/fortran/Makefile +40 -0
  113. data/tracks/fortran/README.md +72 -0
  114. data/tracks/fortran/bin/fetch-configlet +32 -0
  115. data/tracks/fortran/config.json +32 -0
  116. data/tracks/{kotlin/SETUP.md → fortran/docs/ABOUT.md} +0 -0
  117. data/tracks/fortran/docs/INSTALLATION.md +0 -0
  118. data/tracks/fortran/docs/LEARNING.md +0 -0
  119. data/tracks/fortran/docs/RESOURCES.md +0 -0
  120. data/tracks/fortran/docs/TESTS.md +0 -0
  121. data/tracks/fortran/exercises/TRACK_HINTS.md +0 -0
  122. data/tracks/fortran/exercises/bob/bob.fun +114 -0
  123. data/tracks/fortran/exercises/bob/example.f90 +61 -0
  124. data/tracks/fortran/exercises/hello_world/example.f90 +9 -0
  125. data/tracks/fortran/exercises/hello_world/hello_world.fun +10 -0
  126. data/tracks/fortran/img/.keep +0 -0
  127. data/tracks/fsharp/LICENSE +2 -2
  128. data/tracks/fsharp/README.md +0 -5
  129. data/tracks/go/LICENSE +2 -2
  130. data/tracks/go/README.md +0 -5
  131. data/tracks/haskell/LICENSE +2 -2
  132. data/tracks/haskell/README.md +0 -5
  133. data/tracks/idris/LICENSE +2 -2
  134. data/tracks/idris/README.md +0 -5
  135. data/tracks/java/LICENSE +2 -2
  136. data/tracks/java/README.md +0 -7
  137. data/tracks/java/{SETUP.md → TRACK_HINTS.md} +0 -0
  138. data/tracks/java/config.json +5 -0
  139. data/tracks/java/exercises/_template/build.gradle +1 -0
  140. data/tracks/java/exercises/accumulate/build.gradle +1 -0
  141. data/tracks/java/exercises/acronym/build.gradle +1 -0
  142. data/tracks/java/exercises/all-your-base/build.gradle +1 -0
  143. data/tracks/java/exercises/allergies/build.gradle +1 -0
  144. data/tracks/java/exercises/anagram/build.gradle +1 -0
  145. data/tracks/java/exercises/atbash-cipher/build.gradle +1 -0
  146. data/tracks/java/exercises/beer-song/build.gradle +1 -0
  147. data/tracks/java/exercises/binary-search/build.gradle +1 -0
  148. data/tracks/java/exercises/binary/build.gradle +1 -0
  149. data/tracks/java/exercises/bob/build.gradle +1 -0
  150. data/tracks/java/exercises/book-store/build.gradle +1 -0
  151. data/tracks/java/exercises/bracket-push/build.gradle +1 -0
  152. data/tracks/java/exercises/change/build.gradle +1 -0
  153. data/tracks/java/exercises/clock/build.gradle +2 -1
  154. data/tracks/java/exercises/collatz-conjecture/build.gradle +18 -0
  155. data/tracks/java/exercises/collatz-conjecture/src/example/java/CollatzCalculator.java +19 -0
  156. data/tracks/java/exercises/collatz-conjecture/src/main/java/CollatzCalculator.java +7 -0
  157. data/tracks/java/exercises/collatz-conjecture/src/test/java/CollatzCalculatorTest.java +59 -0
  158. data/tracks/java/exercises/crypto-square/build.gradle +1 -0
  159. data/tracks/java/exercises/diamond/build.gradle +1 -0
  160. data/tracks/java/exercises/diamond/src/example/java/DiamondPrinter.java +4 -4
  161. data/tracks/java/exercises/diamond/src/test/java/DiamondPrinterTest.java +6 -2
  162. data/tracks/java/exercises/difference-of-squares/build.gradle +1 -0
  163. data/tracks/java/exercises/difference-of-squares/src/main/java/.keep +0 -0
  164. data/tracks/java/exercises/etl/build.gradle +1 -0
  165. data/tracks/java/exercises/flatten-array/build.gradle +1 -0
  166. data/tracks/java/exercises/gigasecond/build.gradle +1 -0
  167. data/tracks/java/exercises/grade-school/build.gradle +1 -0
  168. data/tracks/java/exercises/hamming/build.gradle +1 -0
  169. data/tracks/java/exercises/hexadecimal/build.gradle +1 -0
  170. data/tracks/java/exercises/isogram/build.gradle +1 -0
  171. data/tracks/java/exercises/kindergarten-garden/build.gradle +1 -0
  172. data/tracks/java/exercises/largest-series-product/build.gradle +1 -0
  173. data/tracks/java/exercises/linked-list/build.gradle +1 -0
  174. data/tracks/java/exercises/luhn/build.gradle +1 -0
  175. data/tracks/java/exercises/matrix/build.gradle +2 -1
  176. data/tracks/java/exercises/meetup/build.gradle +1 -0
  177. data/tracks/java/exercises/minesweeper/build.gradle +1 -0
  178. data/tracks/java/exercises/nth-prime/build.gradle +1 -0
  179. data/tracks/java/exercises/ocr-numbers/build.gradle +1 -0
  180. data/tracks/java/exercises/octal/build.gradle +1 -0
  181. data/tracks/java/exercises/pangram/build.gradle +1 -0
  182. data/tracks/java/exercises/pascals-triangle/build.gradle +1 -0
  183. data/tracks/java/exercises/perfect-numbers/build.gradle +1 -0
  184. data/tracks/java/exercises/phone-number/build.gradle +1 -0
  185. data/tracks/java/exercises/pig-latin/build.gradle +1 -0
  186. data/tracks/java/exercises/queen-attack/build.gradle +1 -0
  187. data/tracks/java/exercises/raindrops/build.gradle +1 -0
  188. data/tracks/java/exercises/rectangles/build.gradle +1 -0
  189. data/tracks/java/exercises/rna-transcription/build.gradle +1 -0
  190. data/tracks/java/exercises/robot-simulator/build.gradle +1 -0
  191. data/tracks/java/exercises/roman-numerals/build.gradle +1 -0
  192. data/tracks/java/exercises/run-length-encoding/build.gradle +1 -0
  193. data/tracks/java/exercises/saddle-points/build.gradle +1 -0
  194. data/tracks/java/exercises/scrabble-score/build.gradle +1 -0
  195. data/tracks/java/exercises/secret-handshake/build.gradle +1 -0
  196. data/tracks/java/exercises/series/build.gradle +1 -0
  197. data/tracks/java/exercises/settings.gradle +1 -0
  198. data/tracks/java/exercises/sieve/build.gradle +1 -0
  199. data/tracks/java/exercises/simple-cipher/build.gradle +1 -0
  200. data/tracks/java/exercises/simple-linked-list/build.gradle +1 -0
  201. data/tracks/java/exercises/space-age/build.gradle +1 -0
  202. data/tracks/java/exercises/strain/build.gradle +1 -0
  203. data/tracks/java/exercises/sublist/build.gradle +1 -0
  204. data/tracks/java/exercises/sum-of-multiples/build.gradle +1 -0
  205. data/tracks/java/exercises/triangle/build.gradle +1 -0
  206. data/tracks/java/exercises/trinary/build.gradle +1 -0
  207. data/tracks/java/exercises/twelve-days/build.gradle +2 -1
  208. data/tracks/java/exercises/word-count/build.gradle +1 -0
  209. data/tracks/java/exercises/wordy/build.gradle +1 -0
  210. data/tracks/javascript/LICENSE +2 -2
  211. data/tracks/javascript/README.md +0 -5
  212. data/tracks/julia/LICENSE +2 -2
  213. data/tracks/kotlin/LICENSE +2 -2
  214. data/tracks/kotlin/README.md +0 -7
  215. data/tracks/kotlin/TRACK_HINTS.md +1 -0
  216. data/tracks/kotlin/exercises/phone-number/src/example/kotlin/PhoneNumber.kt +1 -1
  217. data/tracks/kotlin/exercises/phone-number/src/test/kotlin/PhoneNumberTest.kt +7 -8
  218. data/tracks/kotlin/exercises/rna-transcription/src/test/kotlin/RnaTranscriptionTest.kt +6 -6
  219. data/tracks/lfe/LICENSE +2 -2
  220. data/tracks/lfe/README.md +0 -5
  221. data/tracks/lisp/LICENSE +2 -2
  222. data/tracks/lisp/README.md +0 -5
  223. data/tracks/lua/LICENSE +2 -2
  224. data/tracks/lua/README.md +0 -5
  225. data/tracks/mips/LICENSE +2 -2
  226. data/tracks/mips/README.md +0 -5
  227. data/tracks/objective-c/LICENSE +2 -2
  228. data/tracks/objective-c/README.md +0 -5
  229. data/tracks/ocaml/LICENSE +2 -2
  230. data/tracks/ocaml/README.md +0 -5
  231. data/tracks/ocaml/config.json +54 -25
  232. data/tracks/ocaml/exercises/forth/.merlin +5 -0
  233. data/tracks/ocaml/exercises/forth/Makefile +11 -0
  234. data/tracks/ocaml/exercises/forth/example.ml +89 -0
  235. data/tracks/ocaml/exercises/forth/forth.mli +1 -0
  236. data/tracks/ocaml/exercises/forth/test.ml +147 -0
  237. data/tracks/ocaml/exercises/react/.merlin +5 -0
  238. data/tracks/ocaml/exercises/react/HINT.md +3 -0
  239. data/tracks/ocaml/exercises/react/Makefile +11 -0
  240. data/tracks/ocaml/exercises/react/example.ml +106 -0
  241. data/tracks/ocaml/exercises/react/react.mli +26 -0
  242. data/tracks/ocaml/exercises/react/test.ml +157 -0
  243. data/tracks/ocaml/tools/test-generator/src/special_cases.ml +5 -2
  244. data/tracks/ocaml/tools/test-generator/templates/forth/template.ml +114 -0
  245. data/tracks/perl5/LICENSE +2 -2
  246. data/tracks/perl6/LICENSE +2 -2
  247. data/tracks/perl6/README.md +0 -5
  248. data/tracks/perl6/config.json +5 -0
  249. data/tracks/perl6/exercises/clock/Example.pm6 +2 -2
  250. data/tracks/perl6/exercises/flatten-array/Example.pm6 +13 -0
  251. data/tracks/perl6/exercises/flatten-array/FlattenArray.pm6 +4 -0
  252. data/tracks/perl6/exercises/flatten-array/example.yaml +6 -0
  253. data/tracks/perl6/exercises/flatten-array/flatten-array.t +80 -0
  254. data/tracks/php/LICENSE +2 -2
  255. data/tracks/php/README.md +0 -5
  256. data/tracks/powershell/.travis.yml +1 -0
  257. data/tracks/python/LICENSE +2 -2
  258. data/tracks/python/README.md +0 -5
  259. data/tracks/python/config.json +8 -0
  260. data/tracks/python/exercises/protein-translation/example.py +25 -0
  261. data/tracks/python/exercises/protein-translation/protein_translation.py +6 -0
  262. data/tracks/python/exercises/protein-translation/protein_translation_test.py +59 -0
  263. data/tracks/r/LICENSE +2 -2
  264. data/tracks/r/README.md +0 -5
  265. data/tracks/racket/LICENSE +2 -2
  266. data/tracks/racket/README.md +0 -5
  267. data/tracks/racket/config.json +5 -0
  268. data/tracks/racket/exercises/acronym/acronym-test.rkt +40 -0
  269. data/tracks/racket/exercises/acronym/acronym.rkt +3 -0
  270. data/tracks/racket/exercises/acronym/example.rkt +25 -0
  271. data/tracks/racket/exercises/grep/example.rkt +60 -26
  272. data/tracks/racket/exercises/grep/grep-test.rkt +154 -17
  273. data/tracks/racket/exercises/grep/grep.rkt +1 -18
  274. data/tracks/racket/exercises/grep/iliad.txt +9 -0
  275. data/tracks/racket/exercises/grep/midsummer-night.txt +7 -0
  276. data/tracks/racket/exercises/grep/paradise-lost.txt +8 -0
  277. data/tracks/ruby/README.md +4 -1
  278. data/tracks/ruby/exercises/acronym/.meta/.version +1 -1
  279. data/tracks/ruby/exercises/acronym/.meta/solutions/acronym.rb +1 -1
  280. data/tracks/ruby/exercises/acronym/acronym_test.rb +2 -7
  281. data/tracks/ruby/exercises/anagram/.meta/generator/anagram_case.rb +1 -1
  282. data/tracks/ruby/exercises/beer-song/.meta/generator/beer_song_case.rb +1 -1
  283. data/tracks/ruby/exercises/clock/.meta/generator/clock_case.rb +1 -1
  284. data/tracks/ruby/exercises/connect/.meta/generator/connect_case.rb +0 -3
  285. data/tracks/ruby/exercises/connect/.meta/generator/test_template.erb +1 -1
  286. data/tracks/ruby/exercises/difference-of-squares/.meta/.version +1 -1
  287. data/tracks/ruby/exercises/difference-of-squares/.meta/solutions/difference_of_squares.rb +1 -1
  288. data/tracks/ruby/exercises/difference-of-squares/difference_of_squares_test.rb +12 -17
  289. data/tracks/ruby/exercises/triangle/.meta/generator/triangle_case.rb +10 -2
  290. data/tracks/ruby/exercises/triangle/triangle_test.rb +17 -17
  291. data/tracks/ruby/exercises/wordy/.meta/generator/wordy_case.rb +1 -1
  292. data/tracks/ruby/lib/generator/case_values.rb +1 -1
  293. data/tracks/ruby/lib/generator/exercise_case.rb +16 -2
  294. data/tracks/ruby/lib/generator/exercise_case/assertion.rb +4 -4
  295. data/tracks/ruby/test/fixtures/xruby/exercises/alpha/.meta/generator/alpha_case.rb +1 -1
  296. data/tracks/ruby/test/generator/case_values_test.rb +28 -7
  297. data/tracks/ruby/test/generator/exercise_case/assertion_test.rb +7 -7
  298. data/tracks/ruby/test/generator/exercise_case_test.rb +21 -25
  299. data/tracks/ruby/test/wordy_cases_test.rb +4 -4
  300. data/tracks/rust/LICENSE +2 -2
  301. data/tracks/rust/README.md +0 -5
  302. data/tracks/rust/config.json +1 -0
  303. data/tracks/rust/exercises/etl/Cargo.toml +1 -1
  304. data/tracks/rust/exercises/etl/example.rs +2 -2
  305. data/tracks/rust/exercises/etl/tests/etl.rs +26 -32
  306. data/tracks/rust/exercises/forth/tests/forth.rs +20 -22
  307. data/tracks/rust/exercises/minesweeper/Cargo.toml +1 -1
  308. data/tracks/rust/exercises/minesweeper/example.rs +3 -0
  309. data/tracks/rust/exercises/minesweeper/tests/minesweeper.rs +44 -46
  310. data/tracks/rust/exercises/raindrops/Cargo.toml +1 -1
  311. data/tracks/rust/exercises/raindrops/tests/raindrops.rs +12 -0
  312. data/tracks/rust/exercises/rna-transcription/Cargo.toml +1 -1
  313. data/tracks/rust/exercises/rna-transcription/example.rs +13 -9
  314. data/tracks/rust/exercises/rna-transcription/tests/rna-transcription.rs +23 -5
  315. data/tracks/scala/LICENSE +2 -2
  316. data/tracks/scala/README.md +0 -5
  317. data/tracks/scheme/LICENSE +2 -2
  318. data/tracks/scheme/README.org +0 -6
  319. data/tracks/swift/Dangerfile +1 -1
  320. data/tracks/swift/LICENSE +2 -2
  321. data/tracks/swift/README.md +0 -6
  322. data/tracks/vbnet/.travis.yml +1 -0
  323. data/tracks/vimscript/.travis.yml +4 -0
  324. data/tracks/vimscript/.vintrc.yaml +5 -0
  325. data/tracks/vimscript/TRACK_HINTS.md +19 -1
  326. data/tracks/vimscript/bin/pre-push +18 -0
  327. data/tracks/vimscript/config.json +8 -1
  328. data/tracks/vimscript/docs/ABOUT.md +7 -6
  329. data/tracks/vimscript/docs/TESTS.md +4 -4
  330. data/tracks/vimscript/exercises/allergies/allergies.vader +68 -0
  331. data/tracks/vimscript/exercises/allergies/allergies.vim +29 -0
  332. data/tracks/vimscript/exercises/allergies/example.vim +23 -0
  333. data/tracks/vimscript/lib/generate.vim +89 -0
  334. metadata +81 -6
  335. data/tracks/java/exercises/difference-of-squares/src/main/java/DifferenceOfSquaresCalculator.java +0 -15
  336. data/tracks/ocaml/.vscode/launch.json +0 -13
@@ -0,0 +1,5 @@
1
+ PKG findlib
2
+ PKG core
3
+ PKG ounit
4
+ S *
5
+ B *
@@ -0,0 +1,11 @@
1
+ test: test.native
2
+ @./test.native
3
+
4
+ test.native: *.ml *.mli
5
+ @corebuild -r -quiet -pkg oUnit test.native
6
+
7
+ clean:
8
+ rm -rf _build
9
+ rm -f test.native
10
+
11
+ .PHONY: clean
@@ -0,0 +1,89 @@
1
+ open Core
2
+
3
+ let (>|>) f g x = g (f x)
4
+
5
+ let string_to_int (s: string): int option =
6
+ Option.try_with (fun () -> Int.of_string s)
7
+
8
+ type sentence =
9
+ | Def of string
10
+ | Normal of string list
11
+
12
+ type stack = int list
13
+
14
+ type handler = stack -> stack option
15
+ type vocabulary = handler String.Map.t
16
+
17
+ let handle_binary_op f = function
18
+ | x :: y :: xs -> Some ((f y x) :: xs)
19
+ | _ -> None
20
+
21
+ let std_vocabulary: vocabulary =
22
+ String.Map.empty
23
+ |> Map.add ~key:"+" ~data:(handle_binary_op (+))
24
+ |> Map.add ~key:"-" ~data:(handle_binary_op (-))
25
+ |> Map.add ~key:"*" ~data:(handle_binary_op ( * ))
26
+ |> Map.add ~key:"/" ~data:(function | x :: y :: xs -> if x = 0 then None else Some ((y/x) :: xs) | _ -> None)
27
+ |> Map.add ~key:"DUP" ~data:(function | x :: xs -> Some (x :: x :: xs) | _ -> None)
28
+ |> Map.add ~key:"DROP" ~data:(function | _ :: xs -> Some xs | _ -> None)
29
+ |> Map.add ~key:"SWAP" ~data:(function | x :: y :: xs -> Some (y :: x :: xs) | _ -> None)
30
+ |> Map.add ~key:"OVER" ~data:(function | x :: y :: xs -> Some (y :: x :: y :: xs) | _ -> None)
31
+
32
+ let (<|>) o1 o2 = match o1 with
33
+ | None -> o2
34
+ | Some _ -> o1
35
+
36
+ let apply x f = f x
37
+
38
+ let string_last = function
39
+ | "" -> None
40
+ | s -> Some s.[String.length s - 1]
41
+
42
+ let evaluate_number (word: string) (stack: stack): stack option =
43
+ Option.map (string_to_int word) ~f:(fun n -> n :: stack)
44
+
45
+ let evaluate_word (word: string) (vocabulary: vocabulary) (stack: stack): stack option =
46
+ evaluate_number word stack <|>
47
+ Option.bind (Map.find vocabulary word) ~f:(apply stack) <|>
48
+ None
49
+
50
+ let rec eval_all (stack: 'a) (fs: ('a -> 'a option) list): 'a option = match fs with
51
+ | [] -> Some stack
52
+ | f :: fs -> (match f stack with
53
+ | None -> None
54
+ | Some stack -> eval_all stack fs
55
+ )
56
+
57
+ let to_sentence (words: string): sentence =
58
+ if words.[0] = ':' && string_last words = Some ';'
59
+ then Def words
60
+ else Normal (String.split ~on:' ' words)
61
+
62
+ let rec handle_def vocabulary stack words: (vocabulary * stack) option =
63
+ let words = String.split ~on:' ' words in
64
+ let words = List.drop words 1 in
65
+ let words = List.take words (List.length words - 1) in
66
+ if List.length words < 2
67
+ then None
68
+ else
69
+ let new_vocab = List.hd_exn words in
70
+ if Option.is_some (string_to_int new_vocab)
71
+ then None
72
+ else
73
+ let def = List.drop words 1 in
74
+ let vocabulary = Map.add vocabulary ~key:new_vocab ~data:(fun stack -> Option.map ~f:(snd >|> List.rev) @@ evaluate_stack stack vocabulary def) in
75
+ Some (vocabulary, stack)
76
+
77
+ and evaluate_sentence_or_fail (vocabulary: vocabulary) (stack: stack) (sentence: sentence): (vocabulary * stack) option = match sentence with
78
+ | Normal words -> eval_all (vocabulary, stack) (List.map words ~f:(fun w -> fun (v,s) -> Option.map (evaluate_word w v s) ~f:(fun s -> (v,s))))
79
+ | Def def -> handle_def vocabulary stack def
80
+
81
+ and evaluate_stack (stack: stack) (vocabulary: vocabulary) (words: string list): (vocabulary * stack) option =
82
+ List.fold words ~init:(Some (vocabulary, stack))
83
+ ~f:(fun mvs w -> (match mvs with
84
+ | None -> None
85
+ | Some (v, s) -> evaluate_sentence_or_fail v s (to_sentence w)
86
+ ))
87
+ |> Option.map ~f:(fun (v,s) -> (v, List.rev s))
88
+
89
+ let evaluate words = Option.map ~f:snd @@ evaluate_stack [] std_vocabulary (List.map ~f:String.uppercase words)
@@ -0,0 +1 @@
1
+ val evaluate : string list -> int list option
@@ -0,0 +1,147 @@
1
+ (* Test/exercise version: "1.0.0" *)
2
+
3
+ open Core
4
+ open OUnit2
5
+ open Forth
6
+
7
+ let print_int_list_option (xs: int list option) = match xs with
8
+ | None -> "None"
9
+ | Some xs -> "Some [" ^ String.concat ~sep:";" (List.map ~f:Int.to_string xs) ^ "]"
10
+ let ae exp got _test_ctxt = assert_equal ~printer:print_int_list_option exp got
11
+
12
+ let parsing_and_numbers_tests = [
13
+ "empty input results in empty stack" >::
14
+ ae (Some []) (evaluate []);
15
+ "numbers just get pushed onto the stack" >::
16
+ ae (Some [1; 2; 3; 4; 5]) (evaluate ["1 2 3 4 5"]);
17
+ ]
18
+
19
+
20
+ let addition_tests = [
21
+ "can add two numbers" >::
22
+ ae (Some [3]) (evaluate ["1 2 +"]);
23
+ "errors if there is nothing on the stack" >::
24
+ ae None (evaluate ["+"]);
25
+ "errors if there is only one value on the stack" >::
26
+ ae None (evaluate ["1 +"]);
27
+ ]
28
+
29
+
30
+ let subtraction_tests = [
31
+ "can subtract two numbers" >::
32
+ ae (Some [-1]) (evaluate ["3 4 -"]);
33
+ "errors if there is nothing on the stack" >::
34
+ ae None (evaluate ["-"]);
35
+ "errors if there is only one value on the stack" >::
36
+ ae None (evaluate ["1 -"]);
37
+ ]
38
+
39
+
40
+ let multiplication_tests = [
41
+ "can multiply two numbers" >::
42
+ ae (Some [8]) (evaluate ["2 4 *"]);
43
+ "errors if there is nothing on the stack" >::
44
+ ae None (evaluate ["*"]);
45
+ "errors if there is only one value on the stack" >::
46
+ ae None (evaluate ["1 *"]);
47
+ ]
48
+
49
+
50
+ let division_tests = [
51
+ "can divide two numbers" >::
52
+ ae (Some [4]) (evaluate ["12 3 /"]);
53
+ "performs integer division" >::
54
+ ae (Some [2]) (evaluate ["8 3 /"]);
55
+ "errors if dividing by zero" >::
56
+ ae None (evaluate ["4 0 /"]);
57
+ "errors if there is nothing on the stack" >::
58
+ ae None (evaluate ["/"]);
59
+ "errors if there is only one value on the stack" >::
60
+ ae None (evaluate ["1 /"]);
61
+ ]
62
+
63
+
64
+ let combined_arithmetic_tests = [
65
+ "addition and subtraction" >::
66
+ ae (Some [-1]) (evaluate ["1 2 + 4 -"]);
67
+ "multiplication and division" >::
68
+ ae (Some [2]) (evaluate ["2 4 * 3 /"]);
69
+ ]
70
+
71
+
72
+ let dup_tests = [
73
+ "copies the top value on the stack" >::
74
+ ae (Some [1; 1]) (evaluate ["1 DUP"]);
75
+ "is case-insensitive" >::
76
+ ae (Some [1; 2; 2]) (evaluate ["1 2 Dup"]);
77
+ "errors if there is nothing on the stack" >::
78
+ ae None (evaluate ["dup"]);
79
+ ]
80
+
81
+
82
+ let drop_tests = [
83
+ "removes the top value on the stack if it is the only one" >::
84
+ ae (Some []) (evaluate ["1 drop"]);
85
+ "removes the top value on the stack if it is not the only one" >::
86
+ ae (Some [1]) (evaluate ["1 2 drop"]);
87
+ "errors if there is nothing on the stack" >::
88
+ ae None (evaluate ["drop"]);
89
+ ]
90
+
91
+
92
+ let swap_tests = [
93
+ "swaps the top two values on the stack if they are the only ones" >::
94
+ ae (Some [2; 1]) (evaluate ["1 2 swap"]);
95
+ "swaps the top two values on the stack if they are not the only ones" >::
96
+ ae (Some [1; 3; 2]) (evaluate ["1 2 3 swap"]);
97
+ "errors if there is nothing on the stack" >::
98
+ ae None (evaluate ["swap"]);
99
+ "errors if there is only one value on the stack" >::
100
+ ae None (evaluate ["1 swap"]);
101
+ ]
102
+
103
+
104
+ let over_tests = [
105
+ "copies the second element if there are only two" >::
106
+ ae (Some [1; 2; 1]) (evaluate ["1 2 over"]);
107
+ "copies the second element if there are more than two" >::
108
+ ae (Some [1; 2; 3; 2]) (evaluate ["1 2 3 over"]);
109
+ "errors if there is nothing on the stack" >::
110
+ ae None (evaluate ["over"]);
111
+ "errors if there is only one value on the stack" >::
112
+ ae None (evaluate ["1 over"]);
113
+ ]
114
+
115
+
116
+ let user_defined_words_tests = [
117
+ "can consist of built-in words" >::
118
+ ae (Some [1; 1; 1]) (evaluate [": dup-twice dup dup ;"; "1 dup-twice"]);
119
+ "execute in the right order" >::
120
+ ae (Some [1; 2; 3]) (evaluate [": countup 1 2 3 ;"; "countup"]);
121
+ "can override other user-defined words" >::
122
+ ae (Some [1; 1; 1]) (evaluate [": foo dup ;"; ": foo dup dup ;"; "1 foo"]);
123
+ "can override built-in words" >::
124
+ ae (Some [1; 1]) (evaluate [": swap dup ;"; "1 swap"]);
125
+ "cannot redefine numbers" >::
126
+ ae None (evaluate [": 1 2 ;"]);
127
+ "errors if executing a non-existent word" >::
128
+ ae None (evaluate ["foo"]);
129
+ ]
130
+
131
+ let () =
132
+ run_test_tt_main (
133
+ "forth tests" >:::
134
+ List.concat [
135
+ parsing_and_numbers_tests;
136
+ addition_tests;
137
+ subtraction_tests;
138
+ multiplication_tests;
139
+ division_tests;
140
+ combined_arithmetic_tests;
141
+ dup_tests;
142
+ drop_tests;
143
+ swap_tests;
144
+ over_tests;
145
+ user_defined_words_tests
146
+ ]
147
+ )
@@ -0,0 +1,5 @@
1
+ PKG findlib
2
+ PKG core
3
+ PKG ounit
4
+ S *
5
+ B *
@@ -0,0 +1,3 @@
1
+ Feel free to change the interface if you think it can be improved.
2
+
3
+ For instance, set_value can only be called on input cells, but the interface allows it to be called on compute cells (forcing a runtime check).
@@ -0,0 +1,11 @@
1
+ test: test.native
2
+ @./test.native
3
+
4
+ test.native: *.ml *.mli
5
+ @corebuild -r -quiet -pkg oUnit test.native
6
+
7
+ clean:
8
+ rm -rf _build
9
+ rm -f test.native
10
+
11
+ .PHONY: clean
@@ -0,0 +1,106 @@
1
+ open Core
2
+
3
+ type callback_id = int
4
+
5
+ let latest_callback_id: callback_id ref = ref 0
6
+ let latest_cell_id = ref 0
7
+
8
+ type 'a cell_type =
9
+ InputCell
10
+ | ComputeCell of {one: 'a cell; f: 'a -> 'a;}
11
+ | ComputeCell2 of {one: 'a cell; two: 'a cell; f: 'a -> 'a -> 'a;}
12
+ and 'a cell = {
13
+ cell_id: int;
14
+ eq: 'a -> 'a -> bool;
15
+ value: 'a ref;
16
+ callbacks: (callback_id, ('a -> unit)) Hashtbl.t;
17
+ cell_type: 'a cell_type;
18
+ observers: 'a cell list ref;
19
+ }
20
+
21
+ let next_cell_id () =
22
+ let cell_id = !latest_cell_id in
23
+ latest_cell_id := succ !latest_cell_id;
24
+ cell_id
25
+
26
+ let apply x f = f x
27
+
28
+ let callbacks_tbl = Int.Table.create
29
+
30
+ let value_of {value; _}: 'a = !value
31
+
32
+ let create_input_cell ~(value: 'a) ~eq =
33
+ let cell_id = next_cell_id () in
34
+ latest_cell_id := succ !latest_cell_id;
35
+ {
36
+ cell_id;
37
+ eq = eq;
38
+ value = ref value;
39
+ callbacks = callbacks_tbl ();
40
+ cell_type = InputCell;
41
+ observers = ref [];
42
+ }
43
+
44
+ let add_callback cell ~k =
45
+ let id = !latest_callback_id in
46
+ Hashtbl.set cell.callbacks ~key:id ~data:k;
47
+ latest_callback_id := succ id;
48
+ id
49
+
50
+ let remove_callback cell id =
51
+ Hashtbl.remove cell.callbacks id;;
52
+
53
+ let call_callbacks cell =
54
+ Hashtbl.iter cell.callbacks ~f:(apply !(cell.value));;
55
+
56
+ let dedup_cells (cells: 'a cell list): 'a cell list =
57
+ List.dedup cells ~compare:(fun c1 c2 -> Int.compare c1.cell_id c2.cell_id)
58
+
59
+ let update_compute_cell_value (cell: 'a cell) =
60
+ let computed = match cell.cell_type with
61
+ | ComputeCell {one; f} -> f !(one.value)
62
+ | ComputeCell2 {one; two; f} -> f !(one.value) !(two.value)
63
+ | InputCell -> failwith "cannot call update_compute_cell on an input cell";
64
+ in
65
+ if cell.eq !(cell.value) computed
66
+ then false
67
+ else begin
68
+ cell.value := computed;
69
+ true
70
+ end
71
+
72
+ let breadth_first_update_compute_cells (cell: 'a cell): 'a cell list =
73
+ let update c = List.filter !(c.observers) ~f:update_compute_cell_value in
74
+ let rec go cells acc =
75
+ let updates = dedup_cells (List.concat_map ~f:update cells) in
76
+ if List.is_empty updates
77
+ then acc
78
+ else go updates (updates @ acc)
79
+ in
80
+ go [cell] [cell]
81
+
82
+ let set_value (cell: 'a cell) (x: 'a) = match cell.cell_type with
83
+ | InputCell ->
84
+ if not (cell.eq !(cell.value) x)
85
+ then begin
86
+ cell.value := x;
87
+ let cells = breadth_first_update_compute_cells cell |> dedup_cells in
88
+ List.iter cells ~f:call_callbacks;
89
+ ()
90
+ end
91
+ | _ -> failwith "cannot set the value of a compute cell";;
92
+
93
+ let create_compute_cell_1 one ~f ~eq =
94
+ let callbacks = callbacks_tbl () in
95
+ let value = ref (f !(one.value)) in
96
+ let c = {cell_id = next_cell_id(); value; callbacks; observers = ref []; eq; cell_type = ComputeCell {one; f}} in
97
+ one.observers := c :: !(one.observers);
98
+ c
99
+
100
+ let create_compute_cell_2 one two ~f ~eq =
101
+ let callbacks = callbacks_tbl () in
102
+ let value = ref (f !(one.value) !(two.value)) in
103
+ let c = {cell_id = next_cell_id(); value; callbacks; observers = ref []; eq; cell_type = ComputeCell2 {one; two; f}} in
104
+ one.observers := c :: !(one.observers);
105
+ two.observers := c :: !(two.observers);
106
+ c
@@ -0,0 +1,26 @@
1
+ type 'a cell
2
+ type callback_id
3
+
4
+ (* Creates a cell that has a value which does not depend on the value of any other cell. *)
5
+ val create_input_cell : value: 'a -> eq: ('a -> 'a -> bool) -> 'a cell
6
+
7
+ val value_of : 'a cell -> 'a
8
+
9
+ (* Sets the value of an input cell. Setting the value of a compute cell is not allowed. *)
10
+ val set_value : 'a cell -> 'a -> unit
11
+
12
+ (* Creates a computed cell with one other cell as input, the value of this cell is the value of the input
13
+ with the function f applied to it.
14
+ *)
15
+ val create_compute_cell_1 : 'a cell -> f: ('a -> 'a) -> eq: ('a -> 'a -> bool) -> 'a cell
16
+
17
+ (* Creates a computed cell with two other cells as input, the value of this cell is the value of the inputs
18
+ with the function f applied to them.
19
+ *)
20
+ val create_compute_cell_2 : 'a cell -> 'a cell -> f: ('a -> 'a -> 'a) -> eq: ('a -> 'a -> bool) -> 'a cell
21
+
22
+ (* After this is called, the callback will be called whenever the cell's value is changed. *)
23
+ val add_callback : 'a cell -> k: ('a -> unit) -> callback_id
24
+
25
+ (* After this is called, the callback will be not called on changes to this cell any more. *)
26
+ val remove_callback : 'a cell -> callback_id -> unit
@@ -0,0 +1,157 @@
1
+ (* Test/exercise version: "1.0.0" *)
2
+
3
+ open Core
4
+ open OUnit2
5
+ open React
6
+
7
+ let ae exp got =
8
+ assert_equal ~printer:(Int.to_string) exp got;;
9
+
10
+ let assert_list_eq exp got =
11
+ assert_equal ~printer:(List.to_string ~f:Int.to_string) exp got;;
12
+
13
+ let create_int_input_cell = create_input_cell ~eq:Int.equal
14
+ let create_compute_cell_1 = create_compute_cell_1 ~eq:Int.equal
15
+ let create_compute_cell_2 = create_compute_cell_2 ~eq:Int.equal
16
+
17
+ let tests = [
18
+ "creating an input cell with value then asking for its value returns the same value" >:: (fun _ctx ->
19
+ let cell = create_int_input_cell ~value:10 in
20
+ ae 10 (value_of cell)
21
+ );
22
+ "setting an input cell with a new value then asking for its value returns the new value" >:: (fun _ctx ->
23
+ let cell = create_int_input_cell ~value:10 in
24
+ set_value cell 20;
25
+ ae 20 (value_of cell)
26
+ );
27
+ "compute cells calculate from an initial value" >:: (fun _ctx ->
28
+ let cell = create_int_input_cell ~value:1 in
29
+ let computed = create_compute_cell_1 cell ~f:succ in
30
+ ae 2 (value_of computed)
31
+ );
32
+ "compute cells take inputs in the right order" >:: (fun _ctx ->
33
+ let one = create_int_input_cell ~value:1 in
34
+ let two = create_int_input_cell ~value:2 in
35
+ let computed = create_compute_cell_2 one two ~f:(fun x y -> x + 10 * y) in
36
+ ae 21 (value_of computed)
37
+ );
38
+ "compute cells update value when dependencies are changed" >:: (fun _ctx ->
39
+ let input = create_int_input_cell ~value:1 in
40
+ let computed = create_compute_cell_1 input ~f:succ in
41
+ set_value input 3;
42
+ ae 4 (value_of computed)
43
+ );
44
+ "compute cells can depend on other compute cells" >:: (fun _ctx ->
45
+ let input = create_int_input_cell ~value:1 in
46
+ let times_two = create_compute_cell_1 input ~f:(fun x -> x * 2) in
47
+ let times_thirty = create_compute_cell_1 input ~f:(fun x -> x * 30) in
48
+ let computed = create_compute_cell_2 times_two times_thirty ~f:(+) in
49
+
50
+ ae 32 (value_of computed);
51
+ set_value input 3;
52
+ ae 96 (value_of computed)
53
+ );
54
+ "compute cells fire callbacks" >:: (fun _ctx ->
55
+ let input = create_int_input_cell ~value:1 in
56
+ let output = create_compute_cell_1 input ~f:succ in
57
+ let record = ref [] in
58
+ ignore @@ add_callback output ~k:(fun x -> record := x :: !record);
59
+
60
+ set_value input 3;
61
+ assert_list_eq [4] !record
62
+ );
63
+ "input cells do not fire if no change" >:: (fun _ctx ->
64
+ let input = create_int_input_cell ~value:2 in
65
+ let record = ref [] in
66
+ ignore @@ add_callback input ~k:(fun x -> record := x :: !record);
67
+
68
+ set_value input 2;
69
+
70
+ assert_list_eq [] !record;
71
+ );
72
+ "compute cells do not fire if no change" >:: (fun _ctx ->
73
+ let input = create_int_input_cell ~value:2 in
74
+ let output = create_compute_cell_1 input ~f:(fun x -> if x < 3 then 111 else 222) in
75
+ let record = ref [] in
76
+ ignore @@ add_callback output ~k:(fun x -> record := x :: !record);
77
+
78
+ set_value input 1;
79
+ assert_list_eq [] !record;
80
+
81
+ set_value input 1;
82
+ assert_list_eq [] !record;
83
+ );
84
+ "callback cells do not fire if no change on two calls to set_value" >:: (fun _ctx ->
85
+ let input = create_int_input_cell ~value:2 in
86
+ let output = create_compute_cell_1 input ~f:(fun x -> if x < 3 then 111 else 222) in
87
+ let record = ref [] in
88
+ ignore @@ add_callback output ~k:(fun x -> record := x :: !record);
89
+
90
+ set_value input 2;
91
+ set_value input 2;
92
+ assert_list_eq [] !record;
93
+ );
94
+ "uses the provided eq function to determine if a cell value has changed" >:: (fun _ctx ->
95
+ let called_eq = ref false in
96
+ let input = create_input_cell ~value:2 ~eq:(fun _ _ -> called_eq := true; false) in
97
+
98
+ set_value input 2;
99
+ assert_bool "should call eq" !called_eq;
100
+ );
101
+ "callbacks can be added and removed" >:: (fun _ctx ->
102
+ let input = create_int_input_cell ~value:2 in
103
+ let output = create_compute_cell_1 input ~f:Fn.id in
104
+ let called_callback = ref false in
105
+ let cb = add_callback output ~k:(fun _ -> called_callback := true) in
106
+
107
+ remove_callback output cb;
108
+ set_value input 3;
109
+
110
+ assert_bool "should not call callback" (not !called_callback);
111
+ );
112
+ "removing a callback multiple times doesn't interfere with other callbacks" >:: (fun _ctx ->
113
+ let input = create_int_input_cell ~value:1 in
114
+ let output = create_compute_cell_1 input ~f:succ in
115
+ let callback1_calls = ref [] in
116
+ let callback2_calls = ref [] in
117
+ let cb1 = add_callback output ~k:(fun x -> callback1_calls := x :: !callback1_calls) in
118
+ ignore @@ add_callback output ~k:(fun x -> callback2_calls := x :: !callback1_calls);
119
+ remove_callback output cb1;
120
+ remove_callback output cb1;
121
+
122
+ set_value input 2;
123
+
124
+ assert_list_eq [] !callback1_calls;
125
+ assert_list_eq [3] !callback2_calls;
126
+ );
127
+ "callbacks should only be called once even if multiple dependencies change" >:: (fun _ctx ->
128
+ let input = create_int_input_cell ~value:1 in
129
+ let plus_one = create_compute_cell_1 input ~f:succ in
130
+ let minus_one1 = create_compute_cell_1 input ~f:pred in
131
+ let minus_one2 = create_compute_cell_1 minus_one1 ~f:pred in
132
+ let output = create_compute_cell_2 plus_one minus_one2 ~f:((fun x y -> x * y)) in
133
+
134
+ let callback1_calls = ref [] in
135
+ ignore @@ add_callback output ~k:(fun x -> callback1_calls := x :: !callback1_calls);
136
+ set_value input 4;
137
+
138
+ assert_list_eq [10] !callback1_calls;
139
+ );
140
+ "callbacks should not be called if dependencies change but output value doesn't change" >:: (fun _ctx ->
141
+ let input = create_int_input_cell ~value:1 in
142
+ let plus_one = create_compute_cell_1 input ~f:succ in
143
+ let minus_one1 = create_compute_cell_1 input ~f:pred in
144
+ let always_two = create_compute_cell_2 plus_one minus_one1 ~f:(fun x y -> x - y) in
145
+
146
+ let callback1_calls = ref [] in
147
+ ignore @@ add_callback always_two ~k:(fun x -> callback1_calls := x :: !callback1_calls);
148
+ set_value input 2;
149
+ set_value input 3;
150
+ set_value input 5;
151
+
152
+ assert_list_eq [] !callback1_calls;
153
+ );
154
+ ]
155
+
156
+ let () =
157
+ run_test_tt_main ("react tests" >::: tests)