Compare commits
1259 Commits
no-thin-lo
...
develop
Author | SHA1 | Date | |
---|---|---|---|
|
9e2eace05e | ||
|
a0000cd179 | ||
|
56771820aa | ||
|
33f0647be8 | ||
|
878c6f3d4b | ||
|
4cd045b9c8 | ||
|
a617ec0436 | ||
|
80cc20ac72 | ||
|
666524a36a | ||
|
2ed3633fe5 | ||
|
deb88c60fb | ||
|
838d13086a | ||
|
11e80941bc | ||
|
e11cca658e | ||
|
2d0949c3f6 | ||
|
8dac70082c | ||
|
d7e548d84e | ||
|
2cd22fa8e5 | ||
|
21e4908fbf | ||
|
07802ff151 | ||
|
f079c1f6ef | ||
|
91a3a066c2 | ||
|
8608faa79b | ||
|
e076bd935f | ||
|
fdcdf2fa3d | ||
|
2d9fea4f21 | ||
|
f6322fad13 | ||
|
699960d220 | ||
|
ac0cc01852 | ||
|
c65a9d3ae6 | ||
|
113be7d8cb | ||
|
2596d1a695 | ||
|
41153988d8 | ||
|
8c7340185c | ||
|
bd4250c793 | ||
|
c204ce37ad | ||
|
344ee0ce17 | ||
71db023f0c | |||
|
bd94e51b31 | ||
|
964e95f598 | ||
|
98454a87b5 | ||
|
5efb66c3e5 | ||
|
2389dff69a | ||
|
1ea3c975d5 | ||
|
1e13dc71f1 | ||
|
a515d9560c | ||
|
9582be7331 | ||
|
827bb43354 | ||
|
27c4c9acf2 | ||
|
28e944dd04 | ||
|
9992d7da1a | ||
|
de532c72d6 | ||
|
fd6c4ae1de | ||
|
5621567af2 | ||
|
000b7a6248 | ||
|
13a54f3e3d | ||
|
4abdb69f23 | ||
|
ed86fd9f77 | ||
|
bcc5b5846f | ||
|
da8db5db9c | ||
|
d7c6996284 | ||
|
b7d56a3f1f | ||
|
d037913e7c | ||
|
2c01c77646 | ||
|
7493f2e2e5 | ||
|
07fcbeb568 | ||
|
0f8e7ce6b6 | ||
|
cc846aa74c | ||
|
214a54e57e | ||
|
eaa7d38428 | ||
|
96e0418f60 | ||
|
2bb9622edd | ||
|
76ea024342 | ||
|
d5f0a6bcf8 | ||
|
b2c10924cd | ||
|
7bfe52c2d3 | ||
|
d6a48d9cb9 | ||
|
4efd2c9e05 | ||
|
0ab03eca96 | ||
|
9107b241f5 | ||
|
c1e67066aa | ||
|
ccdcb1a72b | ||
|
85a8c06107 | ||
|
871254dff9 | ||
|
ff5e0dd14c | ||
|
f6e69dad38 | ||
|
7e4139acb3 | ||
|
af885ef58f | ||
|
061d785996 | ||
|
355ff199c1 | ||
|
a48efc23b0 | ||
|
2c5511efc1 | ||
|
f39c69a2d4 | ||
|
b4c649d03b | ||
|
8184396d3e | ||
|
1a131828e6 | ||
|
92d3c761fa | ||
|
42e9ad810d | ||
|
a718b369c2 | ||
|
bac954d949 | ||
|
1474630af8 | ||
|
ffc6cd3840 | ||
|
b5c5b81db4 | ||
|
77dbb223f9 | ||
|
19a44eb12f | ||
|
8cbbdf5489 | ||
|
2d0e6432cd | ||
|
0137b44689 | ||
|
6055320aa2 | ||
|
6463c4db5a | ||
|
a45d6c6c57 | ||
|
9f1c5affc4 | ||
|
234ebe2103 | ||
|
f19c096e7c | ||
|
298492b3b8 | ||
|
bb6fece694 | ||
|
26dbd29ec8 | ||
|
c11fbda8b0 | ||
|
d60792aa93 | ||
|
39cc4a32ca | ||
|
1bb9e75ed3 | ||
|
b512751110 | ||
|
b412eee646 | ||
|
a0e3f70dbb | ||
|
2be9d274f2 | ||
|
60f464ebac | ||
|
2ed234b83b | ||
|
be8dbf8aa1 | ||
|
9caee0fecd | ||
|
156d98c979 | ||
|
20382a5476 | ||
|
d13b14b18c | ||
|
48e19000f2 | ||
|
e21eedd579 | ||
|
ab8f11abe0 | ||
|
c95152f4f5 | ||
|
8dbdc89fa0 | ||
|
42c67c549a | ||
|
344fb8eba4 | ||
|
02eb10f092 | ||
|
058ea0a6bb | ||
|
9f57645c90 | ||
|
fd5ef6720e | ||
|
545d74131c | ||
|
f5d1777ce8 | ||
|
54cbcea5d6 | ||
|
5f5334ed8e | ||
|
f33323c4d7 | ||
|
54d52e4d91 | ||
|
7856669239 | ||
|
ca91604ce2 | ||
|
b1639bf62c | ||
|
d5a0e0fe6c | ||
|
ef3069c665 | ||
|
c47d5b4e62 | ||
|
a764f3075c | ||
|
832bfd82dd | ||
|
6819e6d38d | ||
|
afaabdb5b3 | ||
|
9321fe5681 | ||
|
056bbb65f4 | ||
|
5d38871e25 | ||
|
c5370dd799 | ||
|
6cd368ba7d | ||
|
8477b0c5f7 | ||
|
ecf9d43401 | ||
|
32cd765969 | ||
|
7bff9bafb8 | ||
|
b00edc78f9 | ||
|
33c16bd072 | ||
|
184584c9f3 | ||
|
46e689af51 | ||
|
d001b3c245 | ||
|
c9e5b11416 | ||
|
4c56b2fd07 | ||
|
c44c4baa5f | ||
|
67e8f1a196 | ||
|
acae4bfd3e | ||
|
3098cf7b71 | ||
|
bc1bfeb5cd | ||
|
dd20b52ee8 | ||
|
28ba89b8c8 | ||
|
f02e522a2e | ||
|
730851379e | ||
|
cca9786b8a | ||
|
4521d03664 | ||
|
6d1bc4009f | ||
|
7498eb4925 | ||
|
bf3f71c721 | ||
|
1548033fb4 | ||
|
845d38e375 | ||
|
b2ae31742e | ||
|
96a0d9c489 | ||
|
b4fbd1f8e8 | ||
|
64f9c9ddfc | ||
|
69b32d0b93 | ||
|
111dd5535c | ||
|
41574e08c9 | ||
|
d3bc08ab9d | ||
|
c337211af6 | ||
|
2e9dabccd9 | ||
|
bfcea1df02 | ||
|
f82cff9c19 | ||
|
3ae442ee1f | ||
|
94b4744bea | ||
|
9ddd1bfae0 | ||
|
46fd5900d2 | ||
|
d619794cf6 | ||
|
b7d634de86 | ||
|
2458762acd | ||
|
59e937ad7c | ||
|
483aaf73db | ||
|
4be410e33b | ||
|
14070a589d | ||
|
824749976e | ||
|
59d38f7885 | ||
|
c9fc08672d | ||
|
25dce72f11 | ||
|
d9f4171943 | ||
|
a96f9ffa9a | ||
|
13f352752e | ||
|
5db63fbe9f | ||
|
9f103f5cbe | ||
|
a4fd10e1fb | ||
|
e07236c22d | ||
|
e079404f7c | ||
|
4ed5ce0d47 | ||
|
13f599be33 | ||
|
a247a05340 | ||
|
80bbe2efb0 | ||
|
795b432f86 | ||
|
7021286035 | ||
|
ce89041d68 | ||
|
bdffee0e13 | ||
|
a30c63d547 | ||
|
336c8b7d48 | ||
|
ccc20e472e | ||
|
170b91742c | ||
|
958c451589 | ||
|
0c12abe159 | ||
|
388c2adcd4 | ||
|
7d7cb74ad8 | ||
|
8034de892e | ||
|
bb09fe9018 | ||
|
7045fc6646 | ||
|
e01f12656a | ||
|
01031e9f97 | ||
|
198dcc78c4 | ||
|
cdc49b378a | ||
|
5cf17f5dbf | ||
|
630128ae3f | ||
|
4779ac2476 | ||
|
92483e66e0 | ||
|
2392002172 | ||
|
4a08718184 | ||
|
fcbcfe3b66 | ||
|
6fc21ddda6 | ||
|
35671c5c51 | ||
|
ea71595e23 | ||
|
cb4e1199fc | ||
|
8e396c44f9 | ||
|
4f90dccdf1 | ||
|
8e13caae84 | ||
|
b24cfbc328 | ||
|
7a0c10d895 | ||
|
674213360c | ||
|
f8b743feb5 | ||
|
a8e632f671 | ||
|
72d6fa937d | ||
|
348ecc5eac | ||
|
a15aa26439 | ||
|
69cb8190f4 | ||
|
3a255b397b | ||
|
2d4e529940 | ||
|
de4cac47cb | ||
|
d245971842 | ||
|
271303fb5f | ||
|
bd34b145f0 | ||
|
c20af0edbd | ||
|
07731adc6f | ||
|
2aaf6816f6 | ||
|
a77bb36219 | ||
|
66d9cb0ab1 | ||
|
a78797bb9c | ||
|
6aa2280543 | ||
|
bbc1bee313 | ||
|
fd4137b596 | ||
|
43635bd39b | ||
|
065a7aeec0 | ||
|
7a220eae22 | ||
|
21d84a5396 | ||
|
89397d3584 | ||
|
f62e90fa98 | ||
|
6463b52052 | ||
|
da8860ac41 | ||
|
36f7f00fc3 | ||
|
267023240f | ||
|
a6b24cb9e5 | ||
|
8d268ec433 | ||
|
7b77dd09cf | ||
|
1e6a3d6495 | ||
|
884359597f | ||
|
294acffb89 | ||
|
cfb06afa2b | ||
|
9aa1d1fe93 | ||
|
47d00a350c | ||
|
1b714f0633 | ||
|
02c84983a4 | ||
|
93b021dc52 | ||
|
be934af684 | ||
|
e72ac8a44f | ||
|
b892a97e09 | ||
|
4533d1a4fb | ||
|
4beb1e5378 | ||
|
95d0884ce6 | ||
|
a47107e2a4 | ||
|
4c0b5e5e97 | ||
|
0c9c2c5c50 | ||
|
9cd2b661db | ||
|
1132b54be6 | ||
|
60eba96172 | ||
|
f41447c263 | ||
|
ae130bbef4 | ||
|
159f8721d8 | ||
|
901ee9f2fd | ||
|
20654c4f08 | ||
|
565bcf8331 | ||
|
28310168cb | ||
|
36127d8947 | ||
|
97ff56b46a | ||
|
757d458ecf | ||
|
d02aa61b87 | ||
|
e571700944 | ||
|
d57fe27a0d | ||
|
7beff3f834 | ||
|
3677b9582e | ||
|
b49e8c43f0 | ||
|
8b4ea5079a | ||
|
681a1b42c6 | ||
|
6dc1cea045 | ||
|
4408ed642d | ||
|
f7f3e449d1 | ||
|
d55c72f39e | ||
|
fea89abff9 | ||
|
5de73f171c | ||
|
f2dccd4f1e | ||
|
7a29be3845 | ||
|
5772c458a8 | ||
|
c59404e65f | ||
|
4346ba992f | ||
|
4fc10823fe | ||
|
fe56e81710 | ||
|
1187c0330a | ||
|
4d1f76f80e | ||
|
4dc7619e5a | ||
|
3c0255cfb9 | ||
|
5d17301016 | ||
|
45323b0e08 | ||
|
1c98aaa3e2 | ||
|
2736fb9a17 | ||
|
b5f5cca0dc | ||
|
d3788fb411 | ||
|
ecc82ba8f7 | ||
|
06ec2caae0 | ||
|
c0ccdbc1e9 | ||
|
45dd199925 | ||
|
5b0d7a898f | ||
|
0c614a3fcf | ||
|
7d6ffecc4a | ||
|
7d8a7e19d5 | ||
|
046f4f4f47 | ||
ad32921ed0 | |||
|
6f10b276e9 | ||
|
4a75a97cb4 | ||
|
36cd9271f6 | ||
|
2a8e5866cb | ||
|
9a67b16eee | ||
|
00a22b456d | ||
458c4f65b1 | |||
|
00a0005e56 | ||
fad1b51630 | |||
b7a186f8ac | |||
4917c2e0e2 | |||
|
ceb92ccfc6 | ||
|
0131498aca | ||
|
e0cd95fe5c | ||
|
84be552782 | ||
|
6c87a9e712 | ||
|
3ea747fae4 | ||
3b45355ce7 | |||
cf75152608 | |||
162f158dbc | |||
fc2d647b62 | |||
60e42cf1e8 | |||
ae71bb8ec5 | |||
4903319073 | |||
edce22050a | |||
8ddd1a166a | |||
697e3ba852 | |||
5ce0a6979d | |||
d8d68445b6 | |||
b21d5d09a8 | |||
|
6c3f719e74 | ||
|
38447f1633 | ||
|
c5928129d7 | ||
|
ddfc617776 | ||
|
4fbbd39638 | ||
|
c44d75695d | ||
|
b833da4ac2 | ||
cae8f8cb65 | |||
bfc3b39b2b | |||
e1760dab2d | |||
f76de31d26 | |||
c183dbd24b | |||
70d9a5c9f3 | |||
|
db3326ac34 | ||
|
edc7173fb6 | ||
|
d033db1155 | ||
|
deec676d4e | ||
|
02033b8dc1 | ||
|
2b86fb7723 | ||
|
8db0562f75 | ||
|
660aaf24ab | ||
|
74e54c6e91 | ||
|
3c879432f2 | ||
|
2942067849 | ||
|
5607d65599 | ||
|
86e41e0fe2 | ||
|
f76a5f5ce4 | ||
|
de06586784 | ||
|
41e5f300ef | ||
|
417df076d0 | ||
|
3084c557fd | ||
8bdfa03799 | |||
|
7f8f6f4fd6 | ||
2f33559e77 | |||
|
6a38d6c0bf | ||
9551aae634 | |||
8f8ca02730 | |||
b13da70c38 | |||
|
d71e6cf7d5 | ||
be4e1b1a85 | |||
|
54fe141a7d | ||
|
6ea94b0c8f | ||
fe3075b3bf | |||
aa3173f85b | |||
f370c44771 | |||
c0713b16c2 | |||
2b414191ac | |||
a45e38d75d | |||
6d75af61c3 | |||
27ca86e729 | |||
fd7720b045 | |||
2fa97c20f7 | |||
6c04f9bff1 | |||
e73ffaa10a | |||
abad453523 | |||
|
65e083eda2 | ||
244f3ea8d9 | |||
ecf5249b5c | |||
|
3794909efc | ||
|
b73924135f | ||
|
247e101324 | ||
|
044ed0bea3 | ||
943e63b379 | |||
|
af079d3cb7 | ||
b292a3e6fd | |||
f82c00a6ed | |||
b45ab3dba2 | |||
b57233096d | |||
dc34e7c0b8 | |||
85fe4d16a2 | |||
|
641b51f4d5 | ||
803231d620 | |||
3106a10b90 | |||
19ddd4eb3f | |||
|
cebd8118b0 | ||
|
58a44fe437 | ||
|
520b361f2c | ||
|
7a60f736f1 | ||
|
4ecb692e80 | ||
|
462f1c9649 | ||
|
e522eb4c5f | ||
|
7e3fa1d65a | ||
|
a97d1bae6d | ||
|
4d2fefad69 | ||
|
4f10cabdd3 | ||
|
130af680f6 | ||
|
fc3a7c6cf9 | ||
|
e2abc53200 | ||
|
64ed32a17c | ||
|
8842f7b25e | ||
|
d76c2f8ac7 | ||
|
407984344b | ||
|
f21f635bf0 | ||
|
dd3a8ab736 | ||
|
ec93a82110 | ||
|
1fe0aefd6b | ||
|
7eef1d30f3 | ||
|
82a4e52384 | ||
|
b3d5a98518 | ||
|
59ed741f33 | ||
|
4a3c982c57 | ||
|
48fb634140 | ||
|
eda1a45fda | ||
|
e6d543daef | ||
|
38d0ea79e4 | ||
|
57dca26d2b | ||
|
e582fc5be3 | ||
|
96815b6ee5 | ||
|
ab9a83bcf7 | ||
|
ab5b54413b | ||
|
9366ce61f3 | ||
|
989fa9644a | ||
|
255541a679 | ||
|
729fc2c59d | ||
|
152432b6d6 | ||
|
2209fd179e | ||
|
b6a2eac8fc | ||
|
d50c9ca930 | ||
|
64539bc121 | ||
|
3153b9620a | ||
|
5e9bf521fa | ||
|
0fa34b09e4 | ||
|
6631c9bad7 | ||
|
cd20bef11d | ||
|
2087a30b6b | ||
|
bc64d2882f | ||
|
a9dd18cbcd | ||
|
39a9cd4eb9 | ||
|
22458aa9e5 | ||
|
2f77888365 | ||
|
07b7b26d60 | ||
|
2f525c4f8e | ||
|
ba280c9292 | ||
|
02cbf227ad | ||
|
4b6a565c33 | ||
|
83ee5dfb63 | ||
|
3f50cde0a6 | ||
|
37dfc49c58 | ||
|
d6490be96f | ||
|
842157ee1b | ||
|
b89e4f1bd8 | ||
|
b935621d2a | ||
|
905b5fc569 | ||
|
21eeb63cf2 | ||
|
05a7d5c09a | ||
|
73e1f0b206 | ||
|
8319a484ac | ||
|
9a8ec06ca7 | ||
|
39566696f6 | ||
|
e8e0332e2e | ||
|
c7bc526b38 | ||
|
1c015599a8 | ||
|
4341d62893 | ||
|
4368bb53fa | ||
|
228277d0e8 | ||
|
d137d6a2b4 | ||
|
a83b46af25 | ||
|
a1db813b8b | ||
|
4f3c84c8ac | ||
|
87c6ff1c3d | ||
|
a7bbfad94b | ||
|
496bd0e539 | ||
|
0292c66c80 | ||
|
7c00897833 | ||
|
e88d93ac9e | ||
|
ddfd4e0024 | ||
|
47ccba4c46 | ||
|
d28a1ffe0b | ||
|
48409c7f82 | ||
|
b3549348b0 | ||
|
c52f3b9a55 | ||
|
2b846cae71 | ||
|
4d9eab8303 | ||
|
99551f7d1e | ||
|
745c8f9aeb | ||
|
ea053d4869 | ||
|
7f7aefb129 | ||
|
377e99157c | ||
|
59ff0c61dc | ||
|
28067ffcbd | ||
|
ed7562c6c6 | ||
|
790cffec8a | ||
|
92fb4c47c1 | ||
|
1ce657d634 | ||
|
a68c3c0615 | ||
|
353ea7e130 | ||
|
1c0dc6af06 | ||
|
b6a261bfb0 | ||
|
06cd81eee3 | ||
|
9e342d884f | ||
|
e493437135 | ||
|
25565ce5c0 | ||
|
9c3d41427e | ||
|
fe8e980d27 | ||
|
b7aa126e6a | ||
|
481483e937 | ||
|
b50061a91a | ||
|
feeb9e85f1 | ||
|
32cefb40da | ||
|
68ddf35387 | ||
|
e18a06f584 | ||
|
687576547c | ||
|
dd363d1e45 | ||
|
e2491bda88 | ||
|
2fc325beb6 | ||
|
eaa1f60555 | ||
|
934144fce8 | ||
|
7b4e38a983 | ||
|
efd4fd322c | ||
|
36c3a93d08 | ||
|
f67abe55a8 | ||
|
a8efd66fa3 | ||
|
0a8afc66a4 | ||
|
4e5e05530e | ||
|
ac63b57478 | ||
|
668756f874 | ||
|
d29b77ca61 | ||
|
2d59898b4f | ||
|
9a536d2a38 | ||
|
05299c78e6 | ||
|
1f22d1f837 | ||
|
7dd9a5bb77 | ||
|
1dfc9fe820 | ||
|
754fc8531f | ||
|
a9e43ee7cf | ||
|
a99ee96f5c | ||
|
f8e5396d57 | ||
|
865b01f48d | ||
|
b2fa0db627 | ||
|
10db239920 | ||
|
d0468f9ea3 | ||
|
6a2ff40279 | ||
|
441854ffec | ||
|
8de8cc3633 | ||
|
22e45d0e7b | ||
|
5bc4395ff6 | ||
|
d37f0fe117 | ||
|
63cc9353b1 | ||
|
cd205f2eb9 | ||
|
9cf3a93b70 | ||
|
b7ae9804a5 | ||
|
a394e93e06 | ||
|
f23e26fbbf | ||
|
b8d7737aeb | ||
|
59573e60f2 | ||
|
063b6b67a1 | ||
|
1250e66fb0 | ||
|
ae20ba8a70 | ||
|
7eec5b397c | ||
|
2b92341257 | ||
|
8be02f3344 | ||
|
267c610263 | ||
|
90ad0fdbdc | ||
|
90b8c083df | ||
|
49429fcff8 | ||
|
376e272c08 | ||
|
fabd8daf9a | ||
|
2eb011d965 | ||
|
6b46817f70 | ||
|
e205133c8e | ||
|
9bffa25dea | ||
|
e6464dbe5c | ||
|
e1efefaf7d | ||
|
7237f2709b | ||
|
516f4fa39d | ||
|
72b1d6af3e | ||
|
15debbddea | ||
|
a04761226d | ||
|
25fb6dec82 | ||
|
c380ee2206 | ||
|
347c85324b | ||
|
feb7627ad0 | ||
|
c3bb0ba291 | ||
|
ddb6d7f958 | ||
|
7957b89bb4 | ||
|
05ce6d2872 | ||
|
bc86988453 | ||
|
4bf5f33f25 | ||
|
5c75da4736 | ||
|
d7ed875566 | ||
|
f573c326d2 | ||
|
decc12637f | ||
|
a9ce3ca40d | ||
|
bb4a108e59 | ||
|
3e3ae50498 | ||
|
c468457052 | ||
|
14ceaa2667 | ||
|
514af05fc2 | ||
|
bf02f69aca | ||
|
3c1a49e44d | ||
|
610fa0227e | ||
|
bc7f1317da | ||
|
975b1ffda8 | ||
|
e9c55fe802 | ||
|
af4070a34f | ||
|
f0ba68f64a | ||
|
fc29a7211e | ||
|
7866203ac4 | ||
|
61051ffe5f | ||
|
127fbe50a2 | ||
|
fdc7f06f08 | ||
|
868f9f428a | ||
|
55c03ed520 | ||
|
a8c7297621 | ||
|
3f92e7c3e1 | ||
|
32f3aeba13 | ||
|
c204156b75 | ||
|
454a545ef6 | ||
|
716ec61913 | ||
|
dc642774eb | ||
|
b3a32b1231 | ||
|
ee17bd9e57 | ||
|
966fc7ad98 | ||
|
7925ffd640 | ||
|
6bed1cb7de | ||
|
10ae50aeb6 | ||
|
b21a19015a | ||
|
7c92808c67 | ||
|
bef4dc91f2 | ||
|
d21704e667 | ||
|
7ebb66bb4a | ||
|
0a5c655b5b | ||
|
ef5906db52 | ||
|
6c1513f65b | ||
|
1b9f11612b | ||
|
bce8daf43c | ||
|
39b37960dd | ||
|
00c6ea7d3e | ||
|
e7bae5fcb5 | ||
|
a1e79422a9 | ||
|
1d9d24e08c | ||
|
5e16c2f76e | ||
|
b55bd59219 | ||
|
1b69ce3aa1 | ||
|
66f2f18a00 | ||
|
58e1cf187d | ||
|
9075b74536 | ||
|
0c1c5c9499 | ||
|
e7a1fe1c17 | ||
|
70f26a08ba | ||
|
5dd988ac13 | ||
|
c97d1d457e | ||
|
4aab78b45c | ||
|
ffb9e85d75 | ||
|
ba59047d31 | ||
|
f21725d86c | ||
|
2e8e71bbd0 | ||
|
f1f274127b | ||
|
c288fd58c3 | ||
|
80e17be903 | ||
|
63e2b1f8c9 | ||
|
f015e2ed19 | ||
|
07af79adc8 | ||
|
2aaa43008d | ||
|
31d27a97c8 | ||
|
e2fe12c5f7 | ||
|
e095838a0e | ||
|
46f8570d33 | ||
|
6d07228a9c | ||
|
450634f7e2 | ||
|
29c5d30ef9 | ||
|
d6655bde2f | ||
|
2880e447bb | ||
|
7422e5becf | ||
|
dce0864bde | ||
|
c2f2b644a6 | ||
|
a984e6a5f9 | ||
|
159e9763d7 | ||
|
9c25c90a33 | ||
|
90cd7f2a4a | ||
|
71a237bfb6 | ||
|
0e7ea923d5 | ||
|
e23899f0d2 | ||
|
9ac89ed9a7 | ||
|
582f957b87 | ||
|
0092923543 | ||
|
cc86186e0a | ||
|
b65102721e | ||
|
bc81ee0227 | ||
|
69fa7f2762 | ||
|
f3843d9390 | ||
|
40f61c66dd | ||
|
3884ff8cfb | ||
|
4cbea6826f | ||
|
1e6294b365 | ||
|
6a6fdcfee7 | ||
|
a9d80ea0a2 | ||
|
130e8fb3f6 | ||
|
4fd80a46ee | ||
|
1dd74ed33e | ||
|
14df9d6438 | ||
|
497a5d31ae | ||
|
d3da994230 | ||
|
4d78980530 | ||
|
632f4b7b1d | ||
|
5d1bdbb68c | ||
|
d6e6b8007f | ||
|
31085f8218 | ||
|
1e70bff069 | ||
|
89e5f7e072 | ||
|
1a09f400e0 | ||
|
c366476cf7 | ||
|
fd411c49b9 | ||
|
7ea95225b2 | ||
|
ae9e99fab8 | ||
|
b1cbce1aaa | ||
|
a1853388fd | ||
|
155f03dc09 | ||
|
a120251882 | ||
|
2abe92cf42 | ||
|
29144335c1 | ||
|
ab518a067e | ||
|
6a2584d800 | ||
|
9d238fc42e | ||
|
b1d225765b | ||
|
e7369b61fa | ||
|
8b124376b2 | ||
|
bf90427bfa | ||
|
f431e7fadb | ||
|
079d8304ef | ||
|
f8bcd1c4b3 | ||
|
77fb3b14a0 | ||
|
0ebb948a17 | ||
|
05c2743ac1 | ||
|
75be1eaeae | ||
|
f822552133 | ||
|
f60da1d5a1 | ||
|
ffc22706db | ||
|
49c1881d29 | ||
|
eff4756762 | ||
|
871fc8c3c9 | ||
|
f96bc855b6 | ||
|
fcd143e763 | ||
|
330d35f65a | ||
|
d5b18d2c07 | ||
|
3d34888410 | ||
|
f638baedd3 | ||
|
2783da37b7 | ||
|
1462422e35 | ||
|
636d8c1b53 | ||
|
c569dee224 | ||
|
a313dd61eb | ||
|
37aa63438f | ||
|
b23dbc1c8b | ||
|
5d744fe2d0 | ||
|
4c21aa902e | ||
|
17c3fe9dd8 | ||
|
d9aef8d9c1 | ||
|
2988b9bcb6 | ||
|
52bee6a0e3 | ||
|
482ceb76dc | ||
|
9188dc19f0 | ||
|
1642aa5fce | ||
|
7f99e9b324 | ||
|
f96f8de92a | ||
|
c4bc4a2832 | ||
|
fa1ec55e75 | ||
|
209ecafb22 | ||
|
9096ee7051 | ||
|
c6eb69628d | ||
|
1a5f8dee72 | ||
|
e13fe6277d | ||
|
a23b2e3709 | ||
|
a5adcdbeb6 | ||
|
4e2f0d1413 | ||
|
5576bf2ef3 | ||
|
b37e372815 | ||
|
f7834b0f46 | ||
|
3613a754c5 | ||
|
42528eb07e | ||
|
e1c53c9087 | ||
|
6733319403 | ||
|
f7e6061f17 | ||
|
3d41d8a8d1 | ||
|
73d1586858 | ||
|
b064e6bd7f | ||
|
ffa0f40fb1 | ||
|
55ec328745 | ||
|
3f6e1b2caf | ||
|
91246ffad3 | ||
|
779fcf4fc6 | ||
|
c77795c832 | ||
|
33e5ebd6da | ||
|
7fc646f5b5 | ||
|
a536f1f4e1 | ||
|
ff05685634 | ||
|
e1f178d0ae | ||
|
5618ef3f34 | ||
|
fc88b065f5 | ||
|
31626b05c4 | ||
|
327dc521f1 | ||
|
b1cc459d13 | ||
|
3f8b99aa3a | ||
|
5f3dd12542 | ||
|
2279624750 | ||
|
a46f50dd0f | ||
|
405c1bf8f3 | ||
|
dcd25c2254 | ||
|
ec4777bd61 | ||
|
b1e7164c7e | ||
|
329bb9432e | ||
|
9ec84d2c96 | ||
|
eacc1adf1d | ||
|
724fb4956d | ||
|
3bdc5c67a2 | ||
|
563f82f48c | ||
|
b7d6256032 | ||
|
a599768638 | ||
|
d6cc3a419b | ||
|
8fbf5decf8 | ||
|
e3e9c76af2 | ||
|
8be3f91d8e | ||
|
2abb3968dd | ||
|
e76a4beb4d | ||
|
fd36e9ef9e | ||
|
b76e1df819 | ||
|
fb0aa72aac | ||
|
66e48dd834 | ||
|
79c86146ca | ||
|
4c756a1448 | ||
|
03fba76ae9 | ||
|
556480e8fc | ||
|
12cdc53f47 | ||
|
6f1f23108b | ||
|
69f0f5af87 | ||
|
fbb6b03e5b | ||
|
4265fa651b | ||
|
26a6f06c1a | ||
|
c91defc307 | ||
|
3d2b2f4c62 | ||
|
712f393df6 | ||
|
68238ba914 | ||
|
a11030814c | ||
|
25c95170e6 | ||
|
8414cde819 | ||
|
6341dedaf2 | ||
|
dd778dab76 | ||
|
ff1f8e8e84 | ||
|
111b4975ee | ||
|
ce31dc62c2 | ||
|
0ad09c96aa | ||
|
b9dc7c7033 | ||
|
349b99b3ef | ||
|
3eccd9afd1 | ||
|
c7bf40e3e8 | ||
|
43fed64f82 | ||
|
daf11ea121 | ||
|
9385146d56 | ||
|
d2fb973e1d | ||
|
eefdfbb2fe | ||
|
3250d6009a | ||
|
85a1223f63 | ||
|
4a98cc210a | ||
|
0ef01da963 | ||
|
5c78ad1fc4 | ||
|
7f87398c1f | ||
|
57782aa2a6 | ||
|
3165e1e499 | ||
|
38ef109291 | ||
|
3af6ac9c20 | ||
|
e7d63a463c | ||
|
0d4141a899 | ||
|
8dd7aff77f | ||
|
0a806b4fe2 | ||
|
47f1be0702 | ||
|
acebaa517b | ||
|
223ddf6a3e | ||
|
d3a12eb4e9 | ||
|
666a6311ae | ||
|
aa3d91d78f | ||
|
fe552eb6ef | ||
|
4055d1e93c | ||
|
8f4259cef3 | ||
|
8d390c4433 | ||
|
590ffac552 | ||
|
078534f7c8 | ||
|
1c4557b638 | ||
|
9173b103f5 | ||
|
90d3f89c0a | ||
|
35cd9f37d1 | ||
|
100ec2c1a8 | ||
|
7316d27b54 | ||
|
f8d6db61c5 | ||
|
835ce6b81f | ||
|
12ac2f551b | ||
|
28036bfb36 | ||
|
ee244463fa | ||
|
5891d23462 | ||
|
6f25fca814 | ||
|
173c17e13d | ||
|
dbfee988d2 | ||
|
905d2fbb23 | ||
|
e6b402c7d8 | ||
|
5aa2689832 | ||
|
ff2eb42847 | ||
|
03edf60663 | ||
|
938b20b4cd | ||
|
7887256ca1 | ||
|
b71823b4c5 | ||
|
7e33ea1fe7 | ||
|
5bdc19f35f | ||
|
ffaf349e0a | ||
|
30f721eeb6 | ||
|
2c903a335e | ||
|
5db6ed257e | ||
|
c6ae5107c6 | ||
|
4535041876 | ||
|
fa2561306d | ||
|
6e1be862d2 | ||
|
074a084853 | ||
|
cb17f2a25f | ||
|
e0f97d8af0 | ||
|
beb22274d6 | ||
|
15d63a6a96 | ||
|
925d2e65bb | ||
|
03486a0453 | ||
|
813c97db18 | ||
|
fe4e475ff4 | ||
|
bcd74eefda | ||
|
2b220b59e6 | ||
|
72659050c3 | ||
|
ed83af7568 | ||
|
70994b7244 | ||
|
c1429e0907 | ||
|
9fb33776c5 | ||
|
06134a6524 | ||
|
479f60df01 | ||
|
ac4ea69fa0 | ||
|
f265bf3e7f | ||
|
dda6d652a6 | ||
|
5ee75d343e | ||
|
9a3c569290 | ||
|
8aa6997699 | ||
|
5d50f22010 | ||
|
81782fb402 | ||
|
8f742bf99f | ||
|
628b105535 | ||
|
93d1b486f8 | ||
|
d41ad3bc56 | ||
|
715000daf1 | ||
|
5d02c82915 | ||
|
abee4db340 | ||
|
6966c8d3e5 | ||
|
8fb75d57fc | ||
|
08a7b8586b | ||
|
b502e21a0f | ||
|
c5447b8d7a | ||
|
395477ed12 | ||
|
5e8b46b7df | ||
|
d88fb95767 | ||
|
0c032e710c | ||
|
9e1a7e700d | ||
|
b65aeaf0d2 | ||
|
4522b46900 | ||
|
2f789e983a | ||
|
e3ef7a3048 | ||
|
9d5aaf43a8 | ||
|
8839f1d9c7 | ||
|
3f09f5f307 | ||
|
3c396e0ccc | ||
|
c3b0009fb8 | ||
|
fc95253dd5 | ||
|
feb198d205 | ||
|
cae59cb84b | ||
|
ad1d391f49 | ||
|
04accf0075 | ||
|
fdf1da7886 | ||
|
c1a351a704 | ||
|
742b67d5bd | ||
42730e980f | |||
abedb59a17 | |||
a9d9ed9ae6 | |||
|
606cbd8859 | ||
|
d8b1e0d5ff | ||
|
4853df39bb | ||
|
78e9e48626 | ||
|
3d71d7bf4e | ||
|
9cc000070b | ||
|
a5a131daf1 | ||
|
0c7ac98aaf | ||
|
b536637c98 | ||
|
98ae0459c9 | ||
|
652394f535 | ||
|
9cbeee0451 | ||
|
af4423cc2e | ||
|
e9968288fd | ||
|
cca6ab1df0 | ||
|
50049177e4 | ||
|
20114d9a84 | ||
|
d682f28faf | ||
|
c6b3e05365 | ||
|
4fb6a58689 | ||
|
9a5c101d9d | ||
|
82a64165ea | ||
|
a9177e585c | ||
|
7fd4012454 | ||
|
f4188ef5c9 | ||
|
7fcc3e5137 | ||
|
19d9ab7019 | ||
|
2e5ec355d6 | ||
|
604953a346 | ||
|
d87e5062c2 | ||
|
4516b886df | ||
|
65e78556c2 | ||
|
ba5b83eb02 | ||
|
e9710bb71d | ||
|
e1ea19deb4 | ||
|
b48a7f21e3 | ||
|
6bd584a36a | ||
|
5e4f99f5a1 | ||
|
0250d8eb48 | ||
|
ddbc0026e3 | ||
|
eac958f9e2 | ||
|
f0afcc4a66 | ||
|
d387b86dcf | ||
|
472a6dd6e9 | ||
|
2f620b2a85 | ||
|
efb0c4fac0 | ||
|
3fa0904d28 | ||
|
b5f6aa64ac | ||
|
4524e1e55a | ||
|
89ac966276 | ||
|
af75f4fd1d | ||
|
a344a215c4 | ||
|
0b77a61938 | ||
|
8ae5be9f80 | ||
|
44a13affe0 | ||
|
f542c68eae | ||
|
76fa5dcabe | ||
|
532a878911 | ||
|
d25e052b61 | ||
|
1b3626aeee | ||
|
2d5f52aa5b | ||
|
d401b12839 | ||
|
fbbab10391 | ||
|
cd95992805 | ||
|
740f07a2b6 | ||
|
6973f70e4c | ||
|
2e9872f958 | ||
|
12eb63c789 | ||
|
3ca80daee8 | ||
|
a4684ddf39 | ||
|
c401f39520 | ||
|
7afae96cf4 | ||
|
c866cc812a | ||
|
0ef2428c80 | ||
|
9f0e4f34fe | ||
|
afbc1e1455 | ||
|
012112336f | ||
|
d096bd1f24 | ||
|
7746b5673d | ||
|
8a2269b7d0 | ||
|
ec721b83ce | ||
|
6e968c492b | ||
|
b425743484 | ||
|
f2ccf574ed | ||
|
4054f7dbff | ||
|
82dc860985 | ||
|
cdfddf5888 | ||
|
7d3366389b | ||
|
d46d524859 | ||
|
87bb752c92 | ||
|
4fe30107a4 | ||
|
9208330038 | ||
|
b3ac8848d2 | ||
|
2c2f299e46 | ||
|
04188a55f1 | ||
|
c4d2ad922a | ||
|
e0b2632028 | ||
|
29c9cad510 | ||
|
746ca695af | ||
|
f5f2d94fbc | ||
|
a285d0b493 | ||
|
742fc10e0d | ||
|
6edacc78e4 | ||
|
c0329dc73d | ||
|
308601b358 | ||
|
973ba8a4da | ||
|
7889332052 | ||
|
bceb9821bd | ||
|
e60dff110a | ||
|
a8eb9a3aaa | ||
|
d7952e82f5 | ||
|
c065510413 | ||
|
5bee5cb4b8 | ||
|
4915f6a5b8 | ||
|
a236b3704d | ||
|
0eb2268875 | ||
|
7c0e4e778c | ||
|
4858aa9fe5 | ||
|
9225710d2d | ||
|
7a419742ad | ||
|
d6bdf5bfeb | ||
|
d888f685d0 | ||
|
38cc93bdec | ||
|
5b41019c04 | ||
|
e0289ed23f | ||
|
9dcac8dd48 | ||
|
df3608d9b2 | ||
|
54cd636524 | ||
|
14189fd848 | ||
|
8d7e6297cc | ||
|
c8a25546dc | ||
|
e964814895 | ||
|
812d120d79 | ||
|
581d21be93 | ||
|
587ca1f42b | ||
|
a877be36fa | ||
|
5e7f85dbb7 | ||
|
043205eca8 | ||
|
f836afce7a | ||
|
69aa8a8cc1 | ||
|
acb0d3b9ee | ||
|
c0b632325e | ||
|
8880b91c28 | ||
|
81854702a7 | ||
|
7cbece9212 | ||
|
96c2a982cb | ||
|
5a2b5b2a4d | ||
|
29cf1cebb2 | ||
|
e0e2f12e27 | ||
|
9dbd4f076c | ||
|
3e67180fe1 | ||
|
0c66bb0579 | ||
|
ac7ecd9bdc | ||
|
06f86963d1 | ||
|
fc68a8d069 | ||
|
b74b83a2cb | ||
|
13ba7d1a5e | ||
|
d80ea676c5 | ||
|
95adc67701 | ||
93f5b4ba2a | |||
4b1c226173 | |||
|
7e859c7823 | ||
|
633bab9c30 | ||
|
2c72972097 | ||
|
1a6ced0373 | ||
|
d156b24fd1 | ||
|
f5366540f7 | ||
|
18733f2d09 | ||
|
74fe0296e0 | ||
|
584c639d01 | ||
|
a8ad464623 | ||
|
b63039b803 | ||
|
8d9d4f397d | ||
|
239abbdcf8 | ||
|
6a9179d4b8 | ||
|
3aaad14f11 | ||
|
a266b8ed09 | ||
|
cbaf4c150d | ||
|
249f58d3d8 | ||
|
f2460a5e12 | ||
|
3a56d6fca4 | ||
|
bafcb4f7b8 | ||
|
183ec02376 | ||
|
1806ba80fb | ||
|
09080f8d73 |
20
.github/CODEOWNERS
vendored
20
.github/CODEOWNERS
vendored
|
@ -9,22 +9,22 @@
|
||||||
# This also holds true for GitHub teams.
|
# This also holds true for GitHub teams.
|
||||||
|
|
||||||
# Rescript
|
# Rescript
|
||||||
*.res @OAGr @quinn-dougherty
|
*.res @berekuk @OAGr
|
||||||
*.resi @OAGr @quinn-dougherty
|
*.resi @berekuk @OAGr
|
||||||
|
|
||||||
# Typescript
|
# Typescript
|
||||||
*.tsx @Hazelfire @OAGr
|
*.tsx @Hazelfire @berekuk @OAGr
|
||||||
*.ts @Hazelfire @OAGr
|
*.ts @Hazelfire @berekuk @OAGr
|
||||||
|
|
||||||
# Javascript
|
# Javascript
|
||||||
*.js @Hazelfire @OAGr
|
*.js @Hazelfire @berekuk @OAGr
|
||||||
|
|
||||||
# Any opsy files
|
# Any opsy files
|
||||||
.github/** @quinn-dougherty @OAGr
|
.github/** @quinn-dougherty @berekuk @OAGr
|
||||||
*.json @quinn-dougherty @Hazelfire @OAGr
|
*.json @quinn-dougherty @Hazelfire @berekuk @OAGr
|
||||||
*.y*ml @quinn-dougherty @OAGr
|
*.y*ml @quinn-dougherty @berekuk @OAGr
|
||||||
*.config.js @Hazelfire @OAGr
|
*.config.js @Hazelfire @berekuk @OAGr
|
||||||
netlify.toml @quinn-dougherty @OAGr @Hazelfire
|
vercel.json @OAGr @berekuk @Hazelfire
|
||||||
|
|
||||||
# Documentation
|
# Documentation
|
||||||
*.md @quinn-dougherty @OAGr @Hazelfire
|
*.md @quinn-dougherty @OAGr @Hazelfire
|
||||||
|
|
11
.github/dependabot.yml
vendored
11
.github/dependabot.yml
vendored
|
@ -11,3 +11,14 @@ updates:
|
||||||
interval: "weekly"
|
interval: "weekly"
|
||||||
commit-message:
|
commit-message:
|
||||||
prefix: "⬆️"
|
prefix: "⬆️"
|
||||||
|
open-pull-requests-limit: 100
|
||||||
|
labels:
|
||||||
|
- "dependencies"
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
commit-message:
|
||||||
|
prefix: "⬆️"
|
||||||
|
labels:
|
||||||
|
- "dependencies"
|
||||||
|
|
87
.github/workflows/ci-cachix.yml
vendored
Normal file
87
.github/workflows/ci-cachix.yml
vendored
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
name: Nix build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- develop
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- develop
|
||||||
|
- reducer-dev
|
||||||
|
- epic-reducer-project
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
flake-lints:
|
||||||
|
name: All lint
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Install nix
|
||||||
|
uses: cachix/install-nix-action@v17
|
||||||
|
with:
|
||||||
|
nix_path: nixpkgs=channel:nixos-22.05
|
||||||
|
- name: Use cachix
|
||||||
|
uses: cachix/cachix-action@v10
|
||||||
|
with:
|
||||||
|
name: quantified-uncertainty
|
||||||
|
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||||
|
|
||||||
|
- name: Check that lang lints
|
||||||
|
run: nix build .#lang-lint
|
||||||
|
- name: Check that components lints
|
||||||
|
run: nix build .#components-lint
|
||||||
|
- name: Check that website lints
|
||||||
|
run: nix build .#docusaurus-lint
|
||||||
|
- name: Check that vscode extension lints
|
||||||
|
run: nix build .#vscode-lint
|
||||||
|
- name: Check that cli lints
|
||||||
|
run: nix build .#cli-lint
|
||||||
|
|
||||||
|
flake-packages:
|
||||||
|
name: Builds, tests, and bundles
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: flake-lints
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Install nix
|
||||||
|
uses: cachix/install-nix-action@v17
|
||||||
|
with:
|
||||||
|
nix_path: nixpkgs=channel:nixos-22.05
|
||||||
|
- name: Use cachix
|
||||||
|
uses: cachix/cachix-action@v10
|
||||||
|
with:
|
||||||
|
name: quantified-uncertainty
|
||||||
|
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||||
|
|
||||||
|
- name: Check all lang tests
|
||||||
|
run: nix build .#lang-test
|
||||||
|
- name: Check that lang bundles
|
||||||
|
run: nix build .#lang-bundle
|
||||||
|
- name: Check that components builds
|
||||||
|
run: nix build .#components
|
||||||
|
- name: Check that components bundles
|
||||||
|
run: nix build .#components-bundle
|
||||||
|
|
||||||
|
flake-devshells:
|
||||||
|
name: Development shell environment
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Install nix
|
||||||
|
uses: cachix/install-nix-action@v17
|
||||||
|
with:
|
||||||
|
nix_path: nixpkgs=channel:nixos-22.05
|
||||||
|
- name: Use cachix
|
||||||
|
uses: cachix/cachix-action@v10
|
||||||
|
with:
|
||||||
|
name: quantified-uncertainty
|
||||||
|
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
||||||
|
- name: Build js devshell
|
||||||
|
run: nix develop .#js --profile just-js
|
||||||
|
- name: Build js & wasm devshell
|
||||||
|
run: nix develop --profile full-shell
|
227
.github/workflows/ci.yml
vendored
227
.github/workflows/ci.yml
vendored
|
@ -1,4 +1,4 @@
|
||||||
name: Squiggle packages check
|
name: Squiggle packages checks
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
@ -9,213 +9,40 @@ on:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- develop
|
- develop
|
||||||
- reducer-dev
|
|
||||||
|
env:
|
||||||
|
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||||
|
TURBO_TEAM: quantified-uncertainty
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
pre_check:
|
build-test-lint:
|
||||||
name: Precheck for skipping redundant jobs
|
name: Build, test, lint
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
outputs:
|
|
||||||
should_skip_lang: ${{ steps.skip_lang_check.outputs.should_skip }}
|
|
||||||
should_skip_components: ${{ steps.skip_components_check.outputs.should_skip }}
|
|
||||||
should_skip_website: ${{ steps.skip_website_check.outputs.should_skip }}
|
|
||||||
should_skip_vscodeext: ${{ steps.skip_vscodeext_check.outputs.should_skip }}
|
|
||||||
should_skip_cli: ${{ steps.skip_cli_check.outputs.should_skip }}
|
|
||||||
steps:
|
steps:
|
||||||
- id: skip_lang_check
|
- uses: actions/checkout@v3
|
||||||
name: Check if the changes are about squiggle-lang src files
|
- name: Setup Node.js environment
|
||||||
uses: fkirc/skip-duplicate-actions@v3.4.1
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
paths: '["packages/squiggle-lang/**"]'
|
node-version: 16
|
||||||
- id: skip_components_check
|
cache: 'yarn'
|
||||||
name: Check if the changes are about components src files
|
- name: Install dependencies
|
||||||
uses: fkirc/skip-duplicate-actions@v3.4.1
|
run: yarn --frozen-lockfile
|
||||||
with:
|
- name: Turbo run
|
||||||
paths: '["packages/components/**"]'
|
run: npx turbo run build test lint bundle
|
||||||
- id: skip_website_check
|
|
||||||
name: Check if the changes are about website src files
|
|
||||||
uses: fkirc/skip-duplicate-actions@v3.4.1
|
|
||||||
with:
|
|
||||||
paths: '["packages/website/**"]'
|
|
||||||
- id: skip_vscodeext_check
|
|
||||||
name: Check if the changes are about vscode extension src files
|
|
||||||
uses: fkirc/skip-duplicate-actions@v3.4.1
|
|
||||||
with:
|
|
||||||
paths: '["packages/vscode-ext/**"]'
|
|
||||||
- id: skip_cli_check
|
|
||||||
name: Check if the changes are about cli src files
|
|
||||||
uses: fkirc/skip-duplicate-actions@v3.4.1
|
|
||||||
with:
|
|
||||||
paths: '["packages/cli/**"]'
|
|
||||||
|
|
||||||
lang-lint:
|
coverage:
|
||||||
name: Language lint
|
name: Coverage
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: pre_check
|
|
||||||
if: ${{ needs.pre_check.outputs.should_skip_lang != 'true' }}
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
shell: bash
|
|
||||||
working-directory: packages/squiggle-lang
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- name: Install Dependencies
|
|
||||||
run: cd ../../ && yarn
|
|
||||||
- name: Check rescript lint
|
|
||||||
run: yarn lint:rescript
|
|
||||||
- name: Check javascript, typescript, and markdown lint
|
|
||||||
uses: creyD/prettier_action@v4.2
|
|
||||||
with:
|
|
||||||
dry: true
|
|
||||||
prettier_options: --check packages/squiggle-lang
|
|
||||||
|
|
||||||
lang-build-test-bundle:
|
|
||||||
name: Language build, test, and bundle
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: pre_check
|
|
||||||
if: ${{ needs.pre_check.outputs.should_skip_lang != 'true' }}
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
shell: bash
|
|
||||||
working-directory: packages/squiggle-lang
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
with:
|
with:
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
- name: Install dependencies from monorepo level
|
- name: Setup Node.js environment
|
||||||
run: cd ../../ && yarn
|
uses: actions/setup-node@v2
|
||||||
- name: Build rescript codebase
|
|
||||||
run: yarn build
|
|
||||||
- name: Run rescript tests
|
|
||||||
run: yarn test:rescript
|
|
||||||
- name: Run typescript tests
|
|
||||||
run: yarn test:ts
|
|
||||||
- name: Run webpack
|
|
||||||
run: yarn bundle
|
|
||||||
- name: Upload rescript coverage report
|
|
||||||
run: yarn coverage:rescript:ci
|
|
||||||
- name: Upload typescript coverage report
|
|
||||||
run: yarn coverage:ts:ci
|
|
||||||
|
|
||||||
components-lint:
|
|
||||||
name: Components lint
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: pre_check
|
|
||||||
if: ${{ needs.pre_check.outputs.should_skip_components != 'true' }}
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
shell: bash
|
|
||||||
working-directory: packages/components
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Check javascript, typescript, and markdown lint
|
|
||||||
uses: creyD/prettier_action@v4.2
|
|
||||||
with:
|
with:
|
||||||
dry: true
|
node-version: 16
|
||||||
prettier_options: --check packages/components --ignore-path packages/components/.prettierignore
|
cache: 'yarn'
|
||||||
|
- name: Install dependencies
|
||||||
components-bundle-build:
|
run: yarn
|
||||||
name: Components bundle and build
|
- name: Coverage
|
||||||
runs-on: ubuntu-latest
|
run: npx turbo run coverage
|
||||||
needs: pre_check
|
|
||||||
if: ${{ (needs.pre_check.outputs.should_skip_components != 'true') || (needs.pre_check.outputs.should_skip_lang != 'true') }}
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
shell: bash
|
|
||||||
working-directory: packages/components
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Install dependencies from monorepo level
|
|
||||||
run: cd ../../ && yarn
|
|
||||||
- name: Build rescript codebase in squiggle-lang
|
|
||||||
run: cd ../squiggle-lang && yarn build
|
|
||||||
- name: Run webpack
|
|
||||||
run: yarn bundle
|
|
||||||
- name: Build storybook
|
|
||||||
run: yarn build
|
|
||||||
|
|
||||||
website-lint:
|
|
||||||
name: Website lint
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: pre_check
|
|
||||||
if: ${{ needs.pre_check.outputs.should_skip_website != 'true' }}
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
shell: bash
|
|
||||||
working-directory: packages/website
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Check javascript, typescript, and markdown lint
|
|
||||||
uses: creyD/prettier_action@v4.2
|
|
||||||
with:
|
|
||||||
dry: true
|
|
||||||
prettier_options: --check packages/website
|
|
||||||
|
|
||||||
website-build:
|
|
||||||
name: Website build
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: pre_check
|
|
||||||
if: ${{ (needs.pre_check.outputs.should_skip_website != 'true') || (needs.pre_check.outputs.should_skip_lang != 'true') || (needs.pre_check.outputs.should_skip_components != 'true') }}
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
shell: bash
|
|
||||||
working-directory: packages/website
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Install dependencies from monorepo level
|
|
||||||
run: cd ../../ && yarn
|
|
||||||
- name: Build rescript in squiggle-lang
|
|
||||||
run: cd ../squiggle-lang && yarn build
|
|
||||||
- name: Build components
|
|
||||||
run: cd ../components && yarn build
|
|
||||||
- name: Build website assets
|
|
||||||
run: yarn build
|
|
||||||
|
|
||||||
vscode-ext-lint:
|
|
||||||
name: VS Code extension lint
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: pre_check
|
|
||||||
if: ${{ needs.pre_check.outputs.should_skip_vscodeext != 'true' }}
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
shell: bash
|
|
||||||
working-directory: packages/vscode-ext
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Install dependencies from monorepo level
|
|
||||||
run: cd ../../ && yarn
|
|
||||||
- name: Lint the VSCode Extension source code
|
|
||||||
run: yarn lint
|
|
||||||
|
|
||||||
vscode-ext-build:
|
|
||||||
name: VS Code extension build
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: pre_check
|
|
||||||
if: ${{ (needs.pre_check.outputs.should_skip_components != 'true') || (needs.pre_check.outputs.should_skip_lang != 'true') }} || (needs.pre_check.outputs.should_skip_vscodeext != 'true') }}
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
shell: bash
|
|
||||||
working-directory: packages/vscode-ext
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Install dependencies from monorepo level
|
|
||||||
run: cd ../../ && yarn
|
|
||||||
- name: Build
|
|
||||||
run: yarn compile
|
|
||||||
|
|
||||||
cli-lint:
|
|
||||||
name: CLI lint
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: pre_check
|
|
||||||
if: ${{ needs.pre_check.outputs.should_skip_cli != 'true' }}
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
shell: bash
|
|
||||||
working-directory: packages/cli
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Check javascript, typescript, and markdown lint
|
|
||||||
uses: creyD/prettier_action@v4.2
|
|
||||||
with:
|
|
||||||
dry: true
|
|
||||||
prettier_options: --check packages/cli
|
|
||||||
|
|
8
.github/workflows/codeql-analysis.yml
vendored
8
.github/workflows/codeql-analysis.yml
vendored
|
@ -33,11 +33,11 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v1
|
uses: github/codeql-action/init@v2
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
|
@ -48,7 +48,7 @@ jobs:
|
||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
# If this step fails, then you should remove it and run the build manually (see below)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v1
|
uses: github/codeql-action/autobuild@v2
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: yarn
|
run: yarn
|
||||||
- name: Build rescript
|
- name: Build rescript
|
||||||
|
@ -65,4 +65,4 @@ jobs:
|
||||||
# make release
|
# make release
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v1
|
uses: github/codeql-action/analyze@v2
|
||||||
|
|
141
.github/workflows/release-please.yml
vendored
Normal file
141
.github/workflows/release-please.yml
vendored
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
name: Run Release Please
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
pre_check:
|
||||||
|
name: Precheck for skipping redundant jobs
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
should_skip_lang: ${{ steps.skip_lang_check.outputs.should_skip }}
|
||||||
|
should_skip_components: ${{ steps.skip_components_check.outputs.should_skip }}
|
||||||
|
should_skip_website: ${{ steps.skip_website_check.outputs.should_skip }}
|
||||||
|
should_skip_vscodeext: ${{ steps.skip_vscodeext_check.outputs.should_skip }}
|
||||||
|
should_skip_cli: ${{ steps.skip_cli_check.outputs.should_skip }}
|
||||||
|
steps:
|
||||||
|
- id: skip_lang_check
|
||||||
|
name: Check if the changes are about squiggle-lang src files
|
||||||
|
uses: fkirc/skip-duplicate-actions@v5.2.0
|
||||||
|
with:
|
||||||
|
paths: '["packages/squiggle-lang/**"]'
|
||||||
|
- id: skip_components_check
|
||||||
|
name: Check if the changes are about components src files
|
||||||
|
uses: fkirc/skip-duplicate-actions@v5.2.0
|
||||||
|
with:
|
||||||
|
paths: '["packages/components/**"]'
|
||||||
|
- id: skip_website_check
|
||||||
|
name: Check if the changes are about website src files
|
||||||
|
uses: fkirc/skip-duplicate-actions@v5.2.0
|
||||||
|
with:
|
||||||
|
paths: '["packages/website/**"]'
|
||||||
|
- id: skip_vscodeext_check
|
||||||
|
name: Check if the changes are about vscode extension src files
|
||||||
|
uses: fkirc/skip-duplicate-actions@v5.2.0
|
||||||
|
with:
|
||||||
|
paths: '["packages/vscode-ext/**"]'
|
||||||
|
- id: skip_cli_check
|
||||||
|
name: Check if the changes are about cli src files
|
||||||
|
uses: fkirc/skip-duplicate-actions@v5.2.0
|
||||||
|
with:
|
||||||
|
paths: '["packages/cli/**"]'
|
||||||
|
|
||||||
|
relplz-lang:
|
||||||
|
name: for squiggle-lang
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: pre_check
|
||||||
|
if: ${{ needs.pre_check.outputs.should_skip_lang != 'true' }}
|
||||||
|
steps:
|
||||||
|
- name: Release please (squiggle-lang)
|
||||||
|
uses: google-github-actions/release-please-action@v3
|
||||||
|
id: release
|
||||||
|
with:
|
||||||
|
token: ${{secrets.GITHUB_TOKEN}}
|
||||||
|
command: manifest-pr
|
||||||
|
path: packages/squiggle-lang
|
||||||
|
# bump-patch-for-minor-pre-major: true
|
||||||
|
skip-github-release: true
|
||||||
|
- name: Publish- Checkout source
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
# these if statements ensure that a publication only occurs when
|
||||||
|
# a new release is created:
|
||||||
|
if: ${{ steps.release.outputs.release_created }}
|
||||||
|
- name: Publish- Install dependencies
|
||||||
|
run: yarn
|
||||||
|
if: ${{ steps.release.outputs.release_created }}
|
||||||
|
- name: Publish
|
||||||
|
run: cd packages/squiggle-lang && yarn publish
|
||||||
|
env:
|
||||||
|
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
||||||
|
if: ${{ steps.release.outputs.release_created }}
|
||||||
|
|
||||||
|
relplz-components:
|
||||||
|
name: for components
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: pre_check
|
||||||
|
if: ${{ needs.pre_check.outputs.should_skip_components != 'true' }}
|
||||||
|
steps:
|
||||||
|
- name: Release please (components)
|
||||||
|
uses: google-github-actions/release-please-action@v3
|
||||||
|
with:
|
||||||
|
token: ${{secrets.GITHUB_TOKEN}}
|
||||||
|
command: manifest-pr
|
||||||
|
path: packages/components
|
||||||
|
# bump-patch-for-minor-pre-major: true
|
||||||
|
skip-github-release: true
|
||||||
|
- name: Publish- Checkout source
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
# these if statements ensure that a publication only occurs when
|
||||||
|
# a new release is created:
|
||||||
|
if: ${{ steps.release.outputs.release_created }}
|
||||||
|
- name: Publish- Install dependencies
|
||||||
|
run: yarn
|
||||||
|
if: ${{ steps.release.outputs.release_created }}
|
||||||
|
- name: Publish
|
||||||
|
run: cd packages/components && yarn publish
|
||||||
|
env:
|
||||||
|
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
||||||
|
relplz-website:
|
||||||
|
name: for website
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: pre_check
|
||||||
|
if: ${{ needs.pre_check.outputs.should_skip_website != 'true' }}
|
||||||
|
steps:
|
||||||
|
- name: Release please (website)
|
||||||
|
uses: google-github-actions/release-please-action@v3
|
||||||
|
with:
|
||||||
|
token: ${{secrets.GITHUB_TOKEN}}
|
||||||
|
command: manifest-pr
|
||||||
|
path: packages/website
|
||||||
|
# bump-patch-for-minor-pre-major: true
|
||||||
|
skip-github-release: true
|
||||||
|
relplz-vscodeext:
|
||||||
|
name: for vscode-ext
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: pre_check
|
||||||
|
if: ${{ needs.pre_check.outputs.should_skip_vscodeext != 'true' }}
|
||||||
|
steps:
|
||||||
|
- name: Release please (vscode-ext)
|
||||||
|
uses: google-github-actions/release-please-action@v3
|
||||||
|
with:
|
||||||
|
token: ${{secrets.GITHUB_TOKEN}}
|
||||||
|
command: manifest-pr
|
||||||
|
path: packages/vscode-ext
|
||||||
|
# bump-patch-for-minor-pre-major: true
|
||||||
|
skip-github-release: true
|
||||||
|
relplz-cl:
|
||||||
|
name: for cli
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: pre_check
|
||||||
|
if: ${{ needs.pre_check.outputs.should_skip_cli != 'true' }}
|
||||||
|
steps:
|
||||||
|
- name: Release please (cli)
|
||||||
|
uses: google-github-actions/release-please-action@v3
|
||||||
|
with:
|
||||||
|
token: ${{secrets.GITHUB_TOKEN}}
|
||||||
|
command: manifest-pr
|
||||||
|
path: packages/cli
|
||||||
|
bump-patch-for-minor-pre-major: true
|
||||||
|
skip-github-release: true
|
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -7,3 +7,9 @@ yarn-error.log
|
||||||
**/.sync.ffs_db
|
**/.sync.ffs_db
|
||||||
.direnv
|
.direnv
|
||||||
.log
|
.log
|
||||||
|
|
||||||
|
.vscode
|
||||||
|
todo.txt
|
||||||
|
result
|
||||||
|
shell.nix
|
||||||
|
.turbo
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
.direnv
|
.direnv
|
||||||
*.bs.js
|
*.bs.js
|
||||||
*.gen.tsx
|
*.gen.tsx
|
||||||
packages/*/dist
|
|
||||||
packages/components/storybook-static
|
packages/components/storybook-static
|
||||||
node_modules
|
node_modules
|
||||||
packages/*/node_modules
|
packages/*/node_modules
|
||||||
packages/website/.docusaurus
|
packages/website/.docusaurus
|
||||||
packages/squiggle-lang/lib
|
packages/squiggle-lang/lib
|
||||||
packages/squiggle-lang/.nyc_output/
|
|
||||||
packages/squiggle-lang/coverage/
|
packages/squiggle-lang/coverage/
|
||||||
packages/squiggle-lang/.cache/
|
packages/squiggle-lang/.cache/
|
||||||
packages/website/build/
|
packages/website/build/
|
||||||
packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_GeneratedParser.js
|
packages/squiggle-lang/src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_GeneratedParser.js
|
||||||
packages/vscode-ext/media/vendor/
|
packages/vscode-ext/media/vendor/
|
||||||
|
packages/squiggle-lang/.nyc_output/
|
||||||
|
packages/*/dist
|
||||||
|
result
|
||||||
|
|
7
.release-please-manifest.json
Normal file
7
.release-please-manifest.json
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"packages/cli": "0.0.3",
|
||||||
|
"packages/components": "0.4.1",
|
||||||
|
"packages/squiggle-lang": "0.4.1",
|
||||||
|
"packages/vscode-ext": "0.4.1",
|
||||||
|
"packages/website": "0.0.0"
|
||||||
|
}
|
1
CHANGELOG.md
Normal file
1
CHANGELOG.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
See the [Changelog.mdx page](./packages/website/docs/Changelog.mdx) for the changelog.
|
|
@ -16,7 +16,7 @@ Squiggle is currently pre-alpha.
|
||||||
|
|
||||||
# Bug reports
|
# Bug reports
|
||||||
|
|
||||||
Anyone (with a github account) can file an issue at any time. Please allow Quinn, Sam, and Ozzie to triage, but otherwise just follow the suggestions in the issue templates.
|
Anyone (with a github account) can file an issue at any time. Please allow Slava, Sam, and Ozzie to triage, but otherwise just follow the suggestions in the issue templates.
|
||||||
|
|
||||||
# Project structure
|
# Project structure
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ Squiggle is a **monorepo** with three **packages**.
|
||||||
|
|
||||||
# Deployment ops
|
# Deployment ops
|
||||||
|
|
||||||
We use netlify, and it should only concern Quinn, Sam, and Ozzie.
|
We use Vercel, and it should only concern Slava, Sam, and Ozzie.
|
||||||
|
|
||||||
# Development environment, building, testing, dev server
|
# Development environment, building, testing, dev server
|
||||||
|
|
||||||
|
@ -56,9 +56,9 @@ If you absolutely must, please prefix your commit message with `hotfix: `.
|
||||||
|
|
||||||
Please work against `develop` branch. **Do not** work against `master`.
|
Please work against `develop` branch. **Do not** work against `master`.
|
||||||
|
|
||||||
- For rescript code: Quinn and Ozzie are reviewers
|
- For rescript code: Slava and Ozzie are reviewers
|
||||||
- For js or typescript code: Sam and Ozzie are reviewers
|
- For js or typescript code: Sam and Ozzie are reviewers
|
||||||
- For ops code (i.e. yaml, package.json): Quinn and Sam are reviewers
|
- For ops code (i.e. yaml, package.json): Slava and Sam are reviewers
|
||||||
|
|
||||||
Autopings are set up: if you are not autopinged, you are welcome to comment, but please do not use the formal review feature, send approvals, rejections, or merges.
|
Autopings are set up: if you are not autopinged, you are welcome to comment, but please do not use the formal review feature, send approvals, rejections, or merges.
|
||||||
|
|
||||||
|
|
32
README.md
32
README.md
|
@ -12,8 +12,8 @@ _An estimation language_.
|
||||||
|
|
||||||
- [Gallery](https://www.squiggle-language.com/docs/Discussions/Gallery)
|
- [Gallery](https://www.squiggle-language.com/docs/Discussions/Gallery)
|
||||||
- [Squiggle playground](https://squiggle-language.com/playground)
|
- [Squiggle playground](https://squiggle-language.com/playground)
|
||||||
- [Language basics](https://www.squiggle-language.com/docs/Features/Language)
|
- [Language basics](https://www.squiggle-language.com/docs/Guides/Language)
|
||||||
- [Squiggle functions source of truth](https://www.squiggle-language.com/docs/Features/Functions)
|
- [Squiggle functions source of truth](https://www.squiggle-language.com/docs/Guides/Functions)
|
||||||
- [Known bugs](https://www.squiggle-language.com/docs/Discussions/Bugs)
|
- [Known bugs](https://www.squiggle-language.com/docs/Discussions/Bugs)
|
||||||
- [Original lesswrong sequence](https://www.lesswrong.com/s/rDe8QE5NvXcZYzgZ3)
|
- [Original lesswrong sequence](https://www.lesswrong.com/s/rDe8QE5NvXcZYzgZ3)
|
||||||
- [Author your squiggle models as Observable notebooks](https://observablehq.com/@hazelfire/squiggle)
|
- [Author your squiggle models as Observable notebooks](https://observablehq.com/@hazelfire/squiggle)
|
||||||
|
@ -21,10 +21,10 @@ _An estimation language_.
|
||||||
|
|
||||||
## Our deployments
|
## Our deployments
|
||||||
|
|
||||||
- **website/docs prod**: https://squiggle-language.com [![Netlify Status](https://api.netlify.com/api/v1/badges/2139af5c-671d-473d-a9f6-66c96077d8a1/deploy-status)](https://app.netlify.com/sites/squiggle-documentation/deploys)
|
- **website/docs prod**: https://squiggle-language.com
|
||||||
- **website/docs staging**: https://develop--squiggle-documentation.netlify.app/
|
- **website/docs staging**: https://preview.squiggle-language.com
|
||||||
- **components storybook prod**: https://squiggle-components.netlify.app/ [![Netlify Status](https://api.netlify.com/api/v1/badges/b7f724aa-6b20-4d0e-bf86-3fcd1a3e9a70/deploy-status)](https://app.netlify.com/sites/squiggle-components/deploys)
|
- **components storybook prod**: https://components.squiggle-language.com
|
||||||
- **components storybook staging**: https://develop--squiggle-components.netlify.app/
|
- **components storybook staging**: https://preview-components.squiggle-language.com
|
||||||
- **legacy (2020) playground**: https://playground.squiggle-language.com
|
- **legacy (2020) playground**: https://playground.squiggle-language.com
|
||||||
|
|
||||||
## Packages
|
## Packages
|
||||||
|
@ -51,7 +51,25 @@ For any project in the repo, begin by running `yarn` in the top level
|
||||||
yarn
|
yarn
|
||||||
```
|
```
|
||||||
|
|
||||||
See `packages/*/README.md` to work with whatever project you're interested in.
|
Then use `turbo` to build the specific packages or the entire monorepo:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
turbo run build
|
||||||
|
```
|
||||||
|
|
||||||
|
Or:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
turbo run build --filter=@quri/squiggle-components
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also run specific npm scripts for the package you're working on. See `packages/*/README.md` for the details.
|
||||||
|
|
||||||
|
# NixOS users
|
||||||
|
|
||||||
|
This repository requires the use of bundled binaries from node_modules, which
|
||||||
|
are not linked statically. The easiest way to get them working is to enable
|
||||||
|
[nix-ld](https://github.com/Mic92/nix-ld).
|
||||||
|
|
||||||
# Contributing
|
# Contributing
|
||||||
|
|
||||||
|
|
79
flake.lock
Normal file
79
flake.lock
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-utils": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1659877975,
|
||||||
|
"narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-utils_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1659877975,
|
||||||
|
"narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gentype": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils_2",
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1661855866,
|
||||||
|
"narHash": "sha256-+q0OOTyaq8eOn9BOWdPOCtSDOISW4A59v3mq3JOZyug=",
|
||||||
|
"owner": "rescript-association",
|
||||||
|
"repo": "genType",
|
||||||
|
"rev": "6b5f164b4f6ced456019b7579a0ab7e0a86518ad",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "rescript-association",
|
||||||
|
"repo": "genType",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1661617163,
|
||||||
|
"narHash": "sha256-NN9Ky47j8ohgPhA9JZyfkYIbbAo6RJkGz+7h8/exVpE=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "0ba2543f8c855d7be8e90ef6c8dc89c1617e8a08",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"id": "nixpkgs",
|
||||||
|
"ref": "nixos-22.05",
|
||||||
|
"type": "indirect"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"gentype": "gentype",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
99
flake.nix
Normal file
99
flake.nix
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
{
|
||||||
|
description = "Squiggle packages";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "nixpkgs/nixos-22.05";
|
||||||
|
gentype = {
|
||||||
|
url = "github:rescript-association/genType";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, gentype, flake-utils }:
|
||||||
|
let
|
||||||
|
version = builtins.substring 0 8 self.lastModifiedDate;
|
||||||
|
overlays = [
|
||||||
|
(final: prev: {
|
||||||
|
# set the node version here
|
||||||
|
nodejs = prev.nodejs-18_x;
|
||||||
|
# The override is the only way to get it into mkYarnModules
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
|
commonFn = pkgs: {
|
||||||
|
buildInputs = with pkgs; [ nodejs yarn ];
|
||||||
|
prettier = with pkgs.nodePackages; [ prettier ];
|
||||||
|
which = [ pkgs.which ];
|
||||||
|
};
|
||||||
|
gentypeOutputFn = pkgs: gentype.outputs.packages.${pkgs.system}.default;
|
||||||
|
langFn = { pkgs, ... }:
|
||||||
|
# Probably doesn't work on i686-linux
|
||||||
|
import ./nix/squiggle-lang.nix {
|
||||||
|
inherit pkgs commonFn gentypeOutputFn;
|
||||||
|
};
|
||||||
|
componentsFn = { pkgs, ... }:
|
||||||
|
import ./nix/squiggle-components.nix { inherit pkgs commonFn langFn; };
|
||||||
|
websiteFn = { pkgs, ... }:
|
||||||
|
import ./nix/squiggle-website.nix {
|
||||||
|
inherit pkgs commonFn langFn componentsFn;
|
||||||
|
};
|
||||||
|
vscodeextFn = { pkgs, ... }:
|
||||||
|
import ./nix/squiggle-vscode.nix {
|
||||||
|
inherit pkgs commonFn langFn componentsFn;
|
||||||
|
};
|
||||||
|
cliFn = { pkgs, ... }:
|
||||||
|
import ./nix/squiggle-cli.nix {
|
||||||
|
inherit pkgs commonFn;
|
||||||
|
};
|
||||||
|
|
||||||
|
# local machines
|
||||||
|
localFlakeOutputs = { pkgs, ... }:
|
||||||
|
let
|
||||||
|
lang = langFn pkgs;
|
||||||
|
components = componentsFn pkgs;
|
||||||
|
website = websiteFn pkgs;
|
||||||
|
vscodeext = vscodeextFn pkgs;
|
||||||
|
cli = cliFn pkgs;
|
||||||
|
in {
|
||||||
|
# validating
|
||||||
|
checks = flake-utils.lib.flattenTree {
|
||||||
|
lang-lint = lang.lint;
|
||||||
|
lang-test = lang.test;
|
||||||
|
components-lint = components.lint;
|
||||||
|
docusaurus-lint = website.lint;
|
||||||
|
cli-lint = cli.lint;
|
||||||
|
};
|
||||||
|
# building
|
||||||
|
packages = flake-utils.lib.flattenTree {
|
||||||
|
default = components.build;
|
||||||
|
lang = lang.build;
|
||||||
|
lang-bundle = lang.bundle;
|
||||||
|
lang-test = lang.test;
|
||||||
|
components = components.build;
|
||||||
|
components-bundle = components.bundle;
|
||||||
|
|
||||||
|
# Lint
|
||||||
|
lang-lint = lang.lint;
|
||||||
|
components-lint = components.lint;
|
||||||
|
docusaurus-lint = website.lint;
|
||||||
|
vscode-lint = vscodeext.lint;
|
||||||
|
cli-lint = cli.lint;
|
||||||
|
};
|
||||||
|
|
||||||
|
# developing
|
||||||
|
devShells = let shellNix = import ./nix/shell.nix { inherit pkgs; };
|
||||||
|
in flake-utils.lib.flattenTree {
|
||||||
|
default = shellNix.all;
|
||||||
|
js = shellNix.just-js;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in flake-utils.lib.eachDefaultSystem (system:
|
||||||
|
let
|
||||||
|
pkgs = import nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
overlays = overlays;
|
||||||
|
};
|
||||||
|
|
||||||
|
in localFlakeOutputs pkgs);
|
||||||
|
}
|
1
nix/README.md
Normal file
1
nix/README.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Visit `quantified-uncertainty.cachix.org` for information about how to add our binary cache to your local dev environment.
|
25
nix/shell.nix
Normal file
25
nix/shell.nix
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
{ pkgs }:
|
||||||
|
with pkgs;
|
||||||
|
let
|
||||||
|
js = [ yarn nodejs nodePackages.ts-node ];
|
||||||
|
rust = [
|
||||||
|
wasm-pack
|
||||||
|
cargo
|
||||||
|
rustup
|
||||||
|
pkg-config
|
||||||
|
libressl
|
||||||
|
rustfmt
|
||||||
|
wasmtime
|
||||||
|
binaryen
|
||||||
|
wasm-bindgen-cli
|
||||||
|
];
|
||||||
|
in {
|
||||||
|
all = mkShell {
|
||||||
|
name = "squiggle_yarn-wasm-devshell";
|
||||||
|
buildInputs = builtins.concatLists [ js rust [ nixfmt ] ];
|
||||||
|
};
|
||||||
|
just-js = mkShell {
|
||||||
|
name = "squiggle_yarn-devshell";
|
||||||
|
buildInputs = js ++ [ nixfmt ];
|
||||||
|
};
|
||||||
|
}
|
13
nix/squiggle-cli.nix
Normal file
13
nix/squiggle-cli.nix
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{ pkgs, commonFn }:
|
||||||
|
|
||||||
|
rec {
|
||||||
|
common = commonFn pkgs;
|
||||||
|
|
||||||
|
lint = pkgs.stdenv.mkDerivation {
|
||||||
|
name = "squiggle-cli-lint";
|
||||||
|
buildInputs = common.buildInputs ++ common.prettier;
|
||||||
|
src = ../packages/cli;
|
||||||
|
buildPhase = "prettier --check .";
|
||||||
|
installPhase = "mkdir -p $out";
|
||||||
|
};
|
||||||
|
}
|
75
nix/squiggle-components.nix
Normal file
75
nix/squiggle-components.nix
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
{ pkgs, commonFn, langFn }:
|
||||||
|
|
||||||
|
rec {
|
||||||
|
common = commonFn pkgs;
|
||||||
|
lang = langFn pkgs;
|
||||||
|
componentsPackageJson = let
|
||||||
|
raw = pkgs.lib.importJSON ../packages/components/package.json;
|
||||||
|
modified =
|
||||||
|
pkgs.lib.recursiveUpdate raw { dependencies.react-dom = "^18.2.0"; };
|
||||||
|
packageJsonString = builtins.toJSON modified;
|
||||||
|
in pkgs.writeText "packages/components/patched-package.json"
|
||||||
|
packageJsonString;
|
||||||
|
yarn-source = pkgs.mkYarnPackage {
|
||||||
|
name = "squiggle-components_yarnsource";
|
||||||
|
buildInputs = common.buildInputs;
|
||||||
|
src = ../packages/components;
|
||||||
|
packageJSON = componentsPackageJson;
|
||||||
|
yarnLock = ../yarn.lock;
|
||||||
|
packageResolutions."@quri/squiggle-lang" = lang.build;
|
||||||
|
};
|
||||||
|
lint = pkgs.stdenv.mkDerivation {
|
||||||
|
name = "squiggle-components-lint";
|
||||||
|
src = ../packages/components;
|
||||||
|
buildInputs = common.buildInputs ++ common.prettier;
|
||||||
|
buildPhase = "yarn lint";
|
||||||
|
installPhase = "mkdir -p $out";
|
||||||
|
};
|
||||||
|
build = pkgs.stdenv.mkDerivation {
|
||||||
|
name = "squiggle-components-build";
|
||||||
|
src = yarn-source + "/libexec/@quri/squiggle-components";
|
||||||
|
buildInputs = common.buildInputs;
|
||||||
|
buildPhase = ''
|
||||||
|
cp -r node_modules/@quri/squiggle-lang deps/@quri
|
||||||
|
pushd deps/@quri/squiggle-components
|
||||||
|
|
||||||
|
yarn --offline build:cjs
|
||||||
|
yarn --offline build:css
|
||||||
|
popd
|
||||||
|
'';
|
||||||
|
installPhase = ''
|
||||||
|
mkdir -p $out
|
||||||
|
|
||||||
|
# annoying hack because permissions on transitive dependencies later on
|
||||||
|
mv deps/@quri/squiggle-components/node_modules deps/@quri/squiggle-components/NODE_MODULES
|
||||||
|
mv node_modules deps/@quri/squiggle-components
|
||||||
|
|
||||||
|
# patching .gitignore so flake keeps build artefacts
|
||||||
|
sed -i /dist/d deps/@quri/squiggle-components/.gitignore
|
||||||
|
cp -r deps/@quri/squiggle-components/. $out
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
bundle = pkgs.stdenv.mkDerivation {
|
||||||
|
name = "squiggle-components-bundle";
|
||||||
|
src = yarn-source + "/libexec/@quri/squiggle-components";
|
||||||
|
buildInputs = common.buildInputs;
|
||||||
|
buildPhase = ''
|
||||||
|
cp -r node_modules/@quri/squiggle-lang deps/@quri
|
||||||
|
pushd deps/@quri/squiggle-components
|
||||||
|
|
||||||
|
yarn --offline bundle
|
||||||
|
popd
|
||||||
|
'';
|
||||||
|
installPhase = ''
|
||||||
|
mkdir -p $out
|
||||||
|
|
||||||
|
# annoying hack because permissions on transitive dependencies later on
|
||||||
|
mv deps/@quri/squiggle-components/node_modules deps/@quri/squiggle-components/NODE_MODULES
|
||||||
|
mv node_modules deps/@quri/squiggle-components
|
||||||
|
|
||||||
|
# patching .gitignore so flake keeps build artefacts
|
||||||
|
sed -i /dist/d deps/@quri/squiggle-components/.gitignore
|
||||||
|
cp -r deps/@quri/squiggle-components/. $out
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
}
|
116
nix/squiggle-lang.nix
Normal file
116
nix/squiggle-lang.nix
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
{ pkgs, commonFn, gentypeOutputFn }:
|
||||||
|
|
||||||
|
rec {
|
||||||
|
common = commonFn pkgs;
|
||||||
|
langPackageJson = let
|
||||||
|
raw = pkgs.lib.importJSON ../packages/squiggle-lang/package.json;
|
||||||
|
modified = pkgs.lib.recursiveUpdate raw {
|
||||||
|
devDependencies."@types/lodash" = "^4.14.167";
|
||||||
|
};
|
||||||
|
packageJsonString = builtins.toJSON modified;
|
||||||
|
in pkgs.writeText "packages/squiggle-lang/patched-package.json"
|
||||||
|
packageJsonString;
|
||||||
|
yarn-source = pkgs.mkYarnPackage {
|
||||||
|
name = "squiggle-lang_yarnsource";
|
||||||
|
src = ../packages/squiggle-lang;
|
||||||
|
packageJSON = langPackageJson;
|
||||||
|
yarnLock = ../yarn.lock;
|
||||||
|
pkgConfig = {
|
||||||
|
rescript = {
|
||||||
|
buildInputs = common.which
|
||||||
|
++ (if pkgs.system != "i686-linux" then [ pkgs.gcc_multi ] else [ ]);
|
||||||
|
postInstall = ''
|
||||||
|
echo "PATCHELF'ING RESCRIPT EXECUTABLES (INCL NINJA)"
|
||||||
|
# Patching interpreter for linux/*.exe's
|
||||||
|
THE_LD=$(patchelf --print-interpreter $(which mkdir))
|
||||||
|
patchelf --set-interpreter $THE_LD linux/*.exe && echo "- patched interpreter for linux/*.exe's"
|
||||||
|
|
||||||
|
# Replacing needed shared library for linux/ninja.exe
|
||||||
|
THE_SO=$(find /nix/store/*/lib64 -name libstdc++.so.6 | head -n 1)
|
||||||
|
patchelf --replace-needed libstdc++.so.6 $THE_SO linux/ninja.exe && echo "- replaced needed for linux/ninja.exe"
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
gentype = {
|
||||||
|
postInstall = ''
|
||||||
|
mv gentype.exe ELFLESS-gentype.exe
|
||||||
|
cp ${gentypeOutputFn pkgs}/src/GenType.exe gentype.exe
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
lint = pkgs.stdenv.mkDerivation {
|
||||||
|
name = "squiggle-lang-lint";
|
||||||
|
src = yarn-source + "/libexec/@quri/squiggle-lang/deps/@quri/squiggle-lang";
|
||||||
|
buildInputs = common.buildInputs ++ common.prettier;
|
||||||
|
buildPhase = ''
|
||||||
|
yarn lint:prettier
|
||||||
|
yarn lint:rescript
|
||||||
|
'';
|
||||||
|
installPhase = "mkdir -p $out";
|
||||||
|
};
|
||||||
|
build = pkgs.stdenv.mkDerivation {
|
||||||
|
name = "squiggle-lang-build";
|
||||||
|
# `peggy` is in the `node_modules` that's adjacent to `deps`.
|
||||||
|
src = yarn-source + "/libexec/@quri/squiggle-lang";
|
||||||
|
buildInputs = common.buildInputs;
|
||||||
|
buildPhase = ''
|
||||||
|
# so that the path to ppx doesn't need to be patched.
|
||||||
|
mv node_modules deps
|
||||||
|
|
||||||
|
pushd deps/@quri/squiggle-lang
|
||||||
|
yarn --offline build:peggy
|
||||||
|
yarn --offline build:rescript
|
||||||
|
yarn --offline build:typescript
|
||||||
|
|
||||||
|
# custom gitignore so that the flake keeps build artefacts
|
||||||
|
mv .gitignore GITIGNORE
|
||||||
|
sed -i /Reducer_Peggy_GeneratedParser.js/d GITIGNORE
|
||||||
|
sed -i /ReducerProject_IncludeParser.js/d GITIGNORE
|
||||||
|
sed -i /\*.bs.js/d GITIGNORE
|
||||||
|
sed -i /\*.gen.ts/d GITIGNORE
|
||||||
|
sed -i /\*.gen.tsx/d GITIGNORE
|
||||||
|
sed -i /\*.gen.js/d GITIGNORE
|
||||||
|
sed -i /helpers.js/d GITIGNORE
|
||||||
|
|
||||||
|
popd
|
||||||
|
'';
|
||||||
|
installPhase = ''
|
||||||
|
mkdir -p $out
|
||||||
|
# mkdir -p $out/node_modules
|
||||||
|
mv deps/@quri/squiggle-lang/GITIGNORE deps/@quri/squiggle-lang/.gitignore
|
||||||
|
|
||||||
|
# annoying hack because permissions on transitive dependencies later on
|
||||||
|
mv deps/@quri/squiggle-lang/node_modules deps/@quri/squiggle-lang/NODE_MODULES
|
||||||
|
mv deps/node_modules deps/@quri/squiggle-lang
|
||||||
|
|
||||||
|
# the proper install phase
|
||||||
|
cp -r deps/@quri/squiggle-lang/. $out
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
test = pkgs.stdenv.mkDerivation {
|
||||||
|
name = "squiggle-lang-test";
|
||||||
|
src = build;
|
||||||
|
buildInputs = common.buildInputs;
|
||||||
|
buildPhase = ''
|
||||||
|
yarn --offline test
|
||||||
|
'';
|
||||||
|
installPhase = ''
|
||||||
|
mkdir -p $out
|
||||||
|
cp -r . $out
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
bundle = pkgs.stdenv.mkDerivation {
|
||||||
|
name = "squiggle-lang-bundle";
|
||||||
|
src = test;
|
||||||
|
buildInputs = common.buildInputs;
|
||||||
|
buildPhase = ''
|
||||||
|
yarn --offline bundle
|
||||||
|
'';
|
||||||
|
installPhase = ''
|
||||||
|
mkdir -p $out
|
||||||
|
cp -r dist $out
|
||||||
|
cp *.json $out/dist
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
24
nix/squiggle-vscode.nix
Normal file
24
nix/squiggle-vscode.nix
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{ pkgs, commonFn, langFn, componentsFn }:
|
||||||
|
|
||||||
|
rec {
|
||||||
|
common = commonFn pkgs;
|
||||||
|
lang = langFn pkgs;
|
||||||
|
components = componentsFn pkgs;
|
||||||
|
|
||||||
|
yarn-source = pkgs.mkYarnPackage {
|
||||||
|
name = "squiggle-vscodeext_yarnsource";
|
||||||
|
src = ../packages/vscode-ext;
|
||||||
|
packageJson = ../packages/vscode-ext/package.json;
|
||||||
|
yarnLock = ../yarn.lock;
|
||||||
|
packageResolutions."@quri/squiggle-lang" = lang.build;
|
||||||
|
packageResolutions."@quri/squiggle-components" = components.build;
|
||||||
|
};
|
||||||
|
lint = pkgs.stdenv.mkDerivation {
|
||||||
|
name = "squiggle-vscode-lint";
|
||||||
|
buildInputs = common.buildInputs ++ common.prettier;
|
||||||
|
src =
|
||||||
|
../packages/vscode-ext; # yarn-source + "/libexec/vscode-squiggle/deps/vscode-squiggle";
|
||||||
|
buildPhase = "prettier --check .";
|
||||||
|
installPhase = "mkdir -p $out";
|
||||||
|
};
|
||||||
|
}
|
30
nix/squiggle-website.nix
Normal file
30
nix/squiggle-website.nix
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
{ pkgs, commonFn, langFn, componentsFn }:
|
||||||
|
|
||||||
|
rec {
|
||||||
|
common = commonFn pkgs;
|
||||||
|
lang = langFn pkgs;
|
||||||
|
components = componentsFn pkgs;
|
||||||
|
websitePackageJson = let
|
||||||
|
raw = pkgs.lib.importJSON ../packages/website/package.json;
|
||||||
|
modified = pkgs.lib.recursiveUpdate raw {
|
||||||
|
dependencies.postcss-import = "^14.1.0";
|
||||||
|
dependencies.tailwindcss = "^3.1.8";
|
||||||
|
};
|
||||||
|
packageJsonString = builtins.toJSON modified;
|
||||||
|
in pkgs.writeText "packages/website/patched-package.json" packageJsonString;
|
||||||
|
yarn-source = pkgs.mkYarnPackage {
|
||||||
|
name = "squiggle-website_yarnsource";
|
||||||
|
src = ../packages/website;
|
||||||
|
packageJSON = websitePackageJson;
|
||||||
|
yarnLock = ../yarn.lock;
|
||||||
|
packageResolutions."@quri/squiggle-lang" = lang.build;
|
||||||
|
packageResolutions."@quri/squiggle-components" = components.build;
|
||||||
|
};
|
||||||
|
lint = pkgs.stdenv.mkDerivation {
|
||||||
|
name = "squiggle-website-lint";
|
||||||
|
buildInputs = common.buildInputs ++ common.prettier;
|
||||||
|
src = ../packages/website;
|
||||||
|
buildPhase = "yarn lint";
|
||||||
|
installPhase = "mkdir -p $out";
|
||||||
|
};
|
||||||
|
}
|
18
nixos.sh
18
nixos.sh
|
@ -1,18 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# This script is only relevant if you're rolling nixos.
|
|
||||||
|
|
||||||
# Esy (a bisect_ppx dependency/build tool) is borked on nixos without using an FHS shell. https://github.com/esy/esy/issues/858
|
|
||||||
# We need to patchelf rescript executables. https://github.com/NixOS/nixpkgs/issues/107375
|
|
||||||
set -x
|
|
||||||
|
|
||||||
fhsShellName="squiggle-development"
|
|
||||||
fhsShellDotNix="{pkgs ? import <nixpkgs> {} }: (pkgs.buildFHSUserEnv { name = \"${fhsShellName}\"; targetPkgs = pkgs: [pkgs.yarn]; runScript = \"yarn\"; }).env"
|
|
||||||
nix-shell - <<<"$fhsShellDotNix"
|
|
||||||
|
|
||||||
theLd=$(patchelf --print-interpreter $(which mkdir))
|
|
||||||
patchelf --set-interpreter $theLd ./node_modules/gentype/gentype.exe
|
|
||||||
patchelf --set-interpreter $theLd ./node_modules/rescript/linux/*.exe
|
|
||||||
patchelf --set-interpreter $theLd ./node_modules/bisect_ppx/ppx
|
|
||||||
patchelf --set-interpreter $theLd ./node_moduels/bisect_ppx/bisect-ppx-report
|
|
||||||
theSo=$(find /nix/store/*$fhsShellName*/lib64 -name libstdc++.so.6 | grep $fhsShellName | head -n 1)
|
|
||||||
patchelf --replace-needed libstdc++.so.6 $theSo ./node_modules/rescript/linux/ninja.exe
|
|
|
@ -2,12 +2,11 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"name": "squiggle",
|
"name": "squiggle",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"nodeclean": "rm -r node_modules && rm -r packages/*/node_modules",
|
"nodeclean": "rm -r node_modules && rm -r packages/*/node_modules"
|
||||||
"format:all": "prettier --write . && cd packages/squiggle-lang && yarn format",
|
|
||||||
"lint:all": "prettier --check . && cd packages/squiggle-lang && yarn lint:rescript"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"prettier": "^2.7.1"
|
"prettier": "^2.7.1",
|
||||||
|
"turbo": "^1.5.5"
|
||||||
},
|
},
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -20,3 +20,30 @@ Runs compilation in the current directory and all of its subdirectories.
|
||||||
### `npx squiggle-cli-experimental watch`
|
### `npx squiggle-cli-experimental watch`
|
||||||
|
|
||||||
Watches `.squiggleU` files in the current directory (and subdirectories) and rebuilds them when they are saved. Note that this will _not_ rebuild files when their dependencies are changed, just when they are changed directly.
|
Watches `.squiggleU` files in the current directory (and subdirectories) and rebuilds them when they are saved. Note that this will _not_ rebuild files when their dependencies are changed, just when they are changed directly.
|
||||||
|
|
||||||
|
## Further instructions
|
||||||
|
|
||||||
|
The above requires having node, npm and npx. To install the first two, see [here](https://nodejs.org/en/), to install npx, run:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install -g npx
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, you can run the following without the need for npx:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install squiggle-cli-experimental
|
||||||
|
node node_modules/squiggle-cli-experimental/index.js compile
|
||||||
|
```
|
||||||
|
|
||||||
|
or you can add a script to your `package.json`, like:
|
||||||
|
|
||||||
|
```
|
||||||
|
...
|
||||||
|
scripts: {
|
||||||
|
"compile": "squiggle-cli-experimental compile"
|
||||||
|
}
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
This can be run with `npm run compile`. `npm` knows how to reach into the node_modules directly, so it's not necessary to specify that.
|
||||||
|
|
|
@ -7,13 +7,15 @@
|
||||||
"bin": "index.js",
|
"bin": "index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node ."
|
"start": "node .",
|
||||||
|
"lint": "prettier --check .",
|
||||||
|
"format": "prettier --write ."
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chalk": "^5.0.1",
|
"chalk": "^5.1.0",
|
||||||
"chokidar": "^3.5.3",
|
"chokidar": "^3.5.3",
|
||||||
"commander": "^9.3.0",
|
"commander": "^9.4.1",
|
||||||
"fs": "^0.0.1-security",
|
"fs": "^0.0.1-security",
|
||||||
"glob": "^8.0.3",
|
"glob": "^8.0.3",
|
||||||
"indent-string": "^5.0.0"
|
"indent-string": "^5.0.0"
|
||||||
|
|
|
@ -20,7 +20,7 @@ Add to `App.js`:
|
||||||
```jsx
|
```jsx
|
||||||
import { SquiggleEditor } from "@quri/squiggle-components";
|
import { SquiggleEditor } from "@quri/squiggle-components";
|
||||||
<SquiggleEditor
|
<SquiggleEditor
|
||||||
initialSquiggleString="x = beta($alpha, 10); x + $shift"
|
defaultCode="x = beta($alpha, 10); x + $shift"
|
||||||
jsImports={{ alpha: 3, shift: 20 }}
|
jsImports={{ alpha: 3, shift: 20 }}
|
||||||
/>;
|
/>;
|
||||||
```
|
```
|
||||||
|
@ -50,11 +50,10 @@ export function DynamicSquiggleChart({ squiggleString }) {
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<SquiggleChart
|
<SquiggleChart
|
||||||
squiggleString={squiggleString}
|
defaultCode={squiggleString}
|
||||||
width={445}
|
width={445}
|
||||||
height={200}
|
height={200}
|
||||||
showSummary={true}
|
showSummary={true}
|
||||||
showTypes={true}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
6
packages/components/jest.config.js
Normal file
6
packages/components/jest.config.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
||||||
|
module.exports = {
|
||||||
|
preset: "ts-jest",
|
||||||
|
setupFilesAfterEnv: ["<rootDir>/test/setup.js"],
|
||||||
|
testEnvironment: "jsdom",
|
||||||
|
};
|
|
@ -1,8 +0,0 @@
|
||||||
[build]
|
|
||||||
base = "packages/components/"
|
|
||||||
command = "cd ../squiggle-lang && yarn build && cd ../components && yarn build"
|
|
||||||
publish = "storybook-static/"
|
|
||||||
ignore = "node -e 'process.exitCode = process.env.BRANCH.includes(\"dependabot\") ? 0 : 1' && git diff --quiet $CACHED_COMMIT_REF $COMMIT_REF . ../squiggle-lang"
|
|
||||||
|
|
||||||
[build.environment]
|
|
||||||
NETLIFY_USE_YARN = "true"
|
|
|
@ -1,63 +1,73 @@
|
||||||
{
|
{
|
||||||
"name": "@quri/squiggle-components",
|
"name": "@quri/squiggle-components",
|
||||||
"version": "0.2.20",
|
"version": "0.5.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@headlessui/react": "^1.6.5",
|
"@floating-ui/react-dom": "^1.0.0",
|
||||||
|
"@floating-ui/react-dom-interactions": "^0.10.1",
|
||||||
|
"@headlessui/react": "^1.7.3",
|
||||||
"@heroicons/react": "^1.0.6",
|
"@heroicons/react": "^1.0.6",
|
||||||
"@hookform/resolvers": "^2.9.1",
|
"@hookform/resolvers": "^2.9.8",
|
||||||
"@quri/squiggle-lang": "^0.2.8",
|
"@quri/squiggle-lang": "^0.5.0",
|
||||||
"@react-hook/size": "^2.1.2",
|
"@react-hook/size": "^2.1.2",
|
||||||
"clsx": "^1.1.1",
|
"@types/uuid": "^8.3.4",
|
||||||
|
"clsx": "^1.2.1",
|
||||||
|
"framer-motion": "^7.5.3",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"react": "^18.1.0",
|
"react": "^18.1.0",
|
||||||
"react-ace": "^10.1.0",
|
"react-ace": "^10.1.0",
|
||||||
"react-hook-form": "^7.32.2",
|
"react-hook-form": "^7.37.0",
|
||||||
"react-use": "^17.4.0",
|
"react-use": "^17.4.0",
|
||||||
"react-vega": "^7.5.1",
|
"react-vega": "^7.6.0",
|
||||||
|
"uuid": "^9.0.0",
|
||||||
"vega": "^5.22.1",
|
"vega": "^5.22.1",
|
||||||
"vega-embed": "^6.21.0",
|
"vega-embed": "^6.21.0",
|
||||||
"vega-lite": "^5.2.0",
|
"vega-lite": "^5.5.0",
|
||||||
"vscode-uri": "^3.0.3",
|
"vscode-uri": "^3.0.6",
|
||||||
"yup": "^0.32.11"
|
"yup": "^0.32.11"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/plugin-proposal-private-property-in-object": "^7.17.12",
|
"@babel/plugin-proposal-private-property-in-object": "^7.18.6",
|
||||||
"@storybook/addon-actions": "^6.5.9",
|
"@storybook/addon-actions": "^6.5.12",
|
||||||
"@storybook/addon-essentials": "^6.5.9",
|
"@storybook/addon-essentials": "^6.5.12",
|
||||||
"@storybook/addon-links": "^6.5.9",
|
"@storybook/addon-links": "^6.5.12",
|
||||||
"@storybook/builder-webpack5": "^6.5.9",
|
"@storybook/builder-webpack5": "^6.5.12",
|
||||||
"@storybook/manager-webpack5": "^6.5.9",
|
"@storybook/manager-webpack5": "^6.5.12",
|
||||||
"@storybook/node-logger": "^6.5.9",
|
"@storybook/node-logger": "^6.5.9",
|
||||||
"@storybook/preset-create-react-app": "^4.1.2",
|
"@storybook/preset-create-react-app": "^4.1.2",
|
||||||
"@storybook/react": "^6.5.9",
|
"@storybook/react": "^6.5.12",
|
||||||
"@testing-library/jest-dom": "^5.16.4",
|
"@testing-library/jest-dom": "^5.16.5",
|
||||||
"@testing-library/react": "^13.3.0",
|
"@testing-library/react": "^13.4.0",
|
||||||
"@testing-library/user-event": "^14.2.1",
|
"@testing-library/user-event": "^14.4.3",
|
||||||
"@types/jest": "^27.5.0",
|
"@types/jest": "^27.5.0",
|
||||||
"@types/lodash": "^4.14.182",
|
"@types/lodash": "^4.14.186",
|
||||||
"@types/node": "^18.0.0",
|
"@types/node": "^18.8.3",
|
||||||
"@types/react": "^18.0.9",
|
"@types/react": "^18.0.21",
|
||||||
"@types/react-dom": "^18.0.5",
|
"@types/styled-components": "^5.1.26",
|
||||||
"@types/styled-components": "^5.1.24",
|
"@types/uuid": "^8.3.4",
|
||||||
"@types/webpack": "^5.28.0",
|
"@types/webpack": "^5.28.0",
|
||||||
|
"canvas": "^2.10.1",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
|
"jest": "^29.0.3",
|
||||||
|
"jest-environment-jsdom": "^29.1.2",
|
||||||
|
"jsdom": "^20.0.1",
|
||||||
"mini-css-extract-plugin": "^2.6.1",
|
"mini-css-extract-plugin": "^2.6.1",
|
||||||
"postcss-cli": "^9.1.0",
|
"postcss-cli": "^10.0.0",
|
||||||
"postcss-import": "^14.1.0",
|
"postcss-import": "^15.0.0",
|
||||||
"postcss-loader": "^7.0.0",
|
"postcss-loader": "^7.0.1",
|
||||||
|
"postcss-nesting": "^10.2.0",
|
||||||
"react": "^18.1.0",
|
"react": "^18.1.0",
|
||||||
"react-dom": "^18.2.0",
|
|
||||||
"react-scripts": "^5.0.1",
|
"react-scripts": "^5.0.1",
|
||||||
"style-loader": "^3.3.1",
|
"style-loader": "^3.3.1",
|
||||||
"tailwindcss": "^3.1.3",
|
"tailwindcss": "^3.1.8",
|
||||||
"ts-loader": "^9.3.0",
|
"ts-jest": "^29.0.3",
|
||||||
"tsconfig-paths-webpack-plugin": "^3.5.2",
|
"ts-loader": "^9.4.1",
|
||||||
"typescript": "^4.7.4",
|
"tsconfig-paths-webpack-plugin": "^4.0.0",
|
||||||
"web-vitals": "^2.1.4",
|
"typescript": "^4.8.4",
|
||||||
"webpack": "^5.73.0",
|
"web-vitals": "^3.0.3",
|
||||||
|
"webpack": "^5.74.0",
|
||||||
"webpack-cli": "^4.10.0",
|
"webpack-cli": "^4.10.0",
|
||||||
"webpack-dev-server": "^4.9.2"
|
"webpack-dev-server": "^4.11.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "^16.8.0 || ^17 || ^18",
|
"react": "^16.8.0 || ^17 || ^18",
|
||||||
|
@ -65,7 +75,7 @@
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "cross-env REACT_APP_FAST_REFRESH=false && start-storybook -p 6006 -s public",
|
"start": "cross-env REACT_APP_FAST_REFRESH=false && start-storybook -p 6006 -s public",
|
||||||
"build:cjs": "tsc -b",
|
"build:cjs": "rm -rf dist/src && rm -f dist/tsconfig.tsbuildinfo && tsc -b",
|
||||||
"build:css": "postcss ./src/styles/main.css -o ./dist/main.css",
|
"build:css": "postcss ./src/styles/main.css -o ./dist/main.css",
|
||||||
"build:storybook": "build-storybook -s public",
|
"build:storybook": "build-storybook -s public",
|
||||||
"build": "yarn run build:cjs && yarn run build:css && yarn run build:storybook",
|
"build": "yarn run build:cjs && yarn run build:css && yarn run build:storybook",
|
||||||
|
@ -73,7 +83,10 @@
|
||||||
"all": "yarn bundle && yarn build",
|
"all": "yarn bundle && yarn build",
|
||||||
"lint": "prettier --check .",
|
"lint": "prettier --check .",
|
||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
"prepack": "yarn bundle && tsc -b"
|
"prepack": "yarn run build:cjs && yarn run bundle",
|
||||||
|
"test": "jest",
|
||||||
|
"test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand",
|
||||||
|
"test:profile": "node --cpu-prof node_modules/.bin/jest --runInBand"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": [
|
"extends": [
|
||||||
|
|
|
@ -24,13 +24,13 @@ export const Alert: React.FC<{
|
||||||
children,
|
children,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className={clsx("rounded-md p-4", backgroundColor)}>
|
<div className={clsx("rounded-md p-4", backgroundColor)} role="status">
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<Icon
|
<Icon
|
||||||
className={clsx("h-5 w-5 flex-shrink-0", iconColor)}
|
className={clsx("h-5 w-5 flex-shrink-0", iconColor)}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
<div className="ml-3">
|
<div className="ml-3 grow">
|
||||||
<header className={clsx("text-sm font-medium", headingColor)}>
|
<header className={clsx("text-sm font-medium", headingColor)}>
|
||||||
{heading}
|
{heading}
|
||||||
</header>
|
</header>
|
||||||
|
|
|
@ -1,31 +1,44 @@
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import React, { FC, useMemo } from "react";
|
import React, { FC, useMemo, useRef } from "react";
|
||||||
import AceEditor from "react-ace";
|
import AceEditor from "react-ace";
|
||||||
|
|
||||||
import "ace-builds/src-noconflict/mode-golang";
|
import "ace-builds/src-noconflict/mode-golang";
|
||||||
import "ace-builds/src-noconflict/theme-github";
|
import "ace-builds/src-noconflict/theme-github";
|
||||||
|
|
||||||
|
import { SqLocation } from "@quri/squiggle-lang";
|
||||||
|
|
||||||
interface CodeEditorProps {
|
interface CodeEditorProps {
|
||||||
value: string;
|
value: string;
|
||||||
onChange: (value: string) => void;
|
onChange: (value: string) => void;
|
||||||
|
onSubmit?: () => void;
|
||||||
oneLine?: boolean;
|
oneLine?: boolean;
|
||||||
width?: number;
|
width?: number;
|
||||||
height: number;
|
height: number;
|
||||||
showGutter?: boolean;
|
showGutter?: boolean;
|
||||||
|
errorLocations?: SqLocation[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CodeEditor: FC<CodeEditorProps> = ({
|
export const CodeEditor: FC<CodeEditorProps> = ({
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
|
onSubmit,
|
||||||
|
height,
|
||||||
oneLine = false,
|
oneLine = false,
|
||||||
showGutter = false,
|
showGutter = false,
|
||||||
height,
|
errorLocations = [],
|
||||||
}) => {
|
}) => {
|
||||||
const lineCount = value.split("\n").length;
|
const lineCount = value.split("\n").length;
|
||||||
const id = useMemo(() => _.uniqueId(), []);
|
const id = useMemo(() => _.uniqueId(), []);
|
||||||
|
|
||||||
|
// this is necessary because AceEditor binds commands on mount, see https://github.com/securingsincity/react-ace/issues/684
|
||||||
|
const onSubmitRef = useRef<typeof onSubmit | null>(null);
|
||||||
|
onSubmitRef.current = onSubmit;
|
||||||
|
|
||||||
|
const editorEl = useRef<AceEditor | null>(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AceEditor
|
<AceEditor
|
||||||
|
ref={editorEl}
|
||||||
value={value}
|
value={value}
|
||||||
mode="golang"
|
mode="golang"
|
||||||
theme="github"
|
theme="github"
|
||||||
|
@ -42,10 +55,22 @@ export const CodeEditor: FC<CodeEditorProps> = ({
|
||||||
editorProps={{
|
editorProps={{
|
||||||
$blockScrolling: true,
|
$blockScrolling: true,
|
||||||
}}
|
}}
|
||||||
setOptions={{
|
setOptions={{}}
|
||||||
enableBasicAutocompletion: false,
|
commands={[
|
||||||
enableLiveAutocompletion: false,
|
{
|
||||||
}}
|
name: "submit",
|
||||||
|
bindKey: { mac: "Cmd-Enter", win: "Ctrl-Enter" },
|
||||||
|
exec: () => onSubmitRef.current?.(),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
markers={errorLocations?.map((location) => ({
|
||||||
|
startRow: location.start.line - 1,
|
||||||
|
startCol: location.start.column - 1,
|
||||||
|
endRow: location.end.line - 1,
|
||||||
|
endCol: location.end.column - 1,
|
||||||
|
className: "ace-error-marker",
|
||||||
|
type: "text",
|
||||||
|
}))}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,120 +1,106 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import {
|
import {
|
||||||
Distribution,
|
SqDistribution,
|
||||||
result,
|
result,
|
||||||
distributionError,
|
SqDistributionError,
|
||||||
distributionErrorToString,
|
resultMap,
|
||||||
|
SqRecord,
|
||||||
|
environment,
|
||||||
|
SqDistributionTag,
|
||||||
} from "@quri/squiggle-lang";
|
} from "@quri/squiggle-lang";
|
||||||
import { Vega, VisualizationSpec } from "react-vega";
|
import { Vega } from "react-vega";
|
||||||
import * as chartSpecification from "../vega-specs/spec-distributions.json";
|
|
||||||
import { ErrorAlert } from "./Alert";
|
import { ErrorAlert } from "./Alert";
|
||||||
import { useSize } from "react-use";
|
import { useSize } from "react-use";
|
||||||
import clsx from "clsx";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
linearXScale,
|
buildVegaSpec,
|
||||||
logXScale,
|
DistributionChartSpecOptions,
|
||||||
linearYScale,
|
} from "../lib/distributionSpecBuilder";
|
||||||
expYScale,
|
|
||||||
} from "./DistributionVegaScales";
|
|
||||||
import { NumberShower } from "./NumberShower";
|
import { NumberShower } from "./NumberShower";
|
||||||
|
import { Plot, parsePlot } from "../lib/plotParser";
|
||||||
|
import { flattenResult } from "../lib/utility";
|
||||||
|
import { hasMassBelowZero } from "../lib/distributionUtils";
|
||||||
|
|
||||||
export type DistributionPlottingSettings = {
|
export type DistributionPlottingSettings = {
|
||||||
/** Whether to show a summary of means, stdev, percentiles etc */
|
/** Whether to show a summary of means, stdev, percentiles etc */
|
||||||
showSummary: boolean;
|
showSummary: boolean;
|
||||||
/** Whether to show the user graph controls (scale etc) */
|
actions?: boolean;
|
||||||
showControls: boolean;
|
} & DistributionChartSpecOptions;
|
||||||
/** Set the x scale to be logarithmic by deault */
|
|
||||||
logX: boolean;
|
|
||||||
/** Set the y scale to be exponential by deault */
|
|
||||||
expY: boolean;
|
|
||||||
truncateToNthci: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type DistributionChartProps = {
|
export type DistributionChartProps = {
|
||||||
distribution: Distribution;
|
plot: Plot;
|
||||||
|
environment: environment;
|
||||||
width?: number;
|
width?: number;
|
||||||
height: number;
|
height: number;
|
||||||
|
xAxisType?: "number" | "dateTime";
|
||||||
} & DistributionPlottingSettings;
|
} & DistributionPlottingSettings;
|
||||||
|
|
||||||
export const DistributionChart: React.FC<DistributionChartProps> = ({
|
export function defaultPlot(distribution: SqDistribution): Plot {
|
||||||
distribution,
|
return { distributions: [{ name: "default", distribution }] };
|
||||||
height,
|
}
|
||||||
showSummary,
|
|
||||||
width,
|
|
||||||
showControls,
|
|
||||||
logX,
|
|
||||||
expY,
|
|
||||||
truncateToNthci,
|
|
||||||
}) => {
|
|
||||||
const [isLogX, setLogX] = React.useState(logX);
|
|
||||||
const [isExpY, setExpY] = React.useState(expY);
|
|
||||||
|
|
||||||
React.useEffect(() => setLogX(logX), [logX]);
|
export function makePlot(record: SqRecord): Plot | void {
|
||||||
React.useEffect(() => setExpY(expY), [expY]);
|
const plotResult = parsePlot(record);
|
||||||
|
if (plotResult.tag === "Ok") {
|
||||||
|
return plotResult.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DistributionChart: React.FC<DistributionChartProps> = (props) => {
|
||||||
|
const {
|
||||||
|
plot,
|
||||||
|
environment,
|
||||||
|
height,
|
||||||
|
showSummary,
|
||||||
|
width,
|
||||||
|
logX,
|
||||||
|
actions = false,
|
||||||
|
} = props;
|
||||||
const [sized] = useSize((size) => {
|
const [sized] = useSize((size) => {
|
||||||
const delta = truncateToNthci / 100 / 2;
|
const shapes = flattenResult(
|
||||||
if (truncateToNthci <= 0 || truncateToNthci > 100) {
|
plot.distributions.map((x) =>
|
||||||
return (
|
resultMap(x.distribution.pointSet(environment), (pointSet) => ({
|
||||||
<ErrorAlert heading="Confidence interval error">
|
name: x.name,
|
||||||
{"Confidence interval must be between 0 and 100"}
|
// color: x.color, // not supported yet
|
||||||
</ErrorAlert>
|
...pointSet.asShape(),
|
||||||
);
|
}))
|
||||||
}
|
)
|
||||||
const pMinwrapped = distribution.inv(0.5 - delta);
|
);
|
||||||
const pMaxwrapped = distribution.inv(0.5 + delta);
|
|
||||||
if (pMinwrapped.tag == "Error") {
|
|
||||||
return (
|
|
||||||
<ErrorAlert heading="Distribution Calculation Error">
|
|
||||||
{distributionErrorToString(pMinwrapped.value)}
|
|
||||||
</ErrorAlert>
|
|
||||||
);
|
|
||||||
} else if (pMaxwrapped.tag == "Error") {
|
|
||||||
return (
|
|
||||||
<ErrorAlert heading="Distribution Calculation Error">
|
|
||||||
{distributionErrorToString(pMaxwrapped.value)}
|
|
||||||
</ErrorAlert>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const pMin = pMinwrapped.value;
|
|
||||||
const pMax = pMaxwrapped.value;
|
|
||||||
|
|
||||||
const truncatedDistributionWrapper = distribution.truncate(pMin, pMax);
|
if (shapes.tag === "Error") {
|
||||||
if (truncatedDistributionWrapper.tag == "Error") {
|
|
||||||
return (
|
|
||||||
<ErrorAlert heading="Distribution Truncation For Display Error">
|
|
||||||
{distributionErrorToString(truncatedDistributionWrapper.value)}
|
|
||||||
</ErrorAlert>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const truncatedDistribution = truncatedDistributionWrapper.value;
|
|
||||||
|
|
||||||
const shape = distribution.pointSet();
|
|
||||||
const shapeTruncated = truncatedDistribution.pointSet(); // distribution.pointSet();
|
|
||||||
|
|
||||||
if (shapeTruncated.tag === "Error") {
|
|
||||||
return (
|
return (
|
||||||
<ErrorAlert heading="Distribution Error">
|
<ErrorAlert heading="Distribution Error">
|
||||||
{distributionErrorToString(shapeTruncated.value)}
|
{shapes.value.toString()}
|
||||||
</ErrorAlert>
|
</ErrorAlert>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shape.tag === "Error") {
|
// if this is a sample set, include the samples
|
||||||
return (
|
const samples: number[] = [];
|
||||||
<ErrorAlert heading="Distribution Error">
|
for (const { distribution } of plot?.distributions) {
|
||||||
{distributionErrorToString(shape.value)}
|
if (distribution.tag === SqDistributionTag.SampleSet) {
|
||||||
</ErrorAlert>
|
samples.push(...distribution.value());
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const massBelow0 =
|
const domain = shapes.value.flatMap((shape) =>
|
||||||
shape.value.continuous.some((x) => x.x <= 0) ||
|
shape.discrete.concat(shape.continuous)
|
||||||
shape.value.discrete.some((x) => x.x <= 0);
|
);
|
||||||
const spec = buildVegaSpec(isLogX, isExpY);
|
|
||||||
|
|
||||||
let widthProp = width ? width : size.width;
|
const spec = buildVegaSpec({
|
||||||
|
...props,
|
||||||
|
minX: props.minX ?? Math.min(...domain.map((x) => x.x)),
|
||||||
|
maxX: props.minX ?? Math.max(...domain.map((x) => x.x)),
|
||||||
|
maxY: Math.max(...domain.map((x) => x.y)),
|
||||||
|
});
|
||||||
|
|
||||||
|
// I think size.width is sometimes not finite due to the component not being in a visible context
|
||||||
|
// This occurs during testing
|
||||||
|
let widthProp = width
|
||||||
|
? width
|
||||||
|
: Number.isFinite(size.width)
|
||||||
|
? size.width
|
||||||
|
: 400;
|
||||||
if (widthProp < 20) {
|
if (widthProp < 20) {
|
||||||
console.warn(
|
console.warn(
|
||||||
`Width of Distribution is set to ${widthProp}, which is too small`
|
`Width of Distribution is set to ${widthProp}, which is too small`
|
||||||
|
@ -122,94 +108,37 @@ export const DistributionChart: React.FC<DistributionChartProps> = ({
|
||||||
widthProp = 20;
|
widthProp = 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const vegaData = { data: shapes.value, samples };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ width: widthProp }}>
|
<div style={{ width: widthProp }}>
|
||||||
{!(isLogX && massBelow0) ? (
|
{logX && shapes.value.some(hasMassBelowZero) ? (
|
||||||
<Vega
|
|
||||||
spec={spec}
|
|
||||||
data={
|
|
||||||
truncateToNthci != 100
|
|
||||||
? {
|
|
||||||
con: shapeTruncated.value.continuous,
|
|
||||||
dis: shapeTruncated.value.discrete,
|
|
||||||
}
|
|
||||||
: { con: shape.value.continuous, dis: shape.value.discrete }
|
|
||||||
}
|
|
||||||
width={widthProp - 10}
|
|
||||||
height={height}
|
|
||||||
actions={false}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<ErrorAlert heading="Log Domain Error">
|
<ErrorAlert heading="Log Domain Error">
|
||||||
Cannot graph distribution with negative values on logarithmic scale.
|
Cannot graph distribution with negative values on logarithmic scale.
|
||||||
</ErrorAlert>
|
</ErrorAlert>
|
||||||
|
) : (
|
||||||
|
<Vega
|
||||||
|
spec={spec}
|
||||||
|
data={vegaData}
|
||||||
|
width={widthProp - 10}
|
||||||
|
height={height}
|
||||||
|
actions={actions}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
<div className="flex justify-center">
|
<div className="flex justify-center">
|
||||||
{showSummary && <SummaryTable distribution={distribution} />}
|
{showSummary && plot.distributions.length === 1 && (
|
||||||
</div>
|
<SummaryTable
|
||||||
{showControls && (
|
distribution={plot.distributions[0].distribution}
|
||||||
<div>
|
environment={environment}
|
||||||
<CheckBox
|
|
||||||
label="Log X scale"
|
|
||||||
value={isLogX}
|
|
||||||
onChange={setLogX}
|
|
||||||
// Check whether we should disable the checkbox
|
|
||||||
{...(massBelow0
|
|
||||||
? {
|
|
||||||
disabled: true,
|
|
||||||
tooltip:
|
|
||||||
"Your distribution has mass lower than or equal to 0. Log only works on strictly positive values.",
|
|
||||||
}
|
|
||||||
: {})}
|
|
||||||
/>
|
/>
|
||||||
<CheckBox label="Exp Y scale" value={isExpY} onChange={setExpY} />
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
return sized;
|
return sized;
|
||||||
};
|
};
|
||||||
|
|
||||||
function buildVegaSpec(isLogX: boolean, isExpY: boolean): VisualizationSpec {
|
|
||||||
return {
|
|
||||||
...chartSpecification,
|
|
||||||
scales: [
|
|
||||||
isLogX ? logXScale : linearXScale,
|
|
||||||
isExpY ? expYScale : linearYScale,
|
|
||||||
],
|
|
||||||
} as VisualizationSpec;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CheckBoxProps {
|
|
||||||
label: string;
|
|
||||||
onChange: (x: boolean) => void;
|
|
||||||
value: boolean;
|
|
||||||
disabled?: boolean;
|
|
||||||
tooltip?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const CheckBox: React.FC<CheckBoxProps> = ({
|
|
||||||
label,
|
|
||||||
onChange,
|
|
||||||
value,
|
|
||||||
disabled = false,
|
|
||||||
tooltip,
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<span title={tooltip}>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={value}
|
|
||||||
onChange={() => onChange(!value)}
|
|
||||||
disabled={disabled}
|
|
||||||
className="form-checkbox"
|
|
||||||
/>
|
|
||||||
<label className={clsx(disabled && "text-slate-400")}> {label}</label>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const TableHeadCell: React.FC<{ children: React.ReactNode }> = ({
|
const TableHeadCell: React.FC<{ children: React.ReactNode }> = ({
|
||||||
children,
|
children,
|
||||||
}) => (
|
}) => (
|
||||||
|
@ -225,32 +154,36 @@ const Cell: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
||||||
);
|
);
|
||||||
|
|
||||||
type SummaryTableProps = {
|
type SummaryTableProps = {
|
||||||
distribution: Distribution;
|
distribution: SqDistribution;
|
||||||
|
environment: environment;
|
||||||
};
|
};
|
||||||
|
|
||||||
const SummaryTable: React.FC<SummaryTableProps> = ({ distribution }) => {
|
const SummaryTable: React.FC<SummaryTableProps> = ({
|
||||||
const mean = distribution.mean();
|
distribution,
|
||||||
const stdev = distribution.stdev();
|
environment,
|
||||||
const p5 = distribution.inv(0.05);
|
}) => {
|
||||||
const p10 = distribution.inv(0.1);
|
const mean = distribution.mean(environment);
|
||||||
const p25 = distribution.inv(0.25);
|
const stdev = distribution.stdev(environment);
|
||||||
const p50 = distribution.inv(0.5);
|
const p5 = distribution.inv(environment, 0.05);
|
||||||
const p75 = distribution.inv(0.75);
|
const p10 = distribution.inv(environment, 0.1);
|
||||||
const p90 = distribution.inv(0.9);
|
const p25 = distribution.inv(environment, 0.25);
|
||||||
const p95 = distribution.inv(0.95);
|
const p50 = distribution.inv(environment, 0.5);
|
||||||
|
const p75 = distribution.inv(environment, 0.75);
|
||||||
|
const p90 = distribution.inv(environment, 0.9);
|
||||||
|
const p95 = distribution.inv(environment, 0.95);
|
||||||
|
|
||||||
const hasResult = (x: result<number, distributionError>): boolean =>
|
const hasResult = (x: result<number, SqDistributionError>): boolean =>
|
||||||
x.tag === "Ok";
|
x.tag === "Ok";
|
||||||
|
|
||||||
const unwrapResult = (
|
const unwrapResult = (
|
||||||
x: result<number, distributionError>
|
x: result<number, SqDistributionError>
|
||||||
): React.ReactNode => {
|
): React.ReactNode => {
|
||||||
if (x.tag === "Ok") {
|
if (x.tag === "Ok") {
|
||||||
return <NumberShower number={x.value} />;
|
return <NumberShower number={x.value} />;
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<ErrorAlert heading="Distribution Error">
|
<ErrorAlert heading="Distribution Error">
|
||||||
{distributionErrorToString(x.value)}
|
{x.value.toString()}
|
||||||
</ErrorAlert>
|
</ErrorAlert>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { lambdaValue, environment, runForeign } from "@quri/squiggle-lang";
|
import {
|
||||||
|
SqLambda,
|
||||||
|
environment,
|
||||||
|
SqValueTag,
|
||||||
|
SqError,
|
||||||
|
} from "@quri/squiggle-lang";
|
||||||
import { FunctionChart1Dist } from "./FunctionChart1Dist";
|
import { FunctionChart1Dist } from "./FunctionChart1Dist";
|
||||||
import { FunctionChart1Number } from "./FunctionChart1Number";
|
import { FunctionChart1Number } from "./FunctionChart1Number";
|
||||||
import { DistributionPlottingSettings } from "./DistributionChart";
|
import { DistributionPlottingSettings } from "./DistributionChart";
|
||||||
import { ErrorAlert, MessageAlert } from "./Alert";
|
import { MessageAlert } from "./Alert";
|
||||||
|
import { SquiggleErrorAlert } from "./SquiggleErrorAlert";
|
||||||
|
|
||||||
export type FunctionChartSettings = {
|
export type FunctionChartSettings = {
|
||||||
start: number;
|
start: number;
|
||||||
|
@ -12,13 +18,32 @@ export type FunctionChartSettings = {
|
||||||
};
|
};
|
||||||
|
|
||||||
interface FunctionChartProps {
|
interface FunctionChartProps {
|
||||||
fn: lambdaValue;
|
fn: SqLambda;
|
||||||
chartSettings: FunctionChartSettings;
|
chartSettings: FunctionChartSettings;
|
||||||
distributionPlotSettings: DistributionPlottingSettings;
|
distributionPlotSettings: DistributionPlottingSettings;
|
||||||
environment: environment;
|
environment: environment;
|
||||||
height: number;
|
height: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const FunctionCallErrorAlert = ({ error }: { error: SqError }) => {
|
||||||
|
const [expanded, setExpanded] = React.useState(false);
|
||||||
|
if (expanded) {
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<MessageAlert heading="Function Display Failed">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<span
|
||||||
|
className="underline decoration-dashed cursor-pointer"
|
||||||
|
onClick={() => setExpanded(!expanded)}
|
||||||
|
>
|
||||||
|
{expanded ? "Hide" : "Show"} error details
|
||||||
|
</span>
|
||||||
|
{expanded ? <SquiggleErrorAlert error={error} /> : null}
|
||||||
|
</div>
|
||||||
|
</MessageAlert>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const FunctionChart: React.FC<FunctionChartProps> = ({
|
export const FunctionChart: React.FC<FunctionChartProps> = ({
|
||||||
fn,
|
fn,
|
||||||
chartSettings,
|
chartSettings,
|
||||||
|
@ -26,15 +51,16 @@ export const FunctionChart: React.FC<FunctionChartProps> = ({
|
||||||
distributionPlotSettings,
|
distributionPlotSettings,
|
||||||
height,
|
height,
|
||||||
}) => {
|
}) => {
|
||||||
if (fn.parameters.length > 1) {
|
console.log(fn.parameters().length);
|
||||||
|
if (fn.parameters().length !== 1) {
|
||||||
return (
|
return (
|
||||||
<MessageAlert heading="Function Display Not Supported">
|
<MessageAlert heading="Function Display Not Supported">
|
||||||
Only functions with one parameter are displayed.
|
Only functions with one parameter are displayed.
|
||||||
</MessageAlert>
|
</MessageAlert>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const result1 = runForeign(fn, [chartSettings.start], environment);
|
const result1 = fn.call([chartSettings.start]);
|
||||||
const result2 = runForeign(fn, [chartSettings.stop], environment);
|
const result2 = fn.call([chartSettings.stop]);
|
||||||
const getValidResult = () => {
|
const getValidResult = () => {
|
||||||
if (result1.tag === "Ok") {
|
if (result1.tag === "Ok") {
|
||||||
return result1;
|
return result1;
|
||||||
|
@ -45,11 +71,13 @@ export const FunctionChart: React.FC<FunctionChartProps> = ({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const validResult = getValidResult();
|
const validResult = getValidResult();
|
||||||
const resultType =
|
|
||||||
validResult.tag === "Ok" ? validResult.value.tag : ("Error" as const);
|
|
||||||
|
|
||||||
switch (resultType) {
|
if (validResult.tag === "Error") {
|
||||||
case "distribution":
|
return <FunctionCallErrorAlert error={validResult.value} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (validResult.value.tag) {
|
||||||
|
case SqValueTag.Distribution:
|
||||||
return (
|
return (
|
||||||
<FunctionChart1Dist
|
<FunctionChart1Dist
|
||||||
fn={fn}
|
fn={fn}
|
||||||
|
@ -59,7 +87,7 @@ export const FunctionChart: React.FC<FunctionChartProps> = ({
|
||||||
distributionPlotSettings={distributionPlotSettings}
|
distributionPlotSettings={distributionPlotSettings}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
case "number":
|
case SqValueTag.Number:
|
||||||
return (
|
return (
|
||||||
<FunctionChart1Number
|
<FunctionChart1Number
|
||||||
fn={fn}
|
fn={fn}
|
||||||
|
@ -68,15 +96,11 @@ export const FunctionChart: React.FC<FunctionChartProps> = ({
|
||||||
height={height}
|
height={height}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
case "Error":
|
|
||||||
return (
|
|
||||||
<ErrorAlert heading="Error">The function failed to be run</ErrorAlert>
|
|
||||||
);
|
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
<MessageAlert heading="Function Display Not Supported">
|
<MessageAlert heading="Function Display Not Supported">
|
||||||
There is no function visualization for this type of output:{" "}
|
There is no function visualization for this type of output:{" "}
|
||||||
<span className="font-bold">{resultType}</span>
|
<span className="font-bold">{validResult.value.tag}</span>
|
||||||
</MessageAlert>
|
</MessageAlert>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,20 +2,20 @@ import * as React from "react";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import type { Spec } from "vega";
|
import type { Spec } from "vega";
|
||||||
import {
|
import {
|
||||||
Distribution,
|
SqDistribution,
|
||||||
result,
|
result,
|
||||||
lambdaValue,
|
SqLambda,
|
||||||
environment,
|
environment,
|
||||||
runForeign,
|
SqError,
|
||||||
squiggleExpression,
|
SqValue,
|
||||||
errorValue,
|
SqValueTag,
|
||||||
errorValueToString,
|
|
||||||
} from "@quri/squiggle-lang";
|
} from "@quri/squiggle-lang";
|
||||||
import { createClassFromSpec } from "react-vega";
|
import { createClassFromSpec } from "react-vega";
|
||||||
import * as percentilesSpec from "../vega-specs/spec-percentiles.json";
|
import * as percentilesSpec from "../vega-specs/spec-percentiles.json";
|
||||||
import {
|
import {
|
||||||
DistributionChart,
|
DistributionChart,
|
||||||
DistributionPlottingSettings,
|
DistributionPlottingSettings,
|
||||||
|
defaultPlot,
|
||||||
} from "./DistributionChart";
|
} from "./DistributionChart";
|
||||||
import { NumberShower } from "./NumberShower";
|
import { NumberShower } from "./NumberShower";
|
||||||
import { ErrorAlert } from "./Alert";
|
import { ErrorAlert } from "./Alert";
|
||||||
|
@ -45,7 +45,7 @@ export type FunctionChartSettings = {
|
||||||
};
|
};
|
||||||
|
|
||||||
interface FunctionChart1DistProps {
|
interface FunctionChart1DistProps {
|
||||||
fn: lambdaValue;
|
fn: SqLambda;
|
||||||
chartSettings: FunctionChartSettings;
|
chartSettings: FunctionChartSettings;
|
||||||
distributionPlotSettings: DistributionPlottingSettings;
|
distributionPlotSettings: DistributionPlottingSettings;
|
||||||
environment: environment;
|
environment: environment;
|
||||||
|
@ -76,9 +76,17 @@ type errors = _.Dictionary<
|
||||||
}[]
|
}[]
|
||||||
>;
|
>;
|
||||||
|
|
||||||
type point = { x: number; value: result<Distribution, string> };
|
type point = { x: number; value: result<SqDistribution, string> };
|
||||||
|
|
||||||
let getPercentiles = ({ chartSettings, fn, environment }) => {
|
let getPercentiles = ({
|
||||||
|
chartSettings,
|
||||||
|
fn,
|
||||||
|
environment,
|
||||||
|
}: {
|
||||||
|
chartSettings: FunctionChartSettings;
|
||||||
|
fn: SqLambda;
|
||||||
|
environment: environment;
|
||||||
|
}) => {
|
||||||
let chartPointsToRender = _rangeByCount(
|
let chartPointsToRender = _rangeByCount(
|
||||||
chartSettings.start,
|
chartSettings.start,
|
||||||
chartSettings.stop,
|
chartSettings.stop,
|
||||||
|
@ -86,9 +94,9 @@ let getPercentiles = ({ chartSettings, fn, environment }) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
let chartPointsData: point[] = chartPointsToRender.map((x) => {
|
let chartPointsData: point[] = chartPointsToRender.map((x) => {
|
||||||
let result = runForeign(fn, [x], environment);
|
let result = fn.call([x]);
|
||||||
if (result.tag === "Ok") {
|
if (result.tag === "Ok") {
|
||||||
if (result.value.tag == "distribution") {
|
if (result.value.tag === SqValueTag.Distribution) {
|
||||||
return { x, value: { tag: "Ok", value: result.value.value } };
|
return { x, value: { tag: "Ok", value: result.value.value } };
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
|
@ -103,13 +111,13 @@ let getPercentiles = ({ chartSettings, fn, environment }) => {
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
x,
|
x,
|
||||||
value: { tag: "Error", value: errorValueToString(result.value) },
|
value: { tag: "Error", value: result.value.toString() },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let initialPartition: [
|
let initialPartition: [
|
||||||
{ x: number; value: Distribution }[],
|
{ x: number; value: SqDistribution }[],
|
||||||
{ x: number; value: string }[]
|
{ x: number; value: string }[]
|
||||||
] = [[], []];
|
] = [[], []];
|
||||||
|
|
||||||
|
@ -125,26 +133,23 @@ let getPercentiles = ({ chartSettings, fn, environment }) => {
|
||||||
let groupedErrors: errors = _.groupBy(errors, (x) => x.value);
|
let groupedErrors: errors = _.groupBy(errors, (x) => x.value);
|
||||||
|
|
||||||
let percentiles: percentiles = functionImage.map(({ x, value }) => {
|
let percentiles: percentiles = functionImage.map(({ x, value }) => {
|
||||||
// We convert it to to a pointSet distribution first, so that in case its a sample set
|
const res = {
|
||||||
// distribution, it doesn't internally convert it to a pointSet distribution for every
|
|
||||||
// single inv() call.
|
|
||||||
let toPointSet: Distribution = unwrap(value.toPointSet());
|
|
||||||
return {
|
|
||||||
x: x,
|
x: x,
|
||||||
p1: unwrap(toPointSet.inv(0.01)),
|
p1: unwrap(value.inv(environment, 0.01)),
|
||||||
p5: unwrap(toPointSet.inv(0.05)),
|
p5: unwrap(value.inv(environment, 0.05)),
|
||||||
p10: unwrap(toPointSet.inv(0.1)),
|
p10: unwrap(value.inv(environment, 0.1)),
|
||||||
p20: unwrap(toPointSet.inv(0.2)),
|
p20: unwrap(value.inv(environment, 0.2)),
|
||||||
p30: unwrap(toPointSet.inv(0.3)),
|
p30: unwrap(value.inv(environment, 0.3)),
|
||||||
p40: unwrap(toPointSet.inv(0.4)),
|
p40: unwrap(value.inv(environment, 0.4)),
|
||||||
p50: unwrap(toPointSet.inv(0.5)),
|
p50: unwrap(value.inv(environment, 0.5)),
|
||||||
p60: unwrap(toPointSet.inv(0.6)),
|
p60: unwrap(value.inv(environment, 0.6)),
|
||||||
p70: unwrap(toPointSet.inv(0.7)),
|
p70: unwrap(value.inv(environment, 0.7)),
|
||||||
p80: unwrap(toPointSet.inv(0.8)),
|
p80: unwrap(value.inv(environment, 0.8)),
|
||||||
p90: unwrap(toPointSet.inv(0.9)),
|
p90: unwrap(value.inv(environment, 0.9)),
|
||||||
p95: unwrap(toPointSet.inv(0.95)),
|
p95: unwrap(value.inv(environment, 0.95)),
|
||||||
p99: unwrap(toPointSet.inv(0.99)),
|
p99: unwrap(value.inv(environment, 0.99)),
|
||||||
};
|
};
|
||||||
|
return res;
|
||||||
});
|
});
|
||||||
|
|
||||||
return { percentiles, errors: groupedErrors };
|
return { percentiles, errors: groupedErrors };
|
||||||
|
@ -165,19 +170,22 @@ export const FunctionChart1Dist: React.FC<FunctionChart1DistProps> = ({
|
||||||
setMouseOverlay(NaN);
|
setMouseOverlay(NaN);
|
||||||
}
|
}
|
||||||
const signalListeners = { mousemove: handleHover, mouseout: handleOut };
|
const signalListeners = { mousemove: handleHover, mouseout: handleOut };
|
||||||
let mouseItem: result<squiggleExpression, errorValue> = !!mouseOverlay
|
|
||||||
? runForeign(fn, [mouseOverlay], environment)
|
//TODO: This custom error handling is a bit hacky and should be improved.
|
||||||
|
let mouseItem: result<SqValue, SqError> = !!mouseOverlay
|
||||||
|
? fn.call([mouseOverlay])
|
||||||
: {
|
: {
|
||||||
tag: "Error",
|
tag: "Error",
|
||||||
value: {
|
value: SqError.createOtherError(
|
||||||
tag: "REExpectedType",
|
"Hover x-coordinate returned NaN. Expected a number."
|
||||||
value: "Hover x-coordinate returned NaN. Expected a number.",
|
),
|
||||||
},
|
|
||||||
};
|
};
|
||||||
let showChart =
|
let showChart =
|
||||||
mouseItem.tag === "Ok" && mouseItem.value.tag === "distribution" ? (
|
mouseItem.tag === "Ok" &&
|
||||||
|
mouseItem.value.tag === SqValueTag.Distribution ? (
|
||||||
<DistributionChart
|
<DistributionChart
|
||||||
distribution={mouseItem.value.value}
|
plot={defaultPlot(mouseItem.value.value)}
|
||||||
|
environment={environment}
|
||||||
width={400}
|
width={400}
|
||||||
height={50}
|
height={50}
|
||||||
{...distributionPlotSettings}
|
{...distributionPlotSettings}
|
||||||
|
|
|
@ -1,16 +1,11 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import type { Spec } from "vega";
|
import type { Spec } from "vega";
|
||||||
import {
|
import { result, SqLambda, environment, SqValueTag } from "@quri/squiggle-lang";
|
||||||
result,
|
|
||||||
lambdaValue,
|
|
||||||
environment,
|
|
||||||
runForeign,
|
|
||||||
errorValueToString,
|
|
||||||
} from "@quri/squiggle-lang";
|
|
||||||
import { createClassFromSpec } from "react-vega";
|
import { createClassFromSpec } from "react-vega";
|
||||||
import * as lineChartSpec from "../vega-specs/spec-line-chart.json";
|
import * as lineChartSpec from "../vega-specs/spec-line-chart.json";
|
||||||
import { ErrorAlert } from "./Alert";
|
import { ErrorAlert } from "./Alert";
|
||||||
|
import { squiggleValueTag } from "@quri/squiggle-lang/src/rescript/ForTS/ForTS_SquiggleValue/ForTS_SquiggleValue_tag";
|
||||||
|
|
||||||
let SquiggleLineChart = createClassFromSpec({
|
let SquiggleLineChart = createClassFromSpec({
|
||||||
spec: lineChartSpec as Spec,
|
spec: lineChartSpec as Spec,
|
||||||
|
@ -30,7 +25,7 @@ export type FunctionChartSettings = {
|
||||||
};
|
};
|
||||||
|
|
||||||
interface FunctionChart1NumberProps {
|
interface FunctionChart1NumberProps {
|
||||||
fn: lambdaValue;
|
fn: SqLambda;
|
||||||
chartSettings: FunctionChartSettings;
|
chartSettings: FunctionChartSettings;
|
||||||
environment: environment;
|
environment: environment;
|
||||||
height: number;
|
height: number;
|
||||||
|
@ -38,20 +33,25 @@ interface FunctionChart1NumberProps {
|
||||||
|
|
||||||
type point = { x: number; value: result<number, string> };
|
type point = { x: number; value: result<number, string> };
|
||||||
|
|
||||||
let getFunctionImage = ({ chartSettings, fn, environment }) => {
|
let getFunctionImage = ({
|
||||||
//We adjust the count, because the count is made for distributions, which are much more expensive to estimate
|
chartSettings,
|
||||||
let adjustedCount = chartSettings.count * 20;
|
fn,
|
||||||
|
environment,
|
||||||
|
}: {
|
||||||
|
chartSettings: FunctionChartSettings;
|
||||||
|
fn: SqLambda;
|
||||||
|
environment: environment;
|
||||||
|
}) => {
|
||||||
let chartPointsToRender = _rangeByCount(
|
let chartPointsToRender = _rangeByCount(
|
||||||
chartSettings.start,
|
chartSettings.start,
|
||||||
chartSettings.stop,
|
chartSettings.stop,
|
||||||
adjustedCount
|
chartSettings.count
|
||||||
);
|
);
|
||||||
|
|
||||||
let chartPointsData: point[] = chartPointsToRender.map((x) => {
|
let chartPointsData: point[] = chartPointsToRender.map((x) => {
|
||||||
let result = runForeign(fn, [x], environment);
|
let result = fn.call([x]);
|
||||||
if (result.tag === "Ok") {
|
if (result.tag === "Ok") {
|
||||||
if (result.value.tag == "number") {
|
if (result.value.tag === SqValueTag.Number) {
|
||||||
return { x, value: { tag: "Ok", value: result.value.value } };
|
return { x, value: { tag: "Ok", value: result.value.value } };
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
|
@ -65,7 +65,7 @@ let getFunctionImage = ({ chartSettings, fn, environment }) => {
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
x,
|
x,
|
||||||
value: { tag: "Error", value: errorValueToString(result.value) },
|
value: { tag: "Error", value: result.value.toString() },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,98 +1,158 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import {
|
import {
|
||||||
squiggleExpression,
|
SqValue,
|
||||||
bindings,
|
|
||||||
environment,
|
environment,
|
||||||
jsImports,
|
SqProject,
|
||||||
defaultImports,
|
|
||||||
defaultBindings,
|
|
||||||
defaultEnvironment,
|
defaultEnvironment,
|
||||||
} from "@quri/squiggle-lang";
|
} from "@quri/squiggle-lang";
|
||||||
import { FunctionChartSettings } from "./FunctionChart";
|
|
||||||
import { useSquiggle } from "../lib/hooks";
|
import { useSquiggle } from "../lib/hooks";
|
||||||
import { SquiggleErrorAlert } from "./SquiggleErrorAlert";
|
import { SquiggleViewer } from "./SquiggleViewer";
|
||||||
import { SquiggleItem } from "./SquiggleItem";
|
import { JsImports } from "../lib/jsImports";
|
||||||
|
import { getValueToRender } from "../lib/utility";
|
||||||
|
|
||||||
export interface SquiggleChartProps {
|
export type SquiggleChartProps = {
|
||||||
/** The input string for squiggle */
|
/** The input string for squiggle */
|
||||||
squiggleString?: string;
|
code: string;
|
||||||
|
/** Allows to re-run the code if code hasn't changed */
|
||||||
|
executionId?: number;
|
||||||
/** If the output requires monte carlo sampling, the amount of samples */
|
/** If the output requires monte carlo sampling, the amount of samples */
|
||||||
sampleCount?: number;
|
sampleCount?: number;
|
||||||
/** The amount of points returned to draw the distribution */
|
/** If the result is a function, where the function domain starts */
|
||||||
environment?: environment;
|
diagramStart?: number;
|
||||||
/** If the result is a function, where the function starts, ends and the amount of stops */
|
/** If the result is a function, where the function domain ends */
|
||||||
chartSettings?: FunctionChartSettings;
|
diagramStop?: number;
|
||||||
|
/** If the result is a function, the amount of stops sampled */
|
||||||
|
diagramCount?: number;
|
||||||
/** When the squiggle code gets reevaluated */
|
/** When the squiggle code gets reevaluated */
|
||||||
onChange?(expr: squiggleExpression | undefined): void;
|
onChange?(expr: SqValue | undefined, sourceName: string): void;
|
||||||
/** CSS width of the element */
|
/** CSS width of the element */
|
||||||
width?: number;
|
width?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
/** Bindings of previous variables declared */
|
|
||||||
bindings?: bindings;
|
|
||||||
/** JS imported parameters */
|
/** JS imported parameters */
|
||||||
jsImports?: jsImports;
|
jsImports?: JsImports;
|
||||||
/** Whether to show a summary of the distribution */
|
/** Whether to show a summary of the distribution */
|
||||||
showSummary?: boolean;
|
showSummary?: boolean;
|
||||||
/** Whether to show type information about returns, default false */
|
|
||||||
showTypes?: boolean;
|
|
||||||
/** Whether to show graph controls (scale etc)*/
|
|
||||||
showControls?: boolean;
|
|
||||||
/** Set the x scale to be logarithmic by deault */
|
/** Set the x scale to be logarithmic by deault */
|
||||||
logX?: boolean;
|
logX?: boolean;
|
||||||
/** Set the y scale to be exponential by deault */
|
/** Set the y scale to be exponential by deault */
|
||||||
expY?: boolean;
|
expY?: boolean;
|
||||||
/** Display 94% interval; useful for thin lognormals */
|
/** How to format numbers on the x axis */
|
||||||
truncateToNthci?: number;
|
tickFormat?: string;
|
||||||
}
|
/** Title of the graphed distribution */
|
||||||
|
title?: string;
|
||||||
|
/** Color of the graphed distribution */
|
||||||
|
color?: string;
|
||||||
|
/** Specify the lower bound of the x scale */
|
||||||
|
minX?: number;
|
||||||
|
/** Specify the upper bound of the x scale */
|
||||||
|
maxX?: number;
|
||||||
|
/** Whether the x-axis should be dates or numbers */
|
||||||
|
xAxisType?: "number" | "dateTime";
|
||||||
|
/** Whether to show vega actions to the user, so they can copy the chart spec */
|
||||||
|
distributionChartActions?: boolean;
|
||||||
|
enableLocalSettings?: boolean;
|
||||||
|
} & (StandaloneExecutionProps | ProjectExecutionProps);
|
||||||
|
|
||||||
|
// Props needed for a standalone execution
|
||||||
|
type StandaloneExecutionProps = {
|
||||||
|
project?: undefined;
|
||||||
|
continues?: undefined;
|
||||||
|
/** The amount of points returned to draw the distribution, not needed if using a project */
|
||||||
|
environment?: environment;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Props needed when executing inside a project.
|
||||||
|
type ProjectExecutionProps = {
|
||||||
|
environment?: undefined;
|
||||||
|
/** The project that this execution is part of */
|
||||||
|
project: SqProject;
|
||||||
|
/** What other squiggle sources from the project to continue. Default [] */
|
||||||
|
continues?: string[];
|
||||||
|
};
|
||||||
const defaultOnChange = () => {};
|
const defaultOnChange = () => {};
|
||||||
const defaultChartSettings = { start: 0, stop: 10, count: 20 };
|
const defaultImports: JsImports = {};
|
||||||
|
|
||||||
export const SquiggleChart: React.FC<SquiggleChartProps> = ({
|
export const splitSquiggleChartSettings = (props: SquiggleChartProps) => {
|
||||||
squiggleString = "",
|
const {
|
||||||
environment,
|
showSummary = false,
|
||||||
onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here
|
logX = false,
|
||||||
height = 200,
|
expY = false,
|
||||||
bindings = defaultBindings,
|
diagramStart = 0,
|
||||||
jsImports = defaultImports,
|
diagramStop = 10,
|
||||||
showSummary = false,
|
diagramCount = 20,
|
||||||
width,
|
tickFormat,
|
||||||
showTypes = false,
|
minX,
|
||||||
showControls = false,
|
maxX,
|
||||||
logX = false,
|
color,
|
||||||
expY = false,
|
title,
|
||||||
truncateToNthci = 100,
|
xAxisType = "number",
|
||||||
chartSettings = defaultChartSettings,
|
distributionChartActions,
|
||||||
}) => {
|
} = props;
|
||||||
const { result } = useSquiggle({
|
|
||||||
code: squiggleString,
|
|
||||||
bindings,
|
|
||||||
environment,
|
|
||||||
jsImports,
|
|
||||||
onChange,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result.tag !== "Ok") {
|
const distributionPlotSettings = {
|
||||||
return <SquiggleErrorAlert error={result.value} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
let distributionPlotSettings = {
|
|
||||||
showControls,
|
|
||||||
showSummary,
|
showSummary,
|
||||||
logX,
|
logX,
|
||||||
expY,
|
expY,
|
||||||
truncateToNthci,
|
format: tickFormat,
|
||||||
|
minX,
|
||||||
|
maxX,
|
||||||
|
color,
|
||||||
|
title,
|
||||||
|
xAxisType,
|
||||||
|
actions: distributionChartActions,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
const chartSettings = {
|
||||||
<SquiggleItem
|
start: diagramStart,
|
||||||
expression={result.value}
|
stop: diagramStop,
|
||||||
width={width}
|
count: diagramCount,
|
||||||
height={height}
|
};
|
||||||
distributionPlotSettings={distributionPlotSettings}
|
|
||||||
showTypes={showTypes}
|
return { distributionPlotSettings, chartSettings };
|
||||||
chartSettings={chartSettings}
|
|
||||||
environment={environment ?? defaultEnvironment}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const SquiggleChart: React.FC<SquiggleChartProps> = React.memo(
|
||||||
|
(props) => {
|
||||||
|
const { distributionPlotSettings, chartSettings } =
|
||||||
|
splitSquiggleChartSettings(props);
|
||||||
|
|
||||||
|
const {
|
||||||
|
code,
|
||||||
|
jsImports = defaultImports,
|
||||||
|
onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here
|
||||||
|
executionId = 0,
|
||||||
|
width,
|
||||||
|
height = 200,
|
||||||
|
enableLocalSettings = false,
|
||||||
|
continues,
|
||||||
|
project,
|
||||||
|
environment,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const resultAndBindings = useSquiggle({
|
||||||
|
environment,
|
||||||
|
continues,
|
||||||
|
project,
|
||||||
|
code,
|
||||||
|
jsImports,
|
||||||
|
onChange,
|
||||||
|
executionId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const valueToRender = getValueToRender(resultAndBindings);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SquiggleViewer
|
||||||
|
result={valueToRender}
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
distributionPlotSettings={distributionPlotSettings}
|
||||||
|
chartSettings={chartSettings}
|
||||||
|
environment={
|
||||||
|
project ? project.getEnvironment() : environment ?? defaultEnvironment
|
||||||
|
}
|
||||||
|
enableLocalSettings={enableLocalSettings}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
|
@ -13,6 +13,7 @@ const SquiggleContext = React.createContext<SquiggleContextShape>({
|
||||||
|
|
||||||
export const SquiggleContainer: React.FC<Props> = ({ children }) => {
|
export const SquiggleContainer: React.FC<Props> = ({ children }) => {
|
||||||
const context = useContext(SquiggleContext);
|
const context = useContext(SquiggleContext);
|
||||||
|
|
||||||
if (context.containerized) {
|
if (context.containerized) {
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,188 +1,92 @@
|
||||||
import React, { useState } from "react";
|
import React from "react";
|
||||||
import * as ReactDOM from "react-dom";
|
|
||||||
import { CodeEditor } from "./CodeEditor";
|
import { CodeEditor } from "./CodeEditor";
|
||||||
import {
|
|
||||||
squiggleExpression,
|
|
||||||
environment,
|
|
||||||
bindings,
|
|
||||||
jsImports,
|
|
||||||
defaultEnvironment,
|
|
||||||
} from "@quri/squiggle-lang";
|
|
||||||
import { defaultImports, defaultBindings } from "@quri/squiggle-lang";
|
|
||||||
import { SquiggleContainer } from "./SquiggleContainer";
|
import { SquiggleContainer } from "./SquiggleContainer";
|
||||||
import { useSquiggle, useSquigglePartial } from "../lib/hooks";
|
import {
|
||||||
import { SquiggleErrorAlert } from "./SquiggleErrorAlert";
|
splitSquiggleChartSettings,
|
||||||
import { SquiggleItem } from "./SquiggleItem";
|
SquiggleChartProps,
|
||||||
|
} from "./SquiggleChart";
|
||||||
|
import { useMaybeControlledValue, useSquiggle } from "../lib/hooks";
|
||||||
|
import { JsImports } from "../lib/jsImports";
|
||||||
|
import { defaultEnvironment, SqLocation, SqProject } from "@quri/squiggle-lang";
|
||||||
|
import { SquiggleViewer } from "./SquiggleViewer";
|
||||||
|
import { getErrorLocations, getValueToRender } from "../lib/utility";
|
||||||
|
|
||||||
const WrappedCodeEditor: React.FC<{
|
const WrappedCodeEditor: React.FC<{
|
||||||
code: string;
|
code: string;
|
||||||
setCode: (code: string) => void;
|
setCode: (code: string) => void;
|
||||||
}> = ({ code, setCode }) => (
|
errorLocations?: SqLocation[];
|
||||||
<div className="border border-grey-200 p-2 m-4">
|
}> = ({ code, setCode, errorLocations }) => (
|
||||||
|
<div className="border border-grey-200 p-2 m-4" data-testid="squiggle-editor">
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
value={code}
|
value={code}
|
||||||
onChange={setCode}
|
onChange={setCode}
|
||||||
oneLine={true}
|
oneLine={true}
|
||||||
showGutter={false}
|
showGutter={false}
|
||||||
height={20}
|
height={20}
|
||||||
|
errorLocations={errorLocations}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
export interface SquiggleEditorProps {
|
export type SquiggleEditorProps = SquiggleChartProps & {
|
||||||
/** The input string for squiggle */
|
defaultCode?: string;
|
||||||
initialSquiggleString?: string;
|
onCodeChange?: (code: string) => void;
|
||||||
/** The width of the element */
|
|
||||||
width?: number;
|
|
||||||
/** If the result is a function, where the function starts */
|
|
||||||
diagramStart?: number;
|
|
||||||
/** If the result is a function, where the function ends */
|
|
||||||
diagramStop?: number;
|
|
||||||
/** If the result is a function, how many points along the function it samples */
|
|
||||||
diagramCount?: number;
|
|
||||||
/** When the environment changes. Used again for notebook magic */
|
|
||||||
onChange?(expr: squiggleExpression | undefined): void;
|
|
||||||
/** Previous variable declarations */
|
|
||||||
bindings?: bindings;
|
|
||||||
/** If the output requires monte carlo sampling, the amount of samples */
|
|
||||||
environment?: environment;
|
|
||||||
/** JS Imports */
|
|
||||||
jsImports?: jsImports;
|
|
||||||
/** Whether to show detail about types of the returns, default false */
|
|
||||||
showTypes?: boolean;
|
|
||||||
/** Whether to give users access to graph controls */
|
|
||||||
showControls?: boolean;
|
|
||||||
/** Whether to show a summary table */
|
|
||||||
showSummary?: boolean;
|
|
||||||
/** Whether to log the x coordinate on distribution charts */
|
|
||||||
logX?: boolean;
|
|
||||||
/** Whether to exp the y coordinate on distribution charts */
|
|
||||||
expY?: boolean;
|
|
||||||
/** Display 94% interval; useful for thin lognormals */
|
|
||||||
truncateToNthci?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SquiggleEditor: React.FC<SquiggleEditorProps> = ({
|
|
||||||
initialSquiggleString = "",
|
|
||||||
width,
|
|
||||||
diagramStart = 0,
|
|
||||||
diagramStop = 10,
|
|
||||||
diagramCount = 20,
|
|
||||||
onChange,
|
|
||||||
bindings = defaultBindings,
|
|
||||||
environment,
|
|
||||||
jsImports = defaultImports,
|
|
||||||
showTypes = false,
|
|
||||||
showControls = false,
|
|
||||||
showSummary = false,
|
|
||||||
logX = false,
|
|
||||||
expY = false,
|
|
||||||
truncateToNthci = 100,
|
|
||||||
}: SquiggleEditorProps) => {
|
|
||||||
const [code, setCode] = useState(initialSquiggleString);
|
|
||||||
React.useEffect(
|
|
||||||
() => setCode(initialSquiggleString),
|
|
||||||
[initialSquiggleString]
|
|
||||||
);
|
|
||||||
|
|
||||||
const { result, observableRef } = useSquiggle({
|
|
||||||
code,
|
|
||||||
bindings,
|
|
||||||
environment,
|
|
||||||
jsImports,
|
|
||||||
onChange,
|
|
||||||
});
|
|
||||||
|
|
||||||
const chartSettings = {
|
|
||||||
start: diagramStart,
|
|
||||||
stop: diagramStop,
|
|
||||||
count: diagramCount,
|
|
||||||
};
|
|
||||||
|
|
||||||
const distributionPlotSettings = {
|
|
||||||
showControls,
|
|
||||||
showSummary,
|
|
||||||
logX,
|
|
||||||
expY,
|
|
||||||
truncateToNthci,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div ref={observableRef}>
|
|
||||||
<SquiggleContainer>
|
|
||||||
<WrappedCodeEditor code={code} setCode={setCode} />
|
|
||||||
{result.tag === "Ok" ? (
|
|
||||||
<SquiggleItem
|
|
||||||
expression={result.value}
|
|
||||||
width={width}
|
|
||||||
height={200}
|
|
||||||
distributionPlotSettings={distributionPlotSettings}
|
|
||||||
showTypes={showTypes}
|
|
||||||
chartSettings={chartSettings}
|
|
||||||
environment={environment ?? defaultEnvironment}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<SquiggleErrorAlert error={result.value} />
|
|
||||||
)}
|
|
||||||
</SquiggleContainer>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function renderSquiggleEditorToDom(props: SquiggleEditorProps) {
|
const defaultOnChange = () => {};
|
||||||
const parent = document.createElement("div");
|
const defaultImports: JsImports = {};
|
||||||
ReactDOM.render(<SquiggleEditor {...props} />, parent);
|
|
||||||
return parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SquigglePartialProps {
|
export const SquiggleEditor: React.FC<SquiggleEditorProps> = (props) => {
|
||||||
/** The input string for squiggle */
|
const [code, setCode] = useMaybeControlledValue({
|
||||||
initialSquiggleString?: string;
|
value: props.code,
|
||||||
/** when the environment changes. Used again for notebook magic*/
|
defaultValue: props.defaultCode ?? "",
|
||||||
onChange?(expr: bindings | undefined): void;
|
onChange: props.onCodeChange,
|
||||||
/** Previously declared variables */
|
|
||||||
bindings?: bindings;
|
|
||||||
/** If the output requires monte carlo sampling, the amount of samples */
|
|
||||||
environment?: environment;
|
|
||||||
/** Variables imported from js */
|
|
||||||
jsImports?: jsImports;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SquigglePartial: React.FC<SquigglePartialProps> = ({
|
|
||||||
initialSquiggleString = "",
|
|
||||||
onChange,
|
|
||||||
bindings = defaultBindings,
|
|
||||||
environment,
|
|
||||||
jsImports = defaultImports,
|
|
||||||
}: SquigglePartialProps) => {
|
|
||||||
const [code, setCode] = useState(initialSquiggleString);
|
|
||||||
React.useEffect(
|
|
||||||
() => setCode(initialSquiggleString),
|
|
||||||
[initialSquiggleString]
|
|
||||||
);
|
|
||||||
|
|
||||||
const { result, observableRef } = useSquigglePartial({
|
|
||||||
code,
|
|
||||||
bindings,
|
|
||||||
environment,
|
|
||||||
jsImports,
|
|
||||||
onChange,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { distributionPlotSettings, chartSettings } =
|
||||||
|
splitSquiggleChartSettings(props);
|
||||||
|
|
||||||
|
const {
|
||||||
|
environment,
|
||||||
|
jsImports = defaultImports,
|
||||||
|
onChange = defaultOnChange, // defaultOnChange must be constant, don't move its definition here
|
||||||
|
executionId = 0,
|
||||||
|
width,
|
||||||
|
height = 200,
|
||||||
|
enableLocalSettings = false,
|
||||||
|
continues,
|
||||||
|
project,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const resultAndBindings = useSquiggle({
|
||||||
|
environment,
|
||||||
|
continues,
|
||||||
|
code,
|
||||||
|
project,
|
||||||
|
jsImports,
|
||||||
|
onChange,
|
||||||
|
executionId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const valueToRender = getValueToRender(resultAndBindings);
|
||||||
|
const errorLocations = getErrorLocations(resultAndBindings.result);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={observableRef}>
|
<SquiggleContainer>
|
||||||
<SquiggleContainer>
|
<WrappedCodeEditor
|
||||||
<WrappedCodeEditor code={code} setCode={setCode} />
|
code={code}
|
||||||
{result.tag !== "Ok" ? (
|
setCode={setCode}
|
||||||
<SquiggleErrorAlert error={result.value} />
|
errorLocations={errorLocations}
|
||||||
) : null}
|
/>
|
||||||
</SquiggleContainer>
|
<SquiggleViewer
|
||||||
</div>
|
result={valueToRender}
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
distributionPlotSettings={distributionPlotSettings}
|
||||||
|
chartSettings={chartSettings}
|
||||||
|
environment={environment ?? defaultEnvironment}
|
||||||
|
enableLocalSettings={enableLocalSettings}
|
||||||
|
/>
|
||||||
|
</SquiggleContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export function renderSquigglePartialToDom(props: SquigglePartialProps) {
|
|
||||||
const parent = document.createElement("div");
|
|
||||||
ReactDOM.render(<SquigglePartial {...props} />, parent);
|
|
||||||
return parent;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,11 +1,44 @@
|
||||||
import { errorValue, errorValueToString } from "@quri/squiggle-lang";
|
import { SqError, SqFrame } from "@quri/squiggle-lang";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { ErrorAlert } from "./Alert";
|
import { ErrorAlert } from "./Alert";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
error: errorValue;
|
error: SqError;
|
||||||
|
};
|
||||||
|
|
||||||
|
const StackTraceFrame: React.FC<{ frame: SqFrame }> = ({ frame }) => {
|
||||||
|
const location = frame.location();
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{frame.name()}
|
||||||
|
{location
|
||||||
|
? ` at line ${location.start.line}, column ${location.start.column}`
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const StackTrace: React.FC<Props> = ({ error }) => {
|
||||||
|
const frames = error.getFrameArray();
|
||||||
|
return frames.length ? (
|
||||||
|
<div>
|
||||||
|
<div className="font-medium">Stack trace:</div>
|
||||||
|
<div className="ml-4">
|
||||||
|
{frames.map((frame, i) => (
|
||||||
|
<StackTraceFrame frame={frame} key={i} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SquiggleErrorAlert: React.FC<Props> = ({ error }) => {
|
export const SquiggleErrorAlert: React.FC<Props> = ({ error }) => {
|
||||||
return <ErrorAlert heading="Error">{errorValueToString(error)}</ErrorAlert>;
|
return (
|
||||||
|
<ErrorAlert heading="Error">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>{error.toString()}</div>
|
||||||
|
<StackTrace error={error} />
|
||||||
|
</div>
|
||||||
|
</ErrorAlert>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,281 +0,0 @@
|
||||||
import * as React from "react";
|
|
||||||
import {
|
|
||||||
squiggleExpression,
|
|
||||||
environment,
|
|
||||||
declaration,
|
|
||||||
} from "@quri/squiggle-lang";
|
|
||||||
import { NumberShower } from "./NumberShower";
|
|
||||||
import {
|
|
||||||
DistributionChart,
|
|
||||||
DistributionPlottingSettings,
|
|
||||||
} from "./DistributionChart";
|
|
||||||
import { FunctionChart, FunctionChartSettings } from "./FunctionChart";
|
|
||||||
|
|
||||||
function getRange<a>(x: declaration<a>) {
|
|
||||||
const first = x.args[0];
|
|
||||||
switch (first.tag) {
|
|
||||||
case "Float": {
|
|
||||||
return { floats: { min: first.value.min, max: first.value.max } };
|
|
||||||
}
|
|
||||||
case "Date": {
|
|
||||||
return { time: { min: first.value.min, max: first.value.max } };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getChartSettings<a>(x: declaration<a>): FunctionChartSettings {
|
|
||||||
const range = getRange(x);
|
|
||||||
const min = range.floats ? range.floats.min : 0;
|
|
||||||
const max = range.floats ? range.floats.max : 10;
|
|
||||||
return {
|
|
||||||
start: min,
|
|
||||||
stop: max,
|
|
||||||
count: 20,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
interface VariableBoxProps {
|
|
||||||
heading: string;
|
|
||||||
children: React.ReactNode;
|
|
||||||
showTypes: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const VariableBox: React.FC<VariableBoxProps> = ({
|
|
||||||
heading = "Error",
|
|
||||||
children,
|
|
||||||
showTypes = false,
|
|
||||||
}) => {
|
|
||||||
if (showTypes) {
|
|
||||||
return (
|
|
||||||
<div className="bg-white border border-grey-200 m-2">
|
|
||||||
<div className="border-b border-grey-200 p-3">
|
|
||||||
<header className="font-mono">{heading}</header>
|
|
||||||
</div>
|
|
||||||
<div className="p-3">{children}</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return <div>{children}</div>;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface SquiggleItemProps {
|
|
||||||
/** The input string for squiggle */
|
|
||||||
expression: squiggleExpression;
|
|
||||||
width?: number;
|
|
||||||
height: number;
|
|
||||||
distributionPlotSettings: DistributionPlottingSettings;
|
|
||||||
/** Whether to show type information */
|
|
||||||
showTypes: boolean;
|
|
||||||
/** Settings for displaying functions */
|
|
||||||
chartSettings: FunctionChartSettings;
|
|
||||||
/** Environment for further function executions */
|
|
||||||
environment: environment;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SquiggleItem: React.FC<SquiggleItemProps> = ({
|
|
||||||
expression,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
distributionPlotSettings,
|
|
||||||
showTypes = false,
|
|
||||||
chartSettings,
|
|
||||||
environment,
|
|
||||||
}) => {
|
|
||||||
switch (expression.tag) {
|
|
||||||
case "number":
|
|
||||||
return (
|
|
||||||
<VariableBox heading="Number" showTypes={showTypes}>
|
|
||||||
<div className="font-semibold text-slate-600">
|
|
||||||
<NumberShower precision={3} number={expression.value} />
|
|
||||||
</div>
|
|
||||||
</VariableBox>
|
|
||||||
);
|
|
||||||
case "distribution": {
|
|
||||||
const distType = expression.value.type();
|
|
||||||
return (
|
|
||||||
<VariableBox
|
|
||||||
heading={`Distribution (${distType})`}
|
|
||||||
showTypes={showTypes}
|
|
||||||
>
|
|
||||||
{distType === "Symbolic" && showTypes ? (
|
|
||||||
<div>{expression.value.toString()}</div>
|
|
||||||
) : null}
|
|
||||||
<DistributionChart
|
|
||||||
distribution={expression.value}
|
|
||||||
{...distributionPlotSettings}
|
|
||||||
height={height}
|
|
||||||
width={width}
|
|
||||||
/>
|
|
||||||
</VariableBox>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
case "string":
|
|
||||||
return (
|
|
||||||
<VariableBox heading="String" showTypes={showTypes}>
|
|
||||||
<span className="text-slate-400">"</span>
|
|
||||||
<span className="text-slate-600 font-semibold">
|
|
||||||
{expression.value}
|
|
||||||
</span>
|
|
||||||
<span className="text-slate-400">"</span>
|
|
||||||
</VariableBox>
|
|
||||||
);
|
|
||||||
case "boolean":
|
|
||||||
return (
|
|
||||||
<VariableBox heading="Boolean" showTypes={showTypes}>
|
|
||||||
{expression.value.toString()}
|
|
||||||
</VariableBox>
|
|
||||||
);
|
|
||||||
case "symbol":
|
|
||||||
return (
|
|
||||||
<VariableBox heading="Symbol" showTypes={showTypes}>
|
|
||||||
<span className="text-slate-500 mr-2">Undefined Symbol:</span>
|
|
||||||
<span className="text-slate-600">{expression.value}</span>
|
|
||||||
</VariableBox>
|
|
||||||
);
|
|
||||||
case "call":
|
|
||||||
return (
|
|
||||||
<VariableBox heading="Call" showTypes={showTypes}>
|
|
||||||
{expression.value}
|
|
||||||
</VariableBox>
|
|
||||||
);
|
|
||||||
case "array":
|
|
||||||
return (
|
|
||||||
<VariableBox heading="Array" showTypes={showTypes}>
|
|
||||||
{expression.value.map((r, i) => (
|
|
||||||
<div key={i} className="flex pt-1">
|
|
||||||
<div className="flex-none bg-slate-100 rounded-sm px-1">
|
|
||||||
<header className="text-slate-400 font-mono">{i}</header>
|
|
||||||
</div>
|
|
||||||
<div className="px-2 mb-2 grow">
|
|
||||||
<SquiggleItem
|
|
||||||
key={i}
|
|
||||||
expression={r}
|
|
||||||
width={width !== undefined ? width - 20 : width}
|
|
||||||
height={50}
|
|
||||||
distributionPlotSettings={distributionPlotSettings}
|
|
||||||
showTypes={showTypes}
|
|
||||||
chartSettings={chartSettings}
|
|
||||||
environment={environment}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</VariableBox>
|
|
||||||
);
|
|
||||||
case "record":
|
|
||||||
return (
|
|
||||||
<VariableBox heading="Record" showTypes={showTypes}>
|
|
||||||
<div className="space-y-3">
|
|
||||||
{Object.entries(expression.value).map(([key, r]) => (
|
|
||||||
<div key={key} className="flex space-x-2">
|
|
||||||
<div className="flex-none">
|
|
||||||
<header className="text-slate-500 font-mono">{key}:</header>
|
|
||||||
</div>
|
|
||||||
<div className="px-2 grow bg-gray-50 border border-gray-100 rounded-sm">
|
|
||||||
<SquiggleItem
|
|
||||||
expression={r}
|
|
||||||
width={width !== undefined ? width - 20 : width}
|
|
||||||
height={height / 3}
|
|
||||||
showTypes={showTypes}
|
|
||||||
distributionPlotSettings={distributionPlotSettings}
|
|
||||||
chartSettings={chartSettings}
|
|
||||||
environment={environment}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</VariableBox>
|
|
||||||
);
|
|
||||||
case "arraystring":
|
|
||||||
return (
|
|
||||||
<VariableBox heading="Array String" showTypes={showTypes}>
|
|
||||||
{expression.value.map((r) => `"${r}"`).join(", ")}
|
|
||||||
</VariableBox>
|
|
||||||
);
|
|
||||||
case "date":
|
|
||||||
return (
|
|
||||||
<VariableBox heading="Date" showTypes={showTypes}>
|
|
||||||
{expression.value.toDateString()}
|
|
||||||
</VariableBox>
|
|
||||||
);
|
|
||||||
case "timeDuration": {
|
|
||||||
return (
|
|
||||||
<VariableBox heading="Time Duration" showTypes={showTypes}>
|
|
||||||
<NumberShower precision={3} number={expression.value} />
|
|
||||||
</VariableBox>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
case "lambda":
|
|
||||||
return (
|
|
||||||
<VariableBox heading="Function" showTypes={showTypes}>
|
|
||||||
<div className="text-amber-700 bg-amber-100 rounded-md font-mono p-1 pl-2 mb-3 mt-1 text-sm">{`function(${expression.value.parameters.join(
|
|
||||||
","
|
|
||||||
)})`}</div>
|
|
||||||
<FunctionChart
|
|
||||||
fn={expression.value}
|
|
||||||
chartSettings={chartSettings}
|
|
||||||
distributionPlotSettings={distributionPlotSettings}
|
|
||||||
height={height}
|
|
||||||
environment={{
|
|
||||||
sampleCount: environment.sampleCount / 10,
|
|
||||||
xyPointLength: environment.xyPointLength / 10,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</VariableBox>
|
|
||||||
);
|
|
||||||
case "lambdaDeclaration": {
|
|
||||||
return (
|
|
||||||
<VariableBox heading="Function Declaration" showTypes={showTypes}>
|
|
||||||
<FunctionChart
|
|
||||||
fn={expression.value.fn}
|
|
||||||
chartSettings={getChartSettings(expression.value)}
|
|
||||||
distributionPlotSettings={distributionPlotSettings}
|
|
||||||
height={height}
|
|
||||||
environment={{
|
|
||||||
sampleCount: environment.sampleCount / 10,
|
|
||||||
xyPointLength: environment.xyPointLength / 10,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</VariableBox>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
case "module": {
|
|
||||||
return (
|
|
||||||
<VariableBox heading="Module" showTypes={showTypes}>
|
|
||||||
<div className="space-y-3">
|
|
||||||
{Object.entries(expression.value)
|
|
||||||
.filter(([key, r]) => key !== "Math")
|
|
||||||
.map(([key, r]) => (
|
|
||||||
<div key={key} className="flex space-x-2">
|
|
||||||
<div className="flex-none">
|
|
||||||
<header className="text-slate-500 font-mono">{key}:</header>
|
|
||||||
</div>
|
|
||||||
<div className="px-2 grow bg-gray-50 border border-gray-100 rounded-sm">
|
|
||||||
<SquiggleItem
|
|
||||||
expression={r}
|
|
||||||
width={width !== undefined ? width - 20 : width}
|
|
||||||
height={height / 3}
|
|
||||||
showTypes={showTypes}
|
|
||||||
distributionPlotSettings={distributionPlotSettings}
|
|
||||||
chartSettings={chartSettings}
|
|
||||||
environment={environment}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</VariableBox>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<span>No display for type: </span>{" "}
|
|
||||||
<span className="font-semibold text-slate-600">{expression.tag}</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,53 +1,66 @@
|
||||||
import React, { FC, Fragment, useState, useEffect } from "react";
|
import React, {
|
||||||
import ReactDOM from "react-dom";
|
FC,
|
||||||
import { Path, useForm, UseFormRegister, useWatch } from "react-hook-form";
|
useState,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useCallback,
|
||||||
|
} from "react";
|
||||||
|
import { useForm, UseFormRegister, useWatch } from "react-hook-form";
|
||||||
import * as yup from "yup";
|
import * as yup from "yup";
|
||||||
|
import {
|
||||||
|
useMaybeControlledValue,
|
||||||
|
useRunnerState,
|
||||||
|
useSquiggle,
|
||||||
|
} from "../lib/hooks";
|
||||||
import { yupResolver } from "@hookform/resolvers/yup";
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
import { Tab } from "@headlessui/react";
|
|
||||||
import {
|
import {
|
||||||
ChartSquareBarIcon,
|
ChartSquareBarIcon,
|
||||||
|
CheckCircleIcon,
|
||||||
|
ClipboardCopyIcon,
|
||||||
CodeIcon,
|
CodeIcon,
|
||||||
CogIcon,
|
CogIcon,
|
||||||
CurrencyDollarIcon,
|
CurrencyDollarIcon,
|
||||||
EyeIcon,
|
EyeIcon,
|
||||||
|
PauseIcon,
|
||||||
|
PlayIcon,
|
||||||
|
RefreshIcon,
|
||||||
} from "@heroicons/react/solid";
|
} from "@heroicons/react/solid";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
|
||||||
import { defaultBindings, environment } from "@quri/squiggle-lang";
|
import { environment, SqProject } from "@quri/squiggle-lang";
|
||||||
|
|
||||||
import { SquiggleChart } from "./SquiggleChart";
|
import { SquiggleChartProps } from "./SquiggleChart";
|
||||||
import { CodeEditor } from "./CodeEditor";
|
import { CodeEditor } from "./CodeEditor";
|
||||||
import { JsonEditor } from "./JsonEditor";
|
import { JsonEditor } from "./JsonEditor";
|
||||||
import { ErrorAlert, SuccessAlert } from "./Alert";
|
import { ErrorAlert, SuccessAlert } from "./Alert";
|
||||||
import { SquiggleContainer } from "./SquiggleContainer";
|
import { SquiggleContainer } from "./SquiggleContainer";
|
||||||
|
import { Toggle } from "./ui/Toggle";
|
||||||
|
import { StyledTab } from "./ui/StyledTab";
|
||||||
|
import { InputItem } from "./ui/InputItem";
|
||||||
|
import { Text } from "./ui/Text";
|
||||||
|
import { ViewSettings, viewSettingsSchema } from "./ViewSettings";
|
||||||
|
import { HeadedSection } from "./ui/HeadedSection";
|
||||||
|
import { defaultTickFormat } from "../lib/distributionSpecBuilder";
|
||||||
|
import { Button } from "./ui/Button";
|
||||||
|
import { JsImports } from "../lib/jsImports";
|
||||||
|
import { getErrorLocations, getValueToRender } from "../lib/utility";
|
||||||
|
import { SquiggleViewer } from "./SquiggleViewer";
|
||||||
|
|
||||||
interface PlaygroundProps {
|
type PlaygroundProps = SquiggleChartProps & {
|
||||||
/** The initial squiggle string to put in the playground */
|
/** The initial squiggle string to put in the playground */
|
||||||
initialSquiggleString?: string;
|
defaultCode?: string;
|
||||||
/** How many pixels high is the playground */
|
|
||||||
height?: number;
|
|
||||||
/** Whether to show the types of outputs in the playground */
|
|
||||||
showTypes?: boolean;
|
|
||||||
/** Whether to show the log scale controls in the playground */
|
|
||||||
showControls?: boolean;
|
|
||||||
/** Whether to show the summary table in the playground */
|
|
||||||
showSummary?: boolean;
|
|
||||||
/** Whether to log the x coordinate on distribution charts */
|
|
||||||
logX?: boolean;
|
|
||||||
/** Whether to exp the y coordinate on distribution charts */
|
|
||||||
expY?: boolean;
|
|
||||||
/** Display 94% interval; useful for thin lognormals */
|
|
||||||
truncateToNthci?: number;
|
|
||||||
/** If code is set, component becomes controlled */
|
|
||||||
code?: string;
|
|
||||||
onCodeChange?(expr: string): void;
|
onCodeChange?(expr: string): void;
|
||||||
|
/* When settings change */
|
||||||
onSettingsChange?(settings: any): void;
|
onSettingsChange?(settings: any): void;
|
||||||
/** Should we show the editor? */
|
/** Should we show the editor? */
|
||||||
showEditor?: boolean;
|
showEditor?: boolean;
|
||||||
}
|
/** Useful for playground on squiggle website, where we update the anchor link based on current code and settings */
|
||||||
|
showShareButton?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
const schema = yup
|
const schema = yup
|
||||||
.object()
|
.object({})
|
||||||
.shape({
|
.shape({
|
||||||
sampleCount: yup
|
sampleCount: yup
|
||||||
.number()
|
.number()
|
||||||
|
@ -65,342 +78,68 @@ const schema = yup
|
||||||
.default(1000)
|
.default(1000)
|
||||||
.min(10)
|
.min(10)
|
||||||
.max(10000),
|
.max(10000),
|
||||||
chartHeight: yup.number().required().positive().integer().default(350),
|
|
||||||
leftSizePercent: yup
|
|
||||||
.number()
|
|
||||||
.required()
|
|
||||||
.positive()
|
|
||||||
.integer()
|
|
||||||
.min(10)
|
|
||||||
.max(100)
|
|
||||||
.default(50),
|
|
||||||
showTypes: yup.boolean(),
|
|
||||||
showControls: yup.boolean(),
|
|
||||||
showSummary: yup.boolean(),
|
|
||||||
showEditor: yup.boolean(),
|
|
||||||
logX: yup.boolean(),
|
|
||||||
expY: yup.boolean(),
|
|
||||||
truncateToNthci: yup.number().required().min(0).max(100).default(100),
|
|
||||||
showSettingsPage: yup.boolean().default(false),
|
|
||||||
diagramStart: yup
|
|
||||||
.number()
|
|
||||||
.required()
|
|
||||||
.positive()
|
|
||||||
.integer()
|
|
||||||
.default(0)
|
|
||||||
.min(0),
|
|
||||||
diagramStop: yup
|
|
||||||
.number()
|
|
||||||
.required()
|
|
||||||
.positive()
|
|
||||||
.integer()
|
|
||||||
.default(10)
|
|
||||||
.min(0),
|
|
||||||
diagramCount: yup
|
|
||||||
.number()
|
|
||||||
.required()
|
|
||||||
.positive()
|
|
||||||
.integer()
|
|
||||||
.default(20)
|
|
||||||
.min(2),
|
|
||||||
})
|
})
|
||||||
.required();
|
.concat(viewSettingsSchema);
|
||||||
|
|
||||||
type StyledTabProps = {
|
type FormFields = yup.InferType<typeof schema>;
|
||||||
name: string;
|
|
||||||
icon: (props: React.ComponentProps<"svg">) => JSX.Element;
|
|
||||||
};
|
|
||||||
|
|
||||||
const StyledTab: React.FC<StyledTabProps> = ({ name, icon: Icon }) => {
|
const SamplingSettings: React.FC<{ register: UseFormRegister<FormFields> }> = ({
|
||||||
return (
|
register,
|
||||||
<Tab key={name} as={Fragment}>
|
|
||||||
{({ selected }) => (
|
|
||||||
<button className="group flex rounded-md focus:outline-none focus-visible:ring-offset-gray-100">
|
|
||||||
<span
|
|
||||||
className={clsx(
|
|
||||||
"p-1 pl-2.5 pr-3.5 rounded-md flex items-center text-sm font-medium",
|
|
||||||
selected && "bg-white shadow-sm ring-1 ring-black ring-opacity-5"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
className={clsx(
|
|
||||||
"-ml-0.5 mr-2 h-4 w-4",
|
|
||||||
selected
|
|
||||||
? "text-slate-500"
|
|
||||||
: "text-gray-400 group-hover:text-gray-900"
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
className={clsx(
|
|
||||||
selected
|
|
||||||
? "text-gray-900"
|
|
||||||
: "text-gray-600 group-hover:text-gray-900"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{name}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</Tab>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const HeadedSection: FC<{ title: string; children: React.ReactNode }> = ({
|
|
||||||
title,
|
|
||||||
children,
|
|
||||||
}) => (
|
}) => (
|
||||||
<div>
|
<div className="space-y-6 p-3 max-w-xl">
|
||||||
<header className="text-lg leading-6 font-medium text-gray-900">
|
<div>
|
||||||
{title}
|
<InputItem
|
||||||
</header>
|
name="sampleCount"
|
||||||
<div className="mt-4">{children}</div>
|
type="number"
|
||||||
|
label="Sample Count"
|
||||||
|
register={register}
|
||||||
|
/>
|
||||||
|
<div className="mt-2">
|
||||||
|
<Text>
|
||||||
|
How many samples to use for Monte Carlo simulations. This can
|
||||||
|
occasionally be overridden by specific Squiggle programs.
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<InputItem
|
||||||
|
name="xyPointLength"
|
||||||
|
type="number"
|
||||||
|
register={register}
|
||||||
|
label="Coordinate Count (For PointSet Shapes)"
|
||||||
|
/>
|
||||||
|
<div className="mt-2">
|
||||||
|
<Text>
|
||||||
|
When distributions are converted into PointSet shapes, we need to know
|
||||||
|
how many coordinates to use.
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const Text: FC<{ children: React.ReactNode }> = ({ children }) => (
|
const InputVariablesSettings: React.FC<{
|
||||||
<p className="text-sm text-gray-500">{children}</p>
|
initialImports: JsImports;
|
||||||
);
|
setImports: (imports: JsImports) => void;
|
||||||
|
}> = ({ initialImports, setImports }) => {
|
||||||
function InputItem<T>({
|
const [importString, setImportString] = useState(() =>
|
||||||
name,
|
JSON.stringify(initialImports)
|
||||||
label,
|
|
||||||
type,
|
|
||||||
register,
|
|
||||||
}: {
|
|
||||||
name: Path<T>;
|
|
||||||
label: string;
|
|
||||||
type: "number";
|
|
||||||
register: UseFormRegister<T>;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<label className="block">
|
|
||||||
<div className="text-sm font-medium text-gray-600 mb-1">{label}</div>
|
|
||||||
<input
|
|
||||||
type={type}
|
|
||||||
{...register(name)}
|
|
||||||
className="form-input max-w-lg block w-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:max-w-xs sm:text-sm border-gray-300 rounded-md"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
function Checkbox<T>({
|
|
||||||
name,
|
|
||||||
label,
|
|
||||||
register,
|
|
||||||
}: {
|
|
||||||
name: Path<T>;
|
|
||||||
label: string;
|
|
||||||
register: UseFormRegister<T>;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<label className="flex items-center">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
{...register(name)}
|
|
||||||
className="form-checkbox focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
|
|
||||||
/>
|
|
||||||
{/* Clicking on the div makes the checkbox lose focus while mouse button is pressed, leading to annoying blinking; I couldn't figure out how to fix this. */}
|
|
||||||
<div className="ml-3 text-sm font-medium text-gray-700">{label}</div>
|
|
||||||
</label>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SquigglePlayground: FC<PlaygroundProps> = ({
|
|
||||||
initialSquiggleString = "",
|
|
||||||
height = 500,
|
|
||||||
showTypes = false,
|
|
||||||
showControls = false,
|
|
||||||
showSummary = false,
|
|
||||||
logX = false,
|
|
||||||
expY = false,
|
|
||||||
truncateToNthci = 100,
|
|
||||||
code: controlledCode,
|
|
||||||
onCodeChange,
|
|
||||||
onSettingsChange,
|
|
||||||
showEditor = true,
|
|
||||||
}) => {
|
|
||||||
const [uncontrolledCode, setUncontrolledCode] = useState(
|
|
||||||
initialSquiggleString
|
|
||||||
);
|
|
||||||
const [importString, setImportString] = useState("{}");
|
|
||||||
const [imports, setImports] = useState({});
|
|
||||||
const [importsAreValid, setImportsAreValid] = useState(true);
|
const [importsAreValid, setImportsAreValid] = useState(true);
|
||||||
const { register, control } = useForm({
|
|
||||||
resolver: yupResolver(schema),
|
|
||||||
defaultValues: {
|
|
||||||
sampleCount: 1000,
|
|
||||||
xyPointLength: 1000,
|
|
||||||
chartHeight: 150,
|
|
||||||
showTypes,
|
|
||||||
showControls,
|
|
||||||
logX,
|
|
||||||
expY,
|
|
||||||
truncateToNthci,
|
|
||||||
showSummary,
|
|
||||||
showEditor,
|
|
||||||
leftSizePercent: 50,
|
|
||||||
showSettingsPage: false,
|
|
||||||
diagramStart: 0,
|
|
||||||
diagramStop: 10,
|
|
||||||
diagramCount: 20,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const vars = useWatch({
|
|
||||||
control,
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
const onChange = (value: string) => {
|
||||||
onSettingsChange?.(vars);
|
setImportString(value);
|
||||||
}, [vars, onSettingsChange]);
|
let imports = {};
|
||||||
|
|
||||||
const chartSettings = {
|
|
||||||
start: Number(vars.diagramStart),
|
|
||||||
stop: Number(vars.diagramStop),
|
|
||||||
count: Number(vars.diagramCount),
|
|
||||||
};
|
|
||||||
const env: environment = {
|
|
||||||
sampleCount: Number(vars.sampleCount),
|
|
||||||
xyPointLength: Number(vars.xyPointLength),
|
|
||||||
};
|
|
||||||
const getChangeJson = (r: string) => {
|
|
||||||
setImportString(r);
|
|
||||||
try {
|
try {
|
||||||
setImports(JSON.parse(r));
|
imports = JSON.parse(value);
|
||||||
setImportsAreValid(true);
|
setImportsAreValid(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setImportsAreValid(false);
|
setImportsAreValid(false);
|
||||||
}
|
}
|
||||||
|
setImports(imports);
|
||||||
};
|
};
|
||||||
|
|
||||||
const code = controlledCode ?? uncontrolledCode;
|
return (
|
||||||
|
|
||||||
const samplingSettings = (
|
|
||||||
<div className="space-y-6 p-3 max-w-xl">
|
|
||||||
<div>
|
|
||||||
<InputItem
|
|
||||||
name="sampleCount"
|
|
||||||
type="number"
|
|
||||||
label="Sample Count"
|
|
||||||
register={register}
|
|
||||||
/>
|
|
||||||
<div className="mt-2">
|
|
||||||
<Text>
|
|
||||||
How many samples to use for Monte Carlo simulations. This can
|
|
||||||
occasionally be overridden by specific Squiggle programs.
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<InputItem
|
|
||||||
name="xyPointLength"
|
|
||||||
type="number"
|
|
||||||
register={register}
|
|
||||||
label="Coordinate Count (For PointSet Shapes)"
|
|
||||||
/>
|
|
||||||
<div className="mt-2">
|
|
||||||
<Text>
|
|
||||||
When distributions are converted into PointSet shapes, we need to
|
|
||||||
know how many coordinates to use.
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const viewSettings = (
|
|
||||||
<div className="space-y-6 p-3 divide-y divide-gray-200 max-w-xl">
|
|
||||||
<HeadedSection title="General Display Settings">
|
|
||||||
<div className="space-y-4">
|
|
||||||
<Checkbox
|
|
||||||
name="showEditor"
|
|
||||||
register={register}
|
|
||||||
label="Show code editor on left"
|
|
||||||
/>
|
|
||||||
<InputItem
|
|
||||||
name="chartHeight"
|
|
||||||
type="number"
|
|
||||||
register={register}
|
|
||||||
label="Chart Height (in pixels)"
|
|
||||||
/>
|
|
||||||
<Checkbox
|
|
||||||
name="showTypes"
|
|
||||||
register={register}
|
|
||||||
label="Show information about displayed types"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</HeadedSection>
|
|
||||||
|
|
||||||
<div className="pt-8">
|
|
||||||
<HeadedSection title="Distribution Display Settings">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Checkbox
|
|
||||||
register={register}
|
|
||||||
name="logX"
|
|
||||||
label="Show x scale logarithmically"
|
|
||||||
/>
|
|
||||||
<Checkbox
|
|
||||||
register={register}
|
|
||||||
name="expY"
|
|
||||||
label="Show y scale exponentially"
|
|
||||||
/>
|
|
||||||
<InputItem
|
|
||||||
name="truncateToNthci"
|
|
||||||
type="number"
|
|
||||||
register={register}
|
|
||||||
label="Show nth percentile confidence interval (useful for thin lognormals)"
|
|
||||||
/>
|
|
||||||
<Checkbox
|
|
||||||
register={register}
|
|
||||||
name="showControls"
|
|
||||||
label="Show toggles to adjust scale of x and y axes"
|
|
||||||
/>
|
|
||||||
<Checkbox
|
|
||||||
register={register}
|
|
||||||
name="showSummary"
|
|
||||||
label="Show summary statistics"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</HeadedSection>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="pt-8">
|
|
||||||
<HeadedSection title="Function Display Settings">
|
|
||||||
<div className="space-y-6">
|
|
||||||
<Text>
|
|
||||||
When displaying functions of single variables that return numbers
|
|
||||||
or distributions, we need to use defaults for the x-axis. We need
|
|
||||||
to select a minimum and maximum value of x to sample, and a number
|
|
||||||
n of the number of points to sample.
|
|
||||||
</Text>
|
|
||||||
<div className="space-y-4">
|
|
||||||
<InputItem
|
|
||||||
type="number"
|
|
||||||
name="diagramStart"
|
|
||||||
register={register}
|
|
||||||
label="Min X Value"
|
|
||||||
/>
|
|
||||||
<InputItem
|
|
||||||
type="number"
|
|
||||||
name="diagramStop"
|
|
||||||
register={register}
|
|
||||||
label="Max X Value"
|
|
||||||
/>
|
|
||||||
<InputItem
|
|
||||||
type="number"
|
|
||||||
name="diagramCount"
|
|
||||||
register={register}
|
|
||||||
label="Points between X min and X max to sample"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</HeadedSection>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const inputVariableSettings = (
|
|
||||||
<div className="p-3 max-w-3xl">
|
<div className="p-3 max-w-3xl">
|
||||||
<HeadedSection title="Import Variables from JSON">
|
<HeadedSection title="Import Variables from JSON">
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
|
@ -412,7 +151,7 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
|
||||||
<div className="border border-slate-200 mt-6 mb-2">
|
<div className="border border-slate-200 mt-6 mb-2">
|
||||||
<JsonEditor
|
<JsonEditor
|
||||||
value={importString}
|
value={importString}
|
||||||
onChange={getChangeJson}
|
onChange={onChange}
|
||||||
oneLine={false}
|
oneLine={false}
|
||||||
showGutter={true}
|
showGutter={true}
|
||||||
height={150}
|
height={150}
|
||||||
|
@ -431,35 +170,193 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
|
||||||
</HeadedSection>
|
</HeadedSection>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const squiggleChart = (
|
const RunControls: React.FC<{
|
||||||
<SquiggleChart
|
autorunMode: boolean;
|
||||||
squiggleString={code}
|
isRunning: boolean;
|
||||||
environment={env}
|
isStale: boolean;
|
||||||
chartSettings={chartSettings}
|
onAutorunModeChange: (value: boolean) => void;
|
||||||
height={vars.chartHeight}
|
run: () => void;
|
||||||
showTypes={vars.showTypes}
|
}> = ({ autorunMode, isRunning, isStale, onAutorunModeChange, run }) => {
|
||||||
showControls={vars.showControls}
|
const CurrentPlayIcon = isRunning ? RefreshIcon : PlayIcon;
|
||||||
showSummary={vars.showSummary}
|
|
||||||
logX={vars.logX}
|
return (
|
||||||
expY={vars.expY}
|
<div className="flex space-x-1 items-center" data-testid="autorun-controls">
|
||||||
truncateToNthci={vars.truncateToNthci}
|
{autorunMode ? null : (
|
||||||
bindings={defaultBindings}
|
<button onClick={run}>
|
||||||
jsImports={imports}
|
<CurrentPlayIcon
|
||||||
/>
|
className={clsx(
|
||||||
|
"w-8 h-8",
|
||||||
|
isRunning && "animate-spin",
|
||||||
|
isStale ? "text-indigo-500" : "text-gray-400"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<Toggle
|
||||||
|
texts={["Autorun", "Paused"]}
|
||||||
|
icons={[CheckCircleIcon, PauseIcon]}
|
||||||
|
status={autorunMode}
|
||||||
|
onChange={onAutorunModeChange}
|
||||||
|
spinIcon={autorunMode && isRunning}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ShareButton: React.FC = () => {
|
||||||
|
const [isCopied, setIsCopied] = useState(false);
|
||||||
|
const copy = () => {
|
||||||
|
navigator.clipboard.writeText((window.top || window).location.href);
|
||||||
|
setIsCopied(true);
|
||||||
|
setTimeout(() => setIsCopied(false), 1000);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className="w-36">
|
||||||
|
<Button onClick={copy} wide>
|
||||||
|
{isCopied ? (
|
||||||
|
"Copied to clipboard!"
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center space-x-1">
|
||||||
|
<ClipboardCopyIcon className="w-4 h-4" />
|
||||||
|
<span>Copy share link</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type PlaygroundContextShape = {
|
||||||
|
getLeftPanelElement: () => HTMLDivElement | undefined;
|
||||||
|
};
|
||||||
|
export const PlaygroundContext = React.createContext<PlaygroundContextShape>({
|
||||||
|
getLeftPanelElement: () => undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const SquigglePlayground: FC<PlaygroundProps> = ({
|
||||||
|
defaultCode = "",
|
||||||
|
height = 500,
|
||||||
|
showSummary = true,
|
||||||
|
logX = false,
|
||||||
|
expY = false,
|
||||||
|
title,
|
||||||
|
minX,
|
||||||
|
maxX,
|
||||||
|
tickFormat = defaultTickFormat,
|
||||||
|
distributionChartActions,
|
||||||
|
code: controlledCode,
|
||||||
|
onCodeChange,
|
||||||
|
onSettingsChange,
|
||||||
|
showEditor = true,
|
||||||
|
showShareButton = false,
|
||||||
|
continues,
|
||||||
|
project,
|
||||||
|
}) => {
|
||||||
|
const [code, setCode] = useMaybeControlledValue({
|
||||||
|
value: controlledCode,
|
||||||
|
defaultValue: defaultCode,
|
||||||
|
onChange: onCodeChange,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [imports, setImports] = useState<JsImports>({});
|
||||||
|
|
||||||
|
const { register, control } = useForm({
|
||||||
|
resolver: yupResolver(schema),
|
||||||
|
defaultValues: {
|
||||||
|
sampleCount: 1000,
|
||||||
|
xyPointLength: 1000,
|
||||||
|
chartHeight: 150,
|
||||||
|
logX,
|
||||||
|
expY,
|
||||||
|
title,
|
||||||
|
minX,
|
||||||
|
maxX,
|
||||||
|
tickFormat,
|
||||||
|
distributionChartActions,
|
||||||
|
showSummary,
|
||||||
|
showEditor,
|
||||||
|
diagramStart: 0,
|
||||||
|
diagramStop: 10,
|
||||||
|
diagramCount: 20,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const vars = useWatch({
|
||||||
|
control,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
onSettingsChange?.(vars);
|
||||||
|
}, [vars, onSettingsChange]);
|
||||||
|
|
||||||
|
const environment: environment = useMemo(
|
||||||
|
() => ({
|
||||||
|
sampleCount: Number(vars.sampleCount),
|
||||||
|
xyPointLength: Number(vars.xyPointLength),
|
||||||
|
}),
|
||||||
|
[vars.sampleCount, vars.xyPointLength]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
run,
|
||||||
|
autorunMode,
|
||||||
|
setAutorunMode,
|
||||||
|
isRunning,
|
||||||
|
renderedCode,
|
||||||
|
executionId,
|
||||||
|
} = useRunnerState(code);
|
||||||
|
|
||||||
|
const resultAndBindings = useSquiggle({
|
||||||
|
environment,
|
||||||
|
continues,
|
||||||
|
code: renderedCode,
|
||||||
|
project,
|
||||||
|
jsImports: imports,
|
||||||
|
executionId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const valueToRender = getValueToRender(resultAndBindings);
|
||||||
|
|
||||||
|
const squiggleChart =
|
||||||
|
renderedCode === "" ? null : (
|
||||||
|
<div className="relative">
|
||||||
|
{isRunning ? (
|
||||||
|
<div className="absolute inset-0 bg-white opacity-0 animate-semi-appear" />
|
||||||
|
) : null}
|
||||||
|
<SquiggleViewer
|
||||||
|
result={valueToRender}
|
||||||
|
environment={environment}
|
||||||
|
height={vars.chartHeight || 150}
|
||||||
|
distributionPlotSettings={{
|
||||||
|
showSummary: vars.showSummary ?? false,
|
||||||
|
logX: vars.logX ?? false,
|
||||||
|
expY: vars.expY ?? false,
|
||||||
|
format: vars.tickFormat,
|
||||||
|
minX: vars.minX,
|
||||||
|
maxX: vars.maxX,
|
||||||
|
title: vars.title,
|
||||||
|
actions: vars.distributionChartActions,
|
||||||
|
}}
|
||||||
|
chartSettings={{
|
||||||
|
start: vars.diagramStart ?? 0,
|
||||||
|
stop: vars.diagramStop ?? 10,
|
||||||
|
count: vars.diagramCount ?? 20,
|
||||||
|
}}
|
||||||
|
enableLocalSettings={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const errorLocations = getErrorLocations(resultAndBindings.result);
|
||||||
|
|
||||||
const firstTab = vars.showEditor ? (
|
const firstTab = vars.showEditor ? (
|
||||||
<div className="border border-slate-200">
|
<div className="border border-slate-200" data-testid="squiggle-editor">
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
|
errorLocations={errorLocations}
|
||||||
value={code}
|
value={code}
|
||||||
onChange={(newCode) => {
|
onChange={setCode}
|
||||||
if (controlledCode === undefined) {
|
onSubmit={run}
|
||||||
// uncontrolled mode
|
|
||||||
setUncontrolledCode(newCode);
|
|
||||||
}
|
|
||||||
onCodeChange?.(newCode);
|
|
||||||
}}
|
|
||||||
oneLine={false}
|
oneLine={false}
|
||||||
showGutter={true}
|
showGutter={true}
|
||||||
height={height - 1}
|
height={height - 1}
|
||||||
|
@ -470,45 +367,84 @@ export const SquigglePlayground: FC<PlaygroundProps> = ({
|
||||||
);
|
);
|
||||||
|
|
||||||
const tabs = (
|
const tabs = (
|
||||||
<Tab.Panels>
|
<StyledTab.Panels>
|
||||||
<Tab.Panel>{firstTab}</Tab.Panel>
|
<StyledTab.Panel>{firstTab}</StyledTab.Panel>
|
||||||
<Tab.Panel>{samplingSettings}</Tab.Panel>
|
<StyledTab.Panel>
|
||||||
<Tab.Panel>{viewSettings}</Tab.Panel>
|
<SamplingSettings register={register} />
|
||||||
<Tab.Panel>{inputVariableSettings}</Tab.Panel>
|
</StyledTab.Panel>
|
||||||
</Tab.Panels>
|
<StyledTab.Panel>
|
||||||
|
<ViewSettings
|
||||||
|
register={
|
||||||
|
// This is dangerous, but doesn't cause any problems.
|
||||||
|
// I tried to make `ViewSettings` generic (to allow it to accept any extension of a settings schema), but it didn't work.
|
||||||
|
register as unknown as UseFormRegister<
|
||||||
|
yup.InferType<typeof viewSettingsSchema>
|
||||||
|
>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</StyledTab.Panel>
|
||||||
|
<StyledTab.Panel>
|
||||||
|
<InputVariablesSettings
|
||||||
|
initialImports={imports}
|
||||||
|
setImports={setImports}
|
||||||
|
/>
|
||||||
|
</StyledTab.Panel>
|
||||||
|
</StyledTab.Panels>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const leftPanelRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
const withEditor = (
|
const withEditor = (
|
||||||
<div className="flex mt-1">
|
<div className="flex mt-2">
|
||||||
<div className="w-1/2">{tabs}</div>
|
<div
|
||||||
<div className="w-1/2 p-2 pl-4">{squiggleChart}</div>
|
className="w-1/2 relative"
|
||||||
|
style={{ minHeight: height }}
|
||||||
|
ref={leftPanelRef}
|
||||||
|
>
|
||||||
|
{tabs}
|
||||||
|
</div>
|
||||||
|
<div className="w-1/2 p-2 pl-4" data-testid="playground-result">
|
||||||
|
{squiggleChart}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const withoutEditor = <div className="mt-3">{tabs}</div>;
|
const withoutEditor = <div className="mt-3">{tabs}</div>;
|
||||||
|
|
||||||
|
const getLeftPanelElement = useCallback(() => {
|
||||||
|
return leftPanelRef.current ?? undefined;
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SquiggleContainer>
|
<SquiggleContainer>
|
||||||
<Tab.Group>
|
<PlaygroundContext.Provider value={{ getLeftPanelElement }}>
|
||||||
<div className="pb-4">
|
<StyledTab.Group>
|
||||||
<Tab.List className="flex w-fit p-0.5 mt-2 rounded-md bg-slate-100 hover:bg-slate-200">
|
<div className="pb-4">
|
||||||
<StyledTab
|
<div className="flex justify-between items-center">
|
||||||
name={vars.showEditor ? "Code" : "Display"}
|
<StyledTab.List>
|
||||||
icon={vars.showEditor ? CodeIcon : EyeIcon}
|
<StyledTab
|
||||||
/>
|
name={vars.showEditor ? "Code" : "Display"}
|
||||||
<StyledTab name="Sampling Settings" icon={CogIcon} />
|
icon={vars.showEditor ? CodeIcon : EyeIcon}
|
||||||
<StyledTab name="View Settings" icon={ChartSquareBarIcon} />
|
/>
|
||||||
<StyledTab name="Input Variables" icon={CurrencyDollarIcon} />
|
<StyledTab name="Sampling Settings" icon={CogIcon} />
|
||||||
</Tab.List>
|
<StyledTab name="View Settings" icon={ChartSquareBarIcon} />
|
||||||
{vars.showEditor ? withEditor : withoutEditor}
|
<StyledTab name="Input Variables" icon={CurrencyDollarIcon} />
|
||||||
</div>
|
</StyledTab.List>
|
||||||
</Tab.Group>
|
<div className="flex space-x-2 items-center">
|
||||||
|
<RunControls
|
||||||
|
autorunMode={autorunMode}
|
||||||
|
isStale={renderedCode !== code}
|
||||||
|
run={run}
|
||||||
|
isRunning={isRunning}
|
||||||
|
onAutorunModeChange={setAutorunMode}
|
||||||
|
/>
|
||||||
|
{showShareButton && <ShareButton />}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{vars.showEditor ? withEditor : withoutEditor}
|
||||||
|
</div>
|
||||||
|
</StyledTab.Group>
|
||||||
|
</PlaygroundContext.Provider>
|
||||||
</SquiggleContainer>
|
</SquiggleContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export function renderSquigglePlaygroundToDom(props: PlaygroundProps) {
|
|
||||||
const parent = document.createElement("div");
|
|
||||||
ReactDOM.render(<SquigglePlayground {...props} />, parent);
|
|
||||||
return parent;
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,310 @@
|
||||||
|
import React, { useContext } from "react";
|
||||||
|
import { SqDistributionTag, SqValue, SqValueTag } from "@quri/squiggle-lang";
|
||||||
|
import { NumberShower } from "../NumberShower";
|
||||||
|
import { DistributionChart, defaultPlot, makePlot } from "../DistributionChart";
|
||||||
|
import { FunctionChart } from "../FunctionChart";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import { VariableBox } from "./VariableBox";
|
||||||
|
import { ItemSettingsMenu } from "./ItemSettingsMenu";
|
||||||
|
import { hasMassBelowZero } from "../../lib/distributionUtils";
|
||||||
|
import { MergedItemSettings } from "./utils";
|
||||||
|
import { ViewerContext } from "./ViewerContext";
|
||||||
|
|
||||||
|
/*
|
||||||
|
// DISABLED FOR 0.4 branch, for now
|
||||||
|
function getRange<a>(x: declaration<a>) {
|
||||||
|
const first = x.args[0];
|
||||||
|
switch (first.tag) {
|
||||||
|
case "Float": {
|
||||||
|
return { floats: { min: first.value.min, max: first.value.max } };
|
||||||
|
}
|
||||||
|
case "Date": {
|
||||||
|
return { time: { min: first.value.min, max: first.value.max } };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getChartSettings<a>(x: declaration<a>): FunctionChartSettings {
|
||||||
|
const range = getRange(x);
|
||||||
|
const min = range.floats ? range.floats.min : 0;
|
||||||
|
const max = range.floats ? range.floats.max : 10;
|
||||||
|
return {
|
||||||
|
start: min,
|
||||||
|
stop: max,
|
||||||
|
count: 20,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
const VariableList: React.FC<{
|
||||||
|
value: SqValue;
|
||||||
|
heading: string;
|
||||||
|
children: (settings: MergedItemSettings) => React.ReactNode;
|
||||||
|
}> = ({ value, heading, children }) => (
|
||||||
|
<VariableBox value={value} heading={heading}>
|
||||||
|
{(settings) => (
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
"space-y-3",
|
||||||
|
value.location.path.items.length ? "pt-1 mt-1" : null
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children(settings)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</VariableBox>
|
||||||
|
);
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
/** The output of squiggle's run */
|
||||||
|
value: SqValue;
|
||||||
|
width?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ExpressionViewer: React.FC<Props> = ({ value, width }) => {
|
||||||
|
const { getMergedSettings } = useContext(ViewerContext);
|
||||||
|
|
||||||
|
switch (value.tag) {
|
||||||
|
case SqValueTag.Number:
|
||||||
|
return (
|
||||||
|
<VariableBox value={value} heading="Number">
|
||||||
|
{() => (
|
||||||
|
<div className="font-semibold text-slate-600">
|
||||||
|
<NumberShower precision={3} number={value.value} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</VariableBox>
|
||||||
|
);
|
||||||
|
case SqValueTag.Distribution: {
|
||||||
|
const distType = value.value.tag;
|
||||||
|
return (
|
||||||
|
<VariableBox
|
||||||
|
value={value}
|
||||||
|
heading={`Distribution (${distType})\n${
|
||||||
|
distType === SqDistributionTag.Symbolic
|
||||||
|
? value.value.toString()
|
||||||
|
: ""
|
||||||
|
}`}
|
||||||
|
renderSettingsMenu={({ onChange }) => {
|
||||||
|
const shape = value.value.pointSet(
|
||||||
|
getMergedSettings(value.location).environment
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<ItemSettingsMenu
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
disableLogX={
|
||||||
|
shape.tag === "Ok" && hasMassBelowZero(shape.value.asShape())
|
||||||
|
}
|
||||||
|
withFunctionSettings={false}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(settings) => {
|
||||||
|
return (
|
||||||
|
<DistributionChart
|
||||||
|
plot={defaultPlot(value.value)}
|
||||||
|
environment={settings.environment}
|
||||||
|
{...settings.distributionPlotSettings}
|
||||||
|
height={settings.height}
|
||||||
|
width={width}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</VariableBox>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case SqValueTag.String:
|
||||||
|
return (
|
||||||
|
<VariableBox value={value} heading="String">
|
||||||
|
{() => (
|
||||||
|
<>
|
||||||
|
<span className="text-slate-400">"</span>
|
||||||
|
<span className="text-slate-600 font-semibold font-mono">
|
||||||
|
{value.value}
|
||||||
|
</span>
|
||||||
|
<span className="text-slate-400">"</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</VariableBox>
|
||||||
|
);
|
||||||
|
case SqValueTag.Bool:
|
||||||
|
return (
|
||||||
|
<VariableBox value={value} heading="Boolean">
|
||||||
|
{() => value.value.toString()}
|
||||||
|
</VariableBox>
|
||||||
|
);
|
||||||
|
case SqValueTag.Date:
|
||||||
|
return (
|
||||||
|
<VariableBox value={value} heading="Date">
|
||||||
|
{() => value.value.toDateString()}
|
||||||
|
</VariableBox>
|
||||||
|
);
|
||||||
|
case SqValueTag.Void:
|
||||||
|
return (
|
||||||
|
<VariableBox value={value} heading="Void">
|
||||||
|
{() => "Void"}
|
||||||
|
</VariableBox>
|
||||||
|
);
|
||||||
|
case SqValueTag.TimeDuration: {
|
||||||
|
return (
|
||||||
|
<VariableBox value={value} heading="Time Duration">
|
||||||
|
{() => <NumberShower precision={3} number={value.value} />}
|
||||||
|
</VariableBox>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case SqValueTag.Lambda:
|
||||||
|
return (
|
||||||
|
<VariableBox
|
||||||
|
value={value}
|
||||||
|
heading="Function"
|
||||||
|
renderSettingsMenu={({ onChange }) => {
|
||||||
|
return (
|
||||||
|
<ItemSettingsMenu
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
withFunctionSettings={true}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(settings) => (
|
||||||
|
<>
|
||||||
|
<div className="text-amber-700 bg-amber-100 rounded-md font-mono p-1 pl-2 mb-3 mt-1 text-sm">{`function(${value.value
|
||||||
|
.parameters()
|
||||||
|
.join(",")})`}</div>
|
||||||
|
<FunctionChart
|
||||||
|
fn={value.value}
|
||||||
|
chartSettings={settings.chartSettings}
|
||||||
|
distributionPlotSettings={settings.distributionPlotSettings}
|
||||||
|
height={settings.height}
|
||||||
|
environment={{
|
||||||
|
sampleCount: settings.environment.sampleCount / 10,
|
||||||
|
xyPointLength: settings.environment.xyPointLength / 10,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</VariableBox>
|
||||||
|
);
|
||||||
|
case SqValueTag.Declaration: {
|
||||||
|
return (
|
||||||
|
<VariableBox
|
||||||
|
value={value}
|
||||||
|
heading="Function Declaration"
|
||||||
|
renderSettingsMenu={({ onChange }) => {
|
||||||
|
return (
|
||||||
|
<ItemSettingsMenu
|
||||||
|
onChange={onChange}
|
||||||
|
value={value}
|
||||||
|
withFunctionSettings={true}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(settings) => (
|
||||||
|
<div>NOT IMPLEMENTED IN 0.4 YET</div>
|
||||||
|
// <FunctionChart
|
||||||
|
// fn={expression.value.fn}
|
||||||
|
// chartSettings={getChartSettings(expression.value)}
|
||||||
|
// distributionPlotSettings={settings.distributionPlotSettings}
|
||||||
|
// height={settings.height}
|
||||||
|
// environment={{
|
||||||
|
// sampleCount: settings.environment.sampleCount / 10,
|
||||||
|
// xyPointLength: settings.environment.xyPointLength / 10,
|
||||||
|
// }}
|
||||||
|
// />
|
||||||
|
)}
|
||||||
|
</VariableBox>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case SqValueTag.Record:
|
||||||
|
const plot = makePlot(value.value);
|
||||||
|
if (plot) {
|
||||||
|
return (
|
||||||
|
<VariableBox
|
||||||
|
value={value}
|
||||||
|
heading="Plot"
|
||||||
|
renderSettingsMenu={({ onChange }) => {
|
||||||
|
let disableLogX = plot.distributions.some((x) => {
|
||||||
|
let pointSet = x.distribution.pointSet(
|
||||||
|
getMergedSettings(value.location).environment
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
pointSet.tag === "Ok" &&
|
||||||
|
hasMassBelowZero(pointSet.value.asShape())
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<ItemSettingsMenu
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
disableLogX={disableLogX}
|
||||||
|
withFunctionSettings={false}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(settings) => {
|
||||||
|
return (
|
||||||
|
<DistributionChart
|
||||||
|
plot={plot}
|
||||||
|
environment={settings.environment}
|
||||||
|
{...settings.distributionPlotSettings}
|
||||||
|
height={settings.height}
|
||||||
|
width={width}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</VariableBox>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<VariableList value={value} heading="Record">
|
||||||
|
{(_) =>
|
||||||
|
value.value
|
||||||
|
.entries()
|
||||||
|
.map(([key, r]) => (
|
||||||
|
<ExpressionViewer
|
||||||
|
key={key}
|
||||||
|
value={r}
|
||||||
|
width={width !== undefined ? width - 20 : width}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</VariableList>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case SqValueTag.Array:
|
||||||
|
return (
|
||||||
|
<VariableList value={value} heading="Array">
|
||||||
|
{(_) =>
|
||||||
|
value.value
|
||||||
|
.getValues()
|
||||||
|
.map((r, i) => (
|
||||||
|
<ExpressionViewer
|
||||||
|
key={i}
|
||||||
|
value={r}
|
||||||
|
width={width !== undefined ? width - 20 : width}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</VariableList>
|
||||||
|
);
|
||||||
|
default: {
|
||||||
|
return (
|
||||||
|
<VariableList value={value} heading="Error">
|
||||||
|
{() => (
|
||||||
|
<div>
|
||||||
|
<span>No display for type: </span>{" "}
|
||||||
|
<span className="font-semibold text-slate-600">
|
||||||
|
{(value as { tag: string }).tag}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</VariableList>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,164 @@
|
||||||
|
import { CogIcon } from "@heroicons/react/solid";
|
||||||
|
import React, { useContext, useRef, useState, useEffect } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
|
import { Modal } from "../ui/Modal";
|
||||||
|
import { ViewSettings, viewSettingsSchema } from "../ViewSettings";
|
||||||
|
import { ViewerContext } from "./ViewerContext";
|
||||||
|
import { defaultTickFormat } from "../../lib/distributionSpecBuilder";
|
||||||
|
import { PlaygroundContext } from "../SquigglePlayground";
|
||||||
|
import { SqValue } from "@quri/squiggle-lang";
|
||||||
|
import { locationAsString } from "./utils";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
value: SqValue;
|
||||||
|
onChange: () => void;
|
||||||
|
disableLogX?: boolean;
|
||||||
|
withFunctionSettings: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ItemSettingsModal: React.FC<
|
||||||
|
Props & { close: () => void; resetScroll: () => void }
|
||||||
|
> = ({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
disableLogX,
|
||||||
|
withFunctionSettings,
|
||||||
|
close,
|
||||||
|
resetScroll,
|
||||||
|
}) => {
|
||||||
|
const { setSettings, getSettings, getMergedSettings } =
|
||||||
|
useContext(ViewerContext);
|
||||||
|
|
||||||
|
const mergedSettings = getMergedSettings(value.location);
|
||||||
|
|
||||||
|
const { register, watch } = useForm({
|
||||||
|
resolver: yupResolver(viewSettingsSchema),
|
||||||
|
defaultValues: {
|
||||||
|
// this is a mess and should be fixed
|
||||||
|
showEditor: true, // doesn't matter
|
||||||
|
chartHeight: mergedSettings.height,
|
||||||
|
showSummary: mergedSettings.distributionPlotSettings.showSummary,
|
||||||
|
logX: mergedSettings.distributionPlotSettings.logX,
|
||||||
|
expY: mergedSettings.distributionPlotSettings.expY,
|
||||||
|
tickFormat:
|
||||||
|
mergedSettings.distributionPlotSettings.format || defaultTickFormat,
|
||||||
|
title: mergedSettings.distributionPlotSettings.title,
|
||||||
|
minX: mergedSettings.distributionPlotSettings.minX,
|
||||||
|
maxX: mergedSettings.distributionPlotSettings.maxX,
|
||||||
|
distributionChartActions: mergedSettings.distributionPlotSettings.actions,
|
||||||
|
diagramStart: mergedSettings.chartSettings.start,
|
||||||
|
diagramStop: mergedSettings.chartSettings.stop,
|
||||||
|
diagramCount: mergedSettings.chartSettings.count,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
useEffect(() => {
|
||||||
|
const subscription = watch((vars) => {
|
||||||
|
const settings = getSettings(value.location); // get the latest version
|
||||||
|
setSettings(value.location, {
|
||||||
|
...settings,
|
||||||
|
distributionPlotSettings: {
|
||||||
|
showSummary: vars.showSummary,
|
||||||
|
logX: vars.logX,
|
||||||
|
expY: vars.expY,
|
||||||
|
format: vars.tickFormat,
|
||||||
|
title: vars.title,
|
||||||
|
minX: vars.minX,
|
||||||
|
maxX: vars.maxX,
|
||||||
|
actions: vars.distributionChartActions,
|
||||||
|
},
|
||||||
|
chartSettings: {
|
||||||
|
start: vars.diagramStart,
|
||||||
|
stop: vars.diagramStop,
|
||||||
|
count: vars.diagramCount,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
onChange();
|
||||||
|
});
|
||||||
|
return () => subscription.unsubscribe();
|
||||||
|
}, [getSettings, setSettings, onChange, value.location, watch]);
|
||||||
|
|
||||||
|
const { getLeftPanelElement } = useContext(PlaygroundContext);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal container={getLeftPanelElement()} close={close}>
|
||||||
|
<Modal.Header>
|
||||||
|
Chart settings
|
||||||
|
{value.location.path.items.length ? (
|
||||||
|
<>
|
||||||
|
{" for "}
|
||||||
|
<span
|
||||||
|
title="Scroll to item"
|
||||||
|
className="cursor-pointer"
|
||||||
|
onClick={resetScroll}
|
||||||
|
>
|
||||||
|
{locationAsString(value.location)}
|
||||||
|
</span>{" "}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Body>
|
||||||
|
<ViewSettings
|
||||||
|
register={register}
|
||||||
|
withShowEditorSetting={false}
|
||||||
|
withFunctionSettings={withFunctionSettings}
|
||||||
|
disableLogXSetting={disableLogX}
|
||||||
|
/>
|
||||||
|
</Modal.Body>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ItemSettingsMenu: React.FC<Props> = (props) => {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const { enableLocalSettings, setSettings, getSettings } =
|
||||||
|
useContext(ViewerContext);
|
||||||
|
|
||||||
|
const ref = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
|
if (!enableLocalSettings) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const settings = getSettings(props.value.location);
|
||||||
|
|
||||||
|
const resetScroll = () => {
|
||||||
|
if (!ref.current) return;
|
||||||
|
window.scroll({
|
||||||
|
top: ref.current.getBoundingClientRect().y + window.scrollY,
|
||||||
|
behavior: "smooth",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex gap-2" ref={ref}>
|
||||||
|
<CogIcon
|
||||||
|
className="h-5 w-5 cursor-pointer text-slate-400 hover:text-slate-500"
|
||||||
|
onClick={() => setIsOpen(!isOpen)}
|
||||||
|
/>
|
||||||
|
{settings.distributionPlotSettings || settings.chartSettings ? (
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setSettings(props.value.location, {
|
||||||
|
...settings,
|
||||||
|
distributionPlotSettings: undefined,
|
||||||
|
chartSettings: undefined,
|
||||||
|
});
|
||||||
|
props.onChange();
|
||||||
|
}}
|
||||||
|
className="text-xs px-1 py-0.5 rounded bg-slate-300"
|
||||||
|
>
|
||||||
|
Reset settings
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
|
{isOpen ? (
|
||||||
|
<ItemSettingsModal
|
||||||
|
{...props}
|
||||||
|
close={() => setIsOpen(false)}
|
||||||
|
resetScroll={resetScroll}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,82 @@
|
||||||
|
import { SqValue } from "@quri/squiggle-lang";
|
||||||
|
import React, { useContext, useReducer } from "react";
|
||||||
|
import { Tooltip } from "../ui/Tooltip";
|
||||||
|
import { LocalItemSettings, MergedItemSettings } from "./utils";
|
||||||
|
import { ViewerContext } from "./ViewerContext";
|
||||||
|
|
||||||
|
type SettingsMenuParams = {
|
||||||
|
onChange: () => void; // used to notify VariableBox that settings have changed, so that VariableBox could re-render itself
|
||||||
|
};
|
||||||
|
|
||||||
|
type VariableBoxProps = {
|
||||||
|
value: SqValue;
|
||||||
|
heading: string;
|
||||||
|
renderSettingsMenu?: (params: SettingsMenuParams) => React.ReactNode;
|
||||||
|
children: (settings: MergedItemSettings) => React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const VariableBox: React.FC<VariableBoxProps> = ({
|
||||||
|
value: { location },
|
||||||
|
heading = "Error",
|
||||||
|
renderSettingsMenu,
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
const { setSettings, getSettings, getMergedSettings } =
|
||||||
|
useContext(ViewerContext);
|
||||||
|
|
||||||
|
// Since ViewerContext doesn't keep the actual settings, VariableBox won't rerender when setSettings is called.
|
||||||
|
// So we use `forceUpdate` to force rerendering.
|
||||||
|
const [_, forceUpdate] = useReducer((x) => x + 1, 0);
|
||||||
|
|
||||||
|
const settings = getSettings(location);
|
||||||
|
|
||||||
|
const setSettingsAndUpdate = (newSettings: LocalItemSettings) => {
|
||||||
|
setSettings(location, newSettings);
|
||||||
|
forceUpdate();
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleCollapsed = () => {
|
||||||
|
setSettingsAndUpdate({ ...settings, collapsed: !settings.collapsed });
|
||||||
|
};
|
||||||
|
|
||||||
|
const isTopLevel = location.path.items.length === 0;
|
||||||
|
const name = isTopLevel
|
||||||
|
? { result: "Result", bindings: "Bindings" }[location.path.root]
|
||||||
|
: location.path.items[location.path.items.length - 1];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div role={isTopLevel ? "status" : undefined}>
|
||||||
|
<header className="inline-flex space-x-1">
|
||||||
|
<Tooltip text={heading}>
|
||||||
|
<span
|
||||||
|
className="text-slate-500 font-mono text-sm cursor-pointer"
|
||||||
|
onClick={toggleCollapsed}
|
||||||
|
>
|
||||||
|
{name}:
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
{settings.collapsed ? (
|
||||||
|
<span
|
||||||
|
className="rounded p-0.5 bg-slate-200 text-slate-500 font-mono text-xs cursor-pointer"
|
||||||
|
onClick={toggleCollapsed}
|
||||||
|
>
|
||||||
|
...
|
||||||
|
</span>
|
||||||
|
) : renderSettingsMenu ? (
|
||||||
|
renderSettingsMenu({ onChange: forceUpdate })
|
||||||
|
) : null}
|
||||||
|
</header>
|
||||||
|
{settings.collapsed ? null : (
|
||||||
|
<div className="flex w-full">
|
||||||
|
{location.path.items.length ? (
|
||||||
|
<div
|
||||||
|
className="shrink-0 border-l-2 border-slate-200 hover:border-indigo-600 w-4 cursor-pointer"
|
||||||
|
onClick={toggleCollapsed}
|
||||||
|
></div>
|
||||||
|
) : null}
|
||||||
|
<div className="grow">{children(getMergedSettings(location))}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { defaultEnvironment, SqValueLocation } from "@quri/squiggle-lang";
|
||||||
|
import React from "react";
|
||||||
|
import { LocalItemSettings, MergedItemSettings } from "./utils";
|
||||||
|
|
||||||
|
type ViewerContextShape = {
|
||||||
|
// Note that we don't store settings themselves in the context (that would cause rerenders of the entire tree on each settings update).
|
||||||
|
// Instead, we keep settings in local state and notify the global context via setSettings to pass them down the component tree again if it got rebuilt from scratch.
|
||||||
|
// See ./SquiggleViewer.tsx and ./VariableBox.tsx for other implementation details on this.
|
||||||
|
getSettings(location: SqValueLocation): LocalItemSettings;
|
||||||
|
getMergedSettings(location: SqValueLocation): MergedItemSettings;
|
||||||
|
setSettings(location: SqValueLocation, value: LocalItemSettings): void;
|
||||||
|
enableLocalSettings: boolean; // show local settings icon in the UI
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ViewerContext = React.createContext<ViewerContextShape>({
|
||||||
|
getSettings: () => ({ collapsed: false }),
|
||||||
|
getMergedSettings: () => ({
|
||||||
|
collapsed: false,
|
||||||
|
// copy-pasted from SquiggleChart
|
||||||
|
chartSettings: {
|
||||||
|
start: 0,
|
||||||
|
stop: 10,
|
||||||
|
count: 100,
|
||||||
|
},
|
||||||
|
distributionPlotSettings: {
|
||||||
|
showSummary: false,
|
||||||
|
logX: false,
|
||||||
|
expY: false,
|
||||||
|
},
|
||||||
|
environment: defaultEnvironment,
|
||||||
|
height: 150,
|
||||||
|
}),
|
||||||
|
setSettings() {},
|
||||||
|
enableLocalSettings: false,
|
||||||
|
});
|
99
packages/components/src/components/SquiggleViewer/index.tsx
Normal file
99
packages/components/src/components/SquiggleViewer/index.tsx
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
import React, { useCallback, useRef } from "react";
|
||||||
|
import { environment, SqValueLocation } from "@quri/squiggle-lang";
|
||||||
|
import { DistributionPlottingSettings } from "../DistributionChart";
|
||||||
|
import { FunctionChartSettings } from "../FunctionChart";
|
||||||
|
import { ExpressionViewer } from "./ExpressionViewer";
|
||||||
|
import { ViewerContext } from "./ViewerContext";
|
||||||
|
import {
|
||||||
|
LocalItemSettings,
|
||||||
|
locationAsString,
|
||||||
|
MergedItemSettings,
|
||||||
|
} from "./utils";
|
||||||
|
import { useSquiggle } from "../../lib/hooks";
|
||||||
|
import { SquiggleErrorAlert } from "../SquiggleErrorAlert";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
/** The output of squiggle's run */
|
||||||
|
result: ReturnType<typeof useSquiggle>["result"];
|
||||||
|
width?: number;
|
||||||
|
height: number;
|
||||||
|
distributionPlotSettings: DistributionPlottingSettings;
|
||||||
|
/** Settings for displaying functions */
|
||||||
|
chartSettings: FunctionChartSettings;
|
||||||
|
/** Environment for further function executions */
|
||||||
|
environment: environment;
|
||||||
|
enableLocalSettings?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Settings = {
|
||||||
|
[k: string]: LocalItemSettings;
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultSettings: LocalItemSettings = { collapsed: false };
|
||||||
|
|
||||||
|
export const SquiggleViewer: React.FC<Props> = ({
|
||||||
|
result,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
distributionPlotSettings,
|
||||||
|
chartSettings,
|
||||||
|
environment,
|
||||||
|
enableLocalSettings = false,
|
||||||
|
}) => {
|
||||||
|
// can't store settings in the state because we don't want to rerender the entire tree on every change
|
||||||
|
const settingsRef = useRef<Settings>({});
|
||||||
|
|
||||||
|
const getSettings = useCallback(
|
||||||
|
(location: SqValueLocation) => {
|
||||||
|
return settingsRef.current[locationAsString(location)] || defaultSettings;
|
||||||
|
},
|
||||||
|
[settingsRef]
|
||||||
|
);
|
||||||
|
|
||||||
|
const setSettings = useCallback(
|
||||||
|
(location: SqValueLocation, value: LocalItemSettings) => {
|
||||||
|
settingsRef.current[locationAsString(location)] = value;
|
||||||
|
},
|
||||||
|
[settingsRef]
|
||||||
|
);
|
||||||
|
|
||||||
|
const getMergedSettings = useCallback(
|
||||||
|
(location: SqValueLocation) => {
|
||||||
|
const localSettings = getSettings(location);
|
||||||
|
const result: MergedItemSettings = {
|
||||||
|
distributionPlotSettings: {
|
||||||
|
...distributionPlotSettings,
|
||||||
|
...(localSettings.distributionPlotSettings || {}),
|
||||||
|
},
|
||||||
|
chartSettings: {
|
||||||
|
...chartSettings,
|
||||||
|
...(localSettings.chartSettings || {}),
|
||||||
|
},
|
||||||
|
environment: {
|
||||||
|
...environment,
|
||||||
|
...(localSettings.environment || {}),
|
||||||
|
},
|
||||||
|
height: localSettings.height || height,
|
||||||
|
};
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
[distributionPlotSettings, chartSettings, environment, height, getSettings]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ViewerContext.Provider
|
||||||
|
value={{
|
||||||
|
getSettings,
|
||||||
|
setSettings,
|
||||||
|
getMergedSettings,
|
||||||
|
enableLocalSettings,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{result.tag === "Ok" ? (
|
||||||
|
<ExpressionViewer value={result.value} width={width} />
|
||||||
|
) : (
|
||||||
|
<SquiggleErrorAlert error={result.value} />
|
||||||
|
)}
|
||||||
|
</ViewerContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
21
packages/components/src/components/SquiggleViewer/utils.ts
Normal file
21
packages/components/src/components/SquiggleViewer/utils.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import { DistributionPlottingSettings } from "../DistributionChart";
|
||||||
|
import { FunctionChartSettings } from "../FunctionChart";
|
||||||
|
import { environment, SqValueLocation } from "@quri/squiggle-lang";
|
||||||
|
|
||||||
|
export type LocalItemSettings = {
|
||||||
|
collapsed: boolean;
|
||||||
|
distributionPlotSettings?: Partial<DistributionPlottingSettings>;
|
||||||
|
chartSettings?: Partial<FunctionChartSettings>;
|
||||||
|
height?: number;
|
||||||
|
environment?: Partial<environment>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MergedItemSettings = {
|
||||||
|
distributionPlotSettings: DistributionPlottingSettings;
|
||||||
|
chartSettings: FunctionChartSettings;
|
||||||
|
height: number;
|
||||||
|
environment: environment;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const locationAsString = (location: SqValueLocation) =>
|
||||||
|
location.path.items.join(".");
|
153
packages/components/src/components/ViewSettings.tsx
Normal file
153
packages/components/src/components/ViewSettings.tsx
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
import React from "react";
|
||||||
|
import * as yup from "yup";
|
||||||
|
import { UseFormRegister } from "react-hook-form";
|
||||||
|
import { InputItem } from "./ui/InputItem";
|
||||||
|
import { Checkbox } from "./ui/Checkbox";
|
||||||
|
import { HeadedSection } from "./ui/HeadedSection";
|
||||||
|
import { Text } from "./ui/Text";
|
||||||
|
import { defaultTickFormat } from "../lib/distributionSpecBuilder";
|
||||||
|
|
||||||
|
export const viewSettingsSchema = yup.object({}).shape({
|
||||||
|
chartHeight: yup.number().required().positive().integer().default(350),
|
||||||
|
showSummary: yup.boolean().required(),
|
||||||
|
showEditor: yup.boolean().required(),
|
||||||
|
logX: yup.boolean().required(),
|
||||||
|
expY: yup.boolean().required(),
|
||||||
|
tickFormat: yup.string().default(defaultTickFormat),
|
||||||
|
title: yup.string(),
|
||||||
|
minX: yup.number(),
|
||||||
|
maxX: yup.number(),
|
||||||
|
distributionChartActions: yup.boolean(),
|
||||||
|
diagramStart: yup.number().required().positive().integer().default(0).min(0),
|
||||||
|
diagramStop: yup.number().required().positive().integer().default(10).min(0),
|
||||||
|
diagramCount: yup.number().required().positive().integer().default(20).min(2),
|
||||||
|
});
|
||||||
|
|
||||||
|
type FormFields = yup.InferType<typeof viewSettingsSchema>;
|
||||||
|
|
||||||
|
// This component is used in two places: for global settings in SquigglePlayground, and for item-specific settings in modal dialogs.
|
||||||
|
export const ViewSettings: React.FC<{
|
||||||
|
withShowEditorSetting?: boolean;
|
||||||
|
withFunctionSettings?: boolean;
|
||||||
|
disableLogXSetting?: boolean;
|
||||||
|
register: UseFormRegister<FormFields>;
|
||||||
|
}> = ({
|
||||||
|
withShowEditorSetting = true,
|
||||||
|
withFunctionSettings = true,
|
||||||
|
disableLogXSetting,
|
||||||
|
register,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className="space-y-6 p-3 divide-y divide-gray-200 max-w-xl">
|
||||||
|
<HeadedSection title="General Display Settings">
|
||||||
|
<div className="space-y-4">
|
||||||
|
{withShowEditorSetting ? (
|
||||||
|
<Checkbox
|
||||||
|
name="showEditor"
|
||||||
|
register={register}
|
||||||
|
label="Show code editor on left"
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
<InputItem
|
||||||
|
name="chartHeight"
|
||||||
|
type="number"
|
||||||
|
register={register}
|
||||||
|
label="Chart Height (in pixels)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</HeadedSection>
|
||||||
|
|
||||||
|
<div className="pt-8">
|
||||||
|
<HeadedSection title="Distribution Display Settings">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Checkbox
|
||||||
|
register={register}
|
||||||
|
name="logX"
|
||||||
|
label="Show x scale logarithmically"
|
||||||
|
disabled={disableLogXSetting}
|
||||||
|
tooltip={
|
||||||
|
disableLogXSetting
|
||||||
|
? "Your distribution has mass lower than or equal to 0. Log only works on strictly positive values."
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
register={register}
|
||||||
|
name="expY"
|
||||||
|
label="Show y scale exponentially"
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
register={register}
|
||||||
|
name="distributionChartActions"
|
||||||
|
label="Show vega chart controls"
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
register={register}
|
||||||
|
name="showSummary"
|
||||||
|
label="Show summary statistics"
|
||||||
|
/>
|
||||||
|
<InputItem
|
||||||
|
name="minX"
|
||||||
|
type="number"
|
||||||
|
register={register}
|
||||||
|
label="Min X Value"
|
||||||
|
/>
|
||||||
|
<InputItem
|
||||||
|
name="maxX"
|
||||||
|
type="number"
|
||||||
|
register={register}
|
||||||
|
label="Max X Value"
|
||||||
|
/>
|
||||||
|
<InputItem
|
||||||
|
name="title"
|
||||||
|
type="text"
|
||||||
|
register={register}
|
||||||
|
label="Title"
|
||||||
|
/>
|
||||||
|
<InputItem
|
||||||
|
name="tickFormat"
|
||||||
|
type="text"
|
||||||
|
register={register}
|
||||||
|
label="Tick Format"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</HeadedSection>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{withFunctionSettings ? (
|
||||||
|
<div className="pt-8">
|
||||||
|
<HeadedSection title="Function Display Settings">
|
||||||
|
<div className="space-y-6">
|
||||||
|
<Text>
|
||||||
|
When displaying functions of single variables that return
|
||||||
|
numbers or distributions, we need to use defaults for the
|
||||||
|
x-axis. We need to select a minimum and maximum value of x to
|
||||||
|
sample, and a number n of the number of points to sample.
|
||||||
|
</Text>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<InputItem
|
||||||
|
type="number"
|
||||||
|
name="diagramStart"
|
||||||
|
register={register}
|
||||||
|
label="Min X Value"
|
||||||
|
/>
|
||||||
|
<InputItem
|
||||||
|
type="number"
|
||||||
|
name="diagramStop"
|
||||||
|
register={register}
|
||||||
|
label="Max X Value"
|
||||||
|
/>
|
||||||
|
<InputItem
|
||||||
|
type="number"
|
||||||
|
name="diagramCount"
|
||||||
|
register={register}
|
||||||
|
label="Points between X min and X max to sample"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</HeadedSection>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
22
packages/components/src/components/ui/Button.tsx
Normal file
22
packages/components/src/components/ui/Button.tsx
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import clsx from "clsx";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onClick: () => void;
|
||||||
|
children: React.ReactNode;
|
||||||
|
wide?: boolean; // stretch the button horizontally
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Button: React.FC<Props> = ({ onClick, wide, children }) => {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={clsx(
|
||||||
|
"rounded-md py-1.5 px-2 bg-slate-500 text-white text-xs font-semibold flex items-center justify-center space-x-1",
|
||||||
|
wide && "w-full"
|
||||||
|
)}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
37
packages/components/src/components/ui/Checkbox.tsx
Normal file
37
packages/components/src/components/ui/Checkbox.tsx
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import clsx from "clsx";
|
||||||
|
import React from "react";
|
||||||
|
import { Path, UseFormRegister, FieldValues } from "react-hook-form";
|
||||||
|
|
||||||
|
export function Checkbox<T extends FieldValues>({
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
register,
|
||||||
|
disabled,
|
||||||
|
tooltip,
|
||||||
|
}: {
|
||||||
|
name: Path<T>;
|
||||||
|
label: string;
|
||||||
|
register: UseFormRegister<T>;
|
||||||
|
disabled?: boolean;
|
||||||
|
tooltip?: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<label className="flex items-center" title={tooltip}>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
disabled={disabled}
|
||||||
|
{...register(name)}
|
||||||
|
className="form-checkbox focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
|
||||||
|
/>
|
||||||
|
{/* Clicking on the div makes the checkbox lose focus while mouse button is pressed, leading to annoying blinking; I couldn't figure out how to fix this. */}
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
"ml-3 text-sm font-medium",
|
||||||
|
disabled ? "text-gray-400" : "text-gray-700"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
}
|
13
packages/components/src/components/ui/HeadedSection.tsx
Normal file
13
packages/components/src/components/ui/HeadedSection.tsx
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export const HeadedSection: React.FC<{
|
||||||
|
title: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}> = ({ title, children }) => (
|
||||||
|
<div>
|
||||||
|
<header className="text-lg leading-6 font-medium text-gray-900">
|
||||||
|
{title}
|
||||||
|
</header>
|
||||||
|
<div className="mt-4">{children}</div>
|
||||||
|
</div>
|
||||||
|
);
|
25
packages/components/src/components/ui/InputItem.tsx
Normal file
25
packages/components/src/components/ui/InputItem.tsx
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import React from "react";
|
||||||
|
import { Path, UseFormRegister, FieldValues } from "react-hook-form";
|
||||||
|
|
||||||
|
export function InputItem<T extends FieldValues>({
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
type,
|
||||||
|
register,
|
||||||
|
}: {
|
||||||
|
name: Path<T>;
|
||||||
|
label: string;
|
||||||
|
type: "number" | "text" | "color";
|
||||||
|
register: UseFormRegister<T>;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<label className="block">
|
||||||
|
<div className="text-sm font-medium text-gray-600 mb-1">{label}</div>
|
||||||
|
<input
|
||||||
|
type={type}
|
||||||
|
{...register(name, { valueAsNumber: type === "number" })}
|
||||||
|
className="form-input max-w-lg block w-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:max-w-xs sm:text-sm border-gray-300 rounded-md"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
}
|
184
packages/components/src/components/ui/Modal.tsx
Normal file
184
packages/components/src/components/ui/Modal.tsx
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import React, { useContext } from "react";
|
||||||
|
import * as ReactDOM from "react-dom";
|
||||||
|
import { XIcon } from "@heroicons/react/solid";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import { useWindowScroll, useWindowSize } from "react-use";
|
||||||
|
|
||||||
|
type ModalContextShape = {
|
||||||
|
close: () => void;
|
||||||
|
};
|
||||||
|
const ModalContext = React.createContext<ModalContextShape>({
|
||||||
|
close: () => undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
const Overlay: React.FC = () => {
|
||||||
|
const { close } = useContext(ModalContext);
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
className="absolute inset-0 -z-10 bg-black"
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 0.1 }}
|
||||||
|
onClick={close}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ModalHeader: React.FC<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
}> = ({ children }) => {
|
||||||
|
const { close } = useContext(ModalContext);
|
||||||
|
return (
|
||||||
|
<header className="px-5 py-3 border-b border-gray-200 font-bold flex items-center justify-between">
|
||||||
|
<div>{children}</div>
|
||||||
|
<button
|
||||||
|
className="px-1 bg-transparent cursor-pointer text-gray-700 hover:text-accent-500"
|
||||||
|
type="button"
|
||||||
|
onClick={close}
|
||||||
|
>
|
||||||
|
<XIcon className="h-5 w-5 cursor-pointer text-slate-400 hover:text-slate-500" />
|
||||||
|
</button>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO - get rid of forwardRef, support `focus` and `{...hotkeys}` via smart props
|
||||||
|
const ModalBody = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
JSX.IntrinsicElements["div"]
|
||||||
|
>(function ModalBody(props, ref) {
|
||||||
|
return <div ref={ref} className="px-5 py-3 overflow-auto" {...props} />;
|
||||||
|
});
|
||||||
|
|
||||||
|
const ModalFooter: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
||||||
|
<div className="px-5 py-3 border-t border-gray-200">{children}</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const ModalWindow: React.FC<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
container?: HTMLElement;
|
||||||
|
}> = ({ children, container }) => {
|
||||||
|
// This component works in two possible modes:
|
||||||
|
// 1. container mode - the modal is rendered inside a container element
|
||||||
|
// 2. centered mode - the modal is rendered in the middle of the screen
|
||||||
|
// The mode is determined by the presence of the `container` prop and by whether the available space is large enough to fit the modal.
|
||||||
|
|
||||||
|
// Necessary for container mode - need to reposition the modal on scroll and resize events.
|
||||||
|
useWindowSize();
|
||||||
|
useWindowScroll();
|
||||||
|
|
||||||
|
let position:
|
||||||
|
| {
|
||||||
|
left: number;
|
||||||
|
top: number;
|
||||||
|
maxWidth: number;
|
||||||
|
maxHeight: number;
|
||||||
|
transform: string;
|
||||||
|
}
|
||||||
|
| undefined;
|
||||||
|
|
||||||
|
// If available space in `visibleRect` is smaller than these, fallback to positioning in the middle of the screen.
|
||||||
|
const minWidth = 384;
|
||||||
|
const minHeight = 300;
|
||||||
|
const offset = 8;
|
||||||
|
const naturalWidth = 576; // maximum possible width; modal tries to take this much space, but can be smaller
|
||||||
|
|
||||||
|
if (container) {
|
||||||
|
const { clientWidth: screenWidth, clientHeight: screenHeight } =
|
||||||
|
document.documentElement;
|
||||||
|
const rect = container?.getBoundingClientRect();
|
||||||
|
|
||||||
|
const visibleRect = {
|
||||||
|
left: Math.max(rect.left, 0),
|
||||||
|
right: Math.min(rect.right, screenWidth),
|
||||||
|
top: Math.max(rect.top, 0),
|
||||||
|
bottom: Math.min(rect.bottom, screenHeight),
|
||||||
|
};
|
||||||
|
const maxWidth = visibleRect.right - visibleRect.left - 2 * offset;
|
||||||
|
const maxHeight = visibleRect.bottom - visibleRect.top - 2 * offset;
|
||||||
|
|
||||||
|
const center = {
|
||||||
|
left: visibleRect.left + (visibleRect.right - visibleRect.left) / 2,
|
||||||
|
top: visibleRect.top + (visibleRect.bottom - visibleRect.top) / 2,
|
||||||
|
};
|
||||||
|
position = {
|
||||||
|
left: center.left,
|
||||||
|
top: center.top,
|
||||||
|
transform: "translate(-50%, -50%)",
|
||||||
|
maxWidth,
|
||||||
|
maxHeight,
|
||||||
|
};
|
||||||
|
if (maxWidth < minWidth || maxHeight < minHeight) {
|
||||||
|
position = undefined; // modal is hard to fit in the container, fallback to positioning it in the middle of the screen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
"bg-white rounded-md shadow-toast flex flex-col overflow-auto border",
|
||||||
|
position ? "fixed" : null
|
||||||
|
)}
|
||||||
|
style={{
|
||||||
|
width: naturalWidth,
|
||||||
|
...(position ?? {
|
||||||
|
maxHeight: "calc(100% - 20px)",
|
||||||
|
maxWidth: "calc(100% - 20px)",
|
||||||
|
width: naturalWidth,
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type ModalType = React.FC<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
container?: HTMLElement; // if specified, modal will be positioned over the visible part of the container, if it's not too small
|
||||||
|
close: () => void;
|
||||||
|
}> & {
|
||||||
|
Body: typeof ModalBody;
|
||||||
|
Footer: typeof ModalFooter;
|
||||||
|
Header: typeof ModalHeader;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Modal: ModalType = ({ children, container, close }) => {
|
||||||
|
const [el] = React.useState(() => document.createElement("div"));
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
document.body.appendChild(el);
|
||||||
|
return () => {
|
||||||
|
document.body.removeChild(el);
|
||||||
|
};
|
||||||
|
}, [el]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const handleEscape = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === "Escape") {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener("keydown", handleEscape);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("keydown", handleEscape);
|
||||||
|
};
|
||||||
|
}, [close]);
|
||||||
|
|
||||||
|
const modal = (
|
||||||
|
<ModalContext.Provider value={{ close }}>
|
||||||
|
<div className="squiggle">
|
||||||
|
<div className="fixed inset-0 z-40 flex justify-center items-center">
|
||||||
|
<Overlay />
|
||||||
|
<ModalWindow container={container}>{children}</ModalWindow>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ModalContext.Provider>
|
||||||
|
);
|
||||||
|
|
||||||
|
return ReactDOM.createPortal(modal, container || el);
|
||||||
|
};
|
||||||
|
|
||||||
|
Modal.Body = ModalBody;
|
||||||
|
Modal.Footer = ModalFooter;
|
||||||
|
Modal.Header = ModalHeader;
|
60
packages/components/src/components/ui/StyledTab.tsx
Normal file
60
packages/components/src/components/ui/StyledTab.tsx
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
import React, { Fragment } from "react";
|
||||||
|
import { Tab } from "@headlessui/react";
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
|
type StyledTabProps = {
|
||||||
|
name: string;
|
||||||
|
icon: (props: React.ComponentProps<"svg">) => JSX.Element;
|
||||||
|
};
|
||||||
|
|
||||||
|
type StyledTabType = React.FC<StyledTabProps> & {
|
||||||
|
List: React.FC<{ children: React.ReactNode }>;
|
||||||
|
Group: typeof Tab.Group;
|
||||||
|
Panels: typeof Tab.Panels;
|
||||||
|
Panel: typeof Tab.Panel;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const StyledTab: StyledTabType = ({ name, icon: Icon }) => {
|
||||||
|
return (
|
||||||
|
<Tab as={Fragment}>
|
||||||
|
{({ selected }) => (
|
||||||
|
<button className="group flex rounded-md focus:outline-none focus-visible:ring-offset-gray-100">
|
||||||
|
<span
|
||||||
|
className={clsx(
|
||||||
|
"p-1 pl-2.5 pr-3.5 rounded-md flex items-center text-sm font-medium",
|
||||||
|
selected && "bg-white shadow-sm ring-1 ring-black ring-opacity-5"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
className={clsx(
|
||||||
|
"-ml-0.5 mr-2 h-4 w-4",
|
||||||
|
selected
|
||||||
|
? "text-slate-500"
|
||||||
|
: "text-gray-400 group-hover:text-gray-900"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className={clsx(
|
||||||
|
selected
|
||||||
|
? "text-gray-900"
|
||||||
|
: "text-gray-600 group-hover:text-gray-900"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</Tab>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
StyledTab.List = ({ children }) => (
|
||||||
|
<Tab.List className="flex w-fit p-0.5 rounded-md bg-slate-100 hover:bg-slate-200">
|
||||||
|
{children}
|
||||||
|
</Tab.List>
|
||||||
|
);
|
||||||
|
|
||||||
|
StyledTab.Group = Tab.Group;
|
||||||
|
StyledTab.Panels = Tab.Panels;
|
||||||
|
StyledTab.Panel = Tab.Panel;
|
5
packages/components/src/components/ui/Text.tsx
Normal file
5
packages/components/src/components/ui/Text.tsx
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export const Text: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
||||||
|
<p className="text-sm text-gray-500">{children}</p>
|
||||||
|
);
|
47
packages/components/src/components/ui/Toggle.tsx
Normal file
47
packages/components/src/components/ui/Toggle.tsx
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import { RefreshIcon } from "@heroicons/react/solid";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
type IconType = (props: React.ComponentProps<"svg">) => JSX.Element;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
status: boolean;
|
||||||
|
onChange: (status: boolean) => void;
|
||||||
|
texts: [string, string];
|
||||||
|
icons: [IconType, IconType];
|
||||||
|
spinIcon?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Toggle: React.FC<Props> = ({
|
||||||
|
status,
|
||||||
|
onChange,
|
||||||
|
texts: [onText, offText],
|
||||||
|
icons: [OnIcon, OffIcon],
|
||||||
|
spinIcon,
|
||||||
|
}) => {
|
||||||
|
const CurrentIcon = status ? OnIcon : OffIcon;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={clsx(
|
||||||
|
"rounded-md py-0.5 bg-slate-500 text-white text-xs font-semibold flex items-center space-x-1",
|
||||||
|
status ? "bg-slate-500" : "bg-gray-400",
|
||||||
|
status ? "pl-1 pr-3" : "pl-3 pr-1",
|
||||||
|
!status && "flex-row-reverse space-x-reverse"
|
||||||
|
)}
|
||||||
|
onClick={() => onChange(!status)}
|
||||||
|
>
|
||||||
|
<div className="relative w-6 h-6" key={String(spinIcon)}>
|
||||||
|
<CurrentIcon
|
||||||
|
className={clsx(
|
||||||
|
"w-6 h-6 absolute opacity-100",
|
||||||
|
spinIcon && "animate-hide"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{spinIcon && (
|
||||||
|
<RefreshIcon className="w-6 h-6 absolute opacity-0 animate-appear-and-spin" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<span>{status ? onText : offText}</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
64
packages/components/src/components/ui/Tooltip.tsx
Normal file
64
packages/components/src/components/ui/Tooltip.tsx
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import React, { cloneElement, useState } from "react";
|
||||||
|
import { AnimatePresence, motion } from "framer-motion";
|
||||||
|
import {
|
||||||
|
flip,
|
||||||
|
shift,
|
||||||
|
useDismiss,
|
||||||
|
useFloating,
|
||||||
|
useHover,
|
||||||
|
useInteractions,
|
||||||
|
useRole,
|
||||||
|
} from "@floating-ui/react-dom-interactions";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
text: string;
|
||||||
|
children: JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Tooltip: React.FC<Props> = ({ text, children }) => {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
|
const { x, y, reference, floating, strategy, context } = useFloating({
|
||||||
|
placement: "top",
|
||||||
|
open: isOpen,
|
||||||
|
onOpenChange: setIsOpen,
|
||||||
|
middleware: [shift(), flip()],
|
||||||
|
});
|
||||||
|
|
||||||
|
const { getReferenceProps, getFloatingProps } = useInteractions([
|
||||||
|
useHover(context),
|
||||||
|
useRole(context, { role: "tooltip" }),
|
||||||
|
useDismiss(context),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{cloneElement(
|
||||||
|
children,
|
||||||
|
getReferenceProps({ ref: reference, ...children.props })
|
||||||
|
)}
|
||||||
|
<AnimatePresence>
|
||||||
|
{isOpen && (
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
exit={{ opacity: 0 }}
|
||||||
|
transition={{ duration: 0.15 }}
|
||||||
|
{...getFloatingProps({
|
||||||
|
ref: floating,
|
||||||
|
className:
|
||||||
|
"text-xs p-2 border border-gray-300 rounded bg-white z-10",
|
||||||
|
style: {
|
||||||
|
position: strategy,
|
||||||
|
top: y ?? 0,
|
||||||
|
left: x ?? 0,
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div className="font-mono whitespace-pre">{text}</div>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,14 +1,5 @@
|
||||||
|
export { SqProject } from "@quri/squiggle-lang/";
|
||||||
export { SquiggleChart } from "./components/SquiggleChart";
|
export { SquiggleChart } from "./components/SquiggleChart";
|
||||||
export {
|
export { SquiggleEditor } from "./components/SquiggleEditor";
|
||||||
SquiggleEditor,
|
export { SquigglePlayground } from "./components/SquigglePlayground";
|
||||||
SquigglePartial,
|
|
||||||
renderSquiggleEditorToDom,
|
|
||||||
renderSquigglePartialToDom,
|
|
||||||
} from "./components/SquiggleEditor";
|
|
||||||
export {
|
|
||||||
SquigglePlayground,
|
|
||||||
renderSquigglePlaygroundToDom,
|
|
||||||
} from "./components/SquigglePlayground";
|
|
||||||
export { SquiggleContainer } from "./components/SquiggleContainer";
|
export { SquiggleContainer } from "./components/SquiggleContainer";
|
||||||
|
|
||||||
export { mergeBindings } from "@quri/squiggle-lang";
|
|
||||||
|
|
398
packages/components/src/lib/distributionSpecBuilder.ts
Normal file
398
packages/components/src/lib/distributionSpecBuilder.ts
Normal file
|
@ -0,0 +1,398 @@
|
||||||
|
import { VisualizationSpec } from "react-vega";
|
||||||
|
import type { LogScale, LinearScale, PowScale, TimeScale } from "vega";
|
||||||
|
|
||||||
|
export type DistributionChartSpecOptions = {
|
||||||
|
/** Set the x scale to be logarithmic by deault */
|
||||||
|
logX: boolean;
|
||||||
|
/** Set the y scale to be exponential by deault */
|
||||||
|
expY: boolean;
|
||||||
|
/** The minimum x coordinate shown on the chart */
|
||||||
|
minX?: number;
|
||||||
|
/** The maximum x coordinate shown on the chart */
|
||||||
|
maxX?: number;
|
||||||
|
/** The title of the chart */
|
||||||
|
title?: string;
|
||||||
|
/** The formatting of the ticks */
|
||||||
|
format?: string;
|
||||||
|
/** Whether the x-axis should be dates or numbers */
|
||||||
|
xAxisType?: "number" | "dateTime";
|
||||||
|
};
|
||||||
|
|
||||||
|
/** X Scales */
|
||||||
|
export const linearXScale: LinearScale = {
|
||||||
|
name: "xscale",
|
||||||
|
clamp: true,
|
||||||
|
type: "linear",
|
||||||
|
range: "width",
|
||||||
|
zero: false,
|
||||||
|
nice: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const logXScale: LogScale = {
|
||||||
|
name: "xscale",
|
||||||
|
type: "log",
|
||||||
|
range: "width",
|
||||||
|
zero: false,
|
||||||
|
base: 10,
|
||||||
|
nice: false,
|
||||||
|
clamp: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const timeXScale: TimeScale = {
|
||||||
|
name: "xscale",
|
||||||
|
clamp: true,
|
||||||
|
type: "time",
|
||||||
|
range: "width",
|
||||||
|
nice: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Y Scales */
|
||||||
|
export const linearYScale: LinearScale = {
|
||||||
|
name: "yscale",
|
||||||
|
type: "linear",
|
||||||
|
range: "height",
|
||||||
|
zero: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const expYScale: PowScale = {
|
||||||
|
name: "yscale",
|
||||||
|
type: "pow",
|
||||||
|
exponent: 0.1,
|
||||||
|
range: "height",
|
||||||
|
zero: true,
|
||||||
|
nice: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const defaultTickFormat = ".9~s";
|
||||||
|
export const timeTickFormat = "%b %d, %Y %H:%M";
|
||||||
|
const width = 500;
|
||||||
|
|
||||||
|
export function buildVegaSpec(
|
||||||
|
specOptions: DistributionChartSpecOptions & { maxY: number }
|
||||||
|
): VisualizationSpec {
|
||||||
|
const {
|
||||||
|
title,
|
||||||
|
minX,
|
||||||
|
maxX,
|
||||||
|
logX,
|
||||||
|
expY,
|
||||||
|
xAxisType = "number",
|
||||||
|
maxY,
|
||||||
|
} = specOptions;
|
||||||
|
|
||||||
|
const dateTime = xAxisType === "dateTime";
|
||||||
|
|
||||||
|
// some fallbacks
|
||||||
|
const format = specOptions?.format
|
||||||
|
? specOptions.format
|
||||||
|
: dateTime
|
||||||
|
? timeTickFormat
|
||||||
|
: defaultTickFormat;
|
||||||
|
|
||||||
|
let xScale = dateTime ? timeXScale : logX ? logXScale : linearXScale;
|
||||||
|
|
||||||
|
xScale = {
|
||||||
|
...xScale,
|
||||||
|
domain: [minX ?? 0, maxX ?? 1],
|
||||||
|
domainMin: minX,
|
||||||
|
domainMax: maxX,
|
||||||
|
};
|
||||||
|
|
||||||
|
let yScale = expY ? expYScale : linearYScale;
|
||||||
|
yScale = { ...yScale, domain: [0, maxY ?? 1], domainMin: 0, domainMax: maxY };
|
||||||
|
|
||||||
|
const spec: VisualizationSpec = {
|
||||||
|
$schema: "https://vega.github.io/schema/vega/v5.json",
|
||||||
|
description: "Squiggle plot chart",
|
||||||
|
width: width,
|
||||||
|
height: 100,
|
||||||
|
padding: 5,
|
||||||
|
data: [{ name: "data" }, { name: "domain" }, { name: "samples" }],
|
||||||
|
signals: [
|
||||||
|
{
|
||||||
|
name: "hover",
|
||||||
|
value: null,
|
||||||
|
on: [
|
||||||
|
{ events: "mouseover", update: "datum" },
|
||||||
|
{ events: "mouseout", update: "null" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "position",
|
||||||
|
value: "[0, 0]",
|
||||||
|
on: [
|
||||||
|
{ events: "mousemove", update: "xy() " },
|
||||||
|
{ events: "mouseout", update: "null" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "position_scaled",
|
||||||
|
value: null,
|
||||||
|
update: "isArray(position) ? invert('xscale', position[0]) : ''",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
scales: [
|
||||||
|
xScale,
|
||||||
|
yScale,
|
||||||
|
{
|
||||||
|
name: "color",
|
||||||
|
type: "ordinal",
|
||||||
|
domain: {
|
||||||
|
data: "data",
|
||||||
|
field: "name",
|
||||||
|
},
|
||||||
|
range: { scheme: "blues" },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
axes: [
|
||||||
|
{
|
||||||
|
orient: "bottom",
|
||||||
|
scale: "xscale",
|
||||||
|
labelColor: "#727d93",
|
||||||
|
tickColor: "#fff",
|
||||||
|
tickOpacity: 0.0,
|
||||||
|
domainColor: "#fff",
|
||||||
|
domainOpacity: 0.0,
|
||||||
|
format: format,
|
||||||
|
tickCount: dateTime ? 3 : 10,
|
||||||
|
labelOverlap: "greedy",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
marks: [
|
||||||
|
{
|
||||||
|
name: "all_distributions",
|
||||||
|
type: "group",
|
||||||
|
from: {
|
||||||
|
facet: {
|
||||||
|
name: "distribution_facet",
|
||||||
|
data: "data",
|
||||||
|
groupby: ["name"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
marks: [
|
||||||
|
{
|
||||||
|
name: "continuous_distribution",
|
||||||
|
type: "group",
|
||||||
|
from: {
|
||||||
|
facet: {
|
||||||
|
name: "continuous_facet",
|
||||||
|
data: "distribution_facet",
|
||||||
|
field: "continuous",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
encode: {
|
||||||
|
update: {},
|
||||||
|
},
|
||||||
|
marks: [
|
||||||
|
{
|
||||||
|
name: "continuous_area",
|
||||||
|
type: "area",
|
||||||
|
from: {
|
||||||
|
data: "continuous_facet",
|
||||||
|
},
|
||||||
|
encode: {
|
||||||
|
update: {
|
||||||
|
interpolate: { value: "linear" },
|
||||||
|
x: {
|
||||||
|
scale: "xscale",
|
||||||
|
field: "x",
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
scale: "yscale",
|
||||||
|
field: "y",
|
||||||
|
},
|
||||||
|
fill: {
|
||||||
|
scale: "color",
|
||||||
|
field: { parent: "name" },
|
||||||
|
},
|
||||||
|
y2: {
|
||||||
|
scale: "yscale",
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
fillOpacity: {
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "discrete_distribution",
|
||||||
|
type: "group",
|
||||||
|
from: {
|
||||||
|
facet: {
|
||||||
|
name: "discrete_facet",
|
||||||
|
data: "distribution_facet",
|
||||||
|
field: "discrete",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
marks: [
|
||||||
|
{
|
||||||
|
type: "rect",
|
||||||
|
from: {
|
||||||
|
data: "discrete_facet",
|
||||||
|
},
|
||||||
|
encode: {
|
||||||
|
enter: {
|
||||||
|
width: {
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
x: {
|
||||||
|
scale: "xscale",
|
||||||
|
field: "x",
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
scale: "yscale",
|
||||||
|
field: "y",
|
||||||
|
},
|
||||||
|
y2: {
|
||||||
|
scale: "yscale",
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
fill: {
|
||||||
|
scale: "color",
|
||||||
|
field: { parent: "name" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "symbol",
|
||||||
|
from: {
|
||||||
|
data: "discrete_facet",
|
||||||
|
},
|
||||||
|
encode: {
|
||||||
|
enter: {
|
||||||
|
shape: {
|
||||||
|
value: "circle",
|
||||||
|
},
|
||||||
|
size: [{ value: 100 }],
|
||||||
|
tooltip: {
|
||||||
|
signal: dateTime
|
||||||
|
? "{ probability: datum.y, value: datetime(datum.x) }"
|
||||||
|
: "{ probability: datum.y, value: datum.x }",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
x: {
|
||||||
|
scale: "xscale",
|
||||||
|
field: "x",
|
||||||
|
offset: 0.5, // if this is not included, the circles are slightly left of center.
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
scale: "yscale",
|
||||||
|
field: "y",
|
||||||
|
},
|
||||||
|
fill: {
|
||||||
|
scale: "color",
|
||||||
|
field: { parent: "name" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "sampleset",
|
||||||
|
type: "rect",
|
||||||
|
from: { data: "samples" },
|
||||||
|
encode: {
|
||||||
|
enter: {
|
||||||
|
x: { scale: "xscale", field: "data" },
|
||||||
|
width: { value: 0.1 },
|
||||||
|
|
||||||
|
y: { value: 25, offset: { signal: "height" } },
|
||||||
|
height: { value: 5 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
name: "announcer",
|
||||||
|
interactive: false,
|
||||||
|
encode: {
|
||||||
|
enter: {
|
||||||
|
x: { signal: String(width), offset: 1 }, // vega would prefer its internal ` "width" ` variable, but that breaks the squiggle playground. Just setting it to the same var as used elsewhere in the spec achieves the same result.
|
||||||
|
fill: { value: "black" },
|
||||||
|
fontSize: { value: 20 },
|
||||||
|
align: { value: "right" },
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
text: {
|
||||||
|
signal: dateTime
|
||||||
|
? "position_scaled ? utcyear(position_scaled) + '-' + utcmonth(position_scaled) + '-' + utcdate(position_scaled) + 'T' + utchours(position_scaled)+':' +utcminutes(position_scaled) : ''"
|
||||||
|
: "position_scaled ? format(position_scaled, ',.4r') : ''",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "rule",
|
||||||
|
interactive: false,
|
||||||
|
encode: {
|
||||||
|
enter: {
|
||||||
|
x: { value: 0 },
|
||||||
|
y: { scale: "yscale", value: 0 },
|
||||||
|
|
||||||
|
y2: {
|
||||||
|
signal: "height",
|
||||||
|
offset: 2,
|
||||||
|
},
|
||||||
|
strokeDash: { value: [5, 5] },
|
||||||
|
},
|
||||||
|
|
||||||
|
update: {
|
||||||
|
x: {
|
||||||
|
signal:
|
||||||
|
"position ? position[0] < 0 ? null : position[0] > width ? null : position[0]: null",
|
||||||
|
},
|
||||||
|
|
||||||
|
opacity: {
|
||||||
|
signal:
|
||||||
|
"position ? position[0] < 0 ? 0 : position[0] > width ? 0 : 1 : 0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
legends: [
|
||||||
|
{
|
||||||
|
fill: "color",
|
||||||
|
orient: "top",
|
||||||
|
labelFontSize: 12,
|
||||||
|
encode: {
|
||||||
|
symbols: {
|
||||||
|
update: {
|
||||||
|
fill: [
|
||||||
|
{ test: "length(domain('color')) == 1", value: "transparent" },
|
||||||
|
{ scale: "color", field: "value" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
labels: {
|
||||||
|
interactive: true,
|
||||||
|
update: {
|
||||||
|
fill: [
|
||||||
|
{ test: "length(domain('color')) == 1", value: "transparent" },
|
||||||
|
{ value: "black" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
...(title && {
|
||||||
|
title: {
|
||||||
|
text: title,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
return spec;
|
||||||
|
}
|
5
packages/components/src/lib/distributionUtils.ts
Normal file
5
packages/components/src/lib/distributionUtils.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import { SqShape } from "@quri/squiggle-lang";
|
||||||
|
|
||||||
|
export const hasMassBelowZero = (shape: SqShape) =>
|
||||||
|
shape.continuous.some((x) => x.x <= 0) ||
|
||||||
|
shape.discrete.some((x) => x.x <= 0);
|
|
@ -1,63 +0,0 @@
|
||||||
import {
|
|
||||||
bindings,
|
|
||||||
environment,
|
|
||||||
jsImports,
|
|
||||||
run,
|
|
||||||
runPartial,
|
|
||||||
} from "@quri/squiggle-lang";
|
|
||||||
import { useEffect, useMemo, useRef } from "react";
|
|
||||||
|
|
||||||
type SquiggleArgs<T extends ReturnType<typeof run | typeof runPartial>> = {
|
|
||||||
code: string;
|
|
||||||
bindings?: bindings;
|
|
||||||
jsImports?: jsImports;
|
|
||||||
environment?: environment;
|
|
||||||
onChange?: (expr: Extract<T, { tag: "Ok" }>["value"] | undefined) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
const useSquiggleAny = <T extends ReturnType<typeof run | typeof runPartial>>(
|
|
||||||
args: SquiggleArgs<T>,
|
|
||||||
f: (...args: Parameters<typeof run>) => T
|
|
||||||
) => {
|
|
||||||
// We're using observable, where div elements can have a `value` property:
|
|
||||||
// https://observablehq.com/@observablehq/introduction-to-views
|
|
||||||
//
|
|
||||||
// This is here to get the 'viewof' part of:
|
|
||||||
// viewof env = cell('normal(0,1)')
|
|
||||||
// to work
|
|
||||||
const ref = useRef<
|
|
||||||
HTMLDivElement & { value?: Extract<T, { tag: "Ok" }>["value"] }
|
|
||||||
>(null);
|
|
||||||
const result: T = useMemo<T>(
|
|
||||||
() => f(args.code, args.bindings, args.environment, args.jsImports),
|
|
||||||
[f, args.code, args.bindings, args.environment, args.jsImports]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!ref.current) return;
|
|
||||||
ref.current.value = result.tag === "Ok" ? result.value : undefined;
|
|
||||||
|
|
||||||
ref.current.dispatchEvent(new CustomEvent("input"));
|
|
||||||
}, [result]);
|
|
||||||
|
|
||||||
const { onChange } = args;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
onChange?.(result.tag === "Ok" ? result.value : undefined);
|
|
||||||
}, [result, onChange]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
result, // squiggleExpression or externalBindings
|
|
||||||
observableRef: ref, // can be passed to outermost <div> if you want to use your component as an observablehq's view
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useSquigglePartial = (
|
|
||||||
args: SquiggleArgs<ReturnType<typeof runPartial>>
|
|
||||||
) => {
|
|
||||||
return useSquiggleAny(args, runPartial);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useSquiggle = (args: SquiggleArgs<ReturnType<typeof run>>) => {
|
|
||||||
return useSquiggleAny(args, run);
|
|
||||||
};
|
|
3
packages/components/src/lib/hooks/index.ts
Normal file
3
packages/components/src/lib/hooks/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export { useMaybeControlledValue } from "./useMaybeControlledValue";
|
||||||
|
export { useSquiggle } from "./useSquiggle";
|
||||||
|
export { useRunnerState } from "./useRunnerState";
|
22
packages/components/src/lib/hooks/useMaybeControlledValue.ts
Normal file
22
packages/components/src/lib/hooks/useMaybeControlledValue.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
type ControlledValueArgs<T> = {
|
||||||
|
value?: T;
|
||||||
|
defaultValue: T;
|
||||||
|
onChange?: (x: T) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useMaybeControlledValue<T>(
|
||||||
|
args: ControlledValueArgs<T>
|
||||||
|
): [T, (x: T) => void] {
|
||||||
|
let [uncontrolledValue, setUncontrolledValue] = useState(args.defaultValue);
|
||||||
|
let value = args.value ?? uncontrolledValue;
|
||||||
|
let onChange = (newValue: T) => {
|
||||||
|
if (args.value === undefined) {
|
||||||
|
// uncontrolled mode
|
||||||
|
setUncontrolledValue(newValue);
|
||||||
|
}
|
||||||
|
args.onChange?.(newValue);
|
||||||
|
};
|
||||||
|
return [value, onChange];
|
||||||
|
}
|
100
packages/components/src/lib/hooks/useRunnerState.ts
Normal file
100
packages/components/src/lib/hooks/useRunnerState.ts
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
import { useLayoutEffect, useReducer } from "react";
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
autorunMode: boolean;
|
||||||
|
renderedCode: string;
|
||||||
|
// "prepared" is for rendering a spinner; "run" for executing squiggle code; then it gets back to "none" on the next render
|
||||||
|
runningState: "none" | "prepared" | "run";
|
||||||
|
executionId: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildInitialState = (code: string): State => ({
|
||||||
|
autorunMode: true,
|
||||||
|
renderedCode: "",
|
||||||
|
runningState: "none",
|
||||||
|
executionId: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
type Action =
|
||||||
|
| {
|
||||||
|
type: "SET_AUTORUN_MODE";
|
||||||
|
value: boolean;
|
||||||
|
code: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "PREPARE_RUN";
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "RUN";
|
||||||
|
code: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "STOP_RUN";
|
||||||
|
};
|
||||||
|
|
||||||
|
const reducer = (state: State, action: Action): State => {
|
||||||
|
switch (action.type) {
|
||||||
|
case "SET_AUTORUN_MODE":
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
autorunMode: action.value,
|
||||||
|
};
|
||||||
|
case "PREPARE_RUN":
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
runningState: "prepared",
|
||||||
|
};
|
||||||
|
case "RUN":
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
runningState: "run",
|
||||||
|
renderedCode: action.code,
|
||||||
|
executionId: state.executionId + 1,
|
||||||
|
};
|
||||||
|
case "STOP_RUN":
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
runningState: "none",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useRunnerState = (code: string) => {
|
||||||
|
const [state, dispatch] = useReducer(reducer, buildInitialState(code));
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (state.runningState === "prepared") {
|
||||||
|
// this is necessary for async playground loading - otherwise it executes the code synchronously on the initial load
|
||||||
|
// (it's surprising that this is necessary, but empirically it _is_ necessary, both with `useEffect` and `useLayoutEffect`)
|
||||||
|
setTimeout(() => {
|
||||||
|
dispatch({ type: "RUN", code });
|
||||||
|
}, 0);
|
||||||
|
} else if (state.runningState === "run") {
|
||||||
|
dispatch({ type: "STOP_RUN" });
|
||||||
|
}
|
||||||
|
}, [state.runningState, code]);
|
||||||
|
|
||||||
|
const run = () => {
|
||||||
|
// The rest will be handled by dispatches above on following renders, but we need to update the spinner first.
|
||||||
|
dispatch({ type: "PREPARE_RUN" });
|
||||||
|
};
|
||||||
|
|
||||||
|
if (
|
||||||
|
state.autorunMode &&
|
||||||
|
state.renderedCode !== code &&
|
||||||
|
state.runningState === "none"
|
||||||
|
) {
|
||||||
|
run();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
run,
|
||||||
|
autorunMode: state.autorunMode,
|
||||||
|
renderedCode: state.renderedCode,
|
||||||
|
isRunning: state.runningState !== "none",
|
||||||
|
executionId: state.executionId,
|
||||||
|
setAutorunMode: (newValue: boolean) => {
|
||||||
|
dispatch({ type: "SET_AUTORUN_MODE", value: newValue, code });
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
97
packages/components/src/lib/hooks/useSquiggle.ts
Normal file
97
packages/components/src/lib/hooks/useSquiggle.ts
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
import {
|
||||||
|
result,
|
||||||
|
SqError,
|
||||||
|
SqProject,
|
||||||
|
SqRecord,
|
||||||
|
SqValue,
|
||||||
|
environment,
|
||||||
|
} from "@quri/squiggle-lang";
|
||||||
|
import { useEffect, useMemo } from "react";
|
||||||
|
import { JsImports, jsImportsToSquiggleCode } from "../jsImports";
|
||||||
|
import * as uuid from "uuid";
|
||||||
|
|
||||||
|
type SquiggleArgs = {
|
||||||
|
environment?: environment;
|
||||||
|
code: string;
|
||||||
|
executionId?: number;
|
||||||
|
jsImports?: JsImports;
|
||||||
|
project?: SqProject;
|
||||||
|
continues?: string[];
|
||||||
|
onChange?: (expr: SqValue | undefined, sourceName: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ResultAndBindings = {
|
||||||
|
result: result<SqValue, SqError>;
|
||||||
|
bindings: SqRecord;
|
||||||
|
};
|
||||||
|
|
||||||
|
const importSourceName = (sourceName: string) => "imports-" + sourceName;
|
||||||
|
const defaultContinues = [];
|
||||||
|
|
||||||
|
export const useSquiggle = (args: SquiggleArgs): ResultAndBindings => {
|
||||||
|
const project = useMemo(() => {
|
||||||
|
if (args.project) {
|
||||||
|
return args.project;
|
||||||
|
} else {
|
||||||
|
const p = SqProject.create();
|
||||||
|
if (args.environment) {
|
||||||
|
p.setEnvironment(args.environment);
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
}, [args.project, args.environment]);
|
||||||
|
|
||||||
|
const sourceName = useMemo(() => uuid.v4(), []);
|
||||||
|
|
||||||
|
const env = project.getEnvironment();
|
||||||
|
const continues = args.continues || defaultContinues;
|
||||||
|
|
||||||
|
const result = useMemo(
|
||||||
|
() => {
|
||||||
|
project.setSource(sourceName, args.code);
|
||||||
|
let fullContinues = continues;
|
||||||
|
if (args.jsImports && Object.keys(args.jsImports).length) {
|
||||||
|
const importsSource = jsImportsToSquiggleCode(args.jsImports);
|
||||||
|
project.setSource(importSourceName(sourceName), importsSource);
|
||||||
|
fullContinues = continues.concat(importSourceName(sourceName));
|
||||||
|
}
|
||||||
|
project.setContinues(sourceName, fullContinues);
|
||||||
|
project.run(sourceName);
|
||||||
|
const result = project.getResult(sourceName);
|
||||||
|
const bindings = project.getBindings(sourceName);
|
||||||
|
return { result, bindings };
|
||||||
|
},
|
||||||
|
// This complains about executionId not being used inside the function body.
|
||||||
|
// This is on purpose, as executionId simply allows you to run the squiggle
|
||||||
|
// code again
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[
|
||||||
|
args.code,
|
||||||
|
args.jsImports,
|
||||||
|
args.executionId,
|
||||||
|
sourceName,
|
||||||
|
continues,
|
||||||
|
project,
|
||||||
|
env,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
const { onChange } = args;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
onChange?.(
|
||||||
|
result.result.tag === "Ok" ? result.result.value : undefined,
|
||||||
|
sourceName
|
||||||
|
);
|
||||||
|
}, [result, onChange, sourceName]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
project.removeSource(sourceName);
|
||||||
|
if (project.getSource(importSourceName(sourceName)))
|
||||||
|
project.removeSource(importSourceName(sourceName));
|
||||||
|
};
|
||||||
|
}, [project, sourceName]);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
51
packages/components/src/lib/jsImports.ts
Normal file
51
packages/components/src/lib/jsImports.ts
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
type JsImportsValue =
|
||||||
|
| number
|
||||||
|
| string
|
||||||
|
| JsImportsValue[]
|
||||||
|
| {
|
||||||
|
[k: string]: JsImportsValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type JsImports = {
|
||||||
|
[k: string]: JsImportsValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
const quote = (arg: string) => `"${arg.replace(new RegExp('"', "g"), '\\"')}"`;
|
||||||
|
|
||||||
|
const jsImportsValueToSquiggleCode = (v: JsImportsValue): string => {
|
||||||
|
if (typeof v === "number") {
|
||||||
|
return String(v);
|
||||||
|
} else if (typeof v === "string") {
|
||||||
|
return quote(v);
|
||||||
|
} else if (v instanceof Array) {
|
||||||
|
return "[" + v.map((x) => jsImportsValueToSquiggleCode(x)) + "]";
|
||||||
|
} else {
|
||||||
|
if (Object.keys(v).length) {
|
||||||
|
return (
|
||||||
|
"{" +
|
||||||
|
Object.entries(v)
|
||||||
|
.map(([k, v]) => `${quote(k)}:${jsImportsValueToSquiggleCode(v)},`)
|
||||||
|
.join("") +
|
||||||
|
"}"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return "0"; // squiggle doesn't support empty `{}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const jsImportsToSquiggleCode = (v: JsImports) => {
|
||||||
|
const validId = new RegExp("[a-zA-Z][[a-zA-Z0-9]*");
|
||||||
|
let result = Object.entries(v)
|
||||||
|
.map(([k, v]) => {
|
||||||
|
if (!k.match(validId)) {
|
||||||
|
return ""; // skipping without warnings; can be improved
|
||||||
|
}
|
||||||
|
return `$${k} = ${jsImportsValueToSquiggleCode(v)}\n`;
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
if (!result) {
|
||||||
|
result = "$__no_valid_imports__ = 1"; // without this generated squiggle code can be invalid
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
83
packages/components/src/lib/plotParser.ts
Normal file
83
packages/components/src/lib/plotParser.ts
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
import * as yup from "yup";
|
||||||
|
import {
|
||||||
|
SqValue,
|
||||||
|
SqValueTag,
|
||||||
|
SqDistribution,
|
||||||
|
result,
|
||||||
|
SqRecord,
|
||||||
|
} from "@quri/squiggle-lang";
|
||||||
|
|
||||||
|
export type LabeledDistribution = {
|
||||||
|
name: string;
|
||||||
|
distribution: SqDistribution;
|
||||||
|
color?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Plot = {
|
||||||
|
distributions: LabeledDistribution[];
|
||||||
|
};
|
||||||
|
|
||||||
|
function error<a, b>(err: b): result<a, b> {
|
||||||
|
return { tag: "Error", value: err };
|
||||||
|
}
|
||||||
|
|
||||||
|
function ok<a, b>(x: a): result<a, b> {
|
||||||
|
return { tag: "Ok", value: x };
|
||||||
|
}
|
||||||
|
|
||||||
|
const schema = yup
|
||||||
|
.object()
|
||||||
|
.noUnknown()
|
||||||
|
.strict()
|
||||||
|
.shape({
|
||||||
|
distributions: yup
|
||||||
|
.array()
|
||||||
|
.required()
|
||||||
|
.of(
|
||||||
|
yup.object().required().shape({
|
||||||
|
name: yup.string().required(),
|
||||||
|
distribution: yup.mixed().required(),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
type JsonObject =
|
||||||
|
| string
|
||||||
|
| { [key: string]: JsonObject }
|
||||||
|
| JsonObject[]
|
||||||
|
| SqDistribution;
|
||||||
|
|
||||||
|
function toJson(val: SqValue): JsonObject {
|
||||||
|
if (val.tag === SqValueTag.String) {
|
||||||
|
return val.value;
|
||||||
|
} else if (val.tag === SqValueTag.Record) {
|
||||||
|
return toJsonRecord(val.value);
|
||||||
|
} else if (val.tag === SqValueTag.Array) {
|
||||||
|
return val.value.getValues().map(toJson);
|
||||||
|
} else if (val.tag === SqValueTag.Distribution) {
|
||||||
|
return val.value;
|
||||||
|
} else {
|
||||||
|
throw new Error("Could not parse object of type " + val.tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toJsonRecord(val: SqRecord): JsonObject {
|
||||||
|
let recordObject: JsonObject = {};
|
||||||
|
val.entries().forEach(([key, value]) => (recordObject[key] = toJson(value)));
|
||||||
|
return recordObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parsePlot(record: SqRecord): result<Plot, string> {
|
||||||
|
try {
|
||||||
|
const plotRecord = schema.validateSync(toJsonRecord(record));
|
||||||
|
if (plotRecord.distributions) {
|
||||||
|
return ok({ distributions: plotRecord.distributions.map((x) => x) });
|
||||||
|
} else {
|
||||||
|
// I have no idea why yup's typings thinks this is possible
|
||||||
|
return error("no distributions field. Should never get here");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
const message = e instanceof Error ? e.message : "Unknown error";
|
||||||
|
return error(message);
|
||||||
|
}
|
||||||
|
}
|
53
packages/components/src/lib/utility.ts
Normal file
53
packages/components/src/lib/utility.ts
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
import { result, resultMap, SqValueTag } from "@quri/squiggle-lang";
|
||||||
|
import { ResultAndBindings } from "./hooks/useSquiggle";
|
||||||
|
|
||||||
|
export function flattenResult<a, b>(x: result<a, b>[]): result<a[], b> {
|
||||||
|
if (x.length === 0) {
|
||||||
|
return { tag: "Ok", value: [] };
|
||||||
|
} else {
|
||||||
|
if (x[0].tag === "Error") {
|
||||||
|
return x[0];
|
||||||
|
} else {
|
||||||
|
let rest = flattenResult(x.splice(1));
|
||||||
|
if (rest.tag === "Error") {
|
||||||
|
return rest;
|
||||||
|
} else {
|
||||||
|
return { tag: "Ok", value: [x[0].value].concat(rest.value) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resultBind<a, b, c>(
|
||||||
|
x: result<a, b>,
|
||||||
|
fn: (y: a) => result<c, b>
|
||||||
|
): result<c, b> {
|
||||||
|
if (x.tag === "Ok") {
|
||||||
|
return fn(x.value);
|
||||||
|
} else {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function all(arr: boolean[]): boolean {
|
||||||
|
return arr.reduce((x, y) => x && y, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function some(arr: boolean[]): boolean {
|
||||||
|
return arr.reduce((x, y) => x || y, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getValueToRender({ result, bindings }: ResultAndBindings) {
|
||||||
|
return resultMap(result, (value) =>
|
||||||
|
value.tag === SqValueTag.Void ? bindings.asValue() : value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getErrorLocations(result: ResultAndBindings["result"]) {
|
||||||
|
if (result.tag === "Error") {
|
||||||
|
const location = result.value.location();
|
||||||
|
return location ? [location] : [];
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ import { Canvas, Meta, Story, Props } from "@storybook/addon-docs";
|
||||||
|
|
||||||
<Meta title="Squiggle/SquiggleChart" component={SquiggleChart} />
|
<Meta title="Squiggle/SquiggleChart" component={SquiggleChart} />
|
||||||
|
|
||||||
export const Template = SquiggleChart;
|
export const Template = (props) => <SquiggleChart {...props} />;
|
||||||
/*
|
/*
|
||||||
We have to hardcode a width here, because otherwise some interaction with
|
We have to hardcode a width here, because otherwise some interaction with
|
||||||
Storybook creates an infinite loop with the internal width
|
Storybook creates an infinite loop with the internal width
|
||||||
|
@ -29,7 +29,7 @@ could be continuous, discrete or mixed.
|
||||||
<Story
|
<Story
|
||||||
name="Continuous Symbolic"
|
name="Continuous Symbolic"
|
||||||
args={{
|
args={{
|
||||||
squiggleString: "normal(5,2)",
|
code: "normal(5,2)",
|
||||||
width,
|
width,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -43,7 +43,7 @@ could be continuous, discrete or mixed.
|
||||||
<Story
|
<Story
|
||||||
name="Continuous Pointset"
|
name="Continuous Pointset"
|
||||||
args={{
|
args={{
|
||||||
squiggleString: "toPointSet(normal(5,2))",
|
code: "PointSet.fromDist(normal(5,2))",
|
||||||
width,
|
width,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -57,7 +57,7 @@ could be continuous, discrete or mixed.
|
||||||
<Story
|
<Story
|
||||||
name="Continuous SampleSet"
|
name="Continuous SampleSet"
|
||||||
args={{
|
args={{
|
||||||
squiggleString: "toSampleSet(normal(5,2), 1000)",
|
code: "SampleSet.fromDist(normal(5,2))",
|
||||||
width,
|
width,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -71,7 +71,23 @@ could be continuous, discrete or mixed.
|
||||||
<Story
|
<Story
|
||||||
name="Discrete"
|
name="Discrete"
|
||||||
args={{
|
args={{
|
||||||
squiggleString: "mx(0, 1, 3, 5, 8, 10, [0.1, 0.8, 0.5, 0.3, 0.2, 0.1])",
|
code: "mx(0, 1, 3, 5, 8, 10, [0.1, 0.8, 0.5, 0.3, 0.2, 0.1])",
|
||||||
|
width,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Template.bind({})}
|
||||||
|
</Story>
|
||||||
|
</Canvas>
|
||||||
|
|
||||||
|
### Date Distribution
|
||||||
|
|
||||||
|
<Canvas>
|
||||||
|
<Story
|
||||||
|
name="Date Distribution"
|
||||||
|
args={{
|
||||||
|
code: "mx(1661819770311, 1661829770311, 1661839770311)",
|
||||||
|
width,
|
||||||
|
xAxisType: "dateTime",
|
||||||
width,
|
width,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -85,8 +101,34 @@ could be continuous, discrete or mixed.
|
||||||
<Story
|
<Story
|
||||||
name="Mixed"
|
name="Mixed"
|
||||||
args={{
|
args={{
|
||||||
squiggleString:
|
code: "mx(0, 1, 3, 5, 8, normal(8, 1), [0.1, 0.3, 0.4, 0.35, 0.2, 0.8])",
|
||||||
"mx(0, 1, 3, 5, 8, normal(8, 1), [0.1, 0.3, 0.4, 0.35, 0.2, 0.8])",
|
width,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Template.bind({})}
|
||||||
|
</Story>
|
||||||
|
</Canvas>
|
||||||
|
|
||||||
|
## Multiple plots
|
||||||
|
|
||||||
|
<Canvas>
|
||||||
|
<Story
|
||||||
|
name="Multiple plots"
|
||||||
|
args={{
|
||||||
|
code: `
|
||||||
|
{
|
||||||
|
distributions: [
|
||||||
|
{
|
||||||
|
name: "one",
|
||||||
|
distribution: mx(0.5, normal(0,1))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "two",
|
||||||
|
distribution: mx(2, normal(5, 2)),
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
`,
|
||||||
width,
|
width,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -103,7 +145,7 @@ to allow large and small numbers being printed cleanly.
|
||||||
<Story
|
<Story
|
||||||
name="Constant"
|
name="Constant"
|
||||||
args={{
|
args={{
|
||||||
squiggleString: "500000000",
|
code: "500000000",
|
||||||
width,
|
width,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -117,7 +159,7 @@ to allow large and small numbers being printed cleanly.
|
||||||
<Story
|
<Story
|
||||||
name="Array"
|
name="Array"
|
||||||
args={{
|
args={{
|
||||||
squiggleString: "[normal(5,2), normal(10,1), normal(40,2), 400000]",
|
code: "[normal(5,2), normal(10,1), normal(40,2), 400000]",
|
||||||
width,
|
width,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -131,7 +173,7 @@ to allow large and small numbers being printed cleanly.
|
||||||
<Story
|
<Story
|
||||||
name="Error"
|
name="Error"
|
||||||
args={{
|
args={{
|
||||||
squiggleString: "f(x) = normal(",
|
code: "f(x) = normal(",
|
||||||
width,
|
width,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -145,7 +187,7 @@ to allow large and small numbers being printed cleanly.
|
||||||
<Story
|
<Story
|
||||||
name="Boolean"
|
name="Boolean"
|
||||||
args={{
|
args={{
|
||||||
squiggleString: "3 == 3",
|
code: "3 == 3",
|
||||||
width,
|
width,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -159,7 +201,7 @@ to allow large and small numbers being printed cleanly.
|
||||||
<Story
|
<Story
|
||||||
name="Function to Distribution"
|
name="Function to Distribution"
|
||||||
args={{
|
args={{
|
||||||
squiggleString: "foo(t) = normal(t,2)*normal(5,3); foo",
|
code: "foo(t) = normal(t,2)*normal(5,3); foo",
|
||||||
width,
|
width,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -173,7 +215,7 @@ to allow large and small numbers being printed cleanly.
|
||||||
<Story
|
<Story
|
||||||
name="Function to Number"
|
name="Function to Number"
|
||||||
args={{
|
args={{
|
||||||
squiggleString: "foo(t) = t^2; foo",
|
code: "foo(t) = t^2; foo",
|
||||||
width,
|
width,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -187,7 +229,7 @@ to allow large and small numbers being printed cleanly.
|
||||||
<Story
|
<Story
|
||||||
name="Record"
|
name="Record"
|
||||||
args={{
|
args={{
|
||||||
squiggleString: "{foo: 35 to 50, bar: [1,2,3]}",
|
code: "{foo: 35 to 50, bar: [1,2,3]}",
|
||||||
width,
|
width,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -201,7 +243,7 @@ to allow large and small numbers being printed cleanly.
|
||||||
<Story
|
<Story
|
||||||
name="String"
|
name="String"
|
||||||
args={{
|
args={{
|
||||||
squiggleString: '"Lucky day!"',
|
code: '"Lucky day!"',
|
||||||
width,
|
width,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
@ -14,7 +14,20 @@ the distribution.
|
||||||
<Story
|
<Story
|
||||||
name="Normal"
|
name="Normal"
|
||||||
args={{
|
args={{
|
||||||
initialSquiggleString: "normal(5,2)",
|
defaultCode: "normal(5,2)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Template.bind({})}
|
||||||
|
</Story>
|
||||||
|
</Canvas>
|
||||||
|
|
||||||
|
It's also possible to create a controlled version of the same component
|
||||||
|
|
||||||
|
<Canvas>
|
||||||
|
<Story
|
||||||
|
name="Controlled"
|
||||||
|
args={{
|
||||||
|
code: "normal(5,2)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{Template.bind({})}
|
{Template.bind({})}
|
||||||
|
@ -27,7 +40,7 @@ You can also name variables like so:
|
||||||
<Story
|
<Story
|
||||||
name="Variables"
|
name="Variables"
|
||||||
args={{
|
args={{
|
||||||
initialSquiggleString: "x = 2\nnormal(x,2)",
|
defaultCode: "x = 2\nnormal(x,2)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{Template.bind({})}
|
{Template.bind({})}
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
import { SquigglePartial, SquiggleEditor } from "../components/SquiggleEditor";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { Canvas, Meta, Story, Props } from "@storybook/addon-docs";
|
|
||||||
|
|
||||||
<Meta title="Squiggle/SquigglePartial" component={SquigglePartial} />
|
|
||||||
|
|
||||||
export const Template = (props) => <SquigglePartial {...props} />;
|
|
||||||
|
|
||||||
# Squiggle Partial
|
|
||||||
|
|
||||||
A Squiggle Partial is an editor that does not return a graph to the user, but
|
|
||||||
instead returns bindings that can be used by further Squiggle Editors.
|
|
||||||
|
|
||||||
<Canvas>
|
|
||||||
<Story
|
|
||||||
name="Standalone"
|
|
||||||
args={{
|
|
||||||
initialSquiggleString: "x = normal(5,2)",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{Template.bind({})}
|
|
||||||
</Story>
|
|
||||||
</Canvas>
|
|
||||||
|
|
||||||
<Canvas>
|
|
||||||
<Story
|
|
||||||
name="With Editor"
|
|
||||||
args={{
|
|
||||||
initialPartialString: "x = normal(5,2)",
|
|
||||||
initialEditorString: "x",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{(props) => {
|
|
||||||
let [bindings, setBindings] = useState({});
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<SquigglePartial
|
|
||||||
{...props}
|
|
||||||
initialSquiggleString={props.initialPartialString}
|
|
||||||
onChange={setBindings}
|
|
||||||
/>
|
|
||||||
<SquiggleEditor
|
|
||||||
{...props}
|
|
||||||
initialSquiggleString={props.initialEditorString}
|
|
||||||
bindings={bindings}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</Story>
|
|
||||||
</Canvas>
|
|
|
@ -14,10 +14,23 @@ including sampling settings, in squiggle.
|
||||||
<Story
|
<Story
|
||||||
name="Normal"
|
name="Normal"
|
||||||
args={{
|
args={{
|
||||||
initialSquiggleString: "normal(5,2)",
|
defaultCode: "normal(5,2)",
|
||||||
height: 800,
|
height: 800,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{Template.bind({})}
|
{Template.bind({})}
|
||||||
</Story>
|
</Story>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
|
|
||||||
|
<Canvas>
|
||||||
|
<Story
|
||||||
|
name="With share button"
|
||||||
|
args={{
|
||||||
|
defaultCode: "normal(5,2)",
|
||||||
|
height: 800,
|
||||||
|
showShareButton: true,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Template.bind({})}
|
||||||
|
</Story>
|
||||||
|
</Canvas>
|
||||||
|
|
|
@ -22,3 +22,8 @@ but this line is still necessary for proper initialization of `--tw-*` variables
|
||||||
.ace_cursor {
|
.ace_cursor {
|
||||||
border-left: 2px solid !important;
|
border-left: 2px solid !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ace-error-marker {
|
||||||
|
position: absolute;
|
||||||
|
border-bottom: 1px solid red;
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,27 @@ module.exports = {
|
||||||
},
|
},
|
||||||
important: ".squiggle",
|
important: ".squiggle",
|
||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {
|
||||||
|
animation: {
|
||||||
|
"appear-and-spin":
|
||||||
|
"spin 1s linear infinite, squiggle-appear 0.2s forwards",
|
||||||
|
"semi-appear": "squiggle-semi-appear 0.2s forwards",
|
||||||
|
hide: "squiggle-hide 0.2s forwards",
|
||||||
|
},
|
||||||
|
keyframes: {
|
||||||
|
"squiggle-appear": {
|
||||||
|
from: { opacity: 0 },
|
||||||
|
to: { opacity: 1 },
|
||||||
|
},
|
||||||
|
"squiggle-semi-appear": {
|
||||||
|
from: { opacity: 0 },
|
||||||
|
to: { opacity: 0.5 },
|
||||||
|
},
|
||||||
|
"squiggle-hide": {
|
||||||
|
from: { opacity: 1 },
|
||||||
|
to: { opacity: 0 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
55
packages/components/test/autorun.test.tsx
Normal file
55
packages/components/test/autorun.test.tsx
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
import { render, screen, waitFor, within } from "@testing-library/react";
|
||||||
|
import userEvent from "@testing-library/user-event";
|
||||||
|
import * as React from "react";
|
||||||
|
import "@testing-library/jest-dom";
|
||||||
|
import { SquigglePlayground } from "../src/index";
|
||||||
|
|
||||||
|
test("Autorun is default", async () => {
|
||||||
|
render(<SquigglePlayground code="70*30" />);
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("playground-result")).toHaveTextContent("2100")
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Autorun can be switched off", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
render(<SquigglePlayground code="70*30" />);
|
||||||
|
|
||||||
|
expect(screen.getByTestId("autorun-controls")).toHaveTextContent("Autorun");
|
||||||
|
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("playground-result")).toHaveTextContent("2100")
|
||||||
|
);
|
||||||
|
|
||||||
|
await user.click(screen.getByText("Autorun")); // disable
|
||||||
|
expect(screen.getByTestId("autorun-controls")).toHaveTextContent("Paused");
|
||||||
|
expect(screen.getByTestId("autorun-controls")).not.toHaveTextContent(
|
||||||
|
"Autorun"
|
||||||
|
);
|
||||||
|
|
||||||
|
await user.click(screen.getByText("Paused")); // enable autorun again
|
||||||
|
expect(screen.getByTestId("autorun-controls")).toHaveTextContent("Autorun");
|
||||||
|
|
||||||
|
// we should replace the code here, but it's hard to update react-ace state via user events: https://github.com/securingsincity/react-ace/issues/923
|
||||||
|
// ...or replace react-ace with something else
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
|
||||||
|
/*
|
||||||
|
const editor = screen
|
||||||
|
.getByTestId("squiggle-editor")
|
||||||
|
.querySelector(".ace_editor") as HTMLElement;
|
||||||
|
editor.focus();
|
||||||
|
// await user.clear(editor);
|
||||||
|
await userEvent.paste("40*40"); // https://github.com/securingsincity/react-ace/issues/923#issuecomment-755502696
|
||||||
|
screen.debug(editor);
|
||||||
|
|
||||||
|
// this makes the tests slower, but it's hard to test otherwise that the code _didn't_ execute
|
||||||
|
await new Promise((r) => setTimeout(r, 300));
|
||||||
|
expect(screen.getByTestId("playground-result")).toHaveTextContent("2100"); // still the old value
|
||||||
|
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("playground-result")).toHaveTextContent("1600")
|
||||||
|
);
|
||||||
|
*/
|
||||||
|
});
|
53
packages/components/test/basic.test.tsx
Normal file
53
packages/components/test/basic.test.tsx
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
import { render, screen } from "@testing-library/react";
|
||||||
|
import React from "react";
|
||||||
|
import "@testing-library/jest-dom";
|
||||||
|
import {
|
||||||
|
SquiggleChart,
|
||||||
|
SquiggleEditor,
|
||||||
|
SquigglePlayground,
|
||||||
|
} from "../src/index";
|
||||||
|
import { SqProject } from "@quri/squiggle-lang";
|
||||||
|
|
||||||
|
test("Chart logs nothing on render", async () => {
|
||||||
|
const { unmount } = render(<SquiggleChart code={"normal(0, 1)"} />);
|
||||||
|
unmount();
|
||||||
|
|
||||||
|
expect(console.log).not.toBeCalled();
|
||||||
|
expect(console.warn).not.toBeCalled();
|
||||||
|
expect(console.error).not.toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Editor logs nothing on render", async () => {
|
||||||
|
const { unmount } = render(<SquiggleEditor code={"normal(0, 1)"} />);
|
||||||
|
unmount();
|
||||||
|
|
||||||
|
expect(console.log).not.toBeCalled();
|
||||||
|
expect(console.warn).not.toBeCalled();
|
||||||
|
expect(console.error).not.toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Project dependencies work in editors", async () => {
|
||||||
|
const project = SqProject.create();
|
||||||
|
|
||||||
|
render(<SquiggleEditor code={"x = 1"} project={project} />);
|
||||||
|
const source = project.getSourceIds()[0];
|
||||||
|
const { container } = render(
|
||||||
|
<SquiggleEditor code={"x + 1"} project={project} continues={[source]} />
|
||||||
|
);
|
||||||
|
expect(container).toHaveTextContent("2");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Project dependencies work in playgrounds", async () => {
|
||||||
|
const project = SqProject.create();
|
||||||
|
project.setSource("depend", "x = 1");
|
||||||
|
|
||||||
|
render(
|
||||||
|
<SquigglePlayground
|
||||||
|
code={"x + 1"}
|
||||||
|
project={project}
|
||||||
|
continues={["depend"]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
// We must await here because SquigglePlayground loads results asynchronously
|
||||||
|
expect(await screen.findByRole("status")).toHaveTextContent("2");
|
||||||
|
});
|
39
packages/components/test/cleanup.test.tsx
Normal file
39
packages/components/test/cleanup.test.tsx
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import { render } from "@testing-library/react";
|
||||||
|
import React from "react";
|
||||||
|
import "@testing-library/jest-dom";
|
||||||
|
import { SquiggleChart } from "../src/index";
|
||||||
|
import { SqProject } from "@quri/squiggle-lang";
|
||||||
|
|
||||||
|
test("Creates and cleans up source", async () => {
|
||||||
|
const project = SqProject.create();
|
||||||
|
|
||||||
|
const { unmount } = render(
|
||||||
|
<SquiggleChart code={"normal(0, 1)"} project={project} />
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(project.getSourceIds().length).toBe(1);
|
||||||
|
|
||||||
|
const sourceId = project.getSourceIds()[0];
|
||||||
|
expect(project.getSource(sourceId)).toBe("normal(0, 1)");
|
||||||
|
|
||||||
|
unmount();
|
||||||
|
expect(project.getSourceIds().length).toBe(0);
|
||||||
|
expect(project.getSource(sourceId)).toBe(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Creates and cleans up source and imports", async () => {
|
||||||
|
const project = SqProject.create();
|
||||||
|
|
||||||
|
const { unmount } = render(
|
||||||
|
<SquiggleChart
|
||||||
|
code={"normal($x, 1)"}
|
||||||
|
project={project}
|
||||||
|
jsImports={{ x: 3 }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(project.getSourceIds().length).toBe(2);
|
||||||
|
|
||||||
|
unmount();
|
||||||
|
expect(project.getSourceIds()).toStrictEqual([]);
|
||||||
|
});
|
8
packages/components/test/setup.js
Normal file
8
packages/components/test/setup.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
global.console = {
|
||||||
|
...console,
|
||||||
|
log: jest.fn(console.log),
|
||||||
|
debug: jest.fn(console.debug),
|
||||||
|
info: jest.fn(console.info),
|
||||||
|
warn: jest.fn(console.warn),
|
||||||
|
error: jest.fn(console.error),
|
||||||
|
};
|
4
packages/components/vercel.json
Normal file
4
packages/components/vercel.json
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"buildCommand": "cd ../.. && npx turbo run build --filter=@quri/squiggle-components",
|
||||||
|
"outputDirectory": "storybook-static"
|
||||||
|
}
|
2
packages/squiggle-lang/.gitignore
vendored
2
packages/squiggle-lang/.gitignore
vendored
|
@ -22,3 +22,5 @@ _coverage
|
||||||
coverage
|
coverage
|
||||||
.nyc_output/
|
.nyc_output/
|
||||||
src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_GeneratedParser.js
|
src/rescript/Reducer/Reducer_Peggy/Reducer_Peggy_GeneratedParser.js
|
||||||
|
src/rescript/Reducer/Reducer_Peggy/helpers.js
|
||||||
|
src/rescript/ReducerProject/ReducerProject_IncludeParser.js
|
||||||
|
|
|
@ -3,6 +3,8 @@ lib
|
||||||
*.bs.js
|
*.bs.js
|
||||||
*.gen.tsx
|
*.gen.tsx
|
||||||
.nyc_output/
|
.nyc_output/
|
||||||
_coverage/
|
coverage/
|
||||||
.cache/
|
.cache/
|
||||||
Reducer_Peggy_GeneratedParser.js
|
Reducer_Peggy_GeneratedParser.js
|
||||||
|
ReducerProject_IncludeParser.js
|
||||||
|
src/rescript/Reducer/Reducer_Peggy/helpers.js
|
||||||
|
|
|
@ -20,7 +20,7 @@ environment created from the squiggle code.
|
||||||
```js
|
```js
|
||||||
import { run } from "@quri/squiggle-lang";
|
import { run } from "@quri/squiggle-lang";
|
||||||
run(
|
run(
|
||||||
"normal(0, 1) * fromSamples([-3,-2,-1,1,2,3,3,3,4,9]"
|
"normal(0, 1) * SampleSet.fromList([-3, 2,-1,1,2,3,3,3,4,9])"
|
||||||
).value.value.toSparkline().value;
|
).value.value.toSparkline().value;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
open Jest
|
open Jest
|
||||||
open Expect
|
open Expect
|
||||||
|
|
||||||
let env: DistributionOperation.env = {
|
let env: GenericDist.env = {
|
||||||
sampleCount: 100,
|
sampleCount: 100,
|
||||||
xyPointLength: 100,
|
xyPointLength: 100,
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ describe("sparkline", () => {
|
||||||
expected: DistributionOperation.outputType,
|
expected: DistributionOperation.outputType,
|
||||||
) => {
|
) => {
|
||||||
test(name, () => {
|
test(name, () => {
|
||||||
let result = DistributionOperation.run(~env, FromDist(ToString(ToSparkline(20)), dist))
|
let result = DistributionOperation.run(~env, FromDist(#ToString(ToSparkline(20)), dist))
|
||||||
expect(result)->toEqual(expected)
|
expect(result)->toEqual(expected)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -81,8 +81,8 @@ describe("sparkline", () => {
|
||||||
describe("toPointSet", () => {
|
describe("toPointSet", () => {
|
||||||
test("on symbolic normal distribution", () => {
|
test("on symbolic normal distribution", () => {
|
||||||
let result =
|
let result =
|
||||||
run(FromDist(ToDist(ToPointSet), normalDist5))
|
run(FromDist(#ToDist(ToPointSet), normalDist5))
|
||||||
->outputMap(FromDist(ToFloat(#Mean)))
|
->outputMap(FromDist(#ToFloat(#Mean)))
|
||||||
->toFloat
|
->toFloat
|
||||||
->toExt
|
->toExt
|
||||||
expect(result)->toBeSoCloseTo(5.0, ~digits=0)
|
expect(result)->toBeSoCloseTo(5.0, ~digits=0)
|
||||||
|
@ -90,10 +90,10 @@ describe("toPointSet", () => {
|
||||||
|
|
||||||
test("on sample set", () => {
|
test("on sample set", () => {
|
||||||
let result =
|
let result =
|
||||||
run(FromDist(ToDist(ToPointSet), normalDist5))
|
run(FromDist(#ToDist(ToPointSet), normalDist5))
|
||||||
->outputMap(FromDist(ToDist(ToSampleSet(1000))))
|
->outputMap(FromDist(#ToDist(ToSampleSet(1000))))
|
||||||
->outputMap(FromDist(ToDist(ToPointSet)))
|
->outputMap(FromDist(#ToDist(ToPointSet)))
|
||||||
->outputMap(FromDist(ToFloat(#Mean)))
|
->outputMap(FromDist(#ToFloat(#Mean)))
|
||||||
->toFloat
|
->toFloat
|
||||||
->toExt
|
->toExt
|
||||||
expect(result)->toBeSoCloseTo(5.0, ~digits=-1)
|
expect(result)->toBeSoCloseTo(5.0, ~digits=-1)
|
||||||
|
|
|
@ -32,25 +32,29 @@ describe("dotSubtract", () => {
|
||||||
*/
|
*/
|
||||||
Skip.test("mean of normal minus exponential (property)", () => {
|
Skip.test("mean of normal minus exponential (property)", () => {
|
||||||
assert_(
|
assert_(
|
||||||
property2(float_(), floatRange(1e-5, 1e5), (mean, rate) => {
|
property2(
|
||||||
// We limit ourselves to stdev=1 so that the integral is trivial
|
float_(),
|
||||||
let dotDifference = DistributionOperation.Constructors.pointwiseSubtract(
|
floatRange(1e-5, 1e5),
|
||||||
~env,
|
(mean, rate) => {
|
||||||
mkNormal(mean, 1.0),
|
// We limit ourselves to stdev=1 so that the integral is trivial
|
||||||
mkExponential(rate),
|
let dotDifference = DistributionOperation.Constructors.pointwiseSubtract(
|
||||||
)
|
~env,
|
||||||
let meanResult = E.R2.bind(DistributionOperation.Constructors.mean(~env), dotDifference)
|
mkNormal(mean, 1.0),
|
||||||
// according to algebra or random variables,
|
mkExponential(rate),
|
||||||
let meanAnalytical =
|
|
||||||
mean -.
|
|
||||||
SymbolicDist.Exponential.mean({rate: rate})->E.R2.toExn(
|
|
||||||
"On trusted input this should never happen",
|
|
||||||
)
|
)
|
||||||
switch meanResult {
|
let meanResult = E.R2.bind(DistributionOperation.Constructors.mean(~env), dotDifference)
|
||||||
| Ok(meanValue) => abs_float(meanValue -. meanAnalytical) /. abs_float(meanValue) < 1e-2 // 1% relative error
|
// according to algebra or random variables,
|
||||||
| Error(err) => err === DistributionTypes.OperationError(DivisionByZeroError)
|
let meanAnalytical =
|
||||||
}
|
mean -.
|
||||||
}),
|
SymbolicDist.Exponential.mean({rate: rate})->E.R2.toExn(
|
||||||
|
"On trusted input this should never happen",
|
||||||
|
)
|
||||||
|
switch meanResult {
|
||||||
|
| Ok(meanValue) => abs_float(meanValue -. meanAnalytical) /. abs_float(meanValue) < 1e-2 // 1% relative error
|
||||||
|
| Error(err) => err === DistributionTypes.OperationError(DivisionByZeroError)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
)
|
)
|
||||||
pass
|
pass
|
||||||
})
|
})
|
||||||
|
|
|
@ -19,7 +19,6 @@ exception MixtureFailed
|
||||||
let float1 = 1.0
|
let float1 = 1.0
|
||||||
let float2 = 2.0
|
let float2 = 2.0
|
||||||
let float3 = 3.0
|
let float3 = 3.0
|
||||||
let {mkDelta} = module(TestHelpers)
|
let point1 = TestHelpers.mkDelta(float1)
|
||||||
let point1 = mkDelta(float1)
|
let point2 = TestHelpers.mkDelta(float2)
|
||||||
let point2 = mkDelta(float2)
|
let point3 = TestHelpers.mkDelta(float3)
|
||||||
let point3 = mkDelta(float3)
|
|
||||||
|
|
|
@ -40,51 +40,60 @@ let algebraicPower = algebraicPower(~env)
|
||||||
|
|
||||||
describe("(Algebraic) addition of distributions", () => {
|
describe("(Algebraic) addition of distributions", () => {
|
||||||
describe("mean", () => {
|
describe("mean", () => {
|
||||||
test("normal(mean=5) + normal(mean=20)", () => {
|
test(
|
||||||
normalDist5
|
"normal(mean=5) + normal(mean=20)",
|
||||||
->algebraicAdd(normalDist20)
|
() => {
|
||||||
->E.R2.fmap(DistributionTypes.Constructors.UsingDists.mean)
|
normalDist5
|
||||||
->E.R2.fmap(run)
|
->algebraicAdd(normalDist20)
|
||||||
->E.R2.fmap(toFloat)
|
->E.R2.fmap(DistributionTypes.Constructors.UsingDists.mean)
|
||||||
->E.R.toExn("Expected float", _)
|
->E.R2.fmap(run)
|
||||||
->expect
|
->E.R2.fmap(toFloat)
|
||||||
->toBe(Some(2.5e1))
|
->E.R.toExn("Expected float", _)
|
||||||
})
|
->expect
|
||||||
|
->toBe(Some(2.5e1))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
test("uniform(low=9, high=10) + beta(alpha=2, beta=5)", () => {
|
test(
|
||||||
// let uniformMean = (9.0 +. 10.0) /. 2.0
|
"uniform(low=9, high=10) + beta(alpha=2, beta=5)",
|
||||||
// let betaMean = 1.0 /. (1.0 +. 5.0 /. 2.0)
|
() => {
|
||||||
let received =
|
// let uniformMean = (9.0 +. 10.0) /. 2.0
|
||||||
uniformDist
|
// let betaMean = 1.0 /. (1.0 +. 5.0 /. 2.0)
|
||||||
->algebraicAdd(betaDist)
|
let received =
|
||||||
->E.R2.fmap(DistributionTypes.Constructors.UsingDists.mean)
|
uniformDist
|
||||||
->E.R2.fmap(run)
|
->algebraicAdd(betaDist)
|
||||||
->E.R2.fmap(toFloat)
|
->E.R2.fmap(DistributionTypes.Constructors.UsingDists.mean)
|
||||||
->E.R.toExn("Expected float", _)
|
->E.R2.fmap(run)
|
||||||
switch received {
|
->E.R2.fmap(toFloat)
|
||||||
| None => "algebraicAdd has"->expect->toBe("failed")
|
->E.R.toExn("Expected float", _)
|
||||||
// This is nondeterministic, we could be in a situation where ci fails but you click rerun and it passes, which is bad.
|
switch received {
|
||||||
// sometimes it works with ~digits=2.
|
| None => "algebraicAdd has"->expect->toBe("failed")
|
||||||
| Some(x) => x->expect->toBeSoCloseTo(9.786831807237022, ~digits=1) // (uniformMean +. betaMean)
|
// This is nondeterministic, we could be in a situation where ci fails but you click rerun and it passes, which is bad.
|
||||||
}
|
// sometimes it works with ~digits=2.
|
||||||
})
|
| Some(x) => x->expect->toBeSoCloseTo(9.786831807237022, ~digits=1) // (uniformMean +. betaMean)
|
||||||
test("beta(alpha=2, beta=5) + uniform(low=9, high=10)", () => {
|
}
|
||||||
// let uniformMean = (9.0 +. 10.0) /. 2.0
|
},
|
||||||
// let betaMean = 1.0 /. (1.0 +. 5.0 /. 2.0)
|
)
|
||||||
let received =
|
test(
|
||||||
betaDist
|
"beta(alpha=2, beta=5) + uniform(low=9, high=10)",
|
||||||
->algebraicAdd(uniformDist)
|
() => {
|
||||||
->E.R2.fmap(DistributionTypes.Constructors.UsingDists.mean)
|
// let uniformMean = (9.0 +. 10.0) /. 2.0
|
||||||
->E.R2.fmap(run)
|
// let betaMean = 1.0 /. (1.0 +. 5.0 /. 2.0)
|
||||||
->E.R2.fmap(toFloat)
|
let received =
|
||||||
->E.R.toExn("Expected float", _)
|
betaDist
|
||||||
switch received {
|
->algebraicAdd(uniformDist)
|
||||||
| None => "algebraicAdd has"->expect->toBe("failed")
|
->E.R2.fmap(DistributionTypes.Constructors.UsingDists.mean)
|
||||||
// This is nondeterministic, we could be in a situation where ci fails but you click rerun and it passes, which is bad.
|
->E.R2.fmap(run)
|
||||||
// sometimes it works with ~digits=2.
|
->E.R2.fmap(toFloat)
|
||||||
| Some(x) => x->expect->toBeSoCloseTo(9.784290207736126, ~digits=1) // (uniformMean +. betaMean)
|
->E.R.toExn("Expected float", _)
|
||||||
}
|
switch received {
|
||||||
})
|
| None => "algebraicAdd has"->expect->toBe("failed")
|
||||||
|
// This is nondeterministic, we could be in a situation where ci fails but you click rerun and it passes, which is bad.
|
||||||
|
// sometimes it works with ~digits=2.
|
||||||
|
| Some(x) => x->expect->toBeSoCloseTo(9.784290207736126, ~digits=1) // (uniformMean +. betaMean)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
})
|
})
|
||||||
describe("pdf", () => {
|
describe("pdf", () => {
|
||||||
// TEST IS WRONG. SEE STDEV ADDITION EXPRESSION.
|
// TEST IS WRONG. SEE STDEV ADDITION EXPRESSION.
|
||||||
|
@ -122,247 +131,282 @@ describe("(Algebraic) addition of distributions", () => {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
test("(normal(mean=10) + normal(mean=10)).pdf(1.9e1)", () => {
|
test(
|
||||||
let received =
|
"(normal(mean=10) + normal(mean=10)).pdf(1.9e1)",
|
||||||
normalDist20
|
() => {
|
||||||
->Ok
|
let received =
|
||||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.pdf(d, 1.9e1))
|
normalDist20
|
||||||
->E.R2.fmap(run)
|
->Ok
|
||||||
->E.R2.fmap(toFloat)
|
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.pdf(d, 1.9e1))
|
||||||
->E.R.toOption
|
->E.R2.fmap(run)
|
||||||
->E.O.flatten
|
->E.R2.fmap(toFloat)
|
||||||
let calculated =
|
->E.R.toOption
|
||||||
normalDist10
|
->E.O.flatten
|
||||||
->algebraicAdd(normalDist10)
|
let calculated =
|
||||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.pdf(d, 1.9e1))
|
normalDist10
|
||||||
->E.R2.fmap(run)
|
->algebraicAdd(normalDist10)
|
||||||
->E.R2.fmap(toFloat)
|
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.pdf(d, 1.9e1))
|
||||||
->E.R.toOption
|
->E.R2.fmap(run)
|
||||||
->E.O.flatten
|
->E.R2.fmap(toFloat)
|
||||||
switch received {
|
->E.R.toOption
|
||||||
| None =>
|
->E.O.flatten
|
||||||
"this branch occurs when the dispatch to Jstat on trusted input fails."
|
switch received {
|
||||||
->expect
|
| None =>
|
||||||
->toBe("never")
|
"this branch occurs when the dispatch to Jstat on trusted input fails."
|
||||||
| Some(x) =>
|
->expect
|
||||||
switch calculated {
|
->toBe("never")
|
||||||
| None => "algebraicAdd has"->expect->toBe("failed")
|
| Some(x) =>
|
||||||
| Some(y) => x->expect->toBeSoCloseTo(y, ~digits=1)
|
switch calculated {
|
||||||
|
| None => "algebraicAdd has"->expect->toBe("failed")
|
||||||
|
| Some(y) => x->expect->toBeSoCloseTo(y, ~digits=1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
})
|
)
|
||||||
test("(uniform(low=9, high=10) + beta(alpha=2, beta=5)).pdf(10)", () => {
|
test(
|
||||||
let received =
|
"(uniform(low=9, high=10) + beta(alpha=2, beta=5)).pdf(10)",
|
||||||
uniformDist
|
() => {
|
||||||
->algebraicAdd(betaDist)
|
let received =
|
||||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.pdf(d, 1e1))
|
uniformDist
|
||||||
->E.R2.fmap(run)
|
->algebraicAdd(betaDist)
|
||||||
->E.R2.fmap(toFloat)
|
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.pdf(d, 1e1))
|
||||||
->E.R.toExn("Expected float", _)
|
->E.R2.fmap(run)
|
||||||
switch received {
|
->E.R2.fmap(toFloat)
|
||||||
| None => "algebraicAdd has"->expect->toBe("failed")
|
->E.R.toExn("Expected float", _)
|
||||||
// This is nondeterministic, we could be in a situation where ci fails but you click rerun and it passes, which is bad.
|
switch received {
|
||||||
// sometimes it works with ~digits=4.
|
| None => "algebraicAdd has"->expect->toBe("failed")
|
||||||
// This value was calculated by a python script
|
// This is nondeterministic, we could be in a situation where ci fails but you click rerun and it passes, which is bad.
|
||||||
| Some(x) => x->expect->toBeSoCloseTo(0.979023, ~digits=0)
|
// sometimes it works with ~digits=4.
|
||||||
}
|
// This value was calculated by a python script
|
||||||
})
|
| Some(x) => x->expect->toBeSoCloseTo(0.979023, ~digits=0)
|
||||||
test("(beta(alpha=2, beta=5) + uniform(low=9, high=10)).pdf(10)", () => {
|
}
|
||||||
let received =
|
},
|
||||||
betaDist
|
)
|
||||||
->algebraicAdd(uniformDist)
|
test(
|
||||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.pdf(d, 1e1))
|
"(beta(alpha=2, beta=5) + uniform(low=9, high=10)).pdf(10)",
|
||||||
->E.R2.fmap(run)
|
() => {
|
||||||
->E.R2.fmap(toFloat)
|
let received =
|
||||||
->E.R.toExn("Expected float", _)
|
betaDist
|
||||||
switch received {
|
->algebraicAdd(uniformDist)
|
||||||
| None => "algebraicAdd has"->expect->toBe("failed")
|
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.pdf(d, 1e1))
|
||||||
// This is nondeterministic.
|
->E.R2.fmap(run)
|
||||||
| Some(x) => x->expect->toBeSoCloseTo(0.979023, ~digits=0)
|
->E.R2.fmap(toFloat)
|
||||||
}
|
->E.R.toExn("Expected float", _)
|
||||||
})
|
switch received {
|
||||||
|
| None => "algebraicAdd has"->expect->toBe("failed")
|
||||||
|
// This is nondeterministic.
|
||||||
|
| Some(x) => x->expect->toBeSoCloseTo(0.979023, ~digits=0)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
})
|
})
|
||||||
describe("cdf", () => {
|
describe("cdf", () => {
|
||||||
testAll("(normal(mean=5) + normal(mean=5)).cdf (imprecise)", list{6e0, 8e0, 1e1, 1.2e1}, x => {
|
testAll(
|
||||||
let received =
|
"(normal(mean=5) + normal(mean=5)).cdf (imprecise)",
|
||||||
normalDist10
|
list{6e0, 8e0, 1e1, 1.2e1},
|
||||||
->Ok
|
x => {
|
||||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.cdf(d, x))
|
let received =
|
||||||
->E.R2.fmap(run)
|
normalDist10
|
||||||
->E.R2.fmap(toFloat)
|
->Ok
|
||||||
->E.R.toOption
|
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.cdf(d, x))
|
||||||
->E.O.flatten
|
->E.R2.fmap(run)
|
||||||
let calculated =
|
->E.R2.fmap(toFloat)
|
||||||
normalDist5
|
->E.R.toOption
|
||||||
->algebraicAdd(normalDist5)
|
->E.O.flatten
|
||||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.cdf(d, x))
|
let calculated =
|
||||||
->E.R2.fmap(run)
|
normalDist5
|
||||||
->E.R2.fmap(toFloat)
|
->algebraicAdd(normalDist5)
|
||||||
->E.R.toOption
|
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.cdf(d, x))
|
||||||
->E.O.flatten
|
->E.R2.fmap(run)
|
||||||
|
->E.R2.fmap(toFloat)
|
||||||
|
->E.R.toOption
|
||||||
|
->E.O.flatten
|
||||||
|
|
||||||
switch received {
|
switch received {
|
||||||
| None =>
|
| None =>
|
||||||
"this branch occurs when the dispatch to Jstat on trusted input fails."
|
"this branch occurs when the dispatch to Jstat on trusted input fails."
|
||||||
->expect
|
->expect
|
||||||
->toBe("never")
|
->toBe("never")
|
||||||
| Some(x) =>
|
| Some(x) =>
|
||||||
switch calculated {
|
switch calculated {
|
||||||
| None => "algebraicAdd has"->expect->toBe("failed")
|
| None => "algebraicAdd has"->expect->toBe("failed")
|
||||||
| Some(y) => x->expect->toBeSoCloseTo(y, ~digits=0)
|
| Some(y) => x->expect->toBeSoCloseTo(y, ~digits=0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
})
|
)
|
||||||
test("(normal(mean=10) + normal(mean=10)).cdf(1.25e1)", () => {
|
test(
|
||||||
let received =
|
"(normal(mean=10) + normal(mean=10)).cdf(1.25e1)",
|
||||||
normalDist20
|
() => {
|
||||||
->Ok
|
let received =
|
||||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.cdf(d, 1.25e1))
|
normalDist20
|
||||||
->E.R2.fmap(run)
|
->Ok
|
||||||
->E.R2.fmap(toFloat)
|
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.cdf(d, 1.25e1))
|
||||||
->E.R.toOption
|
->E.R2.fmap(run)
|
||||||
->E.O.flatten
|
->E.R2.fmap(toFloat)
|
||||||
let calculated =
|
->E.R.toOption
|
||||||
normalDist10
|
->E.O.flatten
|
||||||
->algebraicAdd(normalDist10)
|
let calculated =
|
||||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.cdf(d, 1.25e1))
|
normalDist10
|
||||||
->E.R2.fmap(run)
|
->algebraicAdd(normalDist10)
|
||||||
->E.R2.fmap(toFloat)
|
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.cdf(d, 1.25e1))
|
||||||
->E.R.toOption
|
->E.R2.fmap(run)
|
||||||
->E.O.flatten
|
->E.R2.fmap(toFloat)
|
||||||
switch received {
|
->E.R.toOption
|
||||||
| None =>
|
->E.O.flatten
|
||||||
"this branch occurs when the dispatch to Jstat on trusted input fails."
|
switch received {
|
||||||
->expect
|
| None =>
|
||||||
->toBe("never")
|
"this branch occurs when the dispatch to Jstat on trusted input fails."
|
||||||
| Some(x) =>
|
->expect
|
||||||
switch calculated {
|
->toBe("never")
|
||||||
| None => "algebraicAdd has"->expect->toBe("failed")
|
| Some(x) =>
|
||||||
| Some(y) => x->expect->toBeSoCloseTo(y, ~digits=2)
|
switch calculated {
|
||||||
|
| None => "algebraicAdd has"->expect->toBe("failed")
|
||||||
|
| Some(y) => x->expect->toBeSoCloseTo(y, ~digits=2)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
})
|
)
|
||||||
test("(uniform(low=9, high=10) + beta(alpha=2, beta=5)).cdf(10)", () => {
|
test(
|
||||||
let received =
|
"(uniform(low=9, high=10) + beta(alpha=2, beta=5)).cdf(10)",
|
||||||
uniformDist
|
() => {
|
||||||
->algebraicAdd(betaDist)
|
let received =
|
||||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.cdf(d, 1e1))
|
uniformDist
|
||||||
->E.R2.fmap(run)
|
->algebraicAdd(betaDist)
|
||||||
->E.R2.fmap(toFloat)
|
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.cdf(d, 1e1))
|
||||||
->E.R.toExn("Expected float", _)
|
->E.R2.fmap(run)
|
||||||
switch received {
|
->E.R2.fmap(toFloat)
|
||||||
| None => "algebraicAdd has"->expect->toBe("failed")
|
->E.R.toExn("Expected float", _)
|
||||||
// This is nondeterministic, we could be in a situation where ci fails but you click rerun and it passes, which is bad.
|
switch received {
|
||||||
// The value was calculated externally using a python script
|
| None => "algebraicAdd has"->expect->toBe("failed")
|
||||||
| Some(x) => x->expect->toBeSoCloseTo(0.71148, ~digits=1)
|
// This is nondeterministic, we could be in a situation where ci fails but you click rerun and it passes, which is bad.
|
||||||
}
|
// The value was calculated externally using a python script
|
||||||
})
|
| Some(x) => x->expect->toBeSoCloseTo(0.71148, ~digits=1)
|
||||||
test("(beta(alpha=2, beta=5) + uniform(low=9, high=10)).cdf(10)", () => {
|
}
|
||||||
let received =
|
},
|
||||||
betaDist
|
)
|
||||||
->algebraicAdd(uniformDist)
|
test(
|
||||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.cdf(d, 1e1))
|
"(beta(alpha=2, beta=5) + uniform(low=9, high=10)).cdf(10)",
|
||||||
->E.R2.fmap(run)
|
() => {
|
||||||
->E.R2.fmap(toFloat)
|
let received =
|
||||||
->E.R.toExn("Expected float", _)
|
betaDist
|
||||||
switch received {
|
->algebraicAdd(uniformDist)
|
||||||
| None => "algebraicAdd has"->expect->toBe("failed")
|
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.cdf(d, 1e1))
|
||||||
// This is nondeterministic, we could be in a situation where ci fails but you click rerun and it passes, which is bad.
|
->E.R2.fmap(run)
|
||||||
// The value was calculated externally using a python script
|
->E.R2.fmap(toFloat)
|
||||||
| Some(x) => x->expect->toBeSoCloseTo(0.71148, ~digits=1)
|
->E.R.toExn("Expected float", _)
|
||||||
}
|
switch received {
|
||||||
})
|
| None => "algebraicAdd has"->expect->toBe("failed")
|
||||||
|
// This is nondeterministic, we could be in a situation where ci fails but you click rerun and it passes, which is bad.
|
||||||
|
// The value was calculated externally using a python script
|
||||||
|
| Some(x) => x->expect->toBeSoCloseTo(0.71148, ~digits=1)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("inv", () => {
|
describe("inv", () => {
|
||||||
testAll("(normal(mean=5) + normal(mean=5)).inv (imprecise)", list{5e-2, 4.2e-3, 9e-3}, x => {
|
testAll(
|
||||||
let received =
|
"(normal(mean=5) + normal(mean=5)).inv (imprecise)",
|
||||||
normalDist10
|
list{5e-2, 4.2e-3, 9e-3},
|
||||||
->Ok
|
x => {
|
||||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.inv(d, x))
|
let received =
|
||||||
->E.R2.fmap(run)
|
normalDist10
|
||||||
->E.R2.fmap(toFloat)
|
->Ok
|
||||||
->E.R.toOption
|
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.inv(d, x))
|
||||||
->E.O.flatten
|
->E.R2.fmap(run)
|
||||||
let calculated =
|
->E.R2.fmap(toFloat)
|
||||||
normalDist5
|
->E.R.toOption
|
||||||
->algebraicAdd(normalDist5)
|
->E.O.flatten
|
||||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.inv(d, x))
|
let calculated =
|
||||||
->E.R2.fmap(run)
|
normalDist5
|
||||||
->E.R2.fmap(toFloat)
|
->algebraicAdd(normalDist5)
|
||||||
->E.R.toOption
|
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.inv(d, x))
|
||||||
->E.O.flatten
|
->E.R2.fmap(run)
|
||||||
|
->E.R2.fmap(toFloat)
|
||||||
|
->E.R.toOption
|
||||||
|
->E.O.flatten
|
||||||
|
|
||||||
switch received {
|
switch received {
|
||||||
| None =>
|
| None =>
|
||||||
"this branch occurs when the dispatch to Jstat on trusted input fails."
|
"this branch occurs when the dispatch to Jstat on trusted input fails."
|
||||||
->expect
|
->expect
|
||||||
->toBe("never")
|
->toBe("never")
|
||||||
| Some(x) =>
|
| Some(x) =>
|
||||||
switch calculated {
|
switch calculated {
|
||||||
| None => "algebraicAdd has"->expect->toBe("failed")
|
| None => "algebraicAdd has"->expect->toBe("failed")
|
||||||
| Some(y) => x->expect->toBeSoCloseTo(y, ~digits=-1)
|
| Some(y) => x->expect->toBeSoCloseTo(y, ~digits=-1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
})
|
)
|
||||||
test("(normal(mean=10) + normal(mean=10)).inv(1e-1)", () => {
|
test(
|
||||||
let received =
|
"(normal(mean=10) + normal(mean=10)).inv(1e-1)",
|
||||||
normalDist20
|
() => {
|
||||||
->Ok
|
let received =
|
||||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.inv(d, 1e-1))
|
normalDist20
|
||||||
->E.R2.fmap(run)
|
->Ok
|
||||||
->E.R2.fmap(toFloat)
|
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.inv(d, 1e-1))
|
||||||
->E.R.toOption
|
->E.R2.fmap(run)
|
||||||
->E.O.flatten
|
->E.R2.fmap(toFloat)
|
||||||
let calculated =
|
->E.R.toOption
|
||||||
normalDist10
|
->E.O.flatten
|
||||||
->algebraicAdd(normalDist10)
|
let calculated =
|
||||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.inv(d, 1e-1))
|
normalDist10
|
||||||
->E.R2.fmap(run)
|
->algebraicAdd(normalDist10)
|
||||||
->E.R2.fmap(toFloat)
|
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.inv(d, 1e-1))
|
||||||
->E.R.toOption
|
->E.R2.fmap(run)
|
||||||
->E.O.flatten
|
->E.R2.fmap(toFloat)
|
||||||
switch received {
|
->E.R.toOption
|
||||||
| None =>
|
->E.O.flatten
|
||||||
"this branch occurs when the dispatch to Jstat on trusted input fails."
|
switch received {
|
||||||
->expect
|
| None =>
|
||||||
->toBe("never")
|
"this branch occurs when the dispatch to Jstat on trusted input fails."
|
||||||
| Some(x) =>
|
->expect
|
||||||
switch calculated {
|
->toBe("never")
|
||||||
| None => "algebraicAdd has"->expect->toBe("failed")
|
| Some(x) =>
|
||||||
| Some(y) => x->expect->toBeSoCloseTo(y, ~digits=-1)
|
switch calculated {
|
||||||
|
| None => "algebraicAdd has"->expect->toBe("failed")
|
||||||
|
| Some(y) => x->expect->toBeSoCloseTo(y, ~digits=-1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
})
|
)
|
||||||
test("(uniform(low=9, high=10) + beta(alpha=2, beta=5)).inv(2e-2)", () => {
|
test(
|
||||||
let received =
|
"(uniform(low=9, high=10) + beta(alpha=2, beta=5)).inv(2e-2)",
|
||||||
uniformDist
|
() => {
|
||||||
->algebraicAdd(betaDist)
|
let received =
|
||||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.inv(d, 2e-2))
|
uniformDist
|
||||||
->E.R2.fmap(run)
|
->algebraicAdd(betaDist)
|
||||||
->E.R2.fmap(toFloat)
|
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.inv(d, 2e-2))
|
||||||
->E.R.toExn("Expected float", _)
|
->E.R2.fmap(run)
|
||||||
switch received {
|
->E.R2.fmap(toFloat)
|
||||||
| None => "algebraicAdd has"->expect->toBe("failed")
|
->E.R.toExn("Expected float", _)
|
||||||
// This is nondeterministic, we could be in a situation where ci fails but you click rerun and it passes, which is bad.
|
switch received {
|
||||||
// sometimes it works with ~digits=2.
|
| None => "algebraicAdd has"->expect->toBe("failed")
|
||||||
| Some(x) => x->expect->toBeSoCloseTo(9.179319623146968, ~digits=0)
|
// This is nondeterministic, we could be in a situation where ci fails but you click rerun and it passes, which is bad.
|
||||||
}
|
// sometimes it works with ~digits=2.
|
||||||
})
|
| Some(x) => x->expect->toBeSoCloseTo(9.179319623146968, ~digits=0)
|
||||||
test("(beta(alpha=2, beta=5) + uniform(low=9, high=10)).inv(2e-2)", () => {
|
}
|
||||||
let received =
|
},
|
||||||
betaDist
|
)
|
||||||
->algebraicAdd(uniformDist)
|
test(
|
||||||
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.inv(d, 2e-2))
|
"(beta(alpha=2, beta=5) + uniform(low=9, high=10)).inv(2e-2)",
|
||||||
->E.R2.fmap(run)
|
() => {
|
||||||
->E.R2.fmap(toFloat)
|
let received =
|
||||||
->E.R.toExn("Expected float", _)
|
betaDist
|
||||||
switch received {
|
->algebraicAdd(uniformDist)
|
||||||
| None => "algebraicAdd has"->expect->toBe("failed")
|
->E.R2.fmap(d => DistributionTypes.Constructors.UsingDists.inv(d, 2e-2))
|
||||||
// This is nondeterministic, we could be in a situation where ci fails but you click rerun and it passes, which is bad.
|
->E.R2.fmap(run)
|
||||||
// sometimes it works with ~digits=2.
|
->E.R2.fmap(toFloat)
|
||||||
| Some(x) => x->expect->toBeSoCloseTo(9.190872365862756, ~digits=0)
|
->E.R.toExn("Expected float", _)
|
||||||
}
|
switch received {
|
||||||
})
|
| None => "algebraicAdd has"->expect->toBe("failed")
|
||||||
|
// This is nondeterministic, we could be in a situation where ci fails but you click rerun and it passes, which is bad.
|
||||||
|
// sometimes it works with ~digits=2.
|
||||||
|
| Some(x) => x->expect->toBeSoCloseTo(9.190872365862756, ~digits=0)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,7 +3,7 @@ This is the most basic file in our invariants family of tests.
|
||||||
|
|
||||||
Validate that the addition of means equals the mean of the addition, similar for subtraction and multiplication.
|
Validate that the addition of means equals the mean of the addition, similar for subtraction and multiplication.
|
||||||
|
|
||||||
Details in https://develop--squiggle-documentation.netlify.app/docs/internal/invariants/
|
Details in https://squiggle-language.com/docs/internal/invariants/
|
||||||
|
|
||||||
Note: epsilon of 1e3 means the invariants are, in general, not being satisfied.
|
Note: epsilon of 1e3 means the invariants are, in general, not being satisfied.
|
||||||
*/
|
*/
|
||||||
|
@ -87,14 +87,22 @@ describe("Means are invariant", () => {
|
||||||
let testAddInvariant = (t1, t2) =>
|
let testAddInvariant = (t1, t2) =>
|
||||||
E.R.liftM2(testAdditionMean, t1, t2)->E.R.toExn("Means were not invariant", _)
|
E.R.liftM2(testAdditionMean, t1, t2)->E.R.toExn("Means were not invariant", _)
|
||||||
|
|
||||||
testAll("with two of the same distribution", distributions, dist => {
|
testAll(
|
||||||
testAddInvariant(dist, dist)
|
"with two of the same distribution",
|
||||||
})
|
distributions,
|
||||||
|
dist => {
|
||||||
|
testAddInvariant(dist, dist)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
testAll("with two different distributions", pairsOfDifferentDistributions, dists => {
|
testAll(
|
||||||
let (dist1, dist2) = dists
|
"with two different distributions",
|
||||||
testAddInvariant(dist1, dist2)
|
pairsOfDifferentDistributions,
|
||||||
})
|
dists => {
|
||||||
|
let (dist1, dist2) = dists
|
||||||
|
testAddInvariant(dist1, dist2)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
testAll(
|
testAll(
|
||||||
"with two different distributions in swapped order",
|
"with two different distributions in swapped order",
|
||||||
|
@ -116,14 +124,22 @@ describe("Means are invariant", () => {
|
||||||
let testSubtractInvariant = (t1, t2) =>
|
let testSubtractInvariant = (t1, t2) =>
|
||||||
E.R.liftM2(testSubtractionMean, t1, t2)->E.R.toExn("Means were not invariant", _)
|
E.R.liftM2(testSubtractionMean, t1, t2)->E.R.toExn("Means were not invariant", _)
|
||||||
|
|
||||||
testAll("with two of the same distribution", distributions, dist => {
|
testAll(
|
||||||
testSubtractInvariant(dist, dist)
|
"with two of the same distribution",
|
||||||
})
|
distributions,
|
||||||
|
dist => {
|
||||||
|
testSubtractInvariant(dist, dist)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
testAll("with two different distributions", pairsOfDifferentDistributions, dists => {
|
testAll(
|
||||||
let (dist1, dist2) = dists
|
"with two different distributions",
|
||||||
testSubtractInvariant(dist1, dist2)
|
pairsOfDifferentDistributions,
|
||||||
})
|
dists => {
|
||||||
|
let (dist1, dist2) = dists
|
||||||
|
testSubtractInvariant(dist1, dist2)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
testAll(
|
testAll(
|
||||||
"with two different distributions in swapped order",
|
"with two different distributions in swapped order",
|
||||||
|
@ -145,14 +161,22 @@ describe("Means are invariant", () => {
|
||||||
let testMultiplicationInvariant = (t1, t2) =>
|
let testMultiplicationInvariant = (t1, t2) =>
|
||||||
E.R.liftM2(testMultiplicationMean, t1, t2)->E.R.toExn("Means were not invariant", _)
|
E.R.liftM2(testMultiplicationMean, t1, t2)->E.R.toExn("Means were not invariant", _)
|
||||||
|
|
||||||
testAll("with two of the same distribution", distributions, dist => {
|
testAll(
|
||||||
testMultiplicationInvariant(dist, dist)
|
"with two of the same distribution",
|
||||||
})
|
distributions,
|
||||||
|
dist => {
|
||||||
|
testMultiplicationInvariant(dist, dist)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
testAll("with two different distributions", pairsOfDifferentDistributions, dists => {
|
testAll(
|
||||||
let (dist1, dist2) = dists
|
"with two different distributions",
|
||||||
testMultiplicationInvariant(dist1, dist2)
|
pairsOfDifferentDistributions,
|
||||||
})
|
dists => {
|
||||||
|
let (dist1, dist2) = dists
|
||||||
|
testMultiplicationInvariant(dist1, dist2)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
testAll(
|
testAll(
|
||||||
"with two different distributions in swapped order",
|
"with two different distributions in swapped order",
|
||||||
|
|
|
@ -11,7 +11,7 @@ describe("mixture", () => {
|
||||||
let (mean1, mean2) = tup
|
let (mean1, mean2) = tup
|
||||||
let meanValue = {
|
let meanValue = {
|
||||||
run(Mixture([(mkNormal(mean1, 9e-1), 0.5), (mkNormal(mean2, 9e-1), 0.5)]))->outputMap(
|
run(Mixture([(mkNormal(mean1, 9e-1), 0.5), (mkNormal(mean2, 9e-1), 0.5)]))->outputMap(
|
||||||
FromDist(ToFloat(#Mean)),
|
FromDist(#ToFloat(#Mean)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
meanValue->unpackFloat->expect->toBeSoCloseTo((mean1 +. mean2) /. 2.0, ~digits=-1)
|
meanValue->unpackFloat->expect->toBeSoCloseTo((mean1 +. mean2) /. 2.0, ~digits=-1)
|
||||||
|
@ -28,7 +28,7 @@ describe("mixture", () => {
|
||||||
let meanValue = {
|
let meanValue = {
|
||||||
run(
|
run(
|
||||||
Mixture([(mkBeta(alpha, beta), betaWeight), (mkExponential(rate), exponentialWeight)]),
|
Mixture([(mkBeta(alpha, beta), betaWeight), (mkExponential(rate), exponentialWeight)]),
|
||||||
)->outputMap(FromDist(ToFloat(#Mean)))
|
)->outputMap(FromDist(#ToFloat(#Mean)))
|
||||||
}
|
}
|
||||||
let betaMean = 1.0 /. (1.0 +. beta /. alpha)
|
let betaMean = 1.0 /. (1.0 +. beta /. alpha)
|
||||||
let exponentialMean = 1.0 /. rate
|
let exponentialMean = 1.0 /. rate
|
||||||
|
@ -52,7 +52,7 @@ describe("mixture", () => {
|
||||||
(mkUniform(low, high), uniformWeight),
|
(mkUniform(low, high), uniformWeight),
|
||||||
(mkLognormal(mu, sigma), lognormalWeight),
|
(mkLognormal(mu, sigma), lognormalWeight),
|
||||||
]),
|
]),
|
||||||
)->outputMap(FromDist(ToFloat(#Mean)))
|
)->outputMap(FromDist(#ToFloat(#Mean)))
|
||||||
}
|
}
|
||||||
let uniformMean = (low +. high) /. 2.0
|
let uniformMean = (low +. high) /. 2.0
|
||||||
let lognormalMean = mu +. sigma ** 2.0 /. 2.0
|
let lognormalMean = mu +. sigma ** 2.0 /. 2.0
|
||||||
|
|
|
@ -3,6 +3,7 @@ open Expect
|
||||||
open TestHelpers
|
open TestHelpers
|
||||||
open GenericDist_Fixtures
|
open GenericDist_Fixtures
|
||||||
|
|
||||||
|
let klDivergence = DistributionOperation.Constructors.LogScore.distEstimateDistAnswer(~env)
|
||||||
// integral from low to high of 1 / (high - low) log(normal(mean, stdev)(x) / (1 / (high - low))) dx
|
// integral from low to high of 1 / (high - low) log(normal(mean, stdev)(x) / (1 / (high - low))) dx
|
||||||
let klNormalUniform = (mean, stdev, low, high): float =>
|
let klNormalUniform = (mean, stdev, low, high): float =>
|
||||||
-.Js.Math.log((high -. low) /. Js.Math.sqrt(2.0 *. MagicNumbers.Math.pi *. stdev ** 2.0)) +.
|
-.Js.Math.log((high -. low) /. Js.Math.sqrt(2.0 *. MagicNumbers.Math.pi *. stdev ** 2.0)) +.
|
||||||
|
@ -11,17 +12,14 @@ let klNormalUniform = (mean, stdev, low, high): float =>
|
||||||
(mean ** 2.0 -. (high +. low) *. mean +. (low ** 2.0 +. high *. low +. high ** 2.0) /. 3.0)
|
(mean ** 2.0 -. (high +. low) *. mean +. (low ** 2.0 +. high *. low +. high ** 2.0) /. 3.0)
|
||||||
|
|
||||||
describe("klDivergence: continuous -> continuous -> float", () => {
|
describe("klDivergence: continuous -> continuous -> float", () => {
|
||||||
let klDivergence = DistributionOperation.Constructors.klDivergence(~env)
|
|
||||||
|
|
||||||
let testUniform = (lowAnswer, highAnswer, lowPrediction, highPrediction) => {
|
let testUniform = (lowAnswer, highAnswer, lowPrediction, highPrediction) => {
|
||||||
test("of two uniforms is equal to the analytic expression", () => {
|
test("of two uniforms is equal to the analytic expression", () => {
|
||||||
let answer =
|
let answer =
|
||||||
uniformMakeR(lowAnswer, highAnswer)->E.R2.errMap(s => DistributionTypes.ArgumentError(s))
|
uniformMakeR(lowAnswer, highAnswer)->E.R2.errMap(s => DistributionTypes.ArgumentError(s))
|
||||||
let prediction =
|
let prediction =
|
||||||
uniformMakeR(
|
uniformMakeR(lowPrediction, highPrediction)->E.R2.errMap(
|
||||||
lowPrediction,
|
s => DistributionTypes.ArgumentError(s),
|
||||||
highPrediction,
|
)
|
||||||
)->E.R2.errMap(s => DistributionTypes.ArgumentError(s))
|
|
||||||
// integral along the support of the answer of answer.pdf(x) times log of prediction.pdf(x) divided by answer.pdf(x) dx
|
// integral along the support of the answer of answer.pdf(x) times log of prediction.pdf(x) divided by answer.pdf(x) dx
|
||||||
let analyticalKl = Js.Math.log((highPrediction -. lowPrediction) /. (highAnswer -. lowAnswer))
|
let analyticalKl = Js.Math.log((highPrediction -. lowPrediction) /. (highAnswer -. lowAnswer))
|
||||||
let kl = E.R.liftJoin2(klDivergence, prediction, answer)
|
let kl = E.R.liftJoin2(klDivergence, prediction, answer)
|
||||||
|
@ -58,7 +56,7 @@ describe("klDivergence: continuous -> continuous -> float", () => {
|
||||||
let kl = E.R.liftJoin2(klDivergence, prediction, answer)
|
let kl = E.R.liftJoin2(klDivergence, prediction, answer)
|
||||||
|
|
||||||
switch kl {
|
switch kl {
|
||||||
| Ok(kl') => kl'->expect->toBeSoCloseTo(analyticalKl, ~digits=3)
|
| Ok(kl') => kl'->expect->toBeSoCloseTo(analyticalKl, ~digits=2)
|
||||||
| Error(err) => {
|
| Error(err) => {
|
||||||
Js.Console.log(DistributionTypes.Error.toString(err))
|
Js.Console.log(DistributionTypes.Error.toString(err))
|
||||||
raise(KlFailed)
|
raise(KlFailed)
|
||||||
|
@ -82,7 +80,6 @@ describe("klDivergence: continuous -> continuous -> float", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("klDivergence: discrete -> discrete -> float", () => {
|
describe("klDivergence: discrete -> discrete -> float", () => {
|
||||||
let klDivergence = DistributionOperation.Constructors.klDivergence(~env)
|
|
||||||
let mixture = a => DistributionTypes.DistributionOperation.Mixture(a)
|
let mixture = a => DistributionTypes.DistributionOperation.Mixture(a)
|
||||||
let a' = [(point1, 1e0), (point2, 1e0)]->mixture->run
|
let a' = [(point1, 1e0), (point2, 1e0)]->mixture->run
|
||||||
let b' = [(point1, 1e0), (point2, 1e0), (point3, 1e0)]->mixture->run
|
let b' = [(point1, 1e0), (point2, 1e0), (point3, 1e0)]->mixture->run
|
||||||
|
@ -117,7 +114,6 @@ describe("klDivergence: discrete -> discrete -> float", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("klDivergence: mixed -> mixed -> float", () => {
|
describe("klDivergence: mixed -> mixed -> float", () => {
|
||||||
let klDivergence = DistributionOperation.Constructors.klDivergence(~env)
|
|
||||||
let mixture' = a => DistributionTypes.DistributionOperation.Mixture(a)
|
let mixture' = a => DistributionTypes.DistributionOperation.Mixture(a)
|
||||||
let mixture = a => {
|
let mixture = a => {
|
||||||
let dist' = a->mixture'->run
|
let dist' = a->mixture'->run
|
||||||
|
@ -186,18 +182,18 @@ describe("combineAlongSupportOfSecondArgument0", () => {
|
||||||
let answer =
|
let answer =
|
||||||
uniformMakeR(lowAnswer, highAnswer)->E.R2.errMap(s => DistributionTypes.ArgumentError(s))
|
uniformMakeR(lowAnswer, highAnswer)->E.R2.errMap(s => DistributionTypes.ArgumentError(s))
|
||||||
let prediction =
|
let prediction =
|
||||||
uniformMakeR(lowPrediction, highPrediction)->E.R2.errMap(s => DistributionTypes.ArgumentError(
|
uniformMakeR(lowPrediction, highPrediction)->E.R2.errMap(
|
||||||
s,
|
s => DistributionTypes.ArgumentError(s),
|
||||||
))
|
)
|
||||||
let answerWrapped = E.R.fmap(a => run(FromDist(ToDist(ToPointSet), a)), answer)
|
let answerWrapped = E.R.fmap(a => run(FromDist(#ToDist(ToPointSet), a)), answer)
|
||||||
let predictionWrapped = E.R.fmap(a => run(FromDist(ToDist(ToPointSet), a)), prediction)
|
let predictionWrapped = E.R.fmap(a => run(FromDist(#ToDist(ToPointSet), a)), prediction)
|
||||||
|
|
||||||
let interpolator = XYShape.XtoY.continuousInterpolator(#Stepwise, #UseZero)
|
let interpolator = XYShape.XtoY.continuousInterpolator(#Stepwise, #UseZero)
|
||||||
let integrand = PointSetDist_Scoring.KLDivergence.integrand
|
let integrand = PointSetDist_Scoring.WithDistAnswer.integrand
|
||||||
|
|
||||||
let result = switch (answerWrapped, predictionWrapped) {
|
let result = switch (answerWrapped, predictionWrapped) {
|
||||||
| (Ok(Dist(PointSet(Continuous(a)))), Ok(Dist(PointSet(Continuous(b))))) =>
|
| (Ok(Dist(PointSet(Continuous(a)))), Ok(Dist(PointSet(Continuous(b))))) =>
|
||||||
Some(combineAlongSupportOfSecondArgument(integrand, interpolator, a.xyShape, b.xyShape))
|
Some(combineAlongSupportOfSecondArgument(interpolator, integrand, a.xyShape, b.xyShape))
|
||||||
| _ => None
|
| _ => None
|
||||||
}
|
}
|
||||||
result
|
result
|
|
@ -0,0 +1,68 @@
|
||||||
|
open Jest
|
||||||
|
open Expect
|
||||||
|
open TestHelpers
|
||||||
|
open GenericDist_Fixtures
|
||||||
|
exception ScoreFailed
|
||||||
|
|
||||||
|
describe("WithScalarAnswer: discrete -> scalar -> score", () => {
|
||||||
|
let mixture = a => DistributionTypes.DistributionOperation.Mixture(a)
|
||||||
|
let pointA = mkDelta(3.0)
|
||||||
|
let pointB = mkDelta(2.0)
|
||||||
|
let pointC = mkDelta(1.0)
|
||||||
|
let pointD = mkDelta(0.0)
|
||||||
|
|
||||||
|
test("score: agrees with analytical answer when finite", () => {
|
||||||
|
let prediction' = [(pointA, 0.25), (pointB, 0.25), (pointC, 0.25), (pointD, 0.25)]->mixture->run
|
||||||
|
let prediction = switch prediction' {
|
||||||
|
| Dist(PointSet(p)) => p
|
||||||
|
| _ => raise(MixtureFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
let answer = 2.0 // So this is: assigning 100% probability to 2.0
|
||||||
|
let result = PointSetDist_Scoring.WithScalarAnswer.score(~estimate=prediction, ~answer)
|
||||||
|
switch result {
|
||||||
|
| Ok(x) => x->expect->toEqual(-.Js.Math.log(0.25 /. 1.0))
|
||||||
|
| _ => raise(ScoreFailed)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("score: agrees with analytical answer when finite", () => {
|
||||||
|
let prediction' = [(pointA, 0.75), (pointB, 0.25)]->mixture->run
|
||||||
|
let prediction = switch prediction' {
|
||||||
|
| Dist(PointSet(p)) => p
|
||||||
|
| _ => raise(MixtureFailed)
|
||||||
|
}
|
||||||
|
let answer = 3.0 // So this is: assigning 100% probability to 2.0
|
||||||
|
let result = PointSetDist_Scoring.WithScalarAnswer.score(~estimate=prediction, ~answer)
|
||||||
|
switch result {
|
||||||
|
| Ok(x) => x->expect->toEqual(-.Js.Math.log(0.75 /. 1.0))
|
||||||
|
| _ => raise(ScoreFailed)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("scoreWithPrior: agrees with analytical answer when finite", () => {
|
||||||
|
let prior' = [(pointA, 0.5), (pointB, 0.5)]->mixture->run
|
||||||
|
let prediction' = [(pointA, 0.75), (pointB, 0.25)]->mixture->run
|
||||||
|
|
||||||
|
let prediction = switch prediction' {
|
||||||
|
| Dist(PointSet(p)) => p
|
||||||
|
| _ => raise(MixtureFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
let prior = switch prior' {
|
||||||
|
| Dist(PointSet(p)) => p
|
||||||
|
| _ => raise(MixtureFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
let answer = 3.0 // So this is: assigning 100% probability to 2.0
|
||||||
|
let result = PointSetDist_Scoring.WithScalarAnswer.scoreWithPrior(
|
||||||
|
~estimate=prediction,
|
||||||
|
~answer,
|
||||||
|
~prior,
|
||||||
|
)
|
||||||
|
switch result {
|
||||||
|
| Ok(x) => x->expect->toEqual(-.Js.Math.log(0.75 /. 1.0) -. -.Js.Math.log(0.5 /. 1.0))
|
||||||
|
| _ => raise(ScoreFailed)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
|
@ -3,39 +3,39 @@ open Expect
|
||||||
open TestHelpers
|
open TestHelpers
|
||||||
|
|
||||||
// TODO: use Normal.make (but preferably after teh new validation dispatch is in)
|
// TODO: use Normal.make (but preferably after teh new validation dispatch is in)
|
||||||
let mkNormal = (mean, stdev) => DistributionTypes.Symbolic(#Normal({mean: mean, stdev: stdev}))
|
let mkNormal = (mean, stdev) => DistributionTypes.Symbolic(#Normal({mean, stdev}))
|
||||||
|
|
||||||
describe("(Symbolic) normalize", () => {
|
describe("(Symbolic) normalize", () => {
|
||||||
testAll("has no impact on normal distributions", list{-1e8, -1e-2, 0.0, 1e-4, 1e16}, mean => {
|
testAll("has no impact on normal distributions", list{-1e8, -1e-2, 0.0, 1e-4, 1e16}, mean => {
|
||||||
let normalValue = mkNormal(mean, 2.0)
|
let normalValue = mkNormal(mean, 2.0)
|
||||||
let normalizedValue = run(FromDist(ToDist(Normalize), normalValue))
|
let normalizedValue = run(FromDist(#ToDist(Normalize), normalValue))
|
||||||
normalizedValue->unpackDist->expect->toEqual(normalValue)
|
normalizedValue->unpackDist->expect->toEqual(normalValue)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("(Symbolic) mean", () => {
|
describe("(Symbolic) mean", () => {
|
||||||
testAll("of normal distributions", list{-1e8, -16.0, -1e-2, 0.0, 1e-4, 32.0, 1e16}, mean => {
|
testAll("of normal distributions", list{-1e8, -16.0, -1e-2, 0.0, 1e-4, 32.0, 1e16}, mean => {
|
||||||
run(FromDist(ToFloat(#Mean), mkNormal(mean, 4.0)))->unpackFloat->expect->toBeCloseTo(mean)
|
run(FromDist(#ToFloat(#Mean), mkNormal(mean, 4.0)))->unpackFloat->expect->toBeCloseTo(mean)
|
||||||
})
|
})
|
||||||
|
|
||||||
Skip.test("of normal(0, -1) (it NaNs out)", () => {
|
Skip.test("of normal(0, -1) (it NaNs out)", () => {
|
||||||
run(FromDist(ToFloat(#Mean), mkNormal(1e1, -1e0)))->unpackFloat->expect->ExpectJs.toBeFalsy
|
run(FromDist(#ToFloat(#Mean), mkNormal(1e1, -1e0)))->unpackFloat->expect->ExpectJs.toBeFalsy
|
||||||
})
|
})
|
||||||
|
|
||||||
test("of normal(0, 1e-8) (it doesn't freak out at tiny stdev)", () => {
|
test("of normal(0, 1e-8) (it doesn't freak out at tiny stdev)", () => {
|
||||||
run(FromDist(ToFloat(#Mean), mkNormal(0.0, 1e-8)))->unpackFloat->expect->toBeCloseTo(0.0)
|
run(FromDist(#ToFloat(#Mean), mkNormal(0.0, 1e-8)))->unpackFloat->expect->toBeCloseTo(0.0)
|
||||||
})
|
})
|
||||||
|
|
||||||
testAll("of exponential distributions", list{1e-7, 2.0, 10.0, 100.0}, rate => {
|
testAll("of exponential distributions", list{1e-7, 2.0, 10.0, 100.0}, rate => {
|
||||||
let meanValue = run(
|
let meanValue = run(
|
||||||
FromDist(ToFloat(#Mean), DistributionTypes.Symbolic(#Exponential({rate: rate}))),
|
FromDist(#ToFloat(#Mean), DistributionTypes.Symbolic(#Exponential({rate: rate}))),
|
||||||
)
|
)
|
||||||
meanValue->unpackFloat->expect->toBeCloseTo(1.0 /. rate) // https://en.wikipedia.org/wiki/Exponential_distribution#Mean,_variance,_moments,_and_median
|
meanValue->unpackFloat->expect->toBeCloseTo(1.0 /. rate) // https://en.wikipedia.org/wiki/Exponential_distribution#Mean,_variance,_moments,_and_median
|
||||||
})
|
})
|
||||||
|
|
||||||
test("of a cauchy distribution", () => {
|
test("of a cauchy distribution", () => {
|
||||||
let meanValue = run(
|
let meanValue = run(
|
||||||
FromDist(ToFloat(#Mean), DistributionTypes.Symbolic(#Cauchy({local: 1.0, scale: 1.0}))),
|
FromDist(#ToFloat(#Mean), DistributionTypes.Symbolic(#Cauchy({local: 1.0, scale: 1.0}))),
|
||||||
)
|
)
|
||||||
meanValue->unpackFloat->expect->toBeSoCloseTo(1.0098094001641797, ~digits=5)
|
meanValue->unpackFloat->expect->toBeSoCloseTo(1.0098094001641797, ~digits=5)
|
||||||
//-> toBe(GenDistError(Other("Cauchy distributions may have no mean value.")))
|
//-> toBe(GenDistError(Other("Cauchy distributions may have no mean value.")))
|
||||||
|
@ -47,10 +47,7 @@ describe("(Symbolic) mean", () => {
|
||||||
tup => {
|
tup => {
|
||||||
let (low, medium, high) = tup
|
let (low, medium, high) = tup
|
||||||
let meanValue = run(
|
let meanValue = run(
|
||||||
FromDist(
|
FromDist(#ToFloat(#Mean), DistributionTypes.Symbolic(#Triangular({low, medium, high}))),
|
||||||
ToFloat(#Mean),
|
|
||||||
DistributionTypes.Symbolic(#Triangular({low: low, medium: medium, high: high})),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
meanValue->unpackFloat->expect->toBeCloseTo((low +. medium +. high) /. 3.0) // https://www.statology.org/triangular-distribution/
|
meanValue->unpackFloat->expect->toBeCloseTo((low +. medium +. high) /. 3.0) // https://www.statology.org/triangular-distribution/
|
||||||
},
|
},
|
||||||
|
@ -63,7 +60,7 @@ describe("(Symbolic) mean", () => {
|
||||||
tup => {
|
tup => {
|
||||||
let (alpha, beta) = tup
|
let (alpha, beta) = tup
|
||||||
let meanValue = run(
|
let meanValue = run(
|
||||||
FromDist(ToFloat(#Mean), DistributionTypes.Symbolic(#Beta({alpha: alpha, beta: beta}))),
|
FromDist(#ToFloat(#Mean), DistributionTypes.Symbolic(#Beta({alpha, beta}))),
|
||||||
)
|
)
|
||||||
meanValue->unpackFloat->expect->toBeCloseTo(1.0 /. (1.0 +. beta /. alpha)) // https://en.wikipedia.org/wiki/Beta_distribution#Mean
|
meanValue->unpackFloat->expect->toBeCloseTo(1.0 /. (1.0 +. beta /. alpha)) // https://en.wikipedia.org/wiki/Beta_distribution#Mean
|
||||||
},
|
},
|
||||||
|
@ -72,18 +69,35 @@ describe("(Symbolic) mean", () => {
|
||||||
// TODO: When we have our theory of validators we won't want this to be NaN but to be an error.
|
// TODO: When we have our theory of validators we won't want this to be NaN but to be an error.
|
||||||
test("of beta(0, 0)", () => {
|
test("of beta(0, 0)", () => {
|
||||||
let meanValue = run(
|
let meanValue = run(
|
||||||
FromDist(ToFloat(#Mean), DistributionTypes.Symbolic(#Beta({alpha: 0.0, beta: 0.0}))),
|
FromDist(#ToFloat(#Mean), DistributionTypes.Symbolic(#Beta({alpha: 0.0, beta: 0.0}))),
|
||||||
)
|
)
|
||||||
meanValue->unpackFloat->expect->ExpectJs.toBeFalsy
|
meanValue->unpackFloat->expect->ExpectJs.toBeFalsy
|
||||||
})
|
})
|
||||||
|
|
||||||
|
testAll(
|
||||||
|
"of beta distributions from mean and standard dev",
|
||||||
|
list{(0.39, 0.1), (0.08, 0.1), (0.8, 0.3)},
|
||||||
|
tup => {
|
||||||
|
let (mean, stdev) = tup
|
||||||
|
let betaDistribution = SymbolicDist.Beta.fromMeanAndStdev(mean, stdev)
|
||||||
|
let meanValue =
|
||||||
|
betaDistribution->E.R2.fmap(
|
||||||
|
d => run(FromDist(#ToFloat(#Mean), d->DistributionTypes.Symbolic)),
|
||||||
|
)
|
||||||
|
switch meanValue {
|
||||||
|
| Ok(value) => value->unpackFloat->expect->toBeCloseTo(mean)
|
||||||
|
| Error(err) => err->expect->toBe("shouldn't happen")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
testAll(
|
testAll(
|
||||||
"of lognormal distributions",
|
"of lognormal distributions",
|
||||||
list{(2.0, 4.0), (1e-7, 1e-2), (-1e6, 10.0), (1e3, -1e2), (-1e8, -1e4), (1e2, 1e-5)},
|
list{(2.0, 4.0), (1e-7, 1e-2), (-1e6, 10.0), (1e3, -1e2), (-1e8, -1e4), (1e2, 1e-5)},
|
||||||
tup => {
|
tup => {
|
||||||
let (mu, sigma) = tup
|
let (mu, sigma) = tup
|
||||||
let meanValue = run(
|
let meanValue = run(
|
||||||
FromDist(ToFloat(#Mean), DistributionTypes.Symbolic(#Lognormal({mu: mu, sigma: sigma}))),
|
FromDist(#ToFloat(#Mean), DistributionTypes.Symbolic(#Lognormal({mu, sigma}))),
|
||||||
)
|
)
|
||||||
meanValue->unpackFloat->expect->toBeCloseTo(Js.Math.exp(mu +. sigma ** 2.0 /. 2.0)) // https://brilliant.org/wiki/log-normal-distribution/
|
meanValue->unpackFloat->expect->toBeCloseTo(Js.Math.exp(mu +. sigma ** 2.0 /. 2.0)) // https://brilliant.org/wiki/log-normal-distribution/
|
||||||
},
|
},
|
||||||
|
@ -95,14 +109,14 @@ describe("(Symbolic) mean", () => {
|
||||||
tup => {
|
tup => {
|
||||||
let (low, high) = tup
|
let (low, high) = tup
|
||||||
let meanValue = run(
|
let meanValue = run(
|
||||||
FromDist(ToFloat(#Mean), DistributionTypes.Symbolic(#Uniform({low: low, high: high}))),
|
FromDist(#ToFloat(#Mean), DistributionTypes.Symbolic(#Uniform({low, high}))),
|
||||||
)
|
)
|
||||||
meanValue->unpackFloat->expect->toBeCloseTo((low +. high) /. 2.0) // https://en.wikipedia.org/wiki/Continuous_uniform_distribution#Moments
|
meanValue->unpackFloat->expect->toBeCloseTo((low +. high) /. 2.0) // https://en.wikipedia.org/wiki/Continuous_uniform_distribution#Moments
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
test("of a float", () => {
|
test("of a float", () => {
|
||||||
let meanValue = run(FromDist(ToFloat(#Mean), DistributionTypes.Symbolic(#Float(7.7))))
|
let meanValue = run(FromDist(#ToFloat(#Mean), DistributionTypes.Symbolic(#Float(7.7))))
|
||||||
meanValue->unpackFloat->expect->toBeCloseTo(7.7)
|
meanValue->unpackFloat->expect->toBeCloseTo(7.7)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
21
packages/squiggle-lang/__tests__/E/A_test.res
Normal file
21
packages/squiggle-lang/__tests__/E/A_test.res
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
open Jest
|
||||||
|
open TestHelpers
|
||||||
|
|
||||||
|
describe("E.A.getByFmap", () => {
|
||||||
|
makeTest("Empty list returns None", E.A.getByFmap([], x => x + 1, x => mod(x, 2) == 0), None)
|
||||||
|
makeTest(
|
||||||
|
"Never predicate returns None",
|
||||||
|
E.A.getByFmap([1, 2, 3, 4, 5, 6], x => x + 1, _ => false),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
makeTest(
|
||||||
|
"function evaluates",
|
||||||
|
E.A.getByFmap([1, 1, 1, 1, 1, 1, 1, 2, 1, 1], x => 3 * x, x => x > 4),
|
||||||
|
Some(6),
|
||||||
|
)
|
||||||
|
makeTest(
|
||||||
|
"always predicate returns fn(fst(a))",
|
||||||
|
E.A.getByFmap([0, 1, 2, 3, 4, 5, 6], x => 10 + x, _ => true),
|
||||||
|
Some(10),
|
||||||
|
)
|
||||||
|
})
|
|
@ -9,22 +9,28 @@ let prepareInputs = (ar, minWeight) =>
|
||||||
describe("Continuous and discrete splits", () => {
|
describe("Continuous and discrete splits", () => {
|
||||||
makeTest(
|
makeTest(
|
||||||
"is empty, with no common elements",
|
"is empty, with no common elements",
|
||||||
prepareInputs([1.432, 1.33455, 2.0], 2),
|
prepareInputs([1.33455, 1.432, 2.0], 2),
|
||||||
([1.33455, 1.432, 2.0], []),
|
([1.33455, 1.432, 2.0], []),
|
||||||
)
|
)
|
||||||
|
|
||||||
makeTest(
|
makeTest(
|
||||||
"only stores 3.5 as discrete when minWeight is 3",
|
"only stores 3.5 as discrete when minWeight is 3",
|
||||||
prepareInputs([1.432, 1.33455, 2.0, 2.0, 3.5, 3.5, 3.5], 3),
|
prepareInputs([1.33455, 1.432, 2.0, 2.0, 3.5, 3.5, 3.5], 3),
|
||||||
([1.33455, 1.432, 2.0, 2.0], [(3.5, 3.0)]),
|
([1.33455, 1.432, 2.0, 2.0], [(3.5, 3.0)]),
|
||||||
)
|
)
|
||||||
|
|
||||||
makeTest(
|
makeTest(
|
||||||
"doesn't store 3.5 as discrete when minWeight is 5",
|
"doesn't store 3.5 as discrete when minWeight is 5",
|
||||||
prepareInputs([1.432, 1.33455, 2.0, 2.0, 3.5, 3.5, 3.5], 5),
|
prepareInputs([1.33455, 1.432, 2.0, 2.0, 3.5, 3.5, 3.5], 5),
|
||||||
([1.33455, 1.432, 2.0, 2.0, 3.5, 3.5, 3.5], []),
|
([1.33455, 1.432, 2.0, 2.0, 3.5, 3.5, 3.5], []),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
makeTest(
|
||||||
|
"more general test",
|
||||||
|
prepareInputs([10., 10., 11., 11., 11., 12., 13., 13., 13., 13., 13., 14.], 3),
|
||||||
|
([10., 10., 12., 14.], [(11., 3.), (13., 5.)]),
|
||||||
|
)
|
||||||
|
|
||||||
let makeDuplicatedArray = count => {
|
let makeDuplicatedArray = count => {
|
||||||
let arr = Belt.Array.range(1, count) |> E.A.fmap(float_of_int)
|
let arr = Belt.Array.range(1, count) |> E.A.fmap(float_of_int)
|
||||||
let sorted = arr |> Belt.SortArray.stableSortBy(_, compare)
|
let sorted = arr |> Belt.SortArray.stableSortBy(_, compare)
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
open Jest
|
|
||||||
open Expect
|
|
||||||
|
|
||||||
let makeTest = (~only=false, str, item1, item2) =>
|
|
||||||
only
|
|
||||||
? Only.test(str, () => expect(item1)->toEqual(item2))
|
|
||||||
: test(str, () => expect(item1)->toEqual(item2))
|
|
||||||
|
|
||||||
describe("Lodash", () =>
|
|
||||||
describe("Lodash", () => {
|
|
||||||
makeTest("min", Lodash.min([1, 3, 4]), 1)
|
|
||||||
makeTest("max", Lodash.max([1, 3, 4]), 4)
|
|
||||||
makeTest("uniq", Lodash.uniq([1, 3, 4, 4]), [1, 3, 4])
|
|
||||||
makeTest(
|
|
||||||
"countBy",
|
|
||||||
Lodash.countBy([1, 3, 4, 4], r => r),
|
|
||||||
Js.Dict.fromArray([("1", 1), ("3", 1), ("4", 2)]),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
)
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
@@warning("-44")
|
||||||
|
module Bindings = Reducer_Bindings
|
||||||
|
module Namespace = Reducer_Namespace
|
||||||
|
|
||||||
|
open Jest
|
||||||
|
open Expect
|
||||||
|
open Expect.Operators
|
||||||
|
|
||||||
|
describe("Bindings", () => {
|
||||||
|
let value = Reducer_T.IEvNumber(1967.0)
|
||||||
|
let bindings = Bindings.make()->Bindings.set("value", value)
|
||||||
|
test("get", () => {
|
||||||
|
expect(bindings->Bindings.get("value")) == Some(value)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("get nonexisting value", () => {
|
||||||
|
expect(bindings->Bindings.get("nosuchvalue")) == None
|
||||||
|
})
|
||||||
|
|
||||||
|
test("get on extended", () => {
|
||||||
|
expect(bindings->Bindings.extend->Bindings.get("value")) == Some(value)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("locals", () => {
|
||||||
|
expect(bindings->Bindings.locals->Namespace.get("value")) == Some(value)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("locals on extendeed", () => {
|
||||||
|
expect(bindings->Bindings.extend->Bindings.locals->Namespace.get("value")) == None
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("extend", () => {
|
||||||
|
let value2 = Reducer_T.IEvNumber(5.)
|
||||||
|
let extendedBindings = bindings->Bindings.extend->Bindings.set("value", value2)
|
||||||
|
|
||||||
|
test(
|
||||||
|
"get on extended",
|
||||||
|
() => {
|
||||||
|
expect(extendedBindings->Bindings.get("value")) == Some(value2)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
"get on original",
|
||||||
|
() => {
|
||||||
|
expect(bindings->Bindings.get("value")) == Some(value)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
|
@ -1,4 +0,0 @@
|
||||||
open Jest
|
|
||||||
open Expect
|
|
||||||
|
|
||||||
test("todo", () => expect("1")->toBe("1"))
|
|
|
@ -1,146 +0,0 @@
|
||||||
open Jest
|
|
||||||
// open Expect
|
|
||||||
|
|
||||||
open Reducer_Expression_ExpressionBuilder
|
|
||||||
open Reducer_TestMacroHelpers
|
|
||||||
module ExpressionT = Reducer_Expression_T
|
|
||||||
|
|
||||||
let exampleExpression = eNumber(1.)
|
|
||||||
let exampleExpressionY = eSymbol("y")
|
|
||||||
let exampleStatementY = eLetStatement("y", eNumber(1.))
|
|
||||||
let exampleStatementX = eLetStatement("y", eSymbol("x"))
|
|
||||||
let exampleStatementZ = eLetStatement("z", eSymbol("y"))
|
|
||||||
|
|
||||||
// If it is not a macro then it is not expanded
|
|
||||||
testMacro([], exampleExpression, "Ok(1)")
|
|
||||||
|
|
||||||
describe("bindStatement", () => {
|
|
||||||
// A statement is bound by the bindings created by the previous statement
|
|
||||||
testMacro(
|
|
||||||
[],
|
|
||||||
eBindStatement(eBindings([]), exampleStatementY),
|
|
||||||
"Ok((:$_setBindings_$ @{} :y 1) context: @{})",
|
|
||||||
)
|
|
||||||
// Then it answers the bindings for the next statement when reduced
|
|
||||||
testMacroEval([], eBindStatement(eBindings([]), exampleStatementY), "Ok(@{y: 1})")
|
|
||||||
// Now let's feed a binding to see what happens
|
|
||||||
testMacro(
|
|
||||||
[],
|
|
||||||
eBindStatement(eBindings([("x", IEvNumber(2.))]), exampleStatementX),
|
|
||||||
"Ok((:$_setBindings_$ @{x: 2} :y 2) context: @{x: 2})",
|
|
||||||
)
|
|
||||||
// An expression does not return a binding, thus error
|
|
||||||
testMacro([], eBindStatement(eBindings([]), exampleExpression), "Assignment expected")
|
|
||||||
// When bindings from previous statement are missing the context is injected. This must be the first statement of a block
|
|
||||||
testMacro(
|
|
||||||
[("z", IEvNumber(99.))],
|
|
||||||
eBindStatementDefault(exampleStatementY),
|
|
||||||
"Ok((:$_setBindings_$ @{z: 99} :y 1) context: @{z: 99})",
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("bindExpression", () => {
|
|
||||||
// x is simply bound in the expression
|
|
||||||
testMacro(
|
|
||||||
[],
|
|
||||||
eBindExpression(eBindings([("x", IEvNumber(2.))]), eSymbol("x")),
|
|
||||||
"Ok(2 context: @{x: 2})",
|
|
||||||
)
|
|
||||||
// When an let statement is the end expression then bindings are returned
|
|
||||||
testMacro(
|
|
||||||
[],
|
|
||||||
eBindExpression(eBindings([("x", IEvNumber(2.))]), exampleStatementY),
|
|
||||||
"Ok((:$_exportBindings_$ (:$_setBindings_$ @{x: 2} :y 1)) context: @{x: 2})",
|
|
||||||
)
|
|
||||||
// Now let's reduce that expression
|
|
||||||
testMacroEval(
|
|
||||||
[],
|
|
||||||
eBindExpression(eBindings([("x", IEvNumber(2.))]), exampleStatementY),
|
|
||||||
"Ok(@{x: 2,y: 1})",
|
|
||||||
)
|
|
||||||
// When bindings are missing the context is injected. This must be the first and last statement of a block
|
|
||||||
testMacroEval(
|
|
||||||
[("z", IEvNumber(99.))],
|
|
||||||
eBindExpressionDefault(exampleStatementY),
|
|
||||||
"Ok(@{y: 1,z: 99})",
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("block", () => {
|
|
||||||
// Block with a single expression
|
|
||||||
testMacro([], eBlock(list{exampleExpression}), "Ok((:$$_bindExpression_$$ 1))")
|
|
||||||
testMacroEval([], eBlock(list{exampleExpression}), "Ok(1)")
|
|
||||||
// Block with a single statement
|
|
||||||
testMacro([], eBlock(list{exampleStatementY}), "Ok((:$$_bindExpression_$$ (:$_let_$ :y 1)))")
|
|
||||||
testMacroEval([], eBlock(list{exampleStatementY}), "Ok(@{y: 1})")
|
|
||||||
// Block with a statement and an expression
|
|
||||||
testMacro(
|
|
||||||
[],
|
|
||||||
eBlock(list{exampleStatementY, exampleExpressionY}),
|
|
||||||
"Ok((:$$_bindExpression_$$ (:$$_bindStatement_$$ (:$_let_$ :y 1)) :y))",
|
|
||||||
)
|
|
||||||
testMacroEval([], eBlock(list{exampleStatementY, exampleExpressionY}), "Ok(1)")
|
|
||||||
// Block with a statement and another statement
|
|
||||||
testMacro(
|
|
||||||
[],
|
|
||||||
eBlock(list{exampleStatementY, exampleStatementZ}),
|
|
||||||
"Ok((:$$_bindExpression_$$ (:$$_bindStatement_$$ (:$_let_$ :y 1)) (:$_let_$ :z :y)))",
|
|
||||||
)
|
|
||||||
testMacroEval([], eBlock(list{exampleStatementY, exampleStatementZ}), "Ok(@{y: 1,z: 1})")
|
|
||||||
// Block inside a block
|
|
||||||
testMacro([], eBlock(list{eBlock(list{exampleExpression})}), "Ok((:$$_bindExpression_$$ {1}))")
|
|
||||||
testMacroEval([], eBlock(list{eBlock(list{exampleExpression})}), "Ok(1)")
|
|
||||||
// Block assigned to a variable
|
|
||||||
testMacro(
|
|
||||||
[],
|
|
||||||
eBlock(list{eLetStatement("z", eBlock(list{eBlock(list{exampleExpressionY})}))}),
|
|
||||||
"Ok((:$$_bindExpression_$$ (:$_let_$ :z {{:y}})))",
|
|
||||||
)
|
|
||||||
testMacroEval(
|
|
||||||
[],
|
|
||||||
eBlock(list{eLetStatement("z", eBlock(list{eBlock(list{exampleExpressionY})}))}),
|
|
||||||
"Ok(@{z: :y})",
|
|
||||||
)
|
|
||||||
// Empty block
|
|
||||||
testMacro([], eBlock(list{}), "Ok(:undefined block)") //TODO: should be an error
|
|
||||||
// :$$_block_$$ (:$$_block_$$ (:$_let_$ :y (:add :x 1)) :y)"
|
|
||||||
testMacro(
|
|
||||||
[],
|
|
||||||
eBlock(list{
|
|
||||||
eBlock(list{
|
|
||||||
eLetStatement("y", eFunction("add", list{eSymbol("x"), eNumber(1.)})),
|
|
||||||
eSymbol("y"),
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
"Ok((:$$_bindExpression_$$ {(:$_let_$ :y (:add :x 1)); :y}))",
|
|
||||||
)
|
|
||||||
testMacroEval(
|
|
||||||
[("x", IEvNumber(1.))],
|
|
||||||
eBlock(list{
|
|
||||||
eBlock(list{
|
|
||||||
eLetStatement("y", eFunction("add", list{eSymbol("x"), eNumber(1.)})),
|
|
||||||
eSymbol("y"),
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
"Ok(2)",
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("lambda", () => {
|
|
||||||
// assign a lambda to a variable
|
|
||||||
let lambdaExpression = eFunction("$$_lambda_$$", list{eArrayString(["y"]), exampleExpressionY})
|
|
||||||
testMacro([], lambdaExpression, "Ok(lambda(y=>internal code))")
|
|
||||||
// call a lambda
|
|
||||||
let callLambdaExpression = list{lambdaExpression, eNumber(1.)}->ExpressionT.EList
|
|
||||||
testMacro([], callLambdaExpression, "Ok(((:$$_lambda_$$ [y] :y) 1))")
|
|
||||||
testMacroEval([], callLambdaExpression, "Ok(1)")
|
|
||||||
// Parameters shadow the outer scope
|
|
||||||
testMacroEval([("y", IEvNumber(666.))], callLambdaExpression, "Ok(1)")
|
|
||||||
// When not shadowed by the parameters, the outer scope variables are available
|
|
||||||
let lambdaExpression = eFunction(
|
|
||||||
"$$_lambda_$$",
|
|
||||||
list{eArrayString(["z"]), eFunction("add", list{eSymbol("y"), eSymbol("z")})},
|
|
||||||
)
|
|
||||||
let callLambdaExpression = eList(list{lambdaExpression, eNumber(1.)})
|
|
||||||
testMacroEval([("y", IEvNumber(666.))], callLambdaExpression, "Ok(667)")
|
|
||||||
})
|
|
|
@ -1,41 +0,0 @@
|
||||||
module ExpressionValue = ReducerInterface.ExternalExpressionValue
|
|
||||||
|
|
||||||
open Jest
|
|
||||||
open Expect
|
|
||||||
|
|
||||||
let expectEvalToBe = (expr: string, answer: string) =>
|
|
||||||
Reducer.evaluate(expr)->ExpressionValue.toStringResult->expect->toBe(answer)
|
|
||||||
|
|
||||||
let testEval = (expr, answer) => test(expr, () => expectEvalToBe(expr, answer))
|
|
||||||
|
|
||||||
describe("builtin", () => {
|
|
||||||
// All MathJs operators and functions are available for string, number and boolean
|
|
||||||
// .e.g + - / * > >= < <= == /= not and or
|
|
||||||
// See https://mathjs.org/docs/expressions/syntax.html
|
|
||||||
// See https://mathjs.org/docs/reference/functions.html
|
|
||||||
testEval("-1", "Ok(-1)")
|
|
||||||
testEval("1-1", "Ok(0)")
|
|
||||||
testEval("2>1", "Ok(true)")
|
|
||||||
testEval("concat('a','b')", "Ok('ab')")
|
|
||||||
testEval(
|
|
||||||
"addOne(t)=t+1; toList(mapSamples(fromSamples([1,2,3,4,5,6]), addOne))",
|
|
||||||
"Ok([2,3,4,5,6,7])",
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("builtin exception", () => {
|
|
||||||
//It's a pity that MathJs does not return error position
|
|
||||||
test("MathJs Exception", () =>
|
|
||||||
expectEvalToBe("testZadanga(1)", "Error(JS Exception: Error: Undefined function testZadanga)")
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("error reporting from collection functions", () => {
|
|
||||||
testEval("arr=[1,2,3]; map(arr, {|x| x*2})", "Ok([2,4,6])")
|
|
||||||
testEval(
|
|
||||||
"arr = [normal(3,2)]; map(arr, zarathsuzaWasHere)",
|
|
||||||
"Error(zarathsuzaWasHere is not defined)",
|
|
||||||
)
|
|
||||||
// FIXME: returns "Error(Function not found: map(Array,Symbol))"
|
|
||||||
// Actually this error is correct but not informative
|
|
||||||
})
|
|
|
@ -1,22 +0,0 @@
|
||||||
// Reducer_Helpers
|
|
||||||
module ErrorValue = Reducer_ErrorValue
|
|
||||||
module ExternalExpressionValue = ReducerInterface.ExternalExpressionValue
|
|
||||||
module InternalExpressionValue = ReducerInterface.InternalExpressionValue
|
|
||||||
module Module = Reducer_Category_Module
|
|
||||||
|
|
||||||
let removeDefaultsInternal = (iev: InternalExpressionValue.t) => {
|
|
||||||
switch iev {
|
|
||||||
| InternalExpressionValue.IEvModule(nameSpace) =>
|
|
||||||
Module.removeOther(
|
|
||||||
nameSpace,
|
|
||||||
ReducerInterface.StdLib.internalStdLib,
|
|
||||||
)->InternalExpressionValue.IEvModule
|
|
||||||
| value => value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let removeDefaultsExternal = (ev: ExternalExpressionValue.t): ExternalExpressionValue.t =>
|
|
||||||
ev->InternalExpressionValue.toInternal->removeDefaultsInternal->InternalExpressionValue.toExternal
|
|
||||||
|
|
||||||
let rRemoveDefaultsInternal = r => Belt.Result.map(r, removeDefaultsInternal)
|
|
||||||
let rRemoveDefaultsExternal = r => Belt.Result.map(r, removeDefaultsExternal)
|
|
|
@ -1,31 +0,0 @@
|
||||||
module MathJs = Reducer_MathJs
|
|
||||||
module ErrorValue = Reducer.ErrorValue
|
|
||||||
|
|
||||||
open Jest
|
|
||||||
open ExpectJs
|
|
||||||
|
|
||||||
describe("eval", () => {
|
|
||||||
test("Number", () => expect(MathJs.Eval.eval("1"))->toEqual(Ok(IEvNumber(1.))))
|
|
||||||
test("Number expr", () => expect(MathJs.Eval.eval("1-1"))->toEqual(Ok(IEvNumber(0.))))
|
|
||||||
test("String", () => expect(MathJs.Eval.eval("'hello'"))->toEqual(Ok(IEvString("hello"))))
|
|
||||||
test("String expr", () =>
|
|
||||||
expect(MathJs.Eval.eval("concat('hello ','world')"))->toEqual(Ok(IEvString("hello world")))
|
|
||||||
)
|
|
||||||
test("Boolean", () => expect(MathJs.Eval.eval("true"))->toEqual(Ok(IEvBool(true))))
|
|
||||||
test("Boolean expr", () => expect(MathJs.Eval.eval("2>1"))->toEqual(Ok(IEvBool(true))))
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("errors", () => {
|
|
||||||
// All those errors propagete up and are returned by the resolver
|
|
||||||
test("unknown function", () =>
|
|
||||||
expect(MathJs.Eval.eval("testZadanga()"))->toEqual(
|
|
||||||
Error(ErrorValue.REJavaScriptExn(Some("Undefined function testZadanga"), Some("Error"))),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
test("unknown answer type", () =>
|
|
||||||
expect(MathJs.Eval.eval("1+1i"))->toEqual(
|
|
||||||
Error(ErrorValue.RETodo("Unhandled MathJs literal type: object")),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
})
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user