nc-gcode-interpreter 0.2.8__tar.gz → 0.2.9__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.9}/.github/workflows/build-and-release.yml +3 -1
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/CHANGELOG.md +25 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/Cargo.lock +1 -1
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/Cargo.toml +1 -1
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/PKG-INFO +1 -1
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/README.md +13 -12
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/src/flatten.rs +8 -7
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/src/interpreter.rs +88 -9
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/src/lib.rs +25 -22
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/src/line_driver.rs +119 -16
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/src/main.rs +1 -1
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/src/output.rs +109 -22
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/src/state.rs +23 -14
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/.github/workflows/benchmark.yml +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/.gitignore +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/CONTRIBUTING.md +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/Development.md +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/LICENSE +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/TODO.md +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/benches/fixtures.rs +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/benches/workloads.rs +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/build.rs +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/build_backend/nc_build_backend.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/docs/resumable-interpretation-design.md +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/docs/sinumerik-execution-model.md +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/actual_position.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/actual_position.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/arc.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/arc.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/arrays.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/arrays.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/axis_index_assignment.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/axis_index_assignment.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/basic_math.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/basic_math.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/case.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/case.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/case_insensitive_variables.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/case_insensitive_variables.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/custom_vars.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/custom_vars.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/def_string.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/def_string.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/defaults.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/defaults.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/edge_cases.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/edge_cases.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/flattening/flatten_demo.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/flattening/flatten_demo_flattened.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/flattening/flatten_demo_raw.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/for_loop.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/for_loop.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/function_calls.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/function_calls.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/goto.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/goto.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/if_statement.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/if_statement.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/increment.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/increment.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/logic_operators.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/logic_operators.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/loop.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/loop.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/multiple_m_codes.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/multiple_m_codes.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/precedence.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/precedence.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/r_param.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/r_param.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/repeat.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/repeat.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/rot.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/rot.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/simple.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/simple.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/spline.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/spline.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/tool.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/tool.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/trans.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/trans.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/trans2.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/trans2.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/trans_ic.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/trans_ic.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/trans_reset.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/trans_reset.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/trig.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/trig.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/variables.csv +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/examples/variables.mpf +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/ggroups/generate_g_commands.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/pyproject.toml +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/python/example/minimal.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/python/example/streaming.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/python/nc_gcode_interpreter/__init__.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/python/nc_gcode_interpreter/_internal.pyi +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/python/nc_gcode_interpreter/cli.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/python/nc_gcode_interpreter/ggroups.json +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/python/nc_gcode_interpreter/py.typed +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/python/nc_gcode_interpreter/viz.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/python/tests/nc_source.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/python/tests/test_arithmetic_functions.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/python/tests/test_batch_variables.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/python/tests/test_batches.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/python/tests/test_compare_benchmarks.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/python/tests/test_diagnostics.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/python/tests/test_expected_output.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/python/tests/test_field_defects.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/python/tests/test_flags.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/python/tests/test_flatten.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/python/tests/test_frames.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/python/tests/test_g_groups.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/python/tests/test_g_vocabulary.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/python/tests/test_jumps.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/python/tests/test_real_comparisons.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/python/tests/test_stage1.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/python/tests/test_streaming.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/python/tests/test_string_operations.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/python/tests/test_structured_errors.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/python/tests/test_unsupported.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/rustfmt.toml +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/scripts/benchmark.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/scripts/compare_benchmarks.py +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/src/errors.rs +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/src/frame.rs +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/src/grammar.pest +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/src/interpret_rules.rs +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/src/line_driver/experiments.rs +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/src/modal_groups.rs +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/src/structure_scan.rs +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/src/types.rs +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/src/vm.rs +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/tests/cli.rs +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/tests/performance_contract.rs +0 -0
- {nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/uv.lock +0 -0
{nc_gcode_interpreter-0.2.8 → nc_gcode_interpreter-0.2.9}/.github/workflows/build-and-release.yml
RENAMED
|
@@ -156,7 +156,9 @@ jobs:
|
|
|
156
156
|
uses: PyO3/maturin-action@v1
|
|
157
157
|
with:
|
|
158
158
|
target: ${{ matrix.platform.target }}
|
|
159
|
-
|
|
159
|
+
# Named interpreters: --find-interpreter also picks up the image's PyPy,
|
|
160
|
+
# which pyo3-ffi 0.29.0 fails to compile for.
|
|
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
|
|
@@ -4,6 +4,31 @@ 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.9] - 2026-11-01
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- `nc_to_batches` and `nc_to_rows` borrow the Python str's UTF-8 buffer for
|
|
12
|
+
the run instead of copying it twice (once into a Rust `String`, once more
|
|
13
|
+
for error messages). On a 5M-line, 322 MB program the peak footprint of
|
|
14
|
+
`nc_to_batches` drops from 1.28 to 0.72 GiB. The Rust entry points
|
|
15
|
+
(`nc_to_table`, `nc_to_row_stream`, `nc_to_batch_stream*`) take
|
|
16
|
+
`impl Into<SourceText>`: a `&str` still works and is copied once, an owned
|
|
17
|
+
`String` or `Arc<str>` is shared without a copy (#89).
|
|
18
|
+
|
|
19
|
+
- Every jump-free program now runs on the streaming line driver, not only
|
|
20
|
+
those of 64 MiB or more, and the streaming driver decodes ahead on a second
|
|
21
|
+
thread instead of decoding inline. On a 1M-line, 63 MB program
|
|
22
|
+
`nc_to_batches` peaks at 0.21 GiB instead of 0.60 GiB and takes 1.02 s
|
|
23
|
+
instead of 1.24 s; at 5M lines it takes 5.1 s instead of 6.3 s. Programs
|
|
24
|
+
with jumps keep the indexed driver; `NC_STREAM_MIN_BYTES` still sets a size
|
|
25
|
+
threshold (#89).
|
|
26
|
+
|
|
27
|
+
- Less work per output row on the interpreter thread: the batch builder moves
|
|
28
|
+
string cells out of the rows it recycles instead of cloning them, and the
|
|
29
|
+
per-row column set and the flattener's position maps hash with FxHash. At
|
|
30
|
+
5M lines `nc_to_batches` takes 4.4 s instead of 5.2 s (#89).
|
|
31
|
+
|
|
7
32
|
## [v0.2.8] - 2026-10-15
|
|
8
33
|
|
|
9
34
|
### Added
|
|
@@ -161,18 +161,19 @@ 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`.
|
|
@@ -69,6 +69,7 @@
|
|
|
69
69
|
use crate::errors::ParsingError;
|
|
70
70
|
use crate::output::{intern_column, CellMap, Row, FLATTENED_COLUMN};
|
|
71
71
|
use crate::state::emit_warning;
|
|
72
|
+
use crate::state::FxMap;
|
|
72
73
|
use crate::types::Value;
|
|
73
74
|
use std::collections::HashMap;
|
|
74
75
|
|
|
@@ -136,7 +137,7 @@ pub struct Flattener {
|
|
|
136
137
|
/// non-geometric value columns), interned.
|
|
137
138
|
geometric_axes: Vec<&'static str>,
|
|
138
139
|
/// Last known machine coordinate per geometric axis column.
|
|
139
|
-
positions:
|
|
140
|
+
positions: FxMap<&'static str, 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: FxMap<&'static str, 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
|
|
@@ -179,11 +180,11 @@ impl Flattener {
|
|
|
179
180
|
Ok(Flattener {
|
|
180
181
|
tolerance,
|
|
181
182
|
geometric_axes,
|
|
182
|
-
positions:
|
|
183
|
+
positions: FxMap::default(),
|
|
183
184
|
motion: Motion::Linear,
|
|
184
185
|
plane: G17,
|
|
185
186
|
spline_buffer: Vec::new(),
|
|
186
|
-
spline_start:
|
|
187
|
+
spline_start: FxMap::default(),
|
|
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
|
|
@@ -659,7 +660,7 @@ fn points_value(points: &[Vec<f64>], point_rows: &[Option<usize>], item_index: u
|
|
|
659
660
|
}
|
|
660
661
|
|
|
661
662
|
fn row_motion(row: &Row) -> Option<Motion> {
|
|
662
|
-
match row.cells.get(
|
|
663
|
+
match row.cells.get("gg01_motion") {
|
|
663
664
|
Some(Value::Str(m)) => Some(match m.as_str() {
|
|
664
665
|
"G0" | "G1" => Motion::Linear,
|
|
665
666
|
"G2" => Motion::Arc { cw: true },
|
|
@@ -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.
|
|
@@ -22,6 +22,7 @@ pub use line_driver::experiments;
|
|
|
22
22
|
mod python_bindings {
|
|
23
23
|
use pyo3::exceptions::PyValueError;
|
|
24
24
|
use pyo3::prelude::*;
|
|
25
|
+
use pyo3::pybacked::PyBackedStr;
|
|
25
26
|
use pyo3::types::PyCapsule;
|
|
26
27
|
use pyo3::types::{PyDict, PyList};
|
|
27
28
|
use pyo3::wrap_pyfunction;
|
|
@@ -64,7 +65,7 @@ mod python_bindings {
|
|
|
64
65
|
use std::sync::{mpsc, Mutex};
|
|
65
66
|
|
|
66
67
|
use crate::errors::ErrorLocation;
|
|
67
|
-
use crate::interpreter::{nc_to_batch_stream_with_line_numbers, nc_to_row_stream};
|
|
68
|
+
use crate::interpreter::{nc_to_batch_stream_with_line_numbers, nc_to_row_stream, SourceText};
|
|
68
69
|
use crate::output::{is_forward_filled_column, is_string_column, Column, Row, Table, VariableEvents};
|
|
69
70
|
use crate::state::FinalState;
|
|
70
71
|
use crate::types::Value;
|
|
@@ -206,11 +207,23 @@ mod python_bindings {
|
|
|
206
207
|
})
|
|
207
208
|
}
|
|
208
209
|
|
|
210
|
+
/// The program for a run: the file at `input` when `input_is_path`, else
|
|
211
|
+
/// the Python str itself, shared without a copy.
|
|
212
|
+
fn source_text(input: PyBackedStr, input_is_path: bool) -> PyResult<SourceText> {
|
|
213
|
+
if input_is_path {
|
|
214
|
+
let text = std::fs::read_to_string(&*input)
|
|
215
|
+
.map_err(|e| PyErr::new::<PyValueError, _>(format!("Error reading input file '{}': {}", input, e)))?;
|
|
216
|
+
Ok(SourceText::from(text))
|
|
217
|
+
} else {
|
|
218
|
+
Ok(SourceText::new(input))
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
209
222
|
/// Spawn the interpreter on a worker thread, pushing finished rows into a
|
|
210
223
|
/// bounded channel. Shared by the row and batch streaming iterators.
|
|
211
224
|
#[allow(clippy::too_many_arguments)]
|
|
212
225
|
fn spawn_stream(
|
|
213
|
-
input:
|
|
226
|
+
input: SourceText,
|
|
214
227
|
initial_state: Option<String>,
|
|
215
228
|
axis_identifiers: Option<Vec<String>>,
|
|
216
229
|
extra_axes: Option<Vec<String>>,
|
|
@@ -230,7 +243,7 @@ mod python_bindings {
|
|
|
230
243
|
.name("nc-interpreter".to_string())
|
|
231
244
|
.spawn(move || {
|
|
232
245
|
let outcome = nc_to_row_stream(
|
|
233
|
-
|
|
246
|
+
input,
|
|
234
247
|
initial_state.as_deref(),
|
|
235
248
|
axis_identifiers,
|
|
236
249
|
extra_axes,
|
|
@@ -258,7 +271,7 @@ mod python_bindings {
|
|
|
258
271
|
/// another is consumed). Used by `nc_to_batches`.
|
|
259
272
|
#[allow(clippy::too_many_arguments)]
|
|
260
273
|
fn spawn_batch_stream(
|
|
261
|
-
input:
|
|
274
|
+
input: SourceText,
|
|
262
275
|
batch_size: usize,
|
|
263
276
|
initial_state: Option<String>,
|
|
264
277
|
axis_identifiers: Option<Vec<String>>,
|
|
@@ -290,7 +303,7 @@ mod python_bindings {
|
|
|
290
303
|
.name("nc-interpreter".to_string())
|
|
291
304
|
.spawn(move || {
|
|
292
305
|
let outcome = nc_to_batch_stream_with_line_numbers(
|
|
293
|
-
|
|
306
|
+
input,
|
|
294
307
|
initial_state.as_deref(),
|
|
295
308
|
axis_identifiers,
|
|
296
309
|
extra_axes,
|
|
@@ -491,7 +504,7 @@ mod python_bindings {
|
|
|
491
504
|
#[pyo3(signature = (input, initial_state = None, axis_identifiers = None, extra_axes = None, iteration_limit = 10000, forward_fill = true, include_variables = false, axis_index_map = None, allow_undefined_variables = false, input_is_path = false, flatten_tolerance = None))]
|
|
492
505
|
#[allow(clippy::too_many_arguments)]
|
|
493
506
|
fn nc_to_rows(
|
|
494
|
-
input:
|
|
507
|
+
input: PyBackedStr,
|
|
495
508
|
initial_state: Option<String>,
|
|
496
509
|
axis_identifiers: Option<Vec<String>>,
|
|
497
510
|
extra_axes: Option<Vec<String>>,
|
|
@@ -504,15 +517,10 @@ mod python_bindings {
|
|
|
504
517
|
flatten_tolerance: Option<f64>,
|
|
505
518
|
) -> PyResult<NcRowIterator> {
|
|
506
519
|
// When `input_is_path` is set, `input` is a filesystem path: read the
|
|
507
|
-
// program here
|
|
508
|
-
//
|
|
509
|
-
//
|
|
510
|
-
let input =
|
|
511
|
-
std::fs::read_to_string(&input)
|
|
512
|
-
.map_err(|e| PyErr::new::<PyValueError, _>(format!("Error reading input file '{}': {}", input, e)))?
|
|
513
|
-
} else {
|
|
514
|
-
input
|
|
515
|
-
};
|
|
520
|
+
// program here, before the worker spawns, so a missing file surfaces
|
|
521
|
+
// as an immediate, clean error. Otherwise the worker borrows the
|
|
522
|
+
// Python str's UTF-8 buffer for the run instead of copying it.
|
|
523
|
+
let input = source_text(input, input_is_path)?;
|
|
516
524
|
let (row_receiver, result_receiver, handle) = spawn_stream(
|
|
517
525
|
input,
|
|
518
526
|
initial_state,
|
|
@@ -668,7 +676,7 @@ mod python_bindings {
|
|
|
668
676
|
#[pyo3(signature = (input, batch_size = 500_000, initial_state = None, axis_identifiers = None, extra_axes = None, iteration_limit = 10000, disable_forward_fill = false, axis_index_map = None, allow_undefined_variables = false, input_is_path = false, flatten_tolerance = None, include_line_numbers = false, include_variables = false))]
|
|
669
677
|
#[allow(clippy::too_many_arguments)]
|
|
670
678
|
fn nc_to_batches(
|
|
671
|
-
input:
|
|
679
|
+
input: PyBackedStr,
|
|
672
680
|
batch_size: usize,
|
|
673
681
|
initial_state: Option<String>,
|
|
674
682
|
axis_identifiers: Option<Vec<String>>,
|
|
@@ -686,12 +694,7 @@ mod python_bindings {
|
|
|
686
694
|
return Err(PyErr::new::<PyValueError, _>("batch_size must be greater than 0"));
|
|
687
695
|
}
|
|
688
696
|
// Read the program here when given a path (see nc_to_rows).
|
|
689
|
-
let input =
|
|
690
|
-
std::fs::read_to_string(&input)
|
|
691
|
-
.map_err(|e| PyErr::new::<PyValueError, _>(format!("Error reading input file '{}': {}", input, e)))?
|
|
692
|
-
} else {
|
|
693
|
-
input
|
|
694
|
-
};
|
|
697
|
+
let input = source_text(input, input_is_path)?;
|
|
695
698
|
let (batch_receiver, result_receiver, events_receiver, handle) = spawn_batch_stream(
|
|
696
699
|
input,
|
|
697
700
|
batch_size,
|
|
@@ -43,10 +43,16 @@ pub mod experiments;
|
|
|
43
43
|
/// of the test suite, most hand-written files) stay single-threaded.
|
|
44
44
|
const PARALLEL_MIN_BYTES: usize = 4 << 20; // 4 MiB
|
|
45
45
|
|
|
46
|
-
/// Below this many bytes the indexed driver runs jump-free programs
|
|
47
|
-
///
|
|
48
|
-
///
|
|
49
|
-
|
|
46
|
+
/// Below this many bytes the indexed driver runs jump-free programs. The
|
|
47
|
+
/// streaming driver decodes on a second thread, so it is as fast from about
|
|
48
|
+
/// 1 MiB up and within 0.3 ms below, and it retains no per-line state: every
|
|
49
|
+
/// jump-free program streams. NC_STREAM_MIN_BYTES overrides it (a huge value
|
|
50
|
+
/// pins the indexed driver).
|
|
51
|
+
const STREAMING_MIN_BYTES: usize = 0;
|
|
52
|
+
|
|
53
|
+
/// Below this many bytes the streaming driver's preflight and decode stay on
|
|
54
|
+
/// the calling thread; the thread hand-off costs more than it saves there.
|
|
55
|
+
const STREAM_PARALLEL_MIN_BYTES: usize = 64 << 10; // 64 KiB
|
|
50
56
|
|
|
51
57
|
type Output = crate::output::OutputRows;
|
|
52
58
|
|
|
@@ -266,7 +272,7 @@ pub fn interpret_lines(input: &str, output: &mut Output, state: &mut State) -> R
|
|
|
266
272
|
/// holds a jump decides, exactly as a sequential scan would.
|
|
267
273
|
fn preflight_streaming(input: &str, state: &State) -> Result<bool, ParsingError> {
|
|
268
274
|
let threads = rayon::current_num_threads().max(1);
|
|
269
|
-
let parts = if input.len() <
|
|
275
|
+
let parts = if input.len() < STREAM_PARALLEL_MIN_BYTES || threads <= 1 {
|
|
270
276
|
1
|
|
271
277
|
} else {
|
|
272
278
|
threads * 8
|
|
@@ -380,23 +386,88 @@ fn chunk_at_newlines(input: &str, parts: usize) -> Vec<Range<usize>> {
|
|
|
380
386
|
ranges
|
|
381
387
|
}
|
|
382
388
|
|
|
383
|
-
/// O(
|
|
389
|
+
/// O(chunk) decoding/parse storage, independent of the number of lines.
|
|
384
390
|
/// Input ownership, diagnostic line offsets and the selected output sink are
|
|
385
391
|
/// separate: collecting output still necessarily grows with the program.
|
|
386
392
|
fn interpret_streaming(input: &str, output: &mut Output, state: &mut State) -> Result<BlockFlow, ParsingError> {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
393
|
+
if input.len() < STREAM_PARALLEL_MIN_BYTES || rayon::current_num_threads() <= 1 {
|
|
394
|
+
let mut arena = Vec::new();
|
|
395
|
+
for (index, line) in input.lines().enumerate() {
|
|
396
|
+
arena.clear();
|
|
397
|
+
let decoded = decode_line(line, index + 1, &mut arena);
|
|
398
|
+
let flow = execute_streamed(&decoded, line, index, &arena, output, state)?;
|
|
399
|
+
if !matches!(flow, BlockFlow::Continue) {
|
|
400
|
+
return Ok(flow);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return Ok(BlockFlow::Continue);
|
|
404
|
+
}
|
|
405
|
+
// Decode on a second thread, a few chunks ahead, so execution does not pay
|
|
406
|
+
// for the decode the indexed driver runs in parallel.
|
|
407
|
+
std::thread::scope(|scope| {
|
|
408
|
+
let (sender, receiver) = std::sync::mpsc::sync_channel::<DecodedChunk>(2);
|
|
409
|
+
scope.spawn(move || decode_chunks(input, sender));
|
|
410
|
+
// Returning early drops `receiver`, which stops the decode thread.
|
|
411
|
+
for chunk in receiver {
|
|
412
|
+
for (offset, (decoded, line)) in chunk.results.iter().zip(&chunk.lines).enumerate() {
|
|
413
|
+
let flow = execute_streamed(decoded, line, chunk.first_line + offset, &chunk.arena, output, state)?;
|
|
414
|
+
if !matches!(flow, BlockFlow::Continue) {
|
|
415
|
+
return Ok(flow);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
Ok(BlockFlow::Continue)
|
|
420
|
+
})
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/// Lines per chunk the streaming decode thread hands to execution.
|
|
424
|
+
const STREAM_CHUNK_LINES: usize = 4096;
|
|
425
|
+
|
|
426
|
+
/// A run of consecutive decoded lines; `first_line` is the 0-based index of
|
|
427
|
+
/// the first one, and `results` index into `arena` like the indexed driver's.
|
|
428
|
+
struct DecodedChunk<'a> {
|
|
429
|
+
first_line: usize,
|
|
430
|
+
lines: Vec<&'a str>,
|
|
431
|
+
results: Vec<DecodeResult<'a>>,
|
|
432
|
+
arena: Vec<Word<'a>>,
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
fn decode_chunks<'a>(input: &'a str, sender: std::sync::mpsc::SyncSender<DecodedChunk<'a>>) {
|
|
436
|
+
let mut lines = input.lines().peekable();
|
|
437
|
+
let mut first_line = 0;
|
|
438
|
+
while lines.peek().is_some() {
|
|
439
|
+
let mut chunk = DecodedChunk {
|
|
440
|
+
first_line,
|
|
441
|
+
lines: lines.by_ref().take(STREAM_CHUNK_LINES).collect(),
|
|
442
|
+
results: Vec::with_capacity(STREAM_CHUNK_LINES),
|
|
443
|
+
arena: Vec::with_capacity(STREAM_CHUNK_LINES * 4),
|
|
394
444
|
};
|
|
395
|
-
|
|
396
|
-
|
|
445
|
+
for (offset, line) in chunk.lines.iter().enumerate() {
|
|
446
|
+
chunk
|
|
447
|
+
.results
|
|
448
|
+
.push(decode_line(line, first_line + offset + 1, &mut chunk.arena));
|
|
449
|
+
}
|
|
450
|
+
first_line += chunk.lines.len();
|
|
451
|
+
if sender.send(chunk).is_err() {
|
|
452
|
+
return;
|
|
397
453
|
}
|
|
398
454
|
}
|
|
399
|
-
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/// Execute one streamed line (0-based `index`) from its decode result.
|
|
458
|
+
fn execute_streamed(
|
|
459
|
+
decoded: &DecodeResult,
|
|
460
|
+
line: &str,
|
|
461
|
+
index: usize,
|
|
462
|
+
arena: &[Word],
|
|
463
|
+
output: &mut Output,
|
|
464
|
+
state: &mut State,
|
|
465
|
+
) -> Result<BlockFlow, ParsingError> {
|
|
466
|
+
match decoded {
|
|
467
|
+
DecodeResult::Blank => Ok(BlockFlow::Continue),
|
|
468
|
+
DecodeResult::Trivial(decoded, _) => execute_decoded(decoded, arena, output, state),
|
|
469
|
+
DecodeResult::NeedsGrammar => interpret_block(parse_single_line(line, index, state)?, output, state),
|
|
470
|
+
}
|
|
400
471
|
}
|
|
401
472
|
|
|
402
473
|
fn parse_single_line<'i>(line: &'i str, offset: usize, state: &State) -> Result<Pair<'i, Rule>, ParsingError> {
|
|
@@ -1644,6 +1715,38 @@ M30
|
|
|
1644
1715
|
assert!(rx.try_recv().is_err(), "syntax failure must not emit earlier rows");
|
|
1645
1716
|
}
|
|
1646
1717
|
|
|
1718
|
+
/// Programs past STREAM_PARALLEL_MIN_BYTES and several decode chunks long
|
|
1719
|
+
/// take the pipelined decode; output, M30 and late runtime errors agree.
|
|
1720
|
+
#[test]
|
|
1721
|
+
fn pipelined_streaming_matches_full_parse() {
|
|
1722
|
+
use std::fmt::Write;
|
|
1723
|
+
let mut body = String::from("MULTIPLIER=2\nR1=0\n");
|
|
1724
|
+
for i in 0..3 * super::STREAM_CHUNK_LINES {
|
|
1725
|
+
if i % 997 == 0 {
|
|
1726
|
+
body.push_str("X=SIN(30) R1=R1+1\n");
|
|
1727
|
+
} else {
|
|
1728
|
+
writeln!(body, "N{i} G1 X{}.5 Y{} E=IC(MULTIPLIER*0.01) ; c", i % 100, i % 7).unwrap();
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
assert!(body.len() > super::STREAM_PARALLEL_MIN_BYTES);
|
|
1732
|
+
let mid = body.len() / 2 + body[body.len() / 2..].find('\n').unwrap() + 1;
|
|
1733
|
+
for program in [
|
|
1734
|
+
body.clone(),
|
|
1735
|
+
format!("{}M30\n{}", &body[..mid], &body[mid..]),
|
|
1736
|
+
format!("{}X=SIN(MISSING)\n{}", &body[..mid], &body[mid..]),
|
|
1737
|
+
] {
|
|
1738
|
+
assert_paths_agree(&program, false);
|
|
1739
|
+
}
|
|
1740
|
+
// The shared error check compares only the line text; pin the number.
|
|
1741
|
+
let program = format!("{}X=SIN(MISSING)\n{}", &body[..mid], &body[mid..]);
|
|
1742
|
+
let _guard = env_lock();
|
|
1743
|
+
std::env::set_var("NC_STREAM_MIN_BYTES", "0");
|
|
1744
|
+
let error = run(&program, true, false).unwrap_err();
|
|
1745
|
+
std::env::remove_var("NC_STREAM_MIN_BYTES");
|
|
1746
|
+
let expected = body[..mid].lines().count() + 1;
|
|
1747
|
+
assert_eq!(error.location().map(|l| l.line), Some(expected), "{error}");
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1647
1750
|
#[test]
|
|
1648
1751
|
fn preflight_chunks_agree_with_a_single_chunk() {
|
|
1649
1752
|
let mut state = crate::state::State::new(vec!["X".into()], 100, None, false);
|