marple-lang 0.3.0__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 (73) hide show
  1. marple_lang-0.3.0/LICENSE +21 -0
  2. marple_lang-0.3.0/PKG-INFO +171 -0
  3. marple_lang-0.3.0/README.md +143 -0
  4. marple_lang-0.3.0/pyproject.toml +53 -0
  5. marple_lang-0.3.0/setup.cfg +10 -0
  6. marple_lang-0.3.0/src/marple/__init__.py +3 -0
  7. marple_lang-0.3.0/src/marple/arraymodel.py +33 -0
  8. marple_lang-0.3.0/src/marple/backend.py +49 -0
  9. marple_lang-0.3.0/src/marple/cells.py +110 -0
  10. marple_lang-0.3.0/src/marple/errors.py +66 -0
  11. marple_lang-0.3.0/src/marple/functions.py +213 -0
  12. marple_lang-0.3.0/src/marple/glyphs.py +85 -0
  13. marple_lang-0.3.0/src/marple/interpreter.py +1092 -0
  14. marple_lang-0.3.0/src/marple/namespace.py +66 -0
  15. marple_lang-0.3.0/src/marple/parser.py +502 -0
  16. marple_lang-0.3.0/src/marple/repl.py +219 -0
  17. marple_lang-0.3.0/src/marple/script.py +41 -0
  18. marple_lang-0.3.0/src/marple/stdlib/__init__.py +0 -0
  19. marple_lang-0.3.0/src/marple/stdlib/io/nread.apl +1 -0
  20. marple_lang-0.3.0/src/marple/stdlib/io/nwrite.apl +1 -0
  21. marple_lang-0.3.0/src/marple/stdlib/io_impl.py +23 -0
  22. marple_lang-0.3.0/src/marple/stdlib/str/lower.apl +1 -0
  23. marple_lang-0.3.0/src/marple/stdlib/str/trim.apl +1 -0
  24. marple_lang-0.3.0/src/marple/stdlib/str/upper.apl +1 -0
  25. marple_lang-0.3.0/src/marple/stdlib/str_impl.py +19 -0
  26. marple_lang-0.3.0/src/marple/structural.py +303 -0
  27. marple_lang-0.3.0/src/marple/terminal.py +102 -0
  28. marple_lang-0.3.0/src/marple/tokenizer.py +176 -0
  29. marple_lang-0.3.0/src/marple/web/__init__.py +0 -0
  30. marple_lang-0.3.0/src/marple/web/server.py +186 -0
  31. marple_lang-0.3.0/src/marple/web/static/desktop.html +107 -0
  32. marple_lang-0.3.0/src/marple/web/static/index.html +103 -0
  33. marple_lang-0.3.0/src/marple/workspace.py +151 -0
  34. marple_lang-0.3.0/src/marple_lang.egg-info/PKG-INFO +171 -0
  35. marple_lang-0.3.0/src/marple_lang.egg-info/SOURCES.txt +72 -0
  36. marple_lang-0.3.0/src/marple_lang.egg-info/dependency_links.txt +1 -0
  37. marple_lang-0.3.0/src/marple_lang.egg-info/entry_points.txt +2 -0
  38. marple_lang-0.3.0/src/marple_lang.egg-info/requires.txt +6 -0
  39. marple_lang-0.3.0/src/marple_lang.egg-info/top_level.txt +1 -0
  40. marple_lang-0.3.0/tests/test_arraymodel.py +52 -0
  41. marple_lang-0.3.0/tests/test_assignment.py +31 -0
  42. marple_lang-0.3.0/tests/test_backend.py +54 -0
  43. marple_lang-0.3.0/tests/test_cells.py +112 -0
  44. marple_lang-0.3.0/tests/test_ct.py +73 -0
  45. marple_lang-0.3.0/tests/test_dfns.py +84 -0
  46. marple_lang-0.3.0/tests/test_display.py +54 -0
  47. marple_lang-0.3.0/tests/test_ea.py +53 -0
  48. marple_lang-0.3.0/tests/test_extended_functions.py +116 -0
  49. marple_lang-0.3.0/tests/test_from.py +58 -0
  50. marple_lang-0.3.0/tests/test_functions.py +63 -0
  51. marple_lang-0.3.0/tests/test_glyphs.py +33 -0
  52. marple_lang-0.3.0/tests/test_ibeam.py +59 -0
  53. marple_lang-0.3.0/tests/test_indexing.py +75 -0
  54. marple_lang-0.3.0/tests/test_integration.py +42 -0
  55. marple_lang-0.3.0/tests/test_interpreter.py +92 -0
  56. marple_lang-0.3.0/tests/test_match.py +51 -0
  57. marple_lang-0.3.0/tests/test_matrices.py +71 -0
  58. marple_lang-0.3.0/tests/test_matrix_ops.py +35 -0
  59. marple_lang-0.3.0/tests/test_name_table.py +182 -0
  60. marple_lang-0.3.0/tests/test_namespaces.py +56 -0
  61. marple_lang-0.3.0/tests/test_operators.py +53 -0
  62. marple_lang-0.3.0/tests/test_parser.py +60 -0
  63. marple_lang-0.3.0/tests/test_phase4_remaining.py +57 -0
  64. marple_lang-0.3.0/tests/test_products.py +47 -0
  65. marple_lang-0.3.0/tests/test_random.py +100 -0
  66. marple_lang-0.3.0/tests/test_rank.py +93 -0
  67. marple_lang-0.3.0/tests/test_script.py +84 -0
  68. marple_lang-0.3.0/tests/test_statement_separator.py +13 -0
  69. marple_lang-0.3.0/tests/test_structural.py +97 -0
  70. marple_lang-0.3.0/tests/test_sysvar.py +115 -0
  71. marple_lang-0.3.0/tests/test_tokenizer.py +124 -0
  72. marple_lang-0.3.0/tests/test_workspace.py +99 -0
  73. marple_lang-0.3.0/tests/test_workspace_chars.py +28 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Romilly Cocking
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,171 @@
1
+ Metadata-Version: 2.4
2
+ Name: marple-lang
3
+ Version: 0.3.0
4
+ Summary: Mini APL in Python Language Experiment. Uses APL arrays as the internal data model.
5
+ Author-email: Romilly Cocking <romilly.cocking@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/romilly/marple
8
+ Project-URL: Repository, https://github.com/romilly/marple.git
9
+ Project-URL: Issues, https://github.com/romilly/marple/issues
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.8
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Requires-Python: >=3.8
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Provides-Extra: test
23
+ Requires-Dist: pytest>=7.0.0; extra == "test"
24
+ Requires-Dist: pytest-cov; extra == "test"
25
+ Requires-Dist: pyright; extra == "test"
26
+ Requires-Dist: PyHamcrest; extra == "test"
27
+ Dynamic: license-file
28
+
29
+ # marple
30
+
31
+ Mini APL in Python Language Experiment. A first-generation APL interpreter with the rank operator, namespaces, and Python FFI. Uses APL arrays (shape + flat data) as the internal data model. Inspired by Rodrigo Girão Serrão's [RGSPL](https://github.com/rodrigogiraoserrano/RGSPL) and Iverson's [Dictionary of APL](https://www.jsoftware.com/papers/APLDictionary.htm).
32
+
33
+ ## Documentation
34
+
35
+ More extensive documentation is available [here](https://romilly.github.io/marple/)
36
+
37
+ ## Features
38
+
39
+ - **40+ primitive functions** — arithmetic, comparison, boolean, structural, circular/trig, match/tally, membership
40
+ - **Operators** — reduce (`/`), scan (`\`), inner product (`f.g`), outer product (`∘.f`), **rank** (`⍤`)
41
+ - **Rank operator** — `(f⍤k)` applies any function along any axis: `(⌽⍤1) M` reverses rows, `(+/⍤1) M` sums rows
42
+ - **From function** (`⌷`) — leading-axis selection that composes with rank
43
+ - **Direct functions (dfns)** — `{⍵}` syntax with guards, recursion via `∇`, default `⍺`
44
+ - **Symbol table-aware parser** — named functions work without parens: `double ⍳5`
45
+ - **Namespaces** — `$::str::upper 'hello'`, `#import` directives, `::` separator
46
+ - **I-beam operator** (`⌶`) — Python FFI for extending MARPLE with Python code
47
+ - **Error handling** — `⎕EA` (execute alternate), `⎕EN` (error number), `⎕DM` (diagnostic message), `⎕SIGNAL`
48
+ - **System variables** — `⎕IO`, `⎕CT`, `⎕PP`, `⎕RL`, `⎕A`, `⎕D`, `⎕TS`, `⎕WSID`, `⎕UCS`, `⎕NC`, `⎕EX`
49
+ - **Matrices** — reshape, transpose, bracket indexing (`M[r;c]` any rank), matrix inverse (`⌹`)
50
+ - **Numpy backend** — automatic vectorization (73x faster for element-wise, 380x for outer product), with pure-Python fallback
51
+ - **Web REPL** — browser-based REPL at `http://localhost:8888/`, Pico W-ready
52
+ - **Terminal REPL** — live backtick→glyph input, workspace save/load, APL-style formatting
53
+ - **Script runner** — `marple script.marple` with session transcript output
54
+ - **426 tests**, pyright strict, no external runtime dependencies
55
+
56
+ ## Quick start
57
+
58
+ ```bash
59
+ pip install -e .
60
+ marple
61
+ ```
62
+
63
+ ```
64
+ MARPLE v0.2.11 - Mini APL in Python
65
+ CLEAR WS
66
+
67
+ ⍳5
68
+ 1 2 3 4 5
69
+ +/⍳100
70
+ 5050
71
+ fact←{⍵≤1:1⋄⍵×∇ ⍵-1}
72
+ fact 10
73
+ 3628800
74
+ double←{⍵+⍵}
75
+ double ⍳5
76
+ 2 4 6 8 10
77
+ M←3 4⍴⍳12
78
+ (⌽⍤1) M
79
+ 4 3 2 1
80
+ 8 7 6 5
81
+ 12 11 10 9
82
+ $::str::upper 'hello'
83
+ HELLO
84
+ ```
85
+
86
+ ### Running scripts
87
+
88
+ ```bash
89
+ marple examples/01_primitives.marple # run and display
90
+ marple examples/01_primitives.marple > out.txt # capture session transcript
91
+ ```
92
+
93
+ Four demo scripts are included in `examples/`:
94
+ - `01_primitives.marple` — arithmetic, vectors, matrices, reduce, products
95
+ - `02_dfns.marple` — user functions, guards, recursion, rank operator
96
+ - `03_namespaces.marple` — system library, imports, file I/O, i-beams
97
+ - `04_errors.marple` — ea/en error handling, error codes
98
+
99
+ ### APL character input
100
+
101
+ If you have a Dyalog APL keyboard layout installed (e.g. via `setxkbmap` with `grp:win_switch`), you can use the Win key to type APL glyphs directly.
102
+
103
+ Alternatively, type APL glyphs using backtick prefixes — they appear immediately as you type:
104
+
105
+ | Key | Glyph | Key | Glyph | Key | Glyph | Key | Glyph |
106
+ |-----|-------|-----|-------|-----|-------|-----|-------|
107
+ | `` `r `` | ⍴ | `` `i `` | ⍳ | `` `l `` | ← | `` `w `` | ⍵ |
108
+ | `` `a `` | ⍺ | `` `V `` | ∇ | `` `x `` | ⋄ | `` `c `` | ⍝ |
109
+ | `` `- `` | × | `` `= `` | ÷ | `` `< `` | ≤ | `` `> `` | ≥ |
110
+ | `` `/ `` | ≠ | `` `o `` | ○ | `` `* `` | ⍟ | `` `2 `` | ¯ |
111
+ | `` `q `` | ⌽ | `` `Q `` | ⍉ | `` `g `` | ⍋ | `` `G `` | ⍒ |
112
+ | `` `t `` | ↑ | `` `y `` | ↓ | `` `n `` | ⊤ | `` `N `` | ⊥ |
113
+ | `` `J `` | ⍤ | `` `I `` | ⌷ | `` `j `` | ∘ | `` `D `` | ⌹ |
114
+ | `` `B `` | ⌶ | | | | | | |
115
+
116
+ ### System commands
117
+
118
+ | Command | Action |
119
+ |---------|--------|
120
+ | `)off` | Exit |
121
+ | `)clear` | Clear workspace |
122
+ | `)wsid [name]` | Show or set workspace ID |
123
+ | `)save [name]` | Save workspace (sets WSID if name given) |
124
+ | `)load name` | Load workspace |
125
+ | `)lib` | List saved workspaces |
126
+ | `)fns [ns]` | List defined functions (optionally in namespace) |
127
+ | `)vars` | List defined variables |
128
+
129
+ ## Development
130
+
131
+ ```bash
132
+ pip install -e .[test]
133
+ pytest
134
+ pyright src/
135
+ ```
136
+
137
+ To run without numpy (pure-Python mode):
138
+ ```bash
139
+ MARPLE_BACKEND=none pytest
140
+ ```
141
+
142
+ ## Architecture
143
+
144
+ | Module | Purpose |
145
+ |--------|---------|
146
+ | `arraymodel.py` | `APLArray(shape, data)` — the core data structure |
147
+ | `backend.py` | Numpy/CuPy/ulab detection with pure-Python fallback |
148
+ | `tokenizer.py` | Lexer for APL glyphs, numbers, strings, qualified names |
149
+ | `parser.py` | Right-to-left recursive descent with symbol table |
150
+ | `interpreter.py` | Tree-walking evaluator with dfn closures |
151
+ | `functions.py` | Scalar functions with pervasion (numpy-accelerated) |
152
+ | `structural.py` | Shape-manipulating and indexing functions |
153
+ | `cells.py` | Cell decomposition and reassembly for the rank operator |
154
+ | `namespace.py` | Hierarchical namespace resolution and system workspace |
155
+ | `errors.py` | APL error classes with numeric codes |
156
+ | `repl.py` | Interactive read-eval-print loop |
157
+ | `script.py` | Script runner with session transcript output |
158
+ | `terminal.py` | Raw terminal input with live glyph translation |
159
+ | `glyphs.py` | Backtick → APL character mapping |
160
+ | `workspace.py` | Directory-based workspace persistence |
161
+ | `stdlib/` | Standard library: string, I/O, and error handling |
162
+
163
+ ## References
164
+
165
+ - [RGSPL](https://github.com/rodrigogiraoserrano/RGSPL) — Rodrigo Girão Serrão's Python APL interpreter (design reference)
166
+ - [RGSPL blog series](https://mathspp.com/blog/lsbasi-apl-part1) — step-by-step interpreter build
167
+ - [Iverson's Dictionary of APL](https://www.jsoftware.com/papers/APLDictionary.htm) — the rank operator and leading-axis theory
168
+ - [Language spec](docs/MARPLE_Language_Reference.md) — full first-generation APL reference and roadmap
169
+ - [Rank operator spec](docs/MARPLE_Rank_Operator.md) — detailed rank operator design
170
+ - [Indexing spec](docs/MARPLE_Indexing.md) — From function and indexing approach
171
+ - [Namespaces spec](docs/MARPLE_Namespaces_And_IBeams.md) — namespaces, i-beams, and standard library
@@ -0,0 +1,143 @@
1
+ # marple
2
+
3
+ Mini APL in Python Language Experiment. A first-generation APL interpreter with the rank operator, namespaces, and Python FFI. Uses APL arrays (shape + flat data) as the internal data model. Inspired by Rodrigo Girão Serrão's [RGSPL](https://github.com/rodrigogiraoserrano/RGSPL) and Iverson's [Dictionary of APL](https://www.jsoftware.com/papers/APLDictionary.htm).
4
+
5
+ ## Documentation
6
+
7
+ More extensive documentation is available [here](https://romilly.github.io/marple/)
8
+
9
+ ## Features
10
+
11
+ - **40+ primitive functions** — arithmetic, comparison, boolean, structural, circular/trig, match/tally, membership
12
+ - **Operators** — reduce (`/`), scan (`\`), inner product (`f.g`), outer product (`∘.f`), **rank** (`⍤`)
13
+ - **Rank operator** — `(f⍤k)` applies any function along any axis: `(⌽⍤1) M` reverses rows, `(+/⍤1) M` sums rows
14
+ - **From function** (`⌷`) — leading-axis selection that composes with rank
15
+ - **Direct functions (dfns)** — `{⍵}` syntax with guards, recursion via `∇`, default `⍺`
16
+ - **Symbol table-aware parser** — named functions work without parens: `double ⍳5`
17
+ - **Namespaces** — `$::str::upper 'hello'`, `#import` directives, `::` separator
18
+ - **I-beam operator** (`⌶`) — Python FFI for extending MARPLE with Python code
19
+ - **Error handling** — `⎕EA` (execute alternate), `⎕EN` (error number), `⎕DM` (diagnostic message), `⎕SIGNAL`
20
+ - **System variables** — `⎕IO`, `⎕CT`, `⎕PP`, `⎕RL`, `⎕A`, `⎕D`, `⎕TS`, `⎕WSID`, `⎕UCS`, `⎕NC`, `⎕EX`
21
+ - **Matrices** — reshape, transpose, bracket indexing (`M[r;c]` any rank), matrix inverse (`⌹`)
22
+ - **Numpy backend** — automatic vectorization (73x faster for element-wise, 380x for outer product), with pure-Python fallback
23
+ - **Web REPL** — browser-based REPL at `http://localhost:8888/`, Pico W-ready
24
+ - **Terminal REPL** — live backtick→glyph input, workspace save/load, APL-style formatting
25
+ - **Script runner** — `marple script.marple` with session transcript output
26
+ - **426 tests**, pyright strict, no external runtime dependencies
27
+
28
+ ## Quick start
29
+
30
+ ```bash
31
+ pip install -e .
32
+ marple
33
+ ```
34
+
35
+ ```
36
+ MARPLE v0.2.11 - Mini APL in Python
37
+ CLEAR WS
38
+
39
+ ⍳5
40
+ 1 2 3 4 5
41
+ +/⍳100
42
+ 5050
43
+ fact←{⍵≤1:1⋄⍵×∇ ⍵-1}
44
+ fact 10
45
+ 3628800
46
+ double←{⍵+⍵}
47
+ double ⍳5
48
+ 2 4 6 8 10
49
+ M←3 4⍴⍳12
50
+ (⌽⍤1) M
51
+ 4 3 2 1
52
+ 8 7 6 5
53
+ 12 11 10 9
54
+ $::str::upper 'hello'
55
+ HELLO
56
+ ```
57
+
58
+ ### Running scripts
59
+
60
+ ```bash
61
+ marple examples/01_primitives.marple # run and display
62
+ marple examples/01_primitives.marple > out.txt # capture session transcript
63
+ ```
64
+
65
+ Four demo scripts are included in `examples/`:
66
+ - `01_primitives.marple` — arithmetic, vectors, matrices, reduce, products
67
+ - `02_dfns.marple` — user functions, guards, recursion, rank operator
68
+ - `03_namespaces.marple` — system library, imports, file I/O, i-beams
69
+ - `04_errors.marple` — ea/en error handling, error codes
70
+
71
+ ### APL character input
72
+
73
+ If you have a Dyalog APL keyboard layout installed (e.g. via `setxkbmap` with `grp:win_switch`), you can use the Win key to type APL glyphs directly.
74
+
75
+ Alternatively, type APL glyphs using backtick prefixes — they appear immediately as you type:
76
+
77
+ | Key | Glyph | Key | Glyph | Key | Glyph | Key | Glyph |
78
+ |-----|-------|-----|-------|-----|-------|-----|-------|
79
+ | `` `r `` | ⍴ | `` `i `` | ⍳ | `` `l `` | ← | `` `w `` | ⍵ |
80
+ | `` `a `` | ⍺ | `` `V `` | ∇ | `` `x `` | ⋄ | `` `c `` | ⍝ |
81
+ | `` `- `` | × | `` `= `` | ÷ | `` `< `` | ≤ | `` `> `` | ≥ |
82
+ | `` `/ `` | ≠ | `` `o `` | ○ | `` `* `` | ⍟ | `` `2 `` | ¯ |
83
+ | `` `q `` | ⌽ | `` `Q `` | ⍉ | `` `g `` | ⍋ | `` `G `` | ⍒ |
84
+ | `` `t `` | ↑ | `` `y `` | ↓ | `` `n `` | ⊤ | `` `N `` | ⊥ |
85
+ | `` `J `` | ⍤ | `` `I `` | ⌷ | `` `j `` | ∘ | `` `D `` | ⌹ |
86
+ | `` `B `` | ⌶ | | | | | | |
87
+
88
+ ### System commands
89
+
90
+ | Command | Action |
91
+ |---------|--------|
92
+ | `)off` | Exit |
93
+ | `)clear` | Clear workspace |
94
+ | `)wsid [name]` | Show or set workspace ID |
95
+ | `)save [name]` | Save workspace (sets WSID if name given) |
96
+ | `)load name` | Load workspace |
97
+ | `)lib` | List saved workspaces |
98
+ | `)fns [ns]` | List defined functions (optionally in namespace) |
99
+ | `)vars` | List defined variables |
100
+
101
+ ## Development
102
+
103
+ ```bash
104
+ pip install -e .[test]
105
+ pytest
106
+ pyright src/
107
+ ```
108
+
109
+ To run without numpy (pure-Python mode):
110
+ ```bash
111
+ MARPLE_BACKEND=none pytest
112
+ ```
113
+
114
+ ## Architecture
115
+
116
+ | Module | Purpose |
117
+ |--------|---------|
118
+ | `arraymodel.py` | `APLArray(shape, data)` — the core data structure |
119
+ | `backend.py` | Numpy/CuPy/ulab detection with pure-Python fallback |
120
+ | `tokenizer.py` | Lexer for APL glyphs, numbers, strings, qualified names |
121
+ | `parser.py` | Right-to-left recursive descent with symbol table |
122
+ | `interpreter.py` | Tree-walking evaluator with dfn closures |
123
+ | `functions.py` | Scalar functions with pervasion (numpy-accelerated) |
124
+ | `structural.py` | Shape-manipulating and indexing functions |
125
+ | `cells.py` | Cell decomposition and reassembly for the rank operator |
126
+ | `namespace.py` | Hierarchical namespace resolution and system workspace |
127
+ | `errors.py` | APL error classes with numeric codes |
128
+ | `repl.py` | Interactive read-eval-print loop |
129
+ | `script.py` | Script runner with session transcript output |
130
+ | `terminal.py` | Raw terminal input with live glyph translation |
131
+ | `glyphs.py` | Backtick → APL character mapping |
132
+ | `workspace.py` | Directory-based workspace persistence |
133
+ | `stdlib/` | Standard library: string, I/O, and error handling |
134
+
135
+ ## References
136
+
137
+ - [RGSPL](https://github.com/rodrigogiraoserrano/RGSPL) — Rodrigo Girão Serrão's Python APL interpreter (design reference)
138
+ - [RGSPL blog series](https://mathspp.com/blog/lsbasi-apl-part1) — step-by-step interpreter build
139
+ - [Iverson's Dictionary of APL](https://www.jsoftware.com/papers/APLDictionary.htm) — the rank operator and leading-axis theory
140
+ - [Language spec](docs/MARPLE_Language_Reference.md) — full first-generation APL reference and roadmap
141
+ - [Rank operator spec](docs/MARPLE_Rank_Operator.md) — detailed rank operator design
142
+ - [Indexing spec](docs/MARPLE_Indexing.md) — From function and indexing approach
143
+ - [Namespaces spec](docs/MARPLE_Namespaces_And_IBeams.md) — namespaces, i-beams, and standard library
@@ -0,0 +1,53 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "marple-lang"
7
+ version = "0.3.0"
8
+ description = "Mini APL in Python Language Experiment. Uses APL arrays as the internal data model."
9
+ authors = [{name = "Romilly Cocking", email = "romilly.cocking@gmail.com"}]
10
+ license = {text = "MIT"}
11
+ readme = "README.md"
12
+ requires-python = ">=3.8"
13
+ classifiers = [
14
+ "Development Status :: 3 - Alpha",
15
+ "Intended Audience :: Developers",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.8",
19
+ "Programming Language :: Python :: 3.9",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ ]
24
+ dependencies = []
25
+
26
+ [project.scripts]
27
+ marple = "marple.repl:main"
28
+
29
+ [project.optional-dependencies]
30
+ test = [
31
+ "pytest>=7.0.0",
32
+ "pytest-cov",
33
+ "pyright",
34
+ "PyHamcrest",
35
+ ]
36
+
37
+ [project.urls]
38
+ Homepage = "https://github.com/romilly/marple"
39
+ Repository = "https://github.com/romilly/marple.git"
40
+ Issues = "https://github.com/romilly/marple/issues"
41
+
42
+ [tool.setuptools.packages.find]
43
+ where = ["src"]
44
+
45
+ [tool.setuptools.package-data]
46
+ marple = ["web/static/*.html", "stdlib/**/*.apl"]
47
+
48
+ [tool.pytest.ini_options]
49
+ testpaths = ["tests"]
50
+ python_files = ["test_*.py"]
51
+ python_classes = ["Test*"]
52
+ python_functions = ["test_*"]
53
+ addopts = "-v --tb=short"
@@ -0,0 +1,10 @@
1
+ [metadata]
2
+ license_files = LICENSE
3
+
4
+ [bdist_wheel]
5
+ universal = 1
6
+
7
+ [egg_info]
8
+ tag_build =
9
+ tag_date = 0
10
+
@@ -0,0 +1,3 @@
1
+ """Marple - A Python project following TDD principles."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,33 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from marple.backend import is_numeric_array, np, to_array, to_list
6
+
7
+
8
+ class APLArray:
9
+ def __init__(self, shape: list[int], data: Any) -> None:
10
+ self.shape = shape
11
+ self.data = to_array(data) if isinstance(data, list) else data
12
+
13
+ def is_scalar(self) -> bool:
14
+ return self.shape == []
15
+
16
+ def __eq__(self, other: object) -> bool:
17
+ if not isinstance(other, APLArray):
18
+ return NotImplemented
19
+ if self.shape != other.shape:
20
+ return False
21
+ if is_numeric_array(self.data) and is_numeric_array(other.data):
22
+ return bool(np.array_equal(self.data, other.data))
23
+ return to_list(self.data) == to_list(other.data)
24
+
25
+ def __repr__(self) -> str:
26
+ if self.is_scalar():
27
+ return f"S({self.data[0]})"
28
+ data_list = to_list(self.data) if is_numeric_array(self.data) else self.data
29
+ return f"APLArray({self.shape}, {data_list})"
30
+
31
+
32
+ def S(value: Any) -> APLArray:
33
+ return APLArray([], [value])
@@ -0,0 +1,49 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from typing import Any
5
+
6
+ # Backend selection: environment variable overrides auto-detection
7
+ _backend_name = os.environ.get("MARPLE_BACKEND", "auto")
8
+
9
+ np: Any = None
10
+
11
+ if _backend_name != "none":
12
+ try:
13
+ import cupy as np # type: ignore[no-redef]
14
+ except ImportError:
15
+ try:
16
+ import numpy as np # type: ignore[no-redef]
17
+ except ImportError:
18
+ try:
19
+ import ulab.numpy as np # type: ignore[no-redef,import-not-found]
20
+ except ImportError:
21
+ try:
22
+ from ulab import numpy as np # type: ignore[no-redef,import-not-found]
23
+ except ImportError:
24
+ np = None
25
+
26
+ HAS_BACKEND: bool = np is not None
27
+
28
+
29
+ def to_array(data: list[Any]) -> Any:
30
+ """Convert a Python list to an ndarray if numeric and backend is available."""
31
+ if not HAS_BACKEND or len(data) == 0:
32
+ return data
33
+ if not all(isinstance(x, (int, float)) for x in data):
34
+ return data
35
+ return np.array(data)
36
+
37
+
38
+ def to_list(data: Any) -> list[Any]:
39
+ """Convert an ndarray to a Python list. Pass lists through unchanged."""
40
+ if isinstance(data, list):
41
+ return data
42
+ return data.tolist() # type: ignore[union-attr]
43
+
44
+
45
+ def is_numeric_array(data: Any) -> bool:
46
+ """Check if data is an ndarray from the active backend."""
47
+ if not HAS_BACKEND:
48
+ return False
49
+ return hasattr(data, "dtype")
@@ -0,0 +1,110 @@
1
+ from __future__ import annotations
2
+
3
+ from marple.arraymodel import APLArray, S
4
+ from marple.backend import to_list
5
+ from marple.errors import LengthError
6
+
7
+
8
+ def resolve_rank_spec(spec: APLArray) -> tuple[int, int, int]:
9
+ """Resolve rank spec to (monadic, left_dyadic, right_dyadic).
10
+
11
+ Scalar c → (c, c, c)
12
+ 2-vector b c → (c, b, c)
13
+ 3-vector a b c → (a, b, c)
14
+ """
15
+ data = to_list(spec.data)
16
+ if spec.is_scalar():
17
+ c = int(data[0])
18
+ return (c, c, c)
19
+ if len(data) == 2:
20
+ b, c = int(data[0]), int(data[1])
21
+ return (c, b, c)
22
+ if len(data) == 3:
23
+ a, b, c = int(data[0]), int(data[1]), int(data[2])
24
+ return (a, b, c)
25
+ raise LengthError(f"Rank spec must be 1, 2, or 3 elements, got {len(data)}")
26
+
27
+
28
+ def clamp_rank(k: int, r: int) -> int:
29
+ """Clamp cell rank k to [0, r]. Handle negative (complementary) rank."""
30
+ if k < 0:
31
+ k = r + k
32
+ return max(0, min(k, r))
33
+
34
+
35
+ def decompose(array: APLArray, cell_rank: int) -> tuple[list[int], list[APLArray]]:
36
+ """Decompose array into cells of the given rank.
37
+
38
+ Returns (frame_shape, cells) where frame_shape is the leading axes
39
+ and cells is a list of APLArray objects in row-major frame order.
40
+ """
41
+ r = len(array.shape)
42
+ k = clamp_rank(cell_rank, r)
43
+ frame_shape = array.shape[:r - k] if k < r else []
44
+ cell_shape = array.shape[r - k:] if k > 0 else []
45
+ cell_size = 1
46
+ for s in cell_shape:
47
+ cell_size *= s
48
+ if cell_size == 0:
49
+ cell_size = 1
50
+ data = to_list(array.data)
51
+ n_cells = 1
52
+ for s in frame_shape:
53
+ n_cells *= s
54
+ if n_cells == 0:
55
+ n_cells = 1
56
+ cells: list[APLArray] = []
57
+ for i in range(n_cells):
58
+ cell_data = data[i * cell_size : (i + 1) * cell_size]
59
+ cells.append(APLArray(list(cell_shape), cell_data))
60
+ return (list(frame_shape), cells)
61
+
62
+
63
+ def reassemble(frame_shape: list[int], cells: list[APLArray]) -> APLArray:
64
+ """Reassemble cells into a single array.
65
+
66
+ If all cells have the same shape, result shape is frame_shape + cell_shape.
67
+ If shapes differ, pad with fill elements to max shape.
68
+ """
69
+ if len(cells) == 0:
70
+ return APLArray(frame_shape + [0], [])
71
+ if len(cells) == 1 and frame_shape == []:
72
+ return cells[0]
73
+ # Determine max cell shape
74
+ max_rank = max(len(c.shape) for c in cells)
75
+ max_shape: list[int] = [0] * max_rank
76
+ for c in cells:
77
+ # Pad cell shape on the left with 1s to match max_rank
78
+ padded = [1] * (max_rank - len(c.shape)) + c.shape
79
+ for j in range(max_rank):
80
+ max_shape[j] = max(max_shape[j], padded[j])
81
+ # Check if all cells match max_shape (no padding needed)
82
+ all_uniform = all(c.shape == max_shape for c in cells)
83
+ if all_uniform:
84
+ all_data: list[object] = []
85
+ for c in cells:
86
+ all_data.extend(to_list(c.data))
87
+ return APLArray(frame_shape + max_shape, all_data)
88
+ # Padding needed
89
+ max_size = 1
90
+ for s in max_shape:
91
+ max_size *= s
92
+ # Determine fill value
93
+ is_char = any(
94
+ len(c.data) > 0 and isinstance(to_list(c.data)[0], str)
95
+ for c in cells
96
+ )
97
+ fill = " " if is_char else 0
98
+ all_data = []
99
+ for c in cells:
100
+ cell_data = to_list(c.data)
101
+ if c.shape == max_shape:
102
+ all_data.extend(cell_data)
103
+ else:
104
+ # Pad: embed cell data in a max_shape-sized block
105
+ padded_cell: list[object] = [fill] * max_size
106
+ # Simple case: 1D padding
107
+ for j, val in enumerate(cell_data):
108
+ padded_cell[j] = val
109
+ all_data.extend(padded_cell)
110
+ return APLArray(frame_shape + max_shape, all_data)
@@ -0,0 +1,66 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ class APLError(Exception):
5
+ """Base class for APL errors."""
6
+ code: int = 0
7
+ name: str = "ERROR"
8
+
9
+ def __init__(self, message: str = "") -> None:
10
+ self.message = message
11
+ super().__init__(f"{self.name}: {message}" if message else self.name)
12
+
13
+
14
+ class SyntaxError_(APLError):
15
+ code = 1
16
+ name = "SYNTAX ERROR"
17
+
18
+
19
+ class ValueError_(APLError):
20
+ code = 2
21
+ name = "VALUE ERROR"
22
+
23
+
24
+ class DomainError(APLError):
25
+ code = 3
26
+ name = "DOMAIN ERROR"
27
+
28
+
29
+ class LengthError(APLError):
30
+ code = 4
31
+ name = "LENGTH ERROR"
32
+
33
+
34
+ class RankError(APLError):
35
+ code = 5
36
+ name = "RANK ERROR"
37
+
38
+
39
+ class IndexError_(APLError):
40
+ code = 6
41
+ name = "INDEX ERROR"
42
+
43
+
44
+ class LimitError(APLError):
45
+ code = 7
46
+ name = "LIMIT ERROR"
47
+
48
+
49
+ class WSFullError(APLError):
50
+ code = 8
51
+ name = "WS FULL"
52
+
53
+
54
+ class SecurityError(APLError):
55
+ code = 9
56
+ name = "SECURITY ERROR"
57
+
58
+
59
+ class DependencyError(APLError):
60
+ code = 10
61
+ name = "DEPENDENCY ERROR"
62
+
63
+
64
+ class ClassError(APLError):
65
+ code = 11
66
+ name = "CLASS ERROR"