nc-gcode-interpreter 0.2.8__tar.gz → 0.2.10__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.
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/.github/workflows/build-and-release.yml +7 -5
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/CHANGELOG.md +60 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/Cargo.lock +1 -1
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/Cargo.toml +1 -1
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/PKG-INFO +1 -1
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/README.md +14 -13
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/docs/resumable-interpretation-design.md +1 -1
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/python/tests/test_batches.py +17 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/python/tests/test_stage1.py +17 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/src/flatten.rs +60 -37
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/src/interpret_rules.rs +3 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/src/interpreter.rs +89 -9
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/src/lib.rs +38 -22
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/src/line_driver.rs +147 -16
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/src/main.rs +1 -1
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/src/output.rs +368 -54
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/src/state.rs +38 -14
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/uv.lock +6 -27
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/.github/workflows/benchmark.yml +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/.gitignore +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/CONTRIBUTING.md +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/Development.md +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/LICENSE +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/TODO.md +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/benches/fixtures.rs +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/benches/workloads.rs +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/build.rs +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/build_backend/nc_build_backend.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/docs/sinumerik-execution-model.md +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/actual_position.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/actual_position.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/arc.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/arc.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/arrays.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/arrays.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/axis_index_assignment.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/axis_index_assignment.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/basic_math.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/basic_math.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/case.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/case.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/case_insensitive_variables.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/case_insensitive_variables.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/custom_vars.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/custom_vars.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/def_string.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/def_string.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/defaults.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/defaults.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/edge_cases.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/edge_cases.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/flattening/flatten_demo.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/flattening/flatten_demo_flattened.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/flattening/flatten_demo_raw.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/for_loop.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/for_loop.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/function_calls.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/function_calls.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/goto.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/goto.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/if_statement.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/if_statement.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/increment.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/increment.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/logic_operators.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/logic_operators.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/loop.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/loop.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/multiple_m_codes.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/multiple_m_codes.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/precedence.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/precedence.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/r_param.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/r_param.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/repeat.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/repeat.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/rot.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/rot.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/simple.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/simple.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/spline.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/spline.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/tool.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/tool.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/trans.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/trans.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/trans2.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/trans2.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/trans_ic.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/trans_ic.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/trans_reset.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/trans_reset.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/trig.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/trig.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/variables.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/examples/variables.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/ggroups/generate_g_commands.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/pyproject.toml +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/python/example/minimal.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/python/example/streaming.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/python/nc_gcode_interpreter/__init__.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/python/nc_gcode_interpreter/_internal.pyi +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/python/nc_gcode_interpreter/cli.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/python/nc_gcode_interpreter/ggroups.json +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/python/nc_gcode_interpreter/py.typed +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/python/nc_gcode_interpreter/viz.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/python/tests/nc_source.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/python/tests/test_arithmetic_functions.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/python/tests/test_batch_variables.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/python/tests/test_compare_benchmarks.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/python/tests/test_diagnostics.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/python/tests/test_expected_output.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/python/tests/test_field_defects.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/python/tests/test_flags.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/python/tests/test_flatten.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/python/tests/test_frames.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/python/tests/test_g_groups.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/python/tests/test_g_vocabulary.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/python/tests/test_jumps.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/python/tests/test_real_comparisons.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/python/tests/test_streaming.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/python/tests/test_string_operations.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/python/tests/test_structured_errors.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/python/tests/test_unsupported.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/rustfmt.toml +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/scripts/benchmark.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/scripts/compare_benchmarks.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/src/errors.rs +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/src/frame.rs +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/src/grammar.pest +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/src/line_driver/experiments.rs +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/src/modal_groups.rs +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/src/structure_scan.rs +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/src/types.rs +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/src/vm.rs +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/tests/cli.rs +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/tests/performance_contract.rs +0 -0
{nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/.github/workflows/build-and-release.yml
RENAMED
|
@@ -80,7 +80,7 @@ jobs:
|
|
|
80
80
|
uses: PyO3/maturin-action@v1
|
|
81
81
|
with:
|
|
82
82
|
target: ${{ matrix.platform.target }}
|
|
83
|
-
args: --release --out dist
|
|
83
|
+
args: --release --out dist -i python3.12 -i python3.13 -i python3.14 -i python3.14t -i python3.15 -i python3.15t
|
|
84
84
|
sccache: 'true'
|
|
85
85
|
manylinux: auto
|
|
86
86
|
- name: Upload wheels
|
|
@@ -156,7 +156,9 @@ jobs:
|
|
|
156
156
|
uses: PyO3/maturin-action@v1
|
|
157
157
|
with:
|
|
158
158
|
target: ${{ matrix.platform.target }}
|
|
159
|
-
|
|
159
|
+
# Every wheel job names its interpreters (the set 0.2.8 shipped per platform):
|
|
160
|
+
# --find-interpreter picks up whatever the image has, e.g. PyPy, which fails.
|
|
161
|
+
args: --release --out dist -i python3.12 -i python3.13 -i python3.14 -i python3.14t
|
|
160
162
|
sccache: 'true'
|
|
161
163
|
manylinux: auto
|
|
162
164
|
- name: Upload wheels
|
|
@@ -214,7 +216,7 @@ jobs:
|
|
|
214
216
|
uses: PyO3/maturin-action@v1
|
|
215
217
|
with:
|
|
216
218
|
target: ${{ matrix.platform.target }}
|
|
217
|
-
args: --release --out dist
|
|
219
|
+
args: --release --out dist -i python3.12 -i python3.13 -i python3.14
|
|
218
220
|
sccache: 'true'
|
|
219
221
|
- name: Upload wheels
|
|
220
222
|
uses: actions/upload-artifact@v4
|
|
@@ -254,7 +256,7 @@ jobs:
|
|
|
254
256
|
uses: PyO3/maturin-action@v1
|
|
255
257
|
with:
|
|
256
258
|
target: ${{ matrix.platform.target }}
|
|
257
|
-
args: --release --out dist
|
|
259
|
+
args: --release --out dist -i python3.12 -i python3.13 -i python3.14 -i python3.15
|
|
258
260
|
sccache: 'true'
|
|
259
261
|
- name: Upload wheels
|
|
260
262
|
uses: actions/upload-artifact@v4
|
|
@@ -303,7 +305,7 @@ jobs:
|
|
|
303
305
|
contents: write
|
|
304
306
|
attestations: write
|
|
305
307
|
steps:
|
|
306
|
-
# Do NOT use `merge-multiple: true`. With
|
|
308
|
+
# Do NOT use `merge-multiple: true`. With a shared interpreter list, every
|
|
307
309
|
# build matrix leg (py3.12 AND py3.13) produces cp312/cp313/cp314 wheels
|
|
308
310
|
# with identical filenames but non-identical bytes (sccache makes builds
|
|
309
311
|
# nondeterministic). Merging them into one directory makes
|
|
@@ -4,6 +4,66 @@ Notable changes to **nc-gcode-interpreter**. The format loosely follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/); versions are git tags,
|
|
5
5
|
released to PyPI.
|
|
6
6
|
|
|
7
|
+
## [v0.2.10] - 2026-11-10
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- `nc_to_batches` and `nc_to_dataframe` do less work per output row. The
|
|
12
|
+
G-group columns are held as per-batch dictionary codes and written straight
|
|
13
|
+
to the Arrow string array, instead of one `String` per row per column that
|
|
14
|
+
the consumer thread then freed; the batch builder finds a cell's column
|
|
15
|
+
without hashing its name; the streaming driver reuses its decode buffers.
|
|
16
|
+
Python output is unchanged (same columns, `String` dtype, values and
|
|
17
|
+
`variable_events`). With `flatten_tolerance`, line numbers and variables on,
|
|
18
|
+
a 7.2M-row, 442 MiB linear program takes 3.3 s instead of 8.3 s and a
|
|
19
|
+
22M-row, 1.1 GB program 10.5 s instead of 24 s, at the same peak memory
|
|
20
|
+
(#89).
|
|
21
|
+
|
|
22
|
+
- `flatten_tolerance` costs almost nothing on rows that are not curves: the
|
|
23
|
+
flattener tracked positions with an allocation and a lookup per configured
|
|
24
|
+
axis for every row, about 2 s on the 7.2M-row program above, now about
|
|
25
|
+
0.15 s. B-spline flattening reuses one working buffer per curve; a
|
|
26
|
+
5.4M-row, 380 MB spline program takes 8 s instead of 16 s (#89).
|
|
27
|
+
|
|
28
|
+
- Rust API: `Column` has a new `Dict(DictColumn)` variant, used for the
|
|
29
|
+
G-group columns (`values` plus one `u32` code per row,
|
|
30
|
+
`DictColumn::NULL` for a null cell). Code that matches `Column`
|
|
31
|
+
exhaustively needs one more arm; `DictColumn::iter` yields the cells as
|
|
32
|
+
`Option<&str>` (#89).
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
|
|
36
|
+
- A numeric tool selection (`T2`, `T=3`, `T=R1`) now reaches the output table.
|
|
37
|
+
It is written to the same string `T` column as `T="name"`, as the number's
|
|
38
|
+
text (`"2"`), on the row of the block that programs it. Before, only named
|
|
39
|
+
tools appeared and a numeric one was dropped without a warning. The value is
|
|
40
|
+
still stored as the variable `T` in the final state, as before (#101).
|
|
41
|
+
|
|
42
|
+
## [v0.2.9] - 2026-11-01
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
|
|
46
|
+
- `nc_to_batches` and `nc_to_rows` borrow the Python str's UTF-8 buffer for
|
|
47
|
+
the run instead of copying it twice (once into a Rust `String`, once more
|
|
48
|
+
for error messages). On a 5M-line, 322 MB program the peak footprint of
|
|
49
|
+
`nc_to_batches` drops from 1.28 to 0.72 GiB. The Rust entry points
|
|
50
|
+
(`nc_to_table`, `nc_to_row_stream`, `nc_to_batch_stream*`) take
|
|
51
|
+
`impl Into<SourceText>`: a `&str` still works and is copied once, an owned
|
|
52
|
+
`String` or `Arc<str>` is shared without a copy (#89).
|
|
53
|
+
|
|
54
|
+
- Every jump-free program now runs on the streaming line driver, not only
|
|
55
|
+
those of 64 MiB or more, and the streaming driver decodes ahead on a second
|
|
56
|
+
thread instead of decoding inline. On a 1M-line, 63 MB program
|
|
57
|
+
`nc_to_batches` peaks at 0.21 GiB instead of 0.60 GiB and takes 1.02 s
|
|
58
|
+
instead of 1.24 s; at 5M lines it takes 5.1 s instead of 6.3 s. Programs
|
|
59
|
+
with jumps keep the indexed driver; `NC_STREAM_MIN_BYTES` still sets a size
|
|
60
|
+
threshold (#89).
|
|
61
|
+
|
|
62
|
+
- Less work per output row on the interpreter thread: the batch builder moves
|
|
63
|
+
string cells out of the rows it recycles instead of cloning them, and the
|
|
64
|
+
per-row column set and the flattener's position maps hash with FxHash. At
|
|
65
|
+
5M lines `nc_to_batches` takes 4.4 s instead of 5.2 s (#89).
|
|
66
|
+
|
|
7
67
|
## [v0.2.8] - 2026-10-15
|
|
8
68
|
|
|
9
69
|
### Added
|
|
@@ -161,23 +161,24 @@ for line_no, row, variables in nc_to_rows(program, include_variables=True):
|
|
|
161
161
|
|
|
162
162
|
Rows are typed and forward-filled like the batch DataFrame (disable with `forward_fill=False`); errors raise from `next()` at the offending row; after exhaustion the iterator's `state` attribute holds the final interpreter state. See `python/example/streaming.py` for a runnable version.
|
|
163
163
|
|
|
164
|
-
**Memory.** Programs
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
length. The input text
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
164
|
+
**Memory.** Programs without multi-line control blocks or jumps are validated
|
|
165
|
+
in a preflight scan and then executed one line at a time, with the next lines
|
|
166
|
+
decoded on a second thread, so parse storage is bounded by a chunk of lines
|
|
167
|
+
instead of the program length. The input text (held once: the Python str is
|
|
168
|
+
borrowed, not copied, and error messages read from the same buffer), its
|
|
169
|
+
line-offset index, variable state and any output the caller retains still grow
|
|
170
|
+
with the program, so use `nc_to_batches` with a bounded consumer for large
|
|
171
|
+
files. Programs with jumps keep the indexed fast path; `NC_STREAM_MIN_BYTES`
|
|
172
|
+
sets a size below which jump-free programs take it too (default 0). Control
|
|
173
|
+
blocks and `NC_STAGE1=0` still use the whole-file parser. On a 1M-line (63 MB)
|
|
174
|
+
generated program the peak footprint of `nc_to_batches` is about 0.2 GiB, and
|
|
175
|
+
streaming is 5 to 20 percent faster than the indexed driver from 1 MB up.
|
|
176
|
+
Measure a local program with
|
|
176
177
|
`python scripts/benchmark.py --input <file> --modes fresh --edits none
|
|
177
178
|
--threads 4 --samples 1 --batch-size 4096 --allow-undefined --axis-index-e 4
|
|
178
179
|
--axis-index-elx 5 --output tmp/memory.json`.
|
|
179
180
|
|
|
180
|
-
**Cancellation.** Dropping the iterator is the supported way to cancel a `nc_to_rows`/`nc_to_batches` run — leaving a `for` loop early or `del`-ing it drops it immediately; relying on garbage collection works too but only whenever the runtime happens to collect it, so it is not prompt on all interpreters: the background interpreter thread's next attempt to send a row hits a closed channel and returns internally via `ParsingError::StreamClosed`, so the thread unwinds promptly instead of running the rest of the program to completion. No explicit `.close()`/`.cancel()` call is needed or provided — drop is the whole contract, and downstreams (
|
|
181
|
+
**Cancellation.** Dropping the iterator is the supported way to cancel a `nc_to_rows`/`nc_to_batches` run — leaving a `for` loop early or `del`-ing it drops it immediately; relying on garbage collection works too but only whenever the runtime happens to collect it, so it is not prompt on all interpreters: the background interpreter thread's next attempt to send a row hits a closed channel and returns internally via `ParsingError::StreamClosed`, so the thread unwinds promptly instead of running the rest of the program to completion. No explicit `.close()`/`.cancel()` call is needed or provided — drop is the whole contract, and downstreams (for example a live-edit re-interpret in an editor) rely on it for latest-wins cancellation.
|
|
181
182
|
|
|
182
183
|
Additionally, conversion from a Polars DataFrame back to an MPF (NC) program is also supported:
|
|
183
184
|
|
{nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.10}/docs/resumable-interpretation-design.md
RENAMED
|
@@ -15,7 +15,7 @@ Expose a resumable interpretation handle:
|
|
|
15
15
|
- `resume(token, patched_text)` — restart interpretation from a checkpoint,
|
|
16
16
|
against an edited program.
|
|
17
17
|
|
|
18
|
-
Consumer:
|
|
18
|
+
Consumer: a live-edit *incremental re-interpret* in a G-code editor, for multi-million-row
|
|
19
19
|
loop expansions. When the user edits source line *L*, it resumes from the last
|
|
20
20
|
checkpoint whose block cursor precedes *L* instead of re-running the whole program.
|
|
21
21
|
Downstream is evidence-gated: debounced-full re-interpret is fine below ~10⁵ rows,
|
|
@@ -208,3 +208,20 @@ def test_include_line_numbers_survives_flattening():
|
|
|
208
208
|
assert per_line.count(2) > 5
|
|
209
209
|
stream_lines = [line for line, *_ in nc_to_rows(program, flatten_tolerance=0.1)]
|
|
210
210
|
assert per_line == stream_lines
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def test_g_group_columns_stay_plain_strings():
|
|
214
|
+
# The interpreter holds G-group columns as dictionary codes internally;
|
|
215
|
+
# Python must keep seeing String columns with the same values.
|
|
216
|
+
program = "G1 G64 X1\nX2\nG4 F0.5\nG0 X3\nG17 X4\nM30\n"
|
|
217
|
+
frames = list(nc_to_batches(program, batch_size=2))
|
|
218
|
+
assert [f.height for f in frames] == [2, 2, 2]
|
|
219
|
+
for frame in frames:
|
|
220
|
+
for name in frame.columns:
|
|
221
|
+
if name.startswith("gg"):
|
|
222
|
+
assert frame[name].dtype == pl.String, name
|
|
223
|
+
df = pl.concat(frames, how="diagonal")
|
|
224
|
+
assert df["gg01_motion"].to_list() == ["G1", "G1", "G1", "G0", "G0", "G0"]
|
|
225
|
+
assert df["gg10_exact_stop_mode"].to_list() == ["G64"] * 6
|
|
226
|
+
assert df["gg02_wait"].to_list() == [None, None, "G4", None, None, None]
|
|
227
|
+
assert df["gg06_plane_select"].to_list() == [None, None, None, None, "G17", "G17"]
|
|
@@ -159,3 +159,20 @@ def test_fast_path_matches_on_real_corpus(mpf):
|
|
|
159
159
|
extra_axes=["ELX"],
|
|
160
160
|
allow_undefined_variables=True,
|
|
161
161
|
)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@pytest.mark.parametrize(
|
|
165
|
+
"program, expected",
|
|
166
|
+
[
|
|
167
|
+
("G1 X0\nT2\nX1", [None, "2", None]),
|
|
168
|
+
("G1 X0\nT=3\nX1", [None, "3", None]),
|
|
169
|
+
("G1 X0\nt4\nX1", [None, "4", None]),
|
|
170
|
+
("R1=7\nG1 X0\nT=R1\nX1", [None, "7", None]),
|
|
171
|
+
('G1 X0\nT="NOZ_A"\nT5 X1', [None, "NOZ_A", "5"]),
|
|
172
|
+
],
|
|
173
|
+
ids=["T2", "T=3", "lowercase", "expression", "mixed with a named tool"],
|
|
174
|
+
)
|
|
175
|
+
def test_numeric_tool_selection_reaches_the_t_column(program, expected):
|
|
176
|
+
"""A numeric tool selection lands in the same string T column as a named one (#101)."""
|
|
177
|
+
df = run_both(program)
|
|
178
|
+
assert df["T"].to_list() == expected
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
//! than being misread).
|
|
68
68
|
|
|
69
69
|
use crate::errors::ParsingError;
|
|
70
|
-
use crate::output::{intern_column, CellMap, Row, FLATTENED_COLUMN};
|
|
70
|
+
use crate::output::{intern_column, key_eq, CellMap, Row, FLATTENED_COLUMN};
|
|
71
71
|
use crate::state::emit_warning;
|
|
72
72
|
use crate::types::Value;
|
|
73
73
|
use std::collections::HashMap;
|
|
@@ -135,8 +135,9 @@ pub struct Flattener {
|
|
|
135
135
|
/// Geometric axis columns (configured axis identifiers minus the
|
|
136
136
|
/// non-geometric value columns), interned.
|
|
137
137
|
geometric_axes: Vec<&'static str>,
|
|
138
|
-
/// Last known machine coordinate per geometric axis
|
|
139
|
-
|
|
138
|
+
/// Last known machine coordinate per geometric axis, indexed like
|
|
139
|
+
/// `geometric_axes` (a row updates a few slots, with no hashing).
|
|
140
|
+
positions: Vec<Option<f64>>,
|
|
140
141
|
motion: Motion,
|
|
141
142
|
plane: Plane,
|
|
142
143
|
/// Buffered blocks of the active spline, in program order. Non-empty
|
|
@@ -144,7 +145,7 @@ pub struct Flattener {
|
|
|
144
145
|
spline_buffer: Vec<SplineItem>,
|
|
145
146
|
/// Snapshot of `positions` taken when the spline was selected: the
|
|
146
147
|
/// spline's start point.
|
|
147
|
-
spline_start:
|
|
148
|
+
spline_start: Vec<Option<f64>>,
|
|
148
149
|
/// Current B-spline degree (`SD=`, modal within the spline).
|
|
149
150
|
spline_degree: usize,
|
|
150
151
|
/// Curve motion words already warned about (CIP, CT, POLY, ...): one
|
|
@@ -159,8 +160,8 @@ impl Flattener {
|
|
|
159
160
|
/// point even though the interpreter does.
|
|
160
161
|
pub fn seed_position(&mut self, axis: &str, machine_value: f64) {
|
|
161
162
|
let key = intern_column(&axis.to_uppercase());
|
|
162
|
-
if self.
|
|
163
|
-
self.positions
|
|
163
|
+
if let Some(slot) = self.axis_slot(key) {
|
|
164
|
+
self.positions[slot] = Some(machine_value);
|
|
164
165
|
}
|
|
165
166
|
}
|
|
166
167
|
|
|
@@ -170,7 +171,7 @@ impl Flattener {
|
|
|
170
171
|
message: format!("flatten tolerance must be a positive number, got {}", tolerance),
|
|
171
172
|
});
|
|
172
173
|
}
|
|
173
|
-
let geometric_axes = axis_identifiers
|
|
174
|
+
let geometric_axes: Vec<&'static str> = axis_identifiers
|
|
174
175
|
.iter()
|
|
175
176
|
.map(|a| a.to_uppercase())
|
|
176
177
|
.filter(|a| !NON_GEOMETRIC_AXES.contains(&a.as_str()))
|
|
@@ -178,12 +179,12 @@ impl Flattener {
|
|
|
178
179
|
.collect();
|
|
179
180
|
Ok(Flattener {
|
|
180
181
|
tolerance,
|
|
182
|
+
positions: vec![None; geometric_axes.len()],
|
|
181
183
|
geometric_axes,
|
|
182
|
-
positions: HashMap::new(),
|
|
183
184
|
motion: Motion::Linear,
|
|
184
185
|
plane: G17,
|
|
185
186
|
spline_buffer: Vec::new(),
|
|
186
|
-
spline_start:
|
|
187
|
+
spline_start: Vec::new(),
|
|
187
188
|
spline_degree: 3,
|
|
188
189
|
warned_motions: Vec::new(),
|
|
189
190
|
})
|
|
@@ -201,7 +202,7 @@ impl Flattener {
|
|
|
201
202
|
}
|
|
202
203
|
self.motion = new_motion;
|
|
203
204
|
}
|
|
204
|
-
if let Some(Value::Str(plane)) = row.cells.get(
|
|
205
|
+
if let Some(Value::Str(plane)) = row.cells.get("gg06_plane_select") {
|
|
205
206
|
self.plane = match plane.as_str() {
|
|
206
207
|
"G18" => G18,
|
|
207
208
|
"G19" => G19,
|
|
@@ -215,7 +216,7 @@ impl Flattener {
|
|
|
215
216
|
self.spline_start = self.positions.clone();
|
|
216
217
|
self.spline_degree = 3;
|
|
217
218
|
}
|
|
218
|
-
if let Some(Value::Float(sd)) = row.cells.get(
|
|
219
|
+
if let Some(Value::Float(sd)) = row.cells.get("SD") {
|
|
219
220
|
self.spline_degree = (*sd as usize).max(1);
|
|
220
221
|
}
|
|
221
222
|
let has_point = self
|
|
@@ -266,10 +267,23 @@ impl Flattener {
|
|
|
266
267
|
!self.spline_buffer.is_empty()
|
|
267
268
|
}
|
|
268
269
|
|
|
270
|
+
/// Index of `name` in `geometric_axes`.
|
|
271
|
+
fn axis_slot(&self, name: &str) -> Option<usize> {
|
|
272
|
+
self.geometric_axes.iter().position(|&axis| key_eq(axis, name))
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/// Last known machine coordinate of a geometric axis.
|
|
276
|
+
fn position(&self, axis: &str) -> Option<f64> {
|
|
277
|
+
self.axis_slot(axis).and_then(|slot| self.positions[slot])
|
|
278
|
+
}
|
|
279
|
+
|
|
269
280
|
fn track_positions(&mut self, row: &Row) {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
281
|
+
// Walk the row's few cells, not every configured axis.
|
|
282
|
+
for (&key, value) in row.cells.iter() {
|
|
283
|
+
if let Value::Float(v) = value {
|
|
284
|
+
if let Some(slot) = self.axis_slot(key) {
|
|
285
|
+
self.positions[slot] = Some(*v);
|
|
286
|
+
}
|
|
273
287
|
}
|
|
274
288
|
}
|
|
275
289
|
}
|
|
@@ -309,7 +323,7 @@ impl Flattener {
|
|
|
309
323
|
return;
|
|
310
324
|
}
|
|
311
325
|
|
|
312
|
-
let (Some(su), Some(sv)) = (self.
|
|
326
|
+
let (Some(su), Some(sv)) = (self.position(u_axis), self.position(v_axis)) else {
|
|
313
327
|
self.pass_through_with_warning(row, "arc start position is unknown", out);
|
|
314
328
|
return;
|
|
315
329
|
};
|
|
@@ -411,8 +425,8 @@ impl Flattener {
|
|
|
411
425
|
continue;
|
|
412
426
|
}
|
|
413
427
|
if let Some(end) = cell_float(&row, axis) {
|
|
414
|
-
let start = match self.
|
|
415
|
-
Some(
|
|
428
|
+
let start = match self.position(axis) {
|
|
429
|
+
Some(start) => start,
|
|
416
430
|
None => {
|
|
417
431
|
// First-ever position of this axis: there is nothing
|
|
418
432
|
// to ramp from, so the channel holds its end value
|
|
@@ -501,17 +515,19 @@ impl Flattener {
|
|
|
501
515
|
let mut current: Vec<f64> = channels
|
|
502
516
|
.iter()
|
|
503
517
|
.map(|&c| {
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
518
|
+
self.axis_slot(c)
|
|
519
|
+
.and_then(|slot| start.get(slot).copied().flatten())
|
|
520
|
+
.unwrap_or_else(|| {
|
|
521
|
+
// Channel never positioned before the spline: start it at
|
|
522
|
+
// its first programmed value (a zero-length lead-in).
|
|
523
|
+
items
|
|
524
|
+
.iter()
|
|
525
|
+
.find_map(|item| match item {
|
|
526
|
+
SplineItem::Point(row) => cell_float(row, c),
|
|
527
|
+
_ => None,
|
|
528
|
+
})
|
|
529
|
+
.unwrap_or(0.0)
|
|
530
|
+
})
|
|
515
531
|
})
|
|
516
532
|
.collect();
|
|
517
533
|
points.push(current.clone());
|
|
@@ -659,7 +675,7 @@ fn points_value(points: &[Vec<f64>], point_rows: &[Option<usize>], item_index: u
|
|
|
659
675
|
}
|
|
660
676
|
|
|
661
677
|
fn row_motion(row: &Row) -> Option<Motion> {
|
|
662
|
-
match row.cells.get(
|
|
678
|
+
match row.cells.get("gg01_motion") {
|
|
663
679
|
Some(Value::Str(m)) => Some(match m.as_str() {
|
|
664
680
|
"G0" | "G1" => Motion::Linear,
|
|
665
681
|
"G2" => Motion::Arc { cw: true },
|
|
@@ -956,6 +972,9 @@ struct BSpline {
|
|
|
956
972
|
knots: Vec<f64>,
|
|
957
973
|
ctrl: Vec<Vec<f64>>,
|
|
958
974
|
channels: usize,
|
|
975
|
+
/// de Boor working rows, `(p + 1) * (channels + 1)` values, reused by
|
|
976
|
+
/// every `eval` (the flattener evaluates several times per control point).
|
|
977
|
+
scratch: std::cell::RefCell<Vec<f64>>,
|
|
959
978
|
}
|
|
960
979
|
|
|
961
980
|
impl BSpline {
|
|
@@ -984,6 +1003,7 @@ impl BSpline {
|
|
|
984
1003
|
knots,
|
|
985
1004
|
ctrl,
|
|
986
1005
|
channels: points[0].len(),
|
|
1006
|
+
scratch: std::cell::RefCell::new(Vec::new()),
|
|
987
1007
|
}
|
|
988
1008
|
}
|
|
989
1009
|
|
|
@@ -998,7 +1018,13 @@ impl BSpline {
|
|
|
998
1018
|
} else {
|
|
999
1019
|
p + t as usize
|
|
1000
1020
|
};
|
|
1001
|
-
|
|
1021
|
+
// Row j of `d` is control point j + k - p.
|
|
1022
|
+
let width = channels + 1;
|
|
1023
|
+
let mut d = self.scratch.borrow_mut();
|
|
1024
|
+
d.clear();
|
|
1025
|
+
for j in 0..=p {
|
|
1026
|
+
d.extend_from_slice(&self.ctrl[j + k - p]);
|
|
1027
|
+
}
|
|
1002
1028
|
for r in 1..=p {
|
|
1003
1029
|
for j in (r..=p).rev() {
|
|
1004
1030
|
let i = j + k - p;
|
|
@@ -1008,17 +1034,14 @@ impl BSpline {
|
|
|
1008
1034
|
} else {
|
|
1009
1035
|
(t - knots[i]) / denom
|
|
1010
1036
|
};
|
|
1011
|
-
// Indexing two rows of `d` at once: a plain indexed loop is
|
|
1012
|
-
// clearer than a split_at_mut dance.
|
|
1013
|
-
#[allow(clippy::needless_range_loop)]
|
|
1014
1037
|
for c in 0..=channels {
|
|
1015
|
-
let previous = d[j - 1
|
|
1016
|
-
d[j
|
|
1038
|
+
let previous = d[(j - 1) * width + c];
|
|
1039
|
+
d[j * width + c] = (1.0 - alpha) * previous + alpha * d[j * width + c];
|
|
1017
1040
|
}
|
|
1018
1041
|
}
|
|
1019
1042
|
}
|
|
1020
|
-
let w = d[p
|
|
1021
|
-
d[p
|
|
1043
|
+
let w = d[p * width + channels];
|
|
1044
|
+
d[p * width..p * width + channels].iter().map(|v| v / w).collect()
|
|
1022
1045
|
}
|
|
1023
1046
|
|
|
1024
1047
|
/// Greville abscissa of control point `i`, the mean of the `p` knots
|
|
@@ -2514,6 +2514,9 @@ fn interpret_statement(element: Pair<Rule>, output: &mut Output, state: &mut Sta
|
|
|
2514
2514
|
}
|
|
2515
2515
|
None => {
|
|
2516
2516
|
state.warn_unsupported_address(&key, line_no);
|
|
2517
|
+
if key.eq_ignore_ascii_case("T") {
|
|
2518
|
+
last.insert("T", Value::Str(crate::output::tool_number_text(local_value)));
|
|
2519
|
+
}
|
|
2517
2520
|
output.record_variable_change(&key, local_value);
|
|
2518
2521
|
}
|
|
2519
2522
|
}
|
|
@@ -6,6 +6,54 @@ use crate::state::{self, State};
|
|
|
6
6
|
use crate::types::{NCParser, Rule};
|
|
7
7
|
use pest::Parser;
|
|
8
8
|
use std::collections::HashMap;
|
|
9
|
+
use std::sync::Arc;
|
|
10
|
+
|
|
11
|
+
/// Program text shared between the driver that interprets it and the state's
|
|
12
|
+
/// diagnostics, so a run holds a single copy of the source. Converting from a
|
|
13
|
+
/// `&str` copies once; an owned `String`, an `Arc<str>` or any other
|
|
14
|
+
/// [`SourceText::new`] buffer is shared as is.
|
|
15
|
+
#[derive(Clone)]
|
|
16
|
+
pub struct SourceText(Arc<dyn AsRef<str> + Send + Sync>);
|
|
17
|
+
|
|
18
|
+
impl SourceText {
|
|
19
|
+
pub fn new<T: AsRef<str> + Send + Sync + 'static>(text: T) -> Self {
|
|
20
|
+
SourceText(Arc::new(text))
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
pub fn as_str(&self) -> &str {
|
|
24
|
+
(*self.0).as_ref()
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
impl std::fmt::Debug for SourceText {
|
|
29
|
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
30
|
+
f.debug_struct("SourceText").field("len", &self.as_str().len()).finish()
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
impl From<&str> for SourceText {
|
|
35
|
+
fn from(text: &str) -> Self {
|
|
36
|
+
SourceText(Arc::new(Box::<str>::from(text)))
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
impl From<&String> for SourceText {
|
|
41
|
+
fn from(text: &String) -> Self {
|
|
42
|
+
SourceText::from(text.as_str())
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
impl From<String> for SourceText {
|
|
47
|
+
fn from(text: String) -> Self {
|
|
48
|
+
SourceText::new(text)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
impl From<Arc<str>> for SourceText {
|
|
53
|
+
fn from(text: Arc<str>) -> Self {
|
|
54
|
+
SourceText::new(text)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
9
57
|
|
|
10
58
|
const DEFAULT_AXIS_IDENTIFIERS: &[&str] = &[
|
|
11
59
|
"N", "X", "Y", "Z", "A", "B", "C", "D", "E", "F", "S", "U", "V", "RA1", "RA2", "RA3", "RA4", "RA5", "RA6",
|
|
@@ -13,7 +61,7 @@ const DEFAULT_AXIS_IDENTIFIERS: &[&str] = &[
|
|
|
13
61
|
|
|
14
62
|
/// Main function: interpret the input program into a sanitized output table.
|
|
15
63
|
pub fn nc_to_table(
|
|
16
|
-
input:
|
|
64
|
+
input: impl Into<SourceText>,
|
|
17
65
|
initial_state: Option<&str>,
|
|
18
66
|
axis_identifiers: Option<Vec<String>>,
|
|
19
67
|
extra_axes: Option<Vec<String>>,
|
|
@@ -41,7 +89,7 @@ pub fn nc_to_table(
|
|
|
41
89
|
// Now interpret the main input using the axis_index_map from state
|
|
42
90
|
let mut output = OutputRows::collect();
|
|
43
91
|
install_flattener(&mut output, &state, flatten_tolerance)?;
|
|
44
|
-
|
|
92
|
+
interpret_source(input.into(), &mut state, &mut output)?;
|
|
45
93
|
let rows = output.finish()?;
|
|
46
94
|
|
|
47
95
|
let table = Table::from_rows(&rows, disable_forward_fill);
|
|
@@ -104,7 +152,7 @@ fn install_flattener(
|
|
|
104
152
|
#[allow(clippy::too_many_arguments)]
|
|
105
153
|
#[allow(dead_code)] // used by the python-feature bindings, not the bin
|
|
106
154
|
pub fn nc_to_row_stream(
|
|
107
|
-
input:
|
|
155
|
+
input: impl Into<SourceText>,
|
|
108
156
|
initial_state: Option<&str>,
|
|
109
157
|
axis_identifiers: Option<Vec<String>>,
|
|
110
158
|
extra_axes: Option<Vec<String>>,
|
|
@@ -127,7 +175,7 @@ pub fn nc_to_row_stream(
|
|
|
127
175
|
}
|
|
128
176
|
let mut output = OutputRows::stream(sender);
|
|
129
177
|
install_flattener(&mut output, &state, flatten_tolerance)?;
|
|
130
|
-
|
|
178
|
+
interpret_source(input.into(), &mut state, &mut output)?;
|
|
131
179
|
output.finish()?;
|
|
132
180
|
Ok(state)
|
|
133
181
|
}
|
|
@@ -145,7 +193,7 @@ pub fn nc_to_row_stream(
|
|
|
145
193
|
#[allow(dead_code)] // used by the python-feature bindings, not the bin
|
|
146
194
|
#[allow(clippy::too_many_arguments)]
|
|
147
195
|
pub fn nc_to_batch_stream(
|
|
148
|
-
input:
|
|
196
|
+
input: impl Into<SourceText>,
|
|
149
197
|
initial_state: Option<&str>,
|
|
150
198
|
axis_identifiers: Option<Vec<String>>,
|
|
151
199
|
extra_axes: Option<Vec<String>>,
|
|
@@ -183,7 +231,7 @@ pub fn nc_to_batch_stream(
|
|
|
183
231
|
/// callers.
|
|
184
232
|
#[allow(clippy::too_many_arguments)]
|
|
185
233
|
pub fn nc_to_batch_stream_with_line_numbers(
|
|
186
|
-
input:
|
|
234
|
+
input: impl Into<SourceText>,
|
|
187
235
|
initial_state: Option<&str>,
|
|
188
236
|
axis_identifiers: Option<Vec<String>>,
|
|
189
237
|
extra_axes: Option<Vec<String>>,
|
|
@@ -218,15 +266,20 @@ pub fn nc_to_batch_stream_with_line_numbers(
|
|
|
218
266
|
emit_line_no,
|
|
219
267
|
);
|
|
220
268
|
install_flattener(&mut output, &state, flatten_tolerance)?;
|
|
221
|
-
|
|
269
|
+
interpret_source(input.into(), &mut state, &mut output)?;
|
|
222
270
|
output.finish()?;
|
|
223
271
|
Ok(state)
|
|
224
272
|
}
|
|
225
273
|
|
|
226
274
|
/// Interpret a file, pushing rows into `output`.
|
|
227
275
|
fn interpret_file(input: &str, state: &mut State, output: &mut OutputRows) -> Result<(), ParsingError> {
|
|
228
|
-
|
|
229
|
-
|
|
276
|
+
interpret_source(SourceText::from(input), state, output)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/// As [`interpret_file`], sharing `source` with the state's diagnostics.
|
|
280
|
+
fn interpret_source(source: SourceText, state: &mut State, output: &mut OutputRows) -> Result<(), ParsingError> {
|
|
281
|
+
state.set_source(source.clone());
|
|
282
|
+
let input = source.as_str();
|
|
230
283
|
|
|
231
284
|
// Validate control-structure nesting first: a PEG reports an unclosed
|
|
232
285
|
// IF/WHILE at the end of the file; the line scan reports the opener.
|
|
@@ -698,6 +751,32 @@ mod tests {
|
|
|
698
751
|
.unwrap_or_else(|| panic!("column {name} missing; have {:?}", column_names(table)))
|
|
699
752
|
}
|
|
700
753
|
|
|
754
|
+
/// An owned program is shared with the state's diagnostics, not copied.
|
|
755
|
+
#[test]
|
|
756
|
+
fn owned_source_is_shared_with_diagnostics() {
|
|
757
|
+
let program = String::from("G1 X0 Y0 F100\nX10 Y=\n");
|
|
758
|
+
let ptr = program.as_ptr();
|
|
759
|
+
let source = SourceText::from(program);
|
|
760
|
+
assert_eq!(source.as_str().as_ptr(), ptr);
|
|
761
|
+
let mut state = build_state(None, None, 10000, None, false);
|
|
762
|
+
state.set_source(source.clone());
|
|
763
|
+
assert_eq!(state.get_line(2), Some("X10 Y="));
|
|
764
|
+
assert_eq!(state.get_line(2).unwrap().as_ptr(), source.as_str()[14..].as_ptr());
|
|
765
|
+
let err = nc_to_table(
|
|
766
|
+
String::from("G1 X0\nX10 Y=\n"),
|
|
767
|
+
None,
|
|
768
|
+
None,
|
|
769
|
+
None,
|
|
770
|
+
10000,
|
|
771
|
+
false,
|
|
772
|
+
None,
|
|
773
|
+
false,
|
|
774
|
+
None,
|
|
775
|
+
)
|
|
776
|
+
.expect_err("a dangling assignment does not parse");
|
|
777
|
+
assert!(err.to_string().contains("X10 Y="), "{err}");
|
|
778
|
+
}
|
|
779
|
+
|
|
701
780
|
/// `include_line_numbers` (default off) prepends a `line_no` column: one
|
|
702
781
|
/// 1-based source line per output row, repeated under a loop and
|
|
703
782
|
/// non-monotonic across a jump - matching what `nc_to_rows` yields.
|
|
@@ -1399,6 +1478,7 @@ mod tests {
|
|
|
1399
1478
|
let strs = |name: &str| -> Vec<Option<String>> {
|
|
1400
1479
|
match &table.columns.iter().find(|(n, _)| n == name).unwrap().1 {
|
|
1401
1480
|
Column::Str(v) => v.clone(),
|
|
1481
|
+
Column::Dict(v) => v.iter().map(|cell| cell.map(str::to_string)).collect(),
|
|
1402
1482
|
other => panic!("column {name} is not a str column: {other:?}"),
|
|
1403
1483
|
}
|
|
1404
1484
|
};
|