basedpl 0.1.7__tar.gz → 0.1.8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. {basedpl-0.1.7 → basedpl-0.1.8}/Cargo.lock +1 -1
  2. {basedpl-0.1.7 → basedpl-0.1.8}/Cargo.toml +1 -1
  3. {basedpl-0.1.7 → basedpl-0.1.8}/DEV.md +2 -2
  4. {basedpl-0.1.7 → basedpl-0.1.8}/PKG-INFO +4 -1
  5. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/colon.md +10 -7
  6. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/dot.md +1 -1
  7. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/execute.md +4 -3
  8. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/iota.md +3 -2
  9. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/mix.md +3 -3
  10. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/parentheses.md +0 -7
  11. {basedpl-0.1.7 → basedpl-0.1.8}/docs/index.md +5 -5
  12. basedpl-0.1.8/docs/keyed.md +158 -0
  13. {basedpl-0.1.7 → basedpl-0.1.8}/docs/processes.md +1 -1
  14. {basedpl-0.1.7 → basedpl-0.1.8}/docs/python.md +4 -0
  15. {basedpl-0.1.7 → basedpl-0.1.8}/pyproject.toml +2 -1
  16. {basedpl-0.1.7 → basedpl-0.1.8}/python/basedpl/__init__.py +23 -6
  17. {basedpl-0.1.7 → basedpl-0.1.8}/python/basedpl/apltests.py +2 -1
  18. {basedpl-0.1.7 → basedpl-0.1.8}/python/basedpl/reference.py +1 -1
  19. basedpl-0.1.8/src/agreement.rs +109 -0
  20. {basedpl-0.1.7 → basedpl-0.1.8}/src/array.rs +149 -42
  21. {basedpl-0.1.7 → basedpl-0.1.8}/src/display.rs +4 -3
  22. {basedpl-0.1.7 → basedpl-0.1.8}/src/eval.rs +123 -137
  23. basedpl-0.1.8/src/keyed.rs +174 -0
  24. {basedpl-0.1.7 → basedpl-0.1.8}/src/number_theory.rs +8 -6
  25. {basedpl-0.1.7 → basedpl-0.1.8}/src/polynomial.rs +25 -15
  26. {basedpl-0.1.7 → basedpl-0.1.8}/src/primitive.rs +324 -300
  27. {basedpl-0.1.7 → basedpl-0.1.8}/src/protocol.rs +20 -7
  28. {basedpl-0.1.7 → basedpl-0.1.8}/src/python.rs +28 -4
  29. {basedpl-0.1.7 → basedpl-0.1.8}/src/selection.rs +8 -3
  30. {basedpl-0.1.7 → basedpl-0.1.8}/src/symbols.rs +1 -0
  31. {basedpl-0.1.7 → basedpl-0.1.8}/src/syntax.rs +3 -29
  32. {basedpl-0.1.7 → basedpl-0.1.8}/src/system.rs +3 -2
  33. {basedpl-0.1.7 → basedpl-0.1.8}/tests/core.rs +12 -4
  34. {basedpl-0.1.7 → basedpl-0.1.8}/tests/reference/README.md +1 -1
  35. {basedpl-0.1.7 → basedpl-0.1.8}/tests/reference/aplcart.apl +17 -6
  36. {basedpl-0.1.7 → basedpl-0.1.8}/tests/reference/april.apl +4 -5
  37. {basedpl-0.1.7 → basedpl-0.1.8}/tests/reference/core.apl +366 -272
  38. {basedpl-0.1.7 → basedpl-0.1.8}/tests/reference/inventory/aplcart.jsonl +3 -3
  39. {basedpl-0.1.7 → basedpl-0.1.8}/tests/reference.rs +1 -1
  40. {basedpl-0.1.7 → basedpl-0.1.8}/tests/test_api.py +21 -6
  41. {basedpl-0.1.7 → basedpl-0.1.8}/tests/test_apltests.py +5 -1
  42. {basedpl-0.1.7 → basedpl-0.1.8}/tests/test_worker.py +9 -8
  43. basedpl-0.1.7/docs/keyed.md +0 -145
  44. basedpl-0.1.7/src/agreement.rs +0 -55
  45. basedpl-0.1.7/src/keyed.rs +0 -132
  46. {basedpl-0.1.7 → basedpl-0.1.8}/.cargo/config.toml +0 -0
  47. {basedpl-0.1.7 → basedpl-0.1.8}/.github/workflows/ci.yml +0 -0
  48. {basedpl-0.1.7 → basedpl-0.1.8}/.gitignore +0 -0
  49. {basedpl-0.1.7 → basedpl-0.1.8}/LICENSE +0 -0
  50. {basedpl-0.1.7 → basedpl-0.1.8}/README.md +0 -0
  51. {basedpl-0.1.7 → basedpl-0.1.8}/_config.yml +0 -0
  52. {basedpl-0.1.7 → basedpl-0.1.8}/_layouts/default.html +0 -0
  53. {basedpl-0.1.7 → basedpl-0.1.8}/docs/cli.md +0 -0
  54. {basedpl-0.1.7 → basedpl-0.1.8}/docs/getting-started.md +0 -0
  55. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/agenda.md +0 -0
  56. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/alpha-underbar.md +0 -0
  57. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/alpha.md +0 -0
  58. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/alphabet.md +0 -0
  59. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/and.md +0 -0
  60. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/assign.md +0 -0
  61. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/at.md +0 -0
  62. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/backslash-bar.md +0 -0
  63. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/backslash.md +0 -0
  64. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/behind.md +0 -0
  65. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/braces.md +0 -0
  66. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/brackets.md +0 -0
  67. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/bullet.md +0 -0
  68. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/case.md +0 -0
  69. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/ceiling.md +0 -0
  70. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/circle.md +0 -0
  71. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/comma.md +0 -0
  72. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/comment.md +0 -0
  73. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/commute.md +0 -0
  74. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/compose.md +0 -0
  75. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/decode.md +0 -0
  76. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/del-diaeresis.md +0 -0
  77. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/del.md +0 -0
  78. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/derivative.md +0 -0
  79. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/diamond.md +0 -0
  80. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/digits.md +0 -0
  81. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/divide.md +0 -0
  82. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/domino.md +0 -0
  83. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/drop.md +0 -0
  84. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/each.md +0 -0
  85. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/enclose.md +0 -0
  86. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/encode.md +0 -0
  87. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/equal.md +0 -0
  88. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/error-guard.md +0 -0
  89. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/factor.md +0 -0
  90. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/factorial.md +0 -0
  91. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/find.md +0 -0
  92. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/floor.md +0 -0
  93. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/format.md +0 -0
  94. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/grade-down.md +0 -0
  95. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/grade-up.md +0 -0
  96. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/greater-or-equal.md +0 -0
  97. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/greater.md +0 -0
  98. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/infinity.md +0 -0
  99. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/intersection.md +0 -0
  100. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/inverse-pair.md +0 -0
  101. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/key.md +0 -0
  102. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/left.md +0 -0
  103. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/less-or-equal.md +0 -0
  104. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/less.md +0 -0
  105. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/load.md +0 -0
  106. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/log.md +0 -0
  107. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/match.md +0 -0
  108. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/member.md +0 -0
  109. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/minus.md +0 -0
  110. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/nand.md +0 -0
  111. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/nest.md +0 -0
  112. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/nor.md +0 -0
  113. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/not-equal.md +0 -0
  114. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/omega-underbar.md +0 -0
  115. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/omega.md +0 -0
  116. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/or.md +0 -0
  117. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/outer-product.md +0 -0
  118. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/over.md +0 -0
  119. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/overbar.md +0 -0
  120. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/pi.md +0 -0
  121. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/pipe.md +0 -0
  122. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/plus.md +0 -0
  123. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/polynomial.md +0 -0
  124. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/power.md +0 -0
  125. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/prime.md +0 -0
  126. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/quad.md +0 -0
  127. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/question.md +0 -0
  128. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/quote.md +0 -0
  129. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/rank.md +0 -0
  130. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/reverse-first.md +0 -0
  131. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/reverse.md +0 -0
  132. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/rho.md +0 -0
  133. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/right.md +0 -0
  134. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/root.md +0 -0
  135. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/semicolon.md +0 -0
  136. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/slash-bar.md +0 -0
  137. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/slash.md +0 -0
  138. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/squad.md +0 -0
  139. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/star.md +0 -0
  140. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/stencil.md +0 -0
  141. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/stile.md +0 -0
  142. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/strand.md +0 -0
  143. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/table.md +0 -0
  144. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/take.md +0 -0
  145. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/tally.md +0 -0
  146. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/tilde.md +0 -0
  147. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/times.md +0 -0
  148. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/transpose.md +0 -0
  149. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/under.md +0 -0
  150. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/unicode.md +0 -0
  151. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/union.md +0 -0
  152. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/where.md +0 -0
  153. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/windows.md +0 -0
  154. {basedpl-0.1.7 → basedpl-0.1.8}/docs/glyphs/zilde.md +0 -0
  155. {basedpl-0.1.7 → basedpl-0.1.8}/docs/keyboard.md +0 -0
  156. {basedpl-0.1.7 → basedpl-0.1.8}/docs/repl.md +0 -0
  157. {basedpl-0.1.7 → basedpl-0.1.8}/docs/rules.md +0 -0
  158. {basedpl-0.1.7 → basedpl-0.1.8}/editors/vim/ftdetect/apl.vim +0 -0
  159. {basedpl-0.1.7 → basedpl-0.1.8}/editors/vim/syntax/apl.vim +0 -0
  160. {basedpl-0.1.7 → basedpl-0.1.8}/editors/vim/test.vim +0 -0
  161. {basedpl-0.1.7 → basedpl-0.1.8}/lib/LICENSE-april +0 -0
  162. {basedpl-0.1.7 → basedpl-0.1.8}/lib/README.md +0 -0
  163. {basedpl-0.1.7 → basedpl-0.1.8}/lib/array.apl +0 -0
  164. {basedpl-0.1.7 → basedpl-0.1.8}/lib/dyalog.apl +0 -0
  165. {basedpl-0.1.7 → basedpl-0.1.8}/lib/graph.apl +0 -0
  166. {basedpl-0.1.7 → basedpl-0.1.8}/lib/numeric.apl +0 -0
  167. {basedpl-0.1.7 → basedpl-0.1.8}/lib/power.apl +0 -0
  168. {basedpl-0.1.7 → basedpl-0.1.8}/lib/string.apl +0 -0
  169. {basedpl-0.1.7 → basedpl-0.1.8}/lib/tree.apl +0 -0
  170. {basedpl-0.1.7 → basedpl-0.1.8}/python/basedpl/_cli.py +0 -0
  171. {basedpl-0.1.7 → basedpl-0.1.8}/python/basedpl/functions.py +0 -0
  172. {basedpl-0.1.7 → basedpl-0.1.8}/python/basedpl/keyboard.json +0 -0
  173. {basedpl-0.1.7 → basedpl-0.1.8}/python/basedpl/worker.py +0 -0
  174. {basedpl-0.1.7 → basedpl-0.1.8}/rustfmt.toml +0 -0
  175. {basedpl-0.1.7 → basedpl-0.1.8}/scripts/reference.py +0 -0
  176. {basedpl-0.1.7 → basedpl-0.1.8}/src/cli.rs +0 -0
  177. {basedpl-0.1.7 → basedpl-0.1.8}/src/editor.rs +0 -0
  178. {basedpl-0.1.7 → basedpl-0.1.8}/src/error.rs +0 -0
  179. {basedpl-0.1.7 → basedpl-0.1.8}/src/execution.rs +0 -0
  180. {basedpl-0.1.7 → basedpl-0.1.8}/src/kernel.rs +0 -0
  181. {basedpl-0.1.7 → basedpl-0.1.8}/src/lib.rs +0 -0
  182. {basedpl-0.1.7 → basedpl-0.1.8}/src/main.rs +0 -0
  183. {basedpl-0.1.7 → basedpl-0.1.8}/src/number.rs +0 -0
  184. {basedpl-0.1.7 → basedpl-0.1.8}/src/reference.rs +0 -0
  185. {basedpl-0.1.7 → basedpl-0.1.8}/src/worker.rs +0 -0
  186. {basedpl-0.1.7 → basedpl-0.1.8}/tests/cli.rs +0 -0
  187. {basedpl-0.1.7 → basedpl-0.1.8}/tests/reference/LICENSE-aplcart +0 -0
  188. {basedpl-0.1.7 → basedpl-0.1.8}/tests/reference/LICENSE-april +0 -0
  189. {basedpl-0.1.7 → basedpl-0.1.8}/tests/reference/LICENSE-dyalog +0 -0
  190. {basedpl-0.1.7 → basedpl-0.1.8}/tests/reference/LICENSE-ngn +0 -0
  191. {basedpl-0.1.7 → basedpl-0.1.8}/tests/reference/dyalog.apl +0 -0
  192. {basedpl-0.1.7 → basedpl-0.1.8}/tests/reference/inventory/april.jsonl +0 -0
  193. {basedpl-0.1.7 → basedpl-0.1.8}/tests/reference/inventory/core.jsonl +0 -0
  194. {basedpl-0.1.7 → basedpl-0.1.8}/tests/reference/inventory/dyalog.jsonl +0 -0
  195. {basedpl-0.1.7 → basedpl-0.1.8}/tests/reference/inventory/ngn.jsonl +0 -0
  196. {basedpl-0.1.7 → basedpl-0.1.8}/tests/reference/ngn.apl +0 -0
  197. {basedpl-0.1.7 → basedpl-0.1.8}/tests/reference/slow.apl +0 -0
  198. {basedpl-0.1.7 → basedpl-0.1.8}/tests/test_basic.py +0 -0
  199. {basedpl-0.1.7 → basedpl-0.1.8}/tests/test_kernel.py +0 -0
  200. {basedpl-0.1.7 → basedpl-0.1.8}/tests/test_reference.py +0 -0
  201. {basedpl-0.1.7 → basedpl-0.1.8}/tests/test_repl.py +0 -0
  202. {basedpl-0.1.7 → basedpl-0.1.8}/wheel/data/share/jupyter/kernels/apl/kernel.json +0 -0
