nc-gcode-interpreter 0.2.1__tar.gz → 0.2.2__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.1 → nc_gcode_interpreter-0.2.2}/CHANGELOG.md +16 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/Cargo.lock +1 -1
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/Cargo.toml +1 -1
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/PKG-INFO +1 -1
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/python/nc_gcode_interpreter/__init__.py +31 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/python/nc_gcode_interpreter/_internal.pyi +8 -1
- nc_gcode_interpreter-0.2.2/python/tests/test_batch_variables.py +205 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/src/interpreter.rs +16 -2
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/src/lib.rs +85 -16
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/src/output.rs +113 -3
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/src/state.rs +25 -8
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/.github/workflows/build-and-release.yml +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/.gitignore +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/CONTRIBUTING.md +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/Development.md +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/LICENSE +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/README.md +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/TODO.md +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/docs/sinumerik-execution-model.md +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/actual_position.csv +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/actual_position.mpf +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/arc.csv +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/arc.mpf +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/arrays.csv +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/arrays.mpf +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/axis_index_assignment.csv +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/axis_index_assignment.mpf +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/basic_math.csv +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/basic_math.mpf +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/case.csv +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/case.mpf +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/case_insensitive_variables.csv +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/case_insensitive_variables.mpf +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/custom_vars.csv +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/custom_vars.mpf +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/def_string.csv +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/def_string.mpf +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/defaults.csv +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/defaults.mpf +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/edge_cases.csv +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/edge_cases.mpf +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/flattening/flatten_demo.mpf +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/flattening/flatten_demo_flattened.csv +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/flattening/flatten_demo_raw.csv +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/for_loop.csv +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/for_loop.mpf +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/function_calls.csv +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/function_calls.mpf +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/goto.csv +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/goto.mpf +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/if_statement.csv +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/if_statement.mpf +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/increment.csv +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/increment.mpf +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/logic_operators.csv +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/logic_operators.mpf +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/loop.csv +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/loop.mpf +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/multiple_m_codes.csv +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/multiple_m_codes.mpf +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/precedence.csv +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/precedence.mpf +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/r_param.csv +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/r_param.mpf +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/repeat.csv +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/repeat.mpf +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/simple.csv +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/simple.mpf +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/spline.csv +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/spline.mpf +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/tool.csv +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/tool.mpf +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/trans.csv +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/trans.mpf +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/trans2.csv +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/trans2.mpf +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/trans_ic.csv +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/trans_ic.mpf +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/trans_reset.csv +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/trans_reset.mpf +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/trig.csv +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/trig.mpf +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/variables.csv +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/examples/variables.mpf +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/ggroups/generate_g_commands.py +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/pyproject.toml +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/python/example/minimal.py +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/python/example/streaming.py +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/python/nc_gcode_interpreter/cli.py +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/python/nc_gcode_interpreter/ggroups.json +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/python/nc_gcode_interpreter/py.typed +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/python/nc_gcode_interpreter/viz.py +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/python/tests/test_arithmetic_functions.py +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/python/tests/test_batches.py +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/python/tests/test_diagnostics.py +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/python/tests/test_expected_output.py +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/python/tests/test_field_defects.py +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/python/tests/test_flags.py +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/python/tests/test_flatten.py +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/python/tests/test_g_groups.py +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/python/tests/test_g_vocabulary.py +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/python/tests/test_jumps.py +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/python/tests/test_real_comparisons.py +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/python/tests/test_stage1.py +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/python/tests/test_streaming.py +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/python/tests/test_structured_errors.py +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/python/tests/test_unsupported.py +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/rustfmt.toml +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/src/errors.rs +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/src/flatten.rs +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/src/grammar.pest +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/src/interpret_rules.rs +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/src/line_driver.rs +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/src/main.rs +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/src/modal_groups.rs +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/src/structure_scan.rs +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/src/types.rs +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/tests/cli.rs +0 -0
- {nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/uv.lock +0 -0
|
@@ -4,6 +4,22 @@ 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.2] - 2026-07-07
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Variable-change events on the batch path: `nc_to_batches(...,
|
|
12
|
+
include_variables=True)` exposes, once exhausted, a sparse `variable_events`
|
|
13
|
+
DataFrame (`row_idx` / `name_id` / `value`) plus a `variable_names` list -
|
|
14
|
+
the batch-path twin of the per-row `variables` dict `nc_to_rows` already
|
|
15
|
+
yields. `row_idx` is the output-row index a change is seen at (a change on a
|
|
16
|
+
variable-only block is attributed to the next output row), so replaying the
|
|
17
|
+
events reconstructs the symbol table at any row. Off by default (no cost).
|
|
18
|
+
- `string_table` in the interpreter state dict: `DEF STRING` variables now
|
|
19
|
+
round-trip into `.state` / the `nc_to_dataframe` state tuple as
|
|
20
|
+
`state["string_table"]` (`dict[str, str]`), alongside the existing `axes` /
|
|
21
|
+
`symbol_table` / `translation` numeric tables (previously omitted).
|
|
22
|
+
|
|
7
23
|
## [v0.2.1] - 2026-07-07
|
|
8
24
|
|
|
9
25
|
### Added
|
{nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/python/nc_gcode_interpreter/__init__.py
RENAMED
|
@@ -498,6 +498,29 @@ class _BatchIterator:
|
|
|
498
498
|
def state(self) -> dict | None:
|
|
499
499
|
return self._inner.state
|
|
500
500
|
|
|
501
|
+
@property
|
|
502
|
+
def variable_events(self) -> "pl.DataFrame | None":
|
|
503
|
+
"""Sparse variable-change events (``include_variables=True``).
|
|
504
|
+
|
|
505
|
+
A :class:`polars.DataFrame` with columns ``row_idx`` (the output-row
|
|
506
|
+
index the change is seen at), ``name_id`` (index into
|
|
507
|
+
:attr:`variable_names`) and ``value``. ``None`` unless the iterator was
|
|
508
|
+
created with ``include_variables=True``. Available once exhausted.
|
|
509
|
+
Replaying the events in order (decoding ``name_id`` via
|
|
510
|
+
:attr:`variable_names`) reconstructs the symbol table at any row, the
|
|
511
|
+
batch-path twin of the per-row ``variables`` dict ``nc_to_rows`` yields.
|
|
512
|
+
"""
|
|
513
|
+
batch = self._inner.variable_events
|
|
514
|
+
return None if batch is None else pl.DataFrame(batch)
|
|
515
|
+
|
|
516
|
+
@property
|
|
517
|
+
def variable_names(self) -> list[str] | None:
|
|
518
|
+
"""Variable names ``variable_events.name_id`` indexes into.
|
|
519
|
+
|
|
520
|
+
``None`` unless ``include_variables=True``; available once exhausted.
|
|
521
|
+
"""
|
|
522
|
+
return self._inner.variable_names
|
|
523
|
+
|
|
501
524
|
|
|
502
525
|
def nc_to_batches(
|
|
503
526
|
input: "TextFileLike | str | os.PathLike",
|
|
@@ -511,6 +534,7 @@ def nc_to_batches(
|
|
|
511
534
|
allow_undefined_variables: bool = False,
|
|
512
535
|
flatten_tolerance: float | None = None,
|
|
513
536
|
include_line_numbers: bool = False,
|
|
537
|
+
include_variables: bool = False,
|
|
514
538
|
) -> _BatchIterator:
|
|
515
539
|
"""Interpret an NC program into a stream of columnar polars DataFrames.
|
|
516
540
|
|
|
@@ -541,6 +565,12 @@ def nc_to_batches(
|
|
|
541
565
|
a loop, non-monotonic across a jump), never forward-filled. Default False
|
|
542
566
|
leaves the schema unchanged.
|
|
543
567
|
|
|
568
|
+
With ``include_variables=True`` the iterator additionally exposes
|
|
569
|
+
``variable_events`` (a sparse :class:`polars.DataFrame` of ``row_idx`` /
|
|
570
|
+
``name_id`` / ``value``) and ``variable_names`` once exhausted — the
|
|
571
|
+
batch-path equivalent of the per-row ``variables`` dict :func:`nc_to_rows`
|
|
572
|
+
yields; replaying the events reconstructs the symbol table at any row.
|
|
573
|
+
|
|
544
574
|
Example:
|
|
545
575
|
--------
|
|
546
576
|
>>> batches = nc_to_batches("G1 X10\\nX20 Y5", batch_size=1)
|
|
@@ -564,5 +594,6 @@ def nc_to_batches(
|
|
|
564
594
|
input_is_path,
|
|
565
595
|
flatten_tolerance,
|
|
566
596
|
include_line_numbers,
|
|
597
|
+
include_variables,
|
|
567
598
|
)
|
|
568
599
|
return _BatchIterator(inner)
|
{nc_gcode_interpreter-0.2.1 → nc_gcode_interpreter-0.2.2}/python/nc_gcode_interpreter/_internal.pyi
RENAMED
|
@@ -43,8 +43,15 @@ def nc_to_batches(
|
|
|
43
43
|
input_is_path: bool = False,
|
|
44
44
|
flatten_tolerance: Optional[float] = None,
|
|
45
45
|
include_line_numbers: bool = False,
|
|
46
|
+
include_variables: bool = False,
|
|
46
47
|
) -> Any:
|
|
47
|
-
"""Interpret an NC program into an iterator of columnar polars DataFrames.
|
|
48
|
+
"""Interpret an NC program into an iterator of columnar polars DataFrames.
|
|
49
|
+
|
|
50
|
+
The returned iterator exposes ``state`` (dict with ``axes``,
|
|
51
|
+
``symbol_table``, ``translation`` and ``string_table``) once exhausted, and
|
|
52
|
+
- when ``include_variables`` is set - ``variable_events`` (an Arrow batch of
|
|
53
|
+
``row_idx`` / ``name_id`` / ``value``) and ``variable_names`` (list[str]).
|
|
54
|
+
"""
|
|
48
55
|
...
|
|
49
56
|
|
|
50
57
|
__all__ = ["nc_to_rows", "nc_to_batches"]
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"""Batch-path variable-change events (#46) + `string_table` in the state dict.
|
|
2
|
+
|
|
3
|
+
The streaming `nc_to_rows(include_variables=True)` path yields a per-row
|
|
4
|
+
`variables` dict; the batch path (`nc_to_batches` / `nc_to_dataframe`) now
|
|
5
|
+
surfaces the same information as a sparse side-table (`row_idx`, `name_id`,
|
|
6
|
+
`value`) plus a `variable_names` list. These tests pin (a) parity of the
|
|
7
|
+
ordered assignment sequence between the two paths and (b) that `DEF STRING`
|
|
8
|
+
variables round-trip into the returned state dict.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import polars as pl
|
|
12
|
+
|
|
13
|
+
from nc_gcode_interpreter import nc_to_batches, nc_to_dataframe, nc_to_rows
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _stream_sequence(program: str) -> list[tuple[str, float]]:
|
|
17
|
+
"""Flat, ordered `(name, value)` assignment sequence from the stream."""
|
|
18
|
+
return [
|
|
19
|
+
(name, value)
|
|
20
|
+
for _line, _row, variables in nc_to_rows(program, include_variables=True)
|
|
21
|
+
for name, value in variables.items()
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _batch_sequence(it) -> list[tuple[str, float]]:
|
|
26
|
+
"""Flat, ordered `(name, value)` sequence decoded from the batch events."""
|
|
27
|
+
events = it.variable_events
|
|
28
|
+
names = it.variable_names
|
|
29
|
+
return [
|
|
30
|
+
(names[name_id], value)
|
|
31
|
+
for name_id, value in zip(events["name_id"], events["value"])
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_batch_variable_events_match_stream_simple():
|
|
36
|
+
program = "\n".join(
|
|
37
|
+
[
|
|
38
|
+
"DEF REAL Q=2.5", # variable-only
|
|
39
|
+
"R1=0", # variable-only
|
|
40
|
+
"X=R1", # output row, no change
|
|
41
|
+
"X=Q", # output row, no change
|
|
42
|
+
]
|
|
43
|
+
)
|
|
44
|
+
it = nc_to_batches(program, include_variables=True)
|
|
45
|
+
list(it) # exhaust to populate variable_events / state
|
|
46
|
+
assert _batch_sequence(it) == _stream_sequence(program)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_batch_variable_events_match_stream_while_loop():
|
|
50
|
+
# A WHILE loop where R1 changes once per iteration: the per-iteration
|
|
51
|
+
# counter events must appear on the batch path exactly as they do on the
|
|
52
|
+
# stream, in the same order.
|
|
53
|
+
program = "\n".join(
|
|
54
|
+
[
|
|
55
|
+
"DEF REAL Q=2.5",
|
|
56
|
+
"R1=0",
|
|
57
|
+
"WHILE R1<3",
|
|
58
|
+
"X=R1 Q=Q*2", # output row that ALSO assigns a variable
|
|
59
|
+
"R1=R1+1", # variable-only, per iteration
|
|
60
|
+
"ENDWHILE",
|
|
61
|
+
"X=Q",
|
|
62
|
+
]
|
|
63
|
+
)
|
|
64
|
+
it = nc_to_batches(program, include_variables=True)
|
|
65
|
+
list(it)
|
|
66
|
+
assert _batch_sequence(it) == _stream_sequence(program)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_batch_variable_events_row_idx_reconstructs_symbol_table():
|
|
70
|
+
# Replaying every event reconstructs the final symbol table (minus the
|
|
71
|
+
# built-in TRUE/FALSE), mirroring the streaming accumulation invariant.
|
|
72
|
+
program = "\n".join(
|
|
73
|
+
[
|
|
74
|
+
"DEF REAL Q=1",
|
|
75
|
+
"R1=0",
|
|
76
|
+
"WHILE R1<3",
|
|
77
|
+
"X=R1 Q=Q*2",
|
|
78
|
+
"R1=R1+1",
|
|
79
|
+
"ENDWHILE",
|
|
80
|
+
]
|
|
81
|
+
)
|
|
82
|
+
it = nc_to_batches(program, include_variables=True)
|
|
83
|
+
list(it)
|
|
84
|
+
accumulated: dict[str, float] = {}
|
|
85
|
+
for name, value in _batch_sequence(it):
|
|
86
|
+
accumulated[name] = value
|
|
87
|
+
symbol_table = dict(it.state["symbol_table"])
|
|
88
|
+
for builtin in ("TRUE", "FALSE"):
|
|
89
|
+
symbol_table.pop(builtin)
|
|
90
|
+
assert accumulated == symbol_table
|
|
91
|
+
|
|
92
|
+
# row_idx is monotonic non-decreasing (events arrive in program order) and
|
|
93
|
+
# bounded by the number of output rows.
|
|
94
|
+
events = it.variable_events
|
|
95
|
+
row_idx = events["row_idx"].to_list()
|
|
96
|
+
assert row_idx == sorted(row_idx)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def test_batch_variable_events_row_idx_aligns_with_output_rows():
|
|
100
|
+
# A change on a variable-only block is attributed to the NEXT output row;
|
|
101
|
+
# a change on an output row gets that row's own index.
|
|
102
|
+
program = "\n".join(
|
|
103
|
+
[
|
|
104
|
+
"R1=0", # variable-only, before output row 0
|
|
105
|
+
"X=R1", # output row 0
|
|
106
|
+
"R1=1", # variable-only, before output row 1
|
|
107
|
+
"X=R1", # output row 1
|
|
108
|
+
]
|
|
109
|
+
)
|
|
110
|
+
it = nc_to_batches(program, include_variables=True)
|
|
111
|
+
list(it)
|
|
112
|
+
events = it.variable_events
|
|
113
|
+
names = it.variable_names
|
|
114
|
+
decoded = [
|
|
115
|
+
(row_idx, names[name_id], value)
|
|
116
|
+
for row_idx, name_id, value in zip(
|
|
117
|
+
events["row_idx"], events["name_id"], events["value"]
|
|
118
|
+
)
|
|
119
|
+
]
|
|
120
|
+
assert decoded == [(0, "R1", 0.0), (1, "R1", 1.0)]
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def test_batch_variable_events_absent_without_flag():
|
|
124
|
+
it = nc_to_batches("R1=5\nX=R1")
|
|
125
|
+
list(it)
|
|
126
|
+
assert it.variable_events is None
|
|
127
|
+
assert it.variable_names is None
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def test_batch_variable_events_empty_when_no_assignments():
|
|
131
|
+
it = nc_to_batches("G1 X10\nX20 Y5", include_variables=True)
|
|
132
|
+
list(it)
|
|
133
|
+
assert isinstance(it.variable_events, pl.DataFrame)
|
|
134
|
+
assert it.variable_events.height == 0
|
|
135
|
+
assert it.variable_names == []
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def test_string_table_round_trips_through_state_dict():
|
|
139
|
+
program = "\n".join(
|
|
140
|
+
[
|
|
141
|
+
"DEF STRING[16] MSG",
|
|
142
|
+
'MSG="HELLO WORLD"',
|
|
143
|
+
"DEF STRING[8] TAG",
|
|
144
|
+
'TAG="ABC"',
|
|
145
|
+
"G1 X1",
|
|
146
|
+
]
|
|
147
|
+
)
|
|
148
|
+
_df, state = nc_to_dataframe(program)
|
|
149
|
+
assert state["string_table"] == {"MSG": "HELLO WORLD", "TAG": "ABC"}
|
|
150
|
+
# The existing numeric sub-tables are untouched by the new key.
|
|
151
|
+
assert set(state) == {"axes", "symbol_table", "translation", "string_table"}
|
|
152
|
+
# The string variable never leaks into the numeric symbol table.
|
|
153
|
+
assert "MSG" not in state["symbol_table"]
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def test_string_table_on_streaming_and_batch_iterators():
|
|
157
|
+
program = 'DEF STRING[8] MSG\nMSG="HI"\nG1 X1'
|
|
158
|
+
rows = nc_to_rows(program)
|
|
159
|
+
list(rows)
|
|
160
|
+
assert rows.state["string_table"] == {"MSG": "HI"}
|
|
161
|
+
|
|
162
|
+
it = nc_to_batches(program)
|
|
163
|
+
list(it)
|
|
164
|
+
assert it.state["string_table"] == {"MSG": "HI"}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def test_line_numbers_and_variables_compose_on_the_batch_path():
|
|
168
|
+
# include_line_numbers (#45) and include_variables (#46) share the batch
|
|
169
|
+
# path and were merged independently; requesting both at once must give the
|
|
170
|
+
# leading `line_no` column AND the sparse variable-event side-table, with
|
|
171
|
+
# `row_idx` still aligned to the emitted (line_no-carrying) output rows.
|
|
172
|
+
program = "\n".join(
|
|
173
|
+
[
|
|
174
|
+
"R1=0", # variable-only, before output row 0
|
|
175
|
+
"WHILE R1<3",
|
|
176
|
+
"X=R1", # output row (loop body, source line 3)
|
|
177
|
+
"R1=R1+1", # variable-only
|
|
178
|
+
"ENDWHILE",
|
|
179
|
+
"X9", # output row, source line 6
|
|
180
|
+
]
|
|
181
|
+
)
|
|
182
|
+
it = nc_to_batches(
|
|
183
|
+
program,
|
|
184
|
+
batch_size=1_000_000,
|
|
185
|
+
include_line_numbers=True,
|
|
186
|
+
include_variables=True,
|
|
187
|
+
)
|
|
188
|
+
df = pl.concat(list(it), how="diagonal")
|
|
189
|
+
|
|
190
|
+
# line_no leads and tracks source lines (loop body repeats line 3).
|
|
191
|
+
assert df.columns[0] == "line_no"
|
|
192
|
+
assert df["line_no"].to_list() == [3, 3, 3, 6]
|
|
193
|
+
assert df["X"].to_list() == [0.0, 1.0, 2.0, 9.0]
|
|
194
|
+
|
|
195
|
+
# Variable events still decode independently of the line_no column.
|
|
196
|
+
names = it.variable_names
|
|
197
|
+
decoded = [
|
|
198
|
+
(row_idx, names[name_id], value)
|
|
199
|
+
for row_idx, name_id, value in zip(
|
|
200
|
+
it.variable_events["row_idx"],
|
|
201
|
+
it.variable_events["name_id"],
|
|
202
|
+
it.variable_events["value"],
|
|
203
|
+
)
|
|
204
|
+
]
|
|
205
|
+
assert decoded == [(0, "R1", 0.0), (1, "R1", 1.0), (2, "R1", 2.0), (3, "R1", 3.0)]
|
|
@@ -156,6 +156,8 @@ pub fn nc_to_batch_stream(
|
|
|
156
156
|
flatten_tolerance: Option<f64>,
|
|
157
157
|
batch_size: usize,
|
|
158
158
|
sender: std::sync::mpsc::SyncSender<Table>,
|
|
159
|
+
include_variables: bool,
|
|
160
|
+
events_sender: std::sync::mpsc::Sender<crate::output::VariableEvents>,
|
|
159
161
|
) -> Result<state::State, ParsingError> {
|
|
160
162
|
// Back-compatible entry point: never emits the opt-in line_no column.
|
|
161
163
|
nc_to_batch_stream_with_line_numbers(
|
|
@@ -171,6 +173,8 @@ pub fn nc_to_batch_stream(
|
|
|
171
173
|
batch_size,
|
|
172
174
|
false,
|
|
173
175
|
sender,
|
|
176
|
+
include_variables,
|
|
177
|
+
events_sender,
|
|
174
178
|
)
|
|
175
179
|
}
|
|
176
180
|
|
|
@@ -191,6 +195,8 @@ pub fn nc_to_batch_stream_with_line_numbers(
|
|
|
191
195
|
batch_size: usize,
|
|
192
196
|
emit_line_no: bool,
|
|
193
197
|
sender: std::sync::mpsc::SyncSender<Table>,
|
|
198
|
+
include_variables: bool,
|
|
199
|
+
events_sender: std::sync::mpsc::Sender<crate::output::VariableEvents>,
|
|
194
200
|
) -> Result<state::State, ParsingError> {
|
|
195
201
|
let mut state = build_state(
|
|
196
202
|
axis_identifiers,
|
|
@@ -203,7 +209,14 @@ pub fn nc_to_batch_stream_with_line_numbers(
|
|
|
203
209
|
let mut discard = OutputRows::collect();
|
|
204
210
|
interpret_file(initial_state, &mut state, &mut discard)?;
|
|
205
211
|
}
|
|
206
|
-
let mut output = OutputRows::batch_stream_with_line_numbers(
|
|
212
|
+
let mut output = OutputRows::batch_stream_with_line_numbers(
|
|
213
|
+
sender,
|
|
214
|
+
batch_size,
|
|
215
|
+
disable_forward_fill,
|
|
216
|
+
include_variables,
|
|
217
|
+
events_sender,
|
|
218
|
+
emit_line_no,
|
|
219
|
+
);
|
|
207
220
|
install_flattener(&mut output, &state, flatten_tolerance)?;
|
|
208
221
|
interpret_file(input, &mut state, &mut output)?;
|
|
209
222
|
output.finish()?;
|
|
@@ -584,8 +597,9 @@ mod tests {
|
|
|
584
597
|
/// emitted [`Table`] carries the whole program's `line_no` column.
|
|
585
598
|
fn interpret_with_line_numbers(input: &str) -> Table {
|
|
586
599
|
let (tx, rx) = std::sync::mpsc::sync_channel(64);
|
|
600
|
+
let (events_tx, _events_rx) = std::sync::mpsc::channel();
|
|
587
601
|
nc_to_batch_stream_with_line_numbers(
|
|
588
|
-
input, None, None, None, 10000, false, None, false, None, 1_000_000, true, tx,
|
|
602
|
+
input, None, None, None, 10000, false, None, false, None, 1_000_000, true, tx, false, events_tx,
|
|
589
603
|
)
|
|
590
604
|
.expect("program should interpret");
|
|
591
605
|
rx.into_iter().next().expect("one batch for a small program")
|
|
@@ -60,7 +60,8 @@ mod python_bindings {
|
|
|
60
60
|
|
|
61
61
|
use crate::errors::ErrorLocation;
|
|
62
62
|
use crate::interpreter::{nc_to_batch_stream_with_line_numbers, nc_to_row_stream};
|
|
63
|
-
use crate::output::{is_forward_filled_column, is_string_column, Column, Row, Table};
|
|
63
|
+
use crate::output::{is_forward_filled_column, is_string_column, Column, Row, Table, VariableEvents};
|
|
64
|
+
use crate::state::FinalState;
|
|
64
65
|
use crate::types::Value;
|
|
65
66
|
|
|
66
67
|
pyo3::create_exception!(
|
|
@@ -108,6 +109,19 @@ mod python_bindings {
|
|
|
108
109
|
}
|
|
109
110
|
}
|
|
110
111
|
|
|
112
|
+
/// Render a [`FinalState`] as the Python `.state` dict:
|
|
113
|
+
/// `{axes, symbol_table, translation, string_table}`. The three numeric
|
|
114
|
+
/// sub-tables become `dict[str, float]`; `string_table` (DEF STRING
|
|
115
|
+
/// variables) becomes `dict[str, str]`. Shared by both iterators.
|
|
116
|
+
fn final_state_to_py<'py>(py: Python<'py>, state: &FinalState) -> PyResult<Bound<'py, PyDict>> {
|
|
117
|
+
let dict = PyDict::new(py);
|
|
118
|
+
dict.set_item("axes", state.axes.clone())?;
|
|
119
|
+
dict.set_item("symbol_table", state.symbol_table.clone())?;
|
|
120
|
+
dict.set_item("translation", state.translation.clone())?;
|
|
121
|
+
dict.set_item("string_table", state.string_table.clone())?;
|
|
122
|
+
Ok(dict)
|
|
123
|
+
}
|
|
124
|
+
|
|
111
125
|
/// Build one Arrow [`RecordBatch`](arrow::record_batch::RecordBatch) directly
|
|
112
126
|
/// from an output [`Table`], in column order. Each [`Column`] becomes an Arrow
|
|
113
127
|
/// array straight from its `Vec` (a `Vec<Option<f64>>` -> Float64,
|
|
@@ -214,7 +228,7 @@ mod python_bindings {
|
|
|
214
228
|
row_sender,
|
|
215
229
|
);
|
|
216
230
|
let message = match outcome {
|
|
217
|
-
Ok(state) => Ok(state.
|
|
231
|
+
Ok(state) => Ok(state.final_state()),
|
|
218
232
|
// The consumer hung up: nothing to report to nobody.
|
|
219
233
|
Err(crate::errors::ParsingError::StreamClosed) => return,
|
|
220
234
|
Err(error) => Err(ErrInfo::from_error(&error)),
|
|
@@ -242,9 +256,11 @@ mod python_bindings {
|
|
|
242
256
|
allow_undefined_variables: bool,
|
|
243
257
|
flatten_tolerance: Option<f64>,
|
|
244
258
|
emit_line_no: bool,
|
|
259
|
+
include_variables: bool,
|
|
245
260
|
) -> PyResult<(
|
|
246
261
|
mpsc::Receiver<Table>,
|
|
247
262
|
mpsc::Receiver<Result<FinalState, ErrInfo>>,
|
|
263
|
+
mpsc::Receiver<VariableEvents>,
|
|
248
264
|
std::thread::JoinHandle<()>,
|
|
249
265
|
)> {
|
|
250
266
|
// A cap of 3 lets the worker build one batch ahead while the consumer
|
|
@@ -252,6 +268,10 @@ mod python_bindings {
|
|
|
252
268
|
// buffering.
|
|
253
269
|
let (batch_sender, batch_receiver) = mpsc::sync_channel::<Table>(3);
|
|
254
270
|
let (result_sender, result_receiver) = mpsc::sync_channel::<Result<FinalState, ErrInfo>>(1);
|
|
271
|
+
// Variable-change events are a small sparse side-table sent once at the
|
|
272
|
+
// end; an unbounded channel so the worker never blocks emitting them
|
|
273
|
+
// (they are only read after the batch stream is drained).
|
|
274
|
+
let (events_sender, events_receiver) = mpsc::channel::<VariableEvents>();
|
|
255
275
|
|
|
256
276
|
let handle = std::thread::Builder::new()
|
|
257
277
|
.name("nc-interpreter".to_string())
|
|
@@ -269,9 +289,11 @@ mod python_bindings {
|
|
|
269
289
|
batch_size,
|
|
270
290
|
emit_line_no,
|
|
271
291
|
batch_sender,
|
|
292
|
+
include_variables,
|
|
293
|
+
events_sender,
|
|
272
294
|
);
|
|
273
295
|
let message = match outcome {
|
|
274
|
-
Ok(state) => Ok(state.
|
|
296
|
+
Ok(state) => Ok(state.final_state()),
|
|
275
297
|
// The consumer hung up: nothing to report to nobody.
|
|
276
298
|
Err(crate::errors::ParsingError::StreamClosed) => return,
|
|
277
299
|
Err(error) => Err(ErrInfo::from_error(&error)),
|
|
@@ -279,11 +301,9 @@ mod python_bindings {
|
|
|
279
301
|
let _ = result_sender.send(message);
|
|
280
302
|
})
|
|
281
303
|
.map_err(|e| PyErr::new::<PyValueError, _>(format!("failed to spawn interpreter thread: {}", e)))?;
|
|
282
|
-
Ok((batch_receiver, result_receiver, handle))
|
|
304
|
+
Ok((batch_receiver, result_receiver, events_receiver, handle))
|
|
283
305
|
}
|
|
284
306
|
|
|
285
|
-
type FinalState = HashMap<String, HashMap<String, f64>>;
|
|
286
|
-
|
|
287
307
|
/// Iterator over interpreted rows: `next()` yields
|
|
288
308
|
/// `(line_no, {column: value})` while the interpreter runs on a worker
|
|
289
309
|
/// thread behind a bounded channel. Dropping the iterator hangs up the
|
|
@@ -440,11 +460,12 @@ mod python_bindings {
|
|
|
440
460
|
}
|
|
441
461
|
}
|
|
442
462
|
|
|
443
|
-
/// The final interpreter state
|
|
444
|
-
///
|
|
463
|
+
/// The final interpreter state
|
|
464
|
+
/// (`{axes, symbol_table, translation, string_table}`), available once
|
|
465
|
+
/// the iterator is exhausted.
|
|
445
466
|
#[getter]
|
|
446
|
-
fn state(&self) -> Option<
|
|
447
|
-
self.state.
|
|
467
|
+
fn state<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyDict>>> {
|
|
468
|
+
self.state.as_ref().map(|s| final_state_to_py(py, s)).transpose()
|
|
448
469
|
}
|
|
449
470
|
}
|
|
450
471
|
|
|
@@ -512,8 +533,14 @@ mod python_bindings {
|
|
|
512
533
|
struct NcBatchIterator {
|
|
513
534
|
batches: Option<Mutex<mpsc::Receiver<Table>>>,
|
|
514
535
|
result: Option<Mutex<mpsc::Receiver<Result<FinalState, ErrInfo>>>>,
|
|
536
|
+
/// Set at construction only when `include_variables`; drained at finish
|
|
537
|
+
/// into `variable_events`.
|
|
538
|
+
events: Option<Mutex<mpsc::Receiver<VariableEvents>>>,
|
|
515
539
|
handle: Option<std::thread::JoinHandle<()>>,
|
|
516
540
|
state: Option<FinalState>,
|
|
541
|
+
/// The sparse variable-change side-table, available once exhausted.
|
|
542
|
+
/// `None` unless `include_variables` was set.
|
|
543
|
+
variable_events: Option<VariableEvents>,
|
|
517
544
|
}
|
|
518
545
|
|
|
519
546
|
impl NcBatchIterator {
|
|
@@ -529,6 +556,16 @@ mod python_bindings {
|
|
|
529
556
|
Err(_) => {}
|
|
530
557
|
}
|
|
531
558
|
}
|
|
559
|
+
// The worker sends the events (once) before dropping the batch
|
|
560
|
+
// sender, so by the time the batch stream has closed they are
|
|
561
|
+
// already queued on the unbounded channel; a hung-up / disabled run
|
|
562
|
+
// yields `Err` and leaves `variable_events` as `None`.
|
|
563
|
+
if let Some(events) = self.events.take() {
|
|
564
|
+
let receiver = events.into_inner().expect("events receiver mutex poisoned");
|
|
565
|
+
if let Ok(ev) = py.detach(move || receiver.recv()) {
|
|
566
|
+
self.variable_events = Some(ev);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
532
569
|
self.join();
|
|
533
570
|
Ok(())
|
|
534
571
|
}
|
|
@@ -579,11 +616,33 @@ mod python_bindings {
|
|
|
579
616
|
}
|
|
580
617
|
}
|
|
581
618
|
|
|
582
|
-
/// The final interpreter state
|
|
583
|
-
///
|
|
619
|
+
/// The final interpreter state
|
|
620
|
+
/// (`{axes, symbol_table, translation, string_table}`), available once
|
|
621
|
+
/// the iterator is exhausted.
|
|
584
622
|
#[getter]
|
|
585
|
-
fn state(&self) -> Option<
|
|
586
|
-
self.state.
|
|
623
|
+
fn state<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyDict>>> {
|
|
624
|
+
self.state.as_ref().map(|s| final_state_to_py(py, s)).transpose()
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/// The sparse variable-change events (`include_variables=True`) as an
|
|
628
|
+
/// Arrow record batch with columns `row_idx` (Int64, the output-row
|
|
629
|
+
/// index the change is seen at), `name_id` (Int64, index into
|
|
630
|
+
/// `variable_names`) and `value` (Float64). `None` when the iterator was
|
|
631
|
+
/// created without `include_variables`. Available once exhausted.
|
|
632
|
+
/// `pl.DataFrame(it.variable_events)` wraps it zero-copy.
|
|
633
|
+
#[getter]
|
|
634
|
+
fn variable_events(&self) -> PyResult<Option<ArrowBatch>> {
|
|
635
|
+
self.variable_events
|
|
636
|
+
.as_ref()
|
|
637
|
+
.map(|ev| table_to_arrow_batch(ev.to_table()))
|
|
638
|
+
.transpose()
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/// The variable names `name_id` indexes into (in first-seen order).
|
|
642
|
+
/// `None` unless `include_variables` was set; available once exhausted.
|
|
643
|
+
#[getter]
|
|
644
|
+
fn variable_names(&self) -> Option<Vec<String>> {
|
|
645
|
+
self.variable_events.as_ref().map(|ev| ev.names.clone())
|
|
587
646
|
}
|
|
588
647
|
}
|
|
589
648
|
|
|
@@ -593,7 +652,7 @@ mod python_bindings {
|
|
|
593
652
|
/// thread. Wrapping each with `pl.DataFrame` and concatenating reconstructs
|
|
594
653
|
/// `nc_to_dataframe`.
|
|
595
654
|
#[pyfunction]
|
|
596
|
-
#[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))]
|
|
655
|
+
#[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))]
|
|
597
656
|
#[allow(clippy::too_many_arguments)]
|
|
598
657
|
fn nc_to_batches(
|
|
599
658
|
input: String,
|
|
@@ -608,6 +667,7 @@ mod python_bindings {
|
|
|
608
667
|
input_is_path: bool,
|
|
609
668
|
flatten_tolerance: Option<f64>,
|
|
610
669
|
include_line_numbers: bool,
|
|
670
|
+
include_variables: bool,
|
|
611
671
|
) -> PyResult<NcBatchIterator> {
|
|
612
672
|
if batch_size == 0 {
|
|
613
673
|
return Err(PyErr::new::<PyValueError, _>("batch_size must be greater than 0"));
|
|
@@ -619,7 +679,7 @@ mod python_bindings {
|
|
|
619
679
|
} else {
|
|
620
680
|
input
|
|
621
681
|
};
|
|
622
|
-
let (batch_receiver, result_receiver, handle) = spawn_batch_stream(
|
|
682
|
+
let (batch_receiver, result_receiver, events_receiver, handle) = spawn_batch_stream(
|
|
623
683
|
input,
|
|
624
684
|
batch_size,
|
|
625
685
|
initial_state,
|
|
@@ -631,13 +691,22 @@ mod python_bindings {
|
|
|
631
691
|
allow_undefined_variables,
|
|
632
692
|
flatten_tolerance,
|
|
633
693
|
include_line_numbers,
|
|
694
|
+
include_variables,
|
|
634
695
|
)?;
|
|
635
696
|
|
|
636
697
|
Ok(NcBatchIterator {
|
|
637
698
|
batches: Some(Mutex::new(batch_receiver)),
|
|
638
699
|
result: Some(Mutex::new(result_receiver)),
|
|
700
|
+
// Only hold the events receiver when recording; otherwise leave it
|
|
701
|
+
// `None` so `.variable_events` stays `None`.
|
|
702
|
+
events: if include_variables {
|
|
703
|
+
Some(Mutex::new(events_receiver))
|
|
704
|
+
} else {
|
|
705
|
+
None
|
|
706
|
+
},
|
|
639
707
|
handle: Some(handle),
|
|
640
708
|
state: None,
|
|
709
|
+
variable_events: None,
|
|
641
710
|
})
|
|
642
711
|
}
|
|
643
712
|
|