@@ -36,7 +36,7 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
36
36
 
37
37
  [[package]]
38
38
  name = "basedpl"
39
- version = "0.1.7"
39
+ version = "0.1.8"
40
40
  dependencies = [
41
41
  "anyhow",
42
42
  "async-trait",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "basedpl"
3
- version = "0.1.7"
3
+ version = "0.1.8"
4
4
  edition = "2021"
5
5
  rust-version = "1.98"
6
6
  license = "Apache-2.0"
@@ -34,8 +34,8 @@ CI uses the development profile for Rust and Python tests. Distribution wheels u
34
34
  - `lib/*.apl`: Dyalog dfns adapted from April and the Dyalog dfns workspace. Reference cases load these shared definitions with `•LOAD`; case-specific setup stays in each test. See `lib/README.md` for usage and provenance.
35
35
  - `array.rs`: ordinary `Value` atoms and immutable shared arrays; checked construction, recursive prototypes, axis offsets, direct/mapped result frames, cell descriptors, padded cell assembly and row-based display. Arrays retain function handles and cache lexical dependencies without owning lexical frames or Python objects.
36
36
  - `number.rs`: Integer/Exact/Float/Complex values, normalization, checked arithmetic, promotion, comparison and structural conversion. `i64` and BigRational share one exact domain. Checked integer overflow falls back to BigRational; integral rational results return to `i64` when they fit. Representation is private. Complex values use `num_complex::Complex64`, normalizing exactly zero imaginary parts to Float.
37
- - `agreement.rs`: leading/unit-axis broadcast shapes and index maps, shared by scalar functions, Each, rank frames and explicit scalar axes. Equal-shape and repeated-block mappings avoid general coordinate calculations. No expanded input copies.
38
- - `keyed.rs`: keyed arrays. `Keys` holds element names in ravel order with a name-to-position hash, stored in an optional field of the array. Every ordinary constructor leaves that field unset, so a primitive drops keys unless it reattaches them. `construct` and `merge` build keyed arrays, `align` puts two keyed arguments in one layout, and `retain` keeps keys on a positional result of the keyed argument's shape. Structural functions carry keys through `carry_keys` in `primitive.rs`. Dot access is a binder rewrite to Pick in `eval.rs`. The design and its decision log are in `meta/keyed.md`.
37
+ - `agreement.rs`: positional broadcasting and key union through one output layout and two index maps. Scalar functions, Each, rank frames, native mathematical functions and explicit scalar axes use this path. Missing entries use prototype fill. Compact numeric kernels read the maps directly. Equal-shape and repeated-block mappings avoid coordinate calculations and expanded input copies. Contracted-axis maps require equal key sets and retain the first axis's order.
38
+ - `keyed.rs`: axis-label construction, lookup and named updates. Each immutable `Keys` holds unique strings and a name-to-position hash. `Layout` in `array.rs` owns dimensions and keys together. Its axis selection, concatenation and replacement operations describe structural results. Frames and cells carry layouts into assembly, which retains cell-axis labels shared by all result cells. Dot access is a binder rewrite to Pick in `eval.rs`. The design is in `meta/axiskey.md`.
39
39
  - `primitive.rs`: primitive identities/glyphs, valences and array-level implementations. Replicate, products and assignment retain their own agreement rules. Allocation caps are separate from numeric-to-integer conversion.
40
40
  - `number_theory.rs`: segmented prime enumeration, Miller–Rabin primality and Brent/Pollard–rho factorisation; exact integer results and scalar-cell assembly.
41
41
  - `system.rs`: case-insensitive `•Name` table for constant arrays and native functions. System functions use ordinary function nodes and application. `primitive.rs` contains single-character primitives.
@@ -1,14 +1,17 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: basedpl
3
- Version: 0.1.7
3
+ Version: 0.1.8
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Requires-Dist: fastship>=0.0.11 ; extra == 'dev'
7
7
  Requires-Dist: maturin>=1.0,<2.0 ; extra == 'dev'
8
8
  Requires-Dist: pytest ; extra == 'dev'
9
9
  Requires-Dist: numpy ; extra == 'dev'
10
+ Requires-Dist: pandas ; extra == 'dev'
10
11
  Requires-Dist: conkernelclient>=0.0.23 ; extra == 'dev'
12
+ Requires-Dist: pandas ; extra == 'pandas'
11
13
  Provides-Extra: dev
14
+ Provides-Extra: pandas
12
15
  License-File: LICENSE
13
16
  Summary: bAsedPL is an APL-derived array language, borrowing ideas from J and BQN, with an emphasis on simple, consistent notation.
14
17
  Author-email: Jeremy Howard <github@jhoward.fastmail.fm>
@@ -1,16 +1,19 @@
1
- # `:` — Guard
1
+ # `:` — Unkey / Key / Guard
2
2
 
3
- `condition:result` returns `result` when the condition is true. Otherwise execution continues. Only the chosen result evaluates.
3
+ `:Y` removes axis keys. `K:Y` labels leading axes. `K:[axes]Y` labels selected axes. See [axis keys](../keyed.md).
4
4
 
5
5
  ```apl
6
- {⍵<0:-⍵ ⋄ ⍵}¯4 ⍝ 4
7
- {⍵=0:0 ⋄ ÷⍵}0 ⍝ 0
6
+ :('aa' 'bb':1 2) ⍝ 1 2
7
+ 'bb'⊃'aa' 'bb':1 2 ⍝ 2
8
8
  ```
9
9
 
10
- Condition: Boolean singleton. An empty selected result returns no value. See [error guards](error-guard.md).
10
+ Inside a dfn, group functional uses: `(K:Y)` or `(:Y)`.
11
11
 
12
- Inside parentheses, `key:value` names a value in a [keyed array](../keyed.md).
12
+ `condition:result` returns `result` when the condition is true. Otherwise execution continues. Only the chosen result evaluates.
13
13
 
14
14
  ```apl
15
- 'b'⊃('a':1 ⋄ 'b':2) ⍝ 2
15
+ {⍵<0:-⍵ ⋄ ⍵}¯4 ⍝ 4
16
+ {⍵=0:0 ⋄ ÷⍵}0 ⍝ 0
16
17
  ```
18
+
19
+ Condition: Boolean singleton. An empty selected result returns no value. See [error guards](error-guard.md).
@@ -12,7 +12,7 @@ Singleton contraction axes extend. Empty contractions use the reduction identity
12
12
  After an array, `.name` reads a [keyed array](../keyed.md) value: `T.name` is `'name'⊃T`. It also works as an assignment target.
13
13
 
14
14
  ```apl
15
- T←('n':1 ⋄ 'addr':('city':'Paris'))
15
+ T←('n':1),('addr':'city':'Paris')
16
16
  T.addr.city ⍝ 'Paris'
17
17
  T.n+←1 ⋄ T.n ⍝ 2
18
18
  ```
@@ -7,11 +7,12 @@
7
7
  x←7 ⋄ ⍎'x+1' ⍝ 8
8
8
  ```
9
9
 
10
- `X⍎Y` runs no code. It looks up key `Y` in the keyed array `X`, exactly as `Y⊃X` does.
10
+ `X⍎Y` selects `Y⊃X`, using numeric coordinates or axis keys.
11
11
 
12
12
  ```apl
13
- T←('a':10 ⋄ 'b':20)
14
- T⍎'b' ⍝ 20
13
+ T←'aa' 'bb':10 20
14
+ T⍎'bb' ⍝ 20
15
+ 10 20⍎2 ⍝ 20
15
16
  ```
16
17
 
17
18
  Assignments and explicit output take effect in the current session.
@@ -13,10 +13,11 @@ A shape vector generates an array of coordinate vectors.
13
13
  ⍳2 2 ⍝ 2 2⍴(1 1⋄ 1 2⋄ 2 1⋄ 2 2)
14
14
  ```
15
15
 
16
- On a [keyed array](../keyed.md), `⍳` returns the keys with the array's shape.
16
+ `⍳[axis]Y` returns that axis's keys, or `1…length` on an unkeyed axis. Several axes return a vector of selector vectors.
17
17
 
18
18
  ```apl
19
- ⍳('price':1 ⋄ 'qty':2) ⍝ 'price' 'qty'
19
+ ⍳[1]('price' 'qty':1 2) ⍝ 'price' 'qty'
20
+ ⍳[2]2 3⍴0 ⍝ 1x 2x 3x
20
21
  ```
21
22
 
22
23
  `X⍳Y` finds the first matching major cell in `X` for each cell of `Y`. Not found: `1+≢X`. Uses tolerant matching and returns exact positions.
@@ -22,12 +22,12 @@ Array-valued coordinate fields pair using leading-axis agreement:
22
22
  (1 3)˘(2 4)⊃3 4⍴⍳12 ⍝ 2 12
23
23
  ```
24
24
 
25
- On a [keyed array](../keyed.md), a string picks the value stored under that key. An array of strings returns an ordinary array of values with the same shape.
25
+ Coordinate fields may use strings on [keyed axes](../keyed.md).
26
26
 
27
27
  ```apl
28
- T←('price':1 2 3 ⋄ 'qty':4 5 6)
28
+ T←'price' 'qty':(1 2 3 ⋄ 4 5 6)
29
29
  'qty'⊃T ⍝ 4 5 6
30
- ('qty' 'price')⊃T ⍝ (4 5 6 ⋄ 1 2 3)
30
+ (⊂'qty' 'price')⊃T ⍝ (4 5 6 ⋄ 1 2 3)
31
31
  ```
32
32
 
33
33
  On Mix, axes place cell dimensions among frame dimensions; fractional positions insert before an axis. INDEX: position outside the array. Index 1 on an empty axis gives fill, as does [First](take.md).
@@ -14,13 +14,6 @@ Separators construct nested vectors. A trailing separator makes a singleton.
14
14
  (1 2 ⋄ 3 4) ⍝ (1 2)(3 4)
15
15
  ```
16
16
 
17
- `key:value` entries construct a [keyed array](../keyed.md). `()` is the empty keyed vector.
18
-
19
- ```apl
20
- ≢('a':1 ⋄ 'b':2) ⍝ 2x
21
- ≢() ⍝ 0x
22
- ```
23
-
24
17
  Two functions form an atop: `(f g)Y` is `f(g Y)`.
25
18
 
26
19
  ```apl
@@ -22,7 +22,7 @@ bAsedPL distinguishes two application rules. Structural mapping (arithmetic, Eac
22
22
  - [Python](python.md): sessions, arrays, NumPy and callable APL functions.
23
23
  - [Process interfaces](processes.md): JSON lines and interruptible workers.
24
24
  - [Language rules](rules.md): evaluation, numbers, agreement, axes and fill.
25
- - [Keyed arrays](keyed.md): named elements, lookup, agreement, assignment and Python dicts.
25
+ - [Axis keys](keyed.md): named rows and columns, lookup, agreement, assignment and Python conversion.
26
26
 
27
27
  ## Glyph reference
28
28
 
@@ -75,7 +75,7 @@ Each name below links to its definitions and examples. Examples show an equivale
75
75
  | `⊆` [Nest](glyphs/nest.md) | Nest | Partition | |
76
76
  | `⊃` [Mix](glyphs/mix.md) | Mix | Pick | Monad as Dyalog `⎕ML≥2`. Pick takes cells: `2⊃2 3⍴⍳6` is `4 5 6`. A string picks by [key](keyed.md) |
77
77
  | `⌷` [Squad](glyphs/squad.md) | Identity | Index | |
78
- | `⍳` [Iota](glyphs/iota.md) | Index generator | Index of | Monad on a [keyed array](keyed.md) returns its keys |
78
+ | `⍳` [Iota](glyphs/iota.md) | Index generator | Index of | `⍳[axes]Y` returns axis selectors |
79
79
  | `⍸` [Where](glyphs/where.md) | Where | Interval index | |
80
80
  | `∊` [Member](glyphs/member.md) | Enlist | Membership | |
81
81
  | `∪` [Union](glyphs/union.md) | Unique | Union | |
@@ -129,14 +129,14 @@ Each name below links to its definitions and examples. Examples show an equivale
129
129
  | `˘` [Strand](glyphs/strand.md) | Form a vector of values: `1˘+˘'abc'` |
130
130
  | `←` [Assign](glyphs/assign.md) | Assignment, including modified/indexed/selective forms |
131
131
  | `→` [Pipe](glyphs/pipe.md) | Left-to-right function application |
132
- | `(…)` [Parentheses](glyphs/parentheses.md) | Grouping / nested array literals / [keyed arrays](keyed.md) / trains |
132
+ | `(…)` [Parentheses](glyphs/parentheses.md) | Grouping / nested array literals / trains |
133
133
  | `[…]` [Brackets](glyphs/brackets.md) | Enclosure, array literals, indexing and axes |
134
134
  | `;` [Semicolon](glyphs/semicolon.md) | Index-axis separator |
135
135
  | `{…}` [Braces](glyphs/braces.md) | Defined function or operator |
136
136
  | `⍺` [Alpha](glyphs/alpha.md), `⍵` [Omega](glyphs/omega.md) | Left / right argument |
137
137
  | `⍶` [Alpha underbar](glyphs/alpha-underbar.md), `⍹` [Omega underbar](glyphs/omega-underbar.md) | Left / right operand |
138
138
  | `∇` [Del](glyphs/del.md), `⍢` [Del diaeresis](glyphs/del-diaeresis.md) | Function / operator self-reference |
139
- | `:` [Colon](glyphs/colon.md), `::` [Error guard](glyphs/error-guard.md) | Boolean guard or [keyed](keyed.md) entry / error guard |
139
+ | `:` [Colon](glyphs/colon.md), `::` [Error guard](glyphs/error-guard.md) | Unkey / key axes; Boolean guard / error guard inside dfns |
140
140
  | `⋄` [Diamond](glyphs/diamond.md) | Statement or literal separator |
141
141
  | `⍝` [Comment](glyphs/comment.md) | Comment |
142
142
  | `⎕←` [Quad](glyphs/quad.md) | Explicit output |
@@ -169,4 +169,4 @@ Index origin: 1. Comparison tolerance: `1E¯14`.
169
169
  - [Numbers](rules.md#numbers): bare numbers are approximate. `x` and `r` mark exact integers and rationals, as J: `1x÷3x` is `1r3`. Predicates, positions, tally and shape are exact.
170
170
  - [Agreement](rules.md#agreement-and-pervasion): scalar functions, Each and Rank align leading axes, and unit dimensions expand: `(2 3⍴⍳6)+10 20` is `2 3⍴11 12 13 24 25 26`.
171
171
  - [System names](#system-names): written `•NAME`, as BQN, not `⎕NAME`. Most Dyalog system functions and variables are not included.
172
- - Namespaces: none. [Keyed arrays](keyed.md) hold named data instead. `('a':1 ⋄ 'b':2)` evaluates its keys, so they are quoted. `T.a` is `'a'⊃T`, and `X⍎Y` is `Y⊃X`.
172
+ - [Axis keys](keyed.md) name data. `K:Y` evaluates its keys. `T.a` is `'a'⊃T`, and `X⍎Y` is `Y⊃X`.
@@ -0,0 +1,158 @@
1
+ # Axis keys
2
+
3
+ [Glyph index](index.md#glyph-reference)
4
+
5
+ An array can have string keys on any of its axes. Keys name positions on that axis. Numeric coordinates remain 1-origin. Array operations retain their ordinary meaning.
6
+
7
+ ## Construction
8
+
9
+ One string names the whole right value. Several strings name its leading-axis cells.
10
+
11
+ ```apl
12
+ T←'price' 'qty':(1 2 3 ⋄ 4 5 6)
13
+ ≢T ⍝ 2x
14
+ ⍴('ab':11 12 13) ⍝ ,1x
15
+ 'price' 'qty':2 3⍴⍳6 ⍝ ('price' 'qty':[1]2 3⍴⍳6)
16
+ ≢⍬:⍬ ⍝ 0x
17
+ ```
18
+
19
+ A character atom or vector is one string key. Use `,¨'ab'` for two one-character keys.
20
+
21
+ ```apl
22
+ ⍳[1]('a' 'b':1 2) ⍝ ,⊂'ab'
23
+ ⍳[1]((,¨'ab'):1 2) ⍝ ,¨'ab'
24
+ ```
25
+
26
+ Several key vectors label successive axes. `[axes]` chooses which axes to label and always attaches to existing dimensions.
27
+
28
+ ```apl
29
+ M←('alice' 'bob' ⋄ 'price' 'qty'):2 2⍴10 2 20 4
30
+ ⍴M ⍝ 2x 2x
31
+ ⍳[1]M ⍝ 'alice' 'bob'
32
+ ⍳[2]M ⍝ 'price' 'qty'
33
+ :M ⍝ 2 2⍴10 2 20 4
34
+ ⍳[2](:[2]M) ⍝ 1x 2x
35
+ ```
36
+
37
+ Monadic `:` removes axis keys. Inside dfns, group functional colon: `(X:Y)` or `(:Y)`. A direct colon separates a guard.
38
+
39
+ Keys must be unique strings on each axis: DOMAIN. A key-list length must match its axis: LENGTH.
40
+
41
+ ## Selection and assignment
42
+
43
+ A scalar selector removes its axis. A vector selector retains it. A character vector selects one string key.
44
+
45
+ ```apl
46
+ M←('alice' 'bob' ⋄ 'price' 'qty'):2 2⍴10 2 20 4
47
+ M['bob'] ⍝ 'price' 'qty':20 4
48
+ M[;'qty'] ⍝ 'alice' 'bob':2 4
49
+ M['alice';'qty'] ⍝ 2
50
+ M[,⊂'alice'] ⍝ ('alice':[1]('price' 'qty':[2]1 2⍴10 2))
51
+ ('bob' 'qty')⊃M ⍝ 4
52
+ ('bob' 'price')⌷M ⍝ 20
53
+ ```
54
+
55
+ Dot names are literal keys. `T.a.b` is `'b'⊃'a'⊃T`.
56
+
57
+ ```apl
58
+ T←('n':1),('addr':'city':'Paris')
59
+ T.addr.city ⍝ 'Paris'
60
+ T.n+←1 ⋄ T.n ⍝ 2
61
+ ```
62
+
63
+ Plain named assignment appends missing rows or columns. New cells receive the array's prototype before assignment.
64
+
65
+ ```apl
66
+ M←('alice' 'bob' ⋄ 'price' 'qty'):2 2⍴10 2 20 4
67
+ M['cara']←30 5
68
+ M['cara';'qty'] ⍝ 5
69
+ M[;'tax']←1 2 3
70
+ M['alice';'tax'] ⍝ 1
71
+ ```
72
+
73
+ Missing keys on read or modified assignment: INDEX. Repeated selections on a keyed axis: DOMAIN. Numeric out-of-range assignment: INDEX.
74
+
75
+ Scalar selection stores the whole RHS. A vector selection aligns its retained axis.
76
+
77
+ ```apl
78
+ T←'aa' 'bb':1 2 ⋄ U←'bb' 'aa':8 9
79
+ T['aa']←U ⋄ T['aa'] ⍝ 'bb' 'aa':8 9
80
+ T[,⊂'aa']←U ⋄ T['aa'] ⍝ 9
81
+ ```
82
+
83
+ ## Agreement
84
+
85
+ Corresponding keyed axes align by union: left keys, then right-only keys. An absent counterpart receives the present value's prototype.
86
+
87
+ ```apl
88
+ ('aa':2)+('bb':3) ⍝ 'aa' 'bb':2 3
89
+ ('aa':2)×('bb':3) ⍝ 'aa' 'bb':0 0
90
+ ('aa':0)=('bb':0) ⍝ 'aa' 'bb':1x 1x
91
+ ```
92
+
93
+ With one axis unkeyed, agreement is positional. Expanding a keyed singleton drops that axis's labels. Enclosure broadcasts within the named value.
94
+
95
+ ```apl
96
+ ('base':5)+10 20 30 ⍝ 15 25 35
97
+ ('base':5)+⊂10 20 30 ⍝ 'base':15 25 35
98
+ ```
99
+
100
+ Explicit axes choose the correspondence.
101
+
102
+ ```apl
103
+ M←('alice' 'bob' ⋄ 'price' 'qty'):2 2⍴10 2 20 4
104
+ M+[2]('qty' 'tax':10 2) ⍝ ('alice' 'bob' ⋄ 'price' 'qty' 'tax'):2 3⍴10 12 2 20 14 2
105
+ ```
106
+
107
+ ## Array operations
108
+
109
+ Transpose moves axes with their labels. Reverse, rotate and filtering move labels with their positions. Reduction removes the reduced axis. Scan retains it. Shape-changing reshape drops keys.
110
+
111
+ ```apl
112
+ M←('alice' 'bob' ⋄ 'price' 'qty'):2 2⍴10 2 20 4
113
+ +/M ⍝ 'alice' 'bob':12 24
114
+ +⌿M ⍝ 'price' 'qty':30 6
115
+ 4⍴'aa' 'bb':1 2 ⍝ 1 2 1 2
116
+ ```
117
+
118
+ Rank preserves frame labels. Assembled cell axes keep labels shared by every result cell in the same order. Outer product retains both arguments' axes.
119
+
120
+ Per-cell rotation drops labels on the rotated axis when its cells use different rotations. Other axes retain their labels.
121
+
122
+ Inner product, Decode and matrix solve align keyed contracted axes by name. Key sets must match: LENGTH. With either axis unkeyed, contraction is positional.
123
+
124
+ ```apl
125
+ ('hi' 'lo':1 2)+.×('lo' 'hi':10 20) ⍝ 40
126
+ ```
127
+
128
+ Catenate joins positions. Duplicate joined keys give DOMAIN. Replicate may filter a keyed axis, but repetition or padding on that axis gives DOMAIN.
129
+
130
+ ## Match and search
131
+
132
+ Match aligns keys independently on every keyed axis. Shapes and unkeyed axes must match positionally. Searches compare values or cells. Found positions return axis keys.
133
+
134
+ ```apl
135
+ V←'low' 'mid' 'high':10 20 30
136
+ V≡⌽V ⍝ 1x
137
+ V⍳20 99 ⍝ ('mid' ⋄ 4x)
138
+ V⍸5 15 25 40 ⍝ (0x ⋄ 'low' ⋄ 'mid' ⋄ 'high')
139
+ ⍒V ⍝ 'high' 'mid' 'low'
140
+ ('aa':7)∊('bb':7) ⍝ 'aa':1x
141
+ ```
142
+
143
+ Monadic Key passes named positions to its operand. Dyadic Key preserves labels on the grouped values.
144
+
145
+ ```apl
146
+ V←'aa' 'bb' 'cc':1 2 1
147
+ {⊂⍵}⌸V ⍝ (('aa' 'cc') ⋄ ,⊂'bb')
148
+ ```
149
+
150
+ ## Python and JSON
151
+
152
+ Python dicts become keyed vectors recursively. `Array(data, axis_keys=[rows, cols])` attaches labels to existing axes. Use `None` for an unkeyed axis.
153
+
154
+ `.axis_keys` returns a tuple of label tuples or `None`. `.np` copies values to an ndarray. `.py` returns a dict for a keyed vector and a pandas DataFrame for keyed arrays of rank ≥2.
155
+
156
+ `.df` converts any rank to a DataFrame. The last axis supplies columns. Earlier axes supply rows, using a MultiIndex above rank 2. Unkeyed axes use 1-origin labels. Pandas is an optional, lazy dependency: `pip install 'basedpl[pandas]'`.
157
+
158
+ The [process protocol](processes.md) adds `axis_keys`, e.g. `[null, ["price", "qty"]]`. All-unkeyed arrays omit it.
@@ -19,7 +19,7 @@ The second response is:
19
19
 
20
20
  Use `json.dumps(code)` or `JSON.stringify(code)`, followed by a newline. Quotes are required; source newlines are escaped inside the string. Stdout contains only responses. The session remains usable after errors. EOF ends it.
21
21
 
22
- Atoms are encoded directly. Arrays carry shape, row-major data and prototype, including rank-zero arrays. Nested elements use the same encoding. A [keyed array](keyed.md) adds `keys`, a list of one string per element in row-major order.
22
+ Atoms are encoded directly. Arrays carry shape, row-major data and prototype, including rank-zero arrays. Nested elements use the same encoding. A [keyed array](keyed.md) adds `axis_keys`: one string list or `null` per axis. All-unkeyed arrays omit it.
23
23
 
24
24
  | Element | JSON |
25
25
  |---|---|
@@ -38,6 +38,10 @@ Use `with Session() as apl:` or call `apl.close()` when finished. Returned array
38
38
 
39
39
  `Array` retains atoms, shape, nesting, numeric domain and empty prototypes. `.is_atom` distinguishes an atom from a rank-zero array; both have `.shape == ()`. `.apl` is APL display. `.py` converts atoms to Python values and character vectors to strings. Other arrays become NumPy arrays, including rank-zero arrays. `.np` always returns an ndarray.
40
40
 
41
+ For [axis-keyed arrays](keyed.md), `.py` returns a dict at rank 1 and a pandas DataFrame at higher ranks. `.np` copies the values. Attach labels with `Array(data, axis_keys=[rows, cols])`; use `None` for an unkeyed axis. `.axis_keys` returns a tuple of label tuples or `None`.
42
+
43
+ `.df` converts any array to a DataFrame. The last axis supplies columns; earlier axes supply rows, using a MultiIndex above rank 2. Unkeyed axes have 1-origin labels. Install `basedpl[pandas]` for this conversion. Pandas is imported on demand.
44
+
41
45
  ```python
42
46
  from fractions import Fraction
43
47
  from basedpl import Array
@@ -17,7 +17,8 @@ classifiers = [
17
17
  ]
18
18
 
19
19
  [project.optional-dependencies]
20
- dev = ["fastship>=0.0.11", "maturin>=1.0,<2.0", "pytest", "numpy", "conkernelclient>=0.0.23"]
20
+ pandas = ["pandas"]
21
+ dev = ["fastship>=0.0.11", "maturin>=1.0,<2.0", "pytest", "numpy", "pandas", "conkernelclient>=0.0.23"]
21
22
 
22
23
  [project.scripts]
23
24
  bapl = "basedpl._cli:main"
@@ -24,10 +24,11 @@ def _value(raw, as_array=False, session=None):
24
24
  if not as_array: return result
25
25
  import numpy as np
26
26
  return np.array(result, dtype=_dtype([result]))
27
+ if not as_array and 'axis_keys' in raw and len(raw['shape']) > 1: return _dataframe(raw, session)
27
28
  shape, data = tuple(raw['shape']), [item(o) for o in raw['data']]
28
29
  items = raw['data'] or [raw['prototype']]
29
30
  nested = any(isinstance(o, dict) and 'shape' in o for o in items)
30
- if not as_array and 'keys' in raw: return dict(zip(raw['keys'], data))
31
+ if not as_array and 'axis_keys' in raw: return dict(zip(raw['axis_keys'][0], data))
31
32
  if not as_array:
32
33
  if len(shape) == 1 and all(isinstance(o, str) for o in items): return ''.join(data)
33
34
  import numpy as np
@@ -37,6 +38,17 @@ def _value(raw, as_array=False, session=None):
37
38
  for i,o in enumerate(data): result[i] = o
38
39
  return result.reshape(shape)
39
40
 
41
+ def _dataframe(raw, session=None):
42
+ try: import pandas as pd
43
+ except ImportError: raise ImportError('DataFrame conversion requires pandas: pip install "basedpl[pandas]"') from None
44
+ data = _value(raw, as_array=True, session=session)
45
+ shape = data.shape
46
+ keys = raw.get('axis_keys', [None] * len(shape))
47
+ labels = [list(range(1, n+1)) if k is None else k for n,k in zip(shape, keys)]
48
+ if len(shape) < 2: return pd.DataFrame(data.reshape(-1, 1), index=labels[0] if labels else [1], columns=[1])
49
+ index = labels[0] if len(shape) == 2 else pd.MultiIndex.from_product(labels[:-1])
50
+ return pd.DataFrame(data.reshape(math.prod(shape[:-1]), shape[-1]), index=index, columns=labels[-1])
51
+
40
52
  def _element(value, seen):
41
53
  np = sys.modules.get('numpy')
42
54
  if np is not None and isinstance(value, np.generic): value = value.item()
@@ -69,7 +81,7 @@ def _array(value, seen=None):
69
81
  if isinstance(value, dict):
70
82
  if not all(isinstance(k, str) for k in value): raise TypeError('keyed arrays need string keys')
71
83
  seen.add(id(value))
72
- try: return _Array(dict(shape=[len(value)], data=[_element(o, seen) for o in value.values()], prototype=prototype, keys=list(value)))
84
+ try: return _Array(dict(shape=[len(value)], data=[_element(o, seen) for o in value.values()], prototype=prototype, axis_keys=[list(value)]))
73
85
  finally: seen.remove(id(value))
74
86
  if isinstance(value, str): return _Array(dict(shape=[len(value)], data=list(value), prototype=' '))
75
87
  if np is not None and isinstance(value, np.ndarray):
@@ -127,9 +139,9 @@ def _array_repr(raw):
127
139
  def cells(dims):
128
140
  if not dims: return item(next(data))
129
141
  return '[' + ', '.join(cells(dims[1:]) for _ in range(dims[0])) + ']'
130
- if 'keys' in raw:
131
- entries = '{' + ', '.join(f'{k!r}: {item(o)}' for k,o in zip(raw['keys'], raw['data'])) + '}'
132
- return entries if len(shape) == 1 else f'Array({entries}, shape={tuple(shape)})'
142
+ if 'axis_keys' in raw:
143
+ if len(shape) == 1: return '{' + ', '.join(f'{k!r}: {item(o)}' for k,o in zip(raw['axis_keys'][0], raw['data'])) + '}'
144
+ return f'Array({cells(shape)}, axis_keys={raw["axis_keys"]!r})'
133
145
  if math.prod(shape) == 0: return f'Array([], shape={tuple(shape)})'
134
146
  return cells(shape)
135
147
 
@@ -157,18 +169,23 @@ def _context(*values):
157
169
  class Array(_Operators):
158
170
  "An immutable native APL value. Conversion to Python or NumPy makes a copy."
159
171
  __slots__ = ('_inner', '_session')
160
- def __init__(self, value):
172
+ def __init__(self, value, *, axis_keys=None):
161
173
  self._inner = _array(value)
174
+ if axis_keys is not None: self._inner = self._inner.with_axis_keys(axis_keys)
162
175
  self._session = _context(value) if self._inner.needs_session else None
163
176
  @property
164
177
  def shape(self): return tuple(self._inner.shape)
165
178
  @property
179
+ def axis_keys(self): return tuple(None if k is None else tuple(k) for k in self._inner.axis_keys)
180
+ @property
166
181
  def is_atom(self): return self._inner.is_atom
167
182
  @property
168
183
  def py(self): return _value(self._inner.parts(), session=self._session)
169
184
  @property
170
185
  def np(self): return _value(self._inner.parts(), as_array=True, session=self._session)
171
186
  @property
187
+ def df(self): return _dataframe(self._inner.parts(), self._session)
188
+ @property
172
189
  def apl(self): return repr(self._inner)
173
190
  def _scalar(self): return _value(self._inner.scalar(), session=self._session)
174
191
  def __float__(self): return float(self._scalar())
@@ -211,6 +211,7 @@ def description(row):
211
211
  def convert(row):
212
212
  "Convert a prepared reference record without evaluating its program or changing its status."
213
213
  if error := row.get('expected_error'): expect = '⍝ error: '+error
214
+ elif 'expected_code' in row: expect = row['expected_code']
214
215
  elif 'expected' in row: expect = literal(row['expected'])
215
216
  else: raise ValueError(f"{row['id']}: missing independent expectation")
216
217
  return Case(row['code'], expect, row['id'], description(row), row.get('relative_tolerance', 0), row.get('absolute_tolerance', 0))
@@ -281,7 +282,7 @@ def add(ids, output='tests/reference', directory='tests/reference/inventory'):
281
282
  case = convert(row)
282
283
  result = json.loads(_check_reference(json.dumps(row), 2))
283
284
  if result['status']!='pass': raise ValueError(f'{id}: {result}')
284
- if not row.get('expected_error'):
285
+ if not row.get('expected_error') and 'expected' in row and 'expected_code' not in row:
285
286
  result = json.loads(_check_reference(json.dumps(dict(code=case.expect, expected=row['expected'])), 2))
286
287
  if result['status']!='pass': raise ValueError(f'{id}: converted expectation: {result}')
287
288
  pending[id.split(':')[0].split('/')[0]].append(case)
@@ -382,7 +382,7 @@ def scan(directory='tests/reference/inventory', source='', match='', timeout=.25
382
382
  if case['status'] != 'pending':
383
383
  inventory[case['status']] += 1
384
384
  continue
385
- if 'expected' not in case and not case.get('expected_error'):
385
+ if 'expected' not in case and not case.get('expected_code') and not case.get('expected_error'):
386
386
  inventory['needs expectation'] += 1
387
387
  continue
388
388
  clean = re.sub(r"'(?:''|[^'])*'|⍝[^\n]*", '', case['code'])
@@ -0,0 +1,109 @@
1
+ use crate::{
2
+ array::{generated_len, Layout},
3
+ keyed::Keys,
4
+ ErrorKind, Value,
5
+ };
6
+
7
+ pub(crate) enum Mapping {
8
+ Scalar,
9
+ Linear(usize),
10
+ Positions(Vec<usize>),
11
+ Axes(Vec<(usize, usize, usize)>),
12
+ Keyed(Vec<(usize, usize, Vec<Option<usize>>)>),
13
+ }
14
+ impl Mapping {
15
+ pub fn index(&self, i: usize) -> usize { self.get(i).expect("positional mapping") }
16
+ pub fn numeric<T: Copy + Default>(&self, data: &[T], i: usize) -> T { self.get(i).map_or_else(T::default, |j| data[j]) }
17
+ pub fn get(&self, i: usize) -> Option<usize> {
18
+ Some(match self {
19
+ Self::Scalar => 0,
20
+ Self::Linear(repeat) => i / repeat,
21
+ Self::Positions(positions) => positions[i],
22
+ Self::Axes(axes) => axes.iter().map(|&(stride, len, source)| i / stride % len * source).sum(),
23
+ Self::Keyed(axes) => axes.iter().try_fold(0, |n, (stride, source, map)| Some(n + map[i / stride % map.len()]? * source))?,
24
+ })
25
+ }
26
+ pub fn contract(left: &Layout, laxis: usize, right: &Layout, raxis: usize) -> Result<Self, ErrorKind> {
27
+ match (left.keys(laxis), right.keys(raxis)) { (Some(x), Some(y)) if x != y => Ok(Self::Positions(y.positions(x, false)?)), _ => Ok(Self::Linear(1)) }
28
+ }
29
+ fn new(input: &Layout, axes: &[usize], result: &Layout, count: usize) -> Self {
30
+ if count == 0 { return Self::Scalar; }
31
+ let (shape, output) = (input.shape(), result.shape());
32
+ let mut strides = vec![1; output.len()];
33
+ for i in (1..output.len()).rev() { strides[i - 1] = strides[i] * output[i]; }
34
+ if axes.iter().enumerate().any(|(a, &b)| input.keys(a).is_some() && input.keys(a) != result.keys(b)) {
35
+ let mut source = 1;
36
+ let mut map = Vec::new();
37
+ for (a, (&len, &b)) in shape.iter().zip(axes).enumerate().rev() {
38
+ let positions = (0..output[b])
39
+ .map(|i| match (input.keys(a), result.keys(b)) {
40
+ (Some(src), Some(dst)) => src.position(&dst.names()[i]),
41
+ _ => Some(if len == 1 { 0 } else { i }),
42
+ })
43
+ .collect();
44
+ map.push((strides[b], source, positions));
45
+ source *= len;
46
+ }
47
+ return Self::Keyed(map);
48
+ }
49
+ let size: usize = shape.iter().product();
50
+ if size == 1 { return Self::Scalar; }
51
+ let mut source = 1;
52
+ let mut map = Vec::new();
53
+ for (&len, &axis) in shape.iter().zip(axes).rev() {
54
+ if len != 1 { map.push((strides[axis], len, source)); }
55
+ source *= len;
56
+ }
57
+ let repeat = count / size;
58
+ if map.iter().all(|&(stride, _, source)| stride == source * repeat) { Self::Linear(repeat) } else { Self::Axes(map) }
59
+ }
60
+ }
61
+
62
+ pub(crate) struct Agreement {
63
+ pub layout: Layout,
64
+ pub len: usize,
65
+ pub left: Mapping,
66
+ pub right: Mapping,
67
+ }
68
+ impl Agreement {
69
+ pub fn new(left: &Layout, right: &Layout) -> Result<Self, ErrorKind> {
70
+ Self::mapped(left, right, &(0..left.shape().len()).collect::<Vec<_>>(), &(0..right.shape().len()).collect::<Vec<_>>())
71
+ }
72
+ pub fn with_axes(left: &Layout, right: &Layout, axes: &[usize]) -> Result<Self, ErrorKind> {
73
+ if left.shape().len() < right.shape().len() { Self::mapped(left, right, axes, &(0..right.shape().len()).collect::<Vec<_>>()) } else { Self::mapped(left, right, &(0..left.shape().len()).collect::<Vec<_>>(), axes) }
74
+ }
75
+ fn mapped(left: &Layout, right: &Layout, xa: &[usize], ya: &[usize]) -> Result<Self, ErrorKind> {
76
+ let mut shape = vec![1; left.shape().len().max(right.shape().len())];
77
+ let mut keys = vec![None; shape.len()];
78
+ for axis in 0..shape.len() {
79
+ let (x, y) = (xa.iter().position(|&a| a == axis), ya.iter().position(|&a| a == axis));
80
+ let (nx, ny) = (x.map_or(1, |a| left.shape()[a]), y.map_or(1, |a| right.shape()[a]));
81
+ let (kx, ky) = (x.and_then(|a| left.keys(a)), y.and_then(|a| right.keys(a)));
82
+ if let (Some(x), Some(y)) = (kx, ky) {
83
+ let key = if x == y { x.clone() } else { Keys::new(x.names().iter().chain(y.names().iter().filter(|k| x.position(k).is_none())).cloned().collect())? };
84
+ shape[axis] = key.names().len();
85
+ keys[axis] = Some(key);
86
+ } else {
87
+ let n = if nx == ny || ny == 1 { nx } else if nx == 1 { ny } else { return Err(ErrorKind::Length); };
88
+ shape[axis] = n;
89
+ keys[axis] = kx.filter(|_| nx == n).or(ky.filter(|_| ny == n)).cloned();
90
+ }
91
+ }
92
+ let len = generated_len(&shape)?;
93
+ let layout = Layout::from(shape).with_keys(keys)?;
94
+ let x = Mapping::new(left, xa, &layout, len);
95
+ let y = Mapping::new(right, ya, &layout, len);
96
+ Ok(Self { layout, len, left: x, right: y })
97
+ }
98
+ pub fn values(&self, left: Option<&Value>, right: &Value, i: usize) -> (Option<Value>, Value) {
99
+ if self.len == 0 {
100
+ let item = |a: &Value| if a.is_empty() { a.prototype() } else { a.at(0) };
101
+ return (left.map(item), item(right));
102
+ }
103
+ let x = left.and_then(|a| self.left.get(i).map(|j| a.at(j)));
104
+ let y = self.right.get(i).map(|j| right.at(j));
105
+ let xf = left.map(|a| x.clone().unwrap_or_else(|| y.as_ref().map_or_else(|| a.prototype(), Value::fill)));
106
+ let yf = y.unwrap_or_else(|| x.as_ref().map_or_else(|| right.prototype(), Value::fill));
107
+ (xf, yf)
108
+ }
109
+ }