nc-gcode-interpreter 0.2.4__tar.gz → 0.2.6__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.4 → nc_gcode_interpreter-0.2.6}/CHANGELOG.md +44 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/Cargo.lock +1 -1
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/Cargo.toml +1 -1
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/PKG-INFO +1 -1
- nc_gcode_interpreter-0.2.6/python/tests/test_string_operations.py +79 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/src/grammar.pest +27 -7
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/src/interpret_rules.rs +602 -11
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/src/interpreter.rs +241 -1
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/src/output.rs +83 -13
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/tests/cli.rs +42 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/.github/workflows/build-and-release.yml +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/.gitignore +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/CONTRIBUTING.md +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/Development.md +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/LICENSE +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/README.md +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/TODO.md +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/docs/resumable-interpretation-design.md +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/docs/sinumerik-execution-model.md +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/actual_position.csv +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/actual_position.mpf +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/arc.csv +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/arc.mpf +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/arrays.csv +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/arrays.mpf +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/axis_index_assignment.csv +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/axis_index_assignment.mpf +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/basic_math.csv +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/basic_math.mpf +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/case.csv +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/case.mpf +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/case_insensitive_variables.csv +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/case_insensitive_variables.mpf +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/custom_vars.csv +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/custom_vars.mpf +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/def_string.csv +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/def_string.mpf +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/defaults.csv +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/defaults.mpf +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/edge_cases.csv +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/edge_cases.mpf +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/flattening/flatten_demo.mpf +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/flattening/flatten_demo_flattened.csv +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/flattening/flatten_demo_raw.csv +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/for_loop.csv +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/for_loop.mpf +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/function_calls.csv +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/function_calls.mpf +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/goto.csv +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/goto.mpf +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/if_statement.csv +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/if_statement.mpf +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/increment.csv +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/increment.mpf +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/logic_operators.csv +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/logic_operators.mpf +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/loop.csv +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/loop.mpf +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/multiple_m_codes.csv +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/multiple_m_codes.mpf +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/precedence.csv +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/precedence.mpf +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/r_param.csv +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/r_param.mpf +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/repeat.csv +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/repeat.mpf +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/simple.csv +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/simple.mpf +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/spline.csv +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/spline.mpf +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/tool.csv +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/tool.mpf +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/trans.csv +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/trans.mpf +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/trans2.csv +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/trans2.mpf +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/trans_ic.csv +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/trans_ic.mpf +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/trans_reset.csv +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/trans_reset.mpf +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/trig.csv +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/trig.mpf +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/variables.csv +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/examples/variables.mpf +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/ggroups/generate_g_commands.py +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/pyproject.toml +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/python/example/minimal.py +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/python/example/streaming.py +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/python/nc_gcode_interpreter/__init__.py +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/python/nc_gcode_interpreter/_internal.pyi +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/python/nc_gcode_interpreter/cli.py +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/python/nc_gcode_interpreter/ggroups.json +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/python/nc_gcode_interpreter/py.typed +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/python/nc_gcode_interpreter/viz.py +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/python/tests/test_arithmetic_functions.py +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/python/tests/test_batch_variables.py +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/python/tests/test_batches.py +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/python/tests/test_diagnostics.py +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/python/tests/test_expected_output.py +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/python/tests/test_field_defects.py +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/python/tests/test_flags.py +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/python/tests/test_flatten.py +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/python/tests/test_g_groups.py +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/python/tests/test_g_vocabulary.py +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/python/tests/test_jumps.py +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/python/tests/test_real_comparisons.py +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/python/tests/test_stage1.py +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/python/tests/test_streaming.py +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/python/tests/test_structured_errors.py +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/python/tests/test_unsupported.py +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/rustfmt.toml +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/src/errors.rs +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/src/flatten.rs +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/src/lib.rs +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/src/line_driver.rs +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/src/main.rs +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/src/modal_groups.rs +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/src/state.rs +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/src/structure_scan.rs +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/src/types.rs +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/src/vm.rs +0 -0
- {nc_gcode_interpreter-0.2.4 → nc_gcode_interpreter-0.2.6}/uv.lock +0 -0
|
@@ -4,6 +4,50 @@ 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.6] - 2026-07-09
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- String operations (manual 4.1.4), so real CAM programs that build a
|
|
12
|
+
timestamped protocol-file name now parse and run end to end:
|
|
13
|
+
- Single-character writes into a STRING variable, `STRING[<index>] = "<char>"`
|
|
14
|
+
(manual 4.1.4.8), 0-based; only user-defined variables, never system
|
|
15
|
+
variables. The right-hand side must be exactly one character and the index
|
|
16
|
+
in range, both enforced loudly.
|
|
17
|
+
- The `<<` concatenation operator, joining quoted strings, STRING variables,
|
|
18
|
+
string functions and numbers (INT in plain form, REAL with up to 10 decimals
|
|
19
|
+
and trailing zeros trimmed, per manual 4.1.4.1).
|
|
20
|
+
- String functions: `SPRINT` (printf-style: `%d %f %s %x %b %c` with field
|
|
21
|
+
width and precision; unsupported conversions error loudly), `SUBSTR`,
|
|
22
|
+
`INDEX`, `RINDEX`, `NUMBER`, `STRLEN`, `ISNUMBER`. All string indices are
|
|
23
|
+
0-based; the search family returns `-1` when not found; `NUMBER` on a
|
|
24
|
+
non-numeric string is a hard error.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- A quoted string that is only whitespace (e.g. `" "`) no longer loses its
|
|
29
|
+
content: the implicit `WHITESPACE` rule used to eat it, so `INDEX(x, " ")`
|
|
30
|
+
found nothing — exactly the space that date-formatting code searches for.
|
|
31
|
+
Quoted-string bodies are now taken verbatim (leading/trailing spaces too).
|
|
32
|
+
- Declaring a variable whose name collides with a reserved axis letter *with*
|
|
33
|
+
an initializer (e.g. `DEF STRING[13] S = "..."`) now reports the name
|
|
34
|
+
collision ("conflicts with an axis name") instead of a confusing downstream
|
|
35
|
+
"cannot assign a string" error.
|
|
36
|
+
|
|
37
|
+
## [v0.2.5] - 2026-07-08
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
|
|
41
|
+
- Interpreter throughput on the DataFrame/batch path: the per-output-row `Row`
|
|
42
|
+
allocations are now recycled instead of freed. Profiling the 1.1 GB → DataFrame
|
|
43
|
+
conversion showed ~50% of CPU in the system allocator, dominated by allocating
|
|
44
|
+
and freeing each of the 22M rows' cell buffers. After a batch is built its rows
|
|
45
|
+
are cleared (capacity retained) into a pool and handed back to the interpreter
|
|
46
|
+
to refill, bounding live row allocations to ~2× the batch size instead of the
|
|
47
|
+
whole-file row count. Output is byte-identical; the streaming (`nc_to_rows`)
|
|
48
|
+
and in-memory collect paths are unchanged. ~10% off end-to-end on the large
|
|
49
|
+
real-world program (#61).
|
|
50
|
+
|
|
7
51
|
## [v0.2.4] - 2026-07-08
|
|
8
52
|
|
|
9
53
|
### Changed
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""String operations from NC programming manual 4.1.4: single-character
|
|
2
|
+
writes, `<<` concatenation, and the SPRINT/SUBSTR/INDEX/NUMBER family."""
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
from nc_gcode_interpreter import nc_to_dataframe
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_single_character_write_replaces_one_character():
|
|
9
|
+
"""STRING[<index>] = "<char>" overwrites one character in place (manual
|
|
10
|
+
4.1.4.8); index 5 is the space in "AXIS N HERE" (0-based)."""
|
|
11
|
+
_df, state = nc_to_dataframe('DEF STRING[13] MSG = "AXIS N HERE"\nMSG[5] = "X"\n')
|
|
12
|
+
assert state["string_table"]["MSG"] == "AXIS X HERE"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_date_formatting_idiom_zero_pads_with_index_and_char_write():
|
|
16
|
+
"""The real CAM idiom that motivated this feature: SPRINT a date (its
|
|
17
|
+
`%2d` fields space-pad single digits), then replace every space with "0"
|
|
18
|
+
via a WHILE/INDEX/char-write loop."""
|
|
19
|
+
program = (
|
|
20
|
+
"DEF STRING[13] DT\n"
|
|
21
|
+
'DT = SPRINT("20%2d%2d%2dT%2d%2d", 24, 5, 29, 13, 31)\n'
|
|
22
|
+
'WHILE (INDEX(DT, " ") > 0)\n'
|
|
23
|
+
' DT[INDEX(DT, " ")] = "0"\n'
|
|
24
|
+
"ENDWHILE\n"
|
|
25
|
+
)
|
|
26
|
+
_df, state = nc_to_dataframe(program)
|
|
27
|
+
assert state["string_table"]["DT"] == "20240529T1331"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_concat_operator_joins_strings_and_formats_numbers():
|
|
31
|
+
"""`<<` joins quoted strings and STRING variables; an INT converts to
|
|
32
|
+
plain form and a REAL keeps up to 10 decimals with trailing zeros trimmed
|
|
33
|
+
(manual 4.1.4.1) - distinct from SPRINT `%F`'s fixed six decimals."""
|
|
34
|
+
program = (
|
|
35
|
+
"DEF STRING[13] DT = \"20240529T1331\"\n"
|
|
36
|
+
"DEF STRING[100] WF\n"
|
|
37
|
+
'WF = "//NC:/DIR/CAL_" << DT << ".TXT"\n'
|
|
38
|
+
"DEF INT IDX = 2\n"
|
|
39
|
+
"DEF REAL VAL = 9.654\n"
|
|
40
|
+
"DEF STRING[50] MSGS\n"
|
|
41
|
+
'MSGS = "i:" << IDX << "/v:" << VAL\n'
|
|
42
|
+
)
|
|
43
|
+
_df, state = nc_to_dataframe(program)
|
|
44
|
+
assert state["string_table"]["WF"] == "//NC:/DIR/CAL_20240529T1331.TXT"
|
|
45
|
+
assert state["string_table"]["MSGS"] == "i:2/v:9.654"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@pytest.mark.parametrize(
|
|
49
|
+
"expression, expected",
|
|
50
|
+
[
|
|
51
|
+
('NUMBER(SUBSTR("20240529T1331", 2, 2))', 24.0),
|
|
52
|
+
('STRLEN("20240529T1331")', 13.0),
|
|
53
|
+
('INDEX("20240529T1331", "T")', 8.0),
|
|
54
|
+
('INDEX("20240529T1331", "Q")', -1.0),
|
|
55
|
+
('RINDEX("20240529T1331", "3")', 11.0),
|
|
56
|
+
('ISNUMBER("12.5")', 1.0),
|
|
57
|
+
('ISNUMBER("x9")', 0.0),
|
|
58
|
+
],
|
|
59
|
+
)
|
|
60
|
+
def test_string_query_functions(expression, expected):
|
|
61
|
+
"""INDEX/RINDEX/STRLEN/ISNUMBER are 0-based; the search family returns -1
|
|
62
|
+
when the character is not found (manual 4.1.4.6)."""
|
|
63
|
+
_df, state = nc_to_dataframe(f"R1={expression}")
|
|
64
|
+
assert state["symbol_table"]["R1"] == expected
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_number_on_non_numeric_string_raises():
|
|
68
|
+
"""NUMBER on a string that is not a valid number is a loud failure, never
|
|
69
|
+
a silent 0 (manual 4.1.4.2)."""
|
|
70
|
+
with pytest.raises(ValueError, match="not a valid number"):
|
|
71
|
+
nc_to_dataframe('R1 = NUMBER("abc")')
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_whitespace_only_string_literal_keeps_its_content():
|
|
75
|
+
"""Regression: a quoted string that is only whitespace (e.g. " ") must
|
|
76
|
+
keep its content rather than being eaten by implicit whitespace skipping -
|
|
77
|
+
exactly the space the date-zeroing idiom above searches for."""
|
|
78
|
+
_df, state = nc_to_dataframe('DEF STRING[8] STR = " X"\nR1 = INDEX(STR, " ")\n')
|
|
79
|
+
assert state["symbol_table"]["R1"] == 0.0
|
|
@@ -145,16 +145,34 @@ assignment = {
|
|
|
145
145
|
|
|
146
146
|
| (variable ~ "=" ~ axis_increment) // variable must be an axis, and may be a normal variable. Sort it out after parsing
|
|
147
147
|
|
|
148
|
+
// A string concatenation (`<<`) must be tried before `expression`/`string_value`:
|
|
149
|
+
// those would each match only the first operand and leave `<< ...` dangling.
|
|
150
|
+
| (variable ~ "=" ~ string_expression) // string variable built with << (manual 4.1.4)
|
|
151
|
+
|
|
148
152
|
| (variable ~ "=" ~ expression) // variable may be an axis, and may be a normal variable. Sort it out after parsing
|
|
149
153
|
|
|
150
154
|
| (variable ~ "=" ~ string_value) // string variable (DEF STRING or reassignment); never an axis
|
|
151
155
|
|
|
156
|
+
| (variable_array ~ "=" ~ string_expression) // string-valued array element (never an axis)
|
|
157
|
+
|
|
152
158
|
| (variable_array ~ "=" ~ expression) // variable may not be an axis
|
|
159
|
+
|
|
160
|
+
| (variable_array ~ "=" ~ string_value) // single-character write into a STRING variable: STRING[<index>] = "<char>" (manual 4.1.4.8)
|
|
153
161
|
}
|
|
154
162
|
axis_increment = { ^"IC" ~ "(" ~ expression ~ ")" }
|
|
155
|
-
// A quoted string
|
|
156
|
-
//
|
|
157
|
-
|
|
163
|
+
// A quoted string. Compound-atomic ($): no implicit WHITESPACE is skipped
|
|
164
|
+
// between the quotes, so a string that is only spaces (e.g. " ", searched for
|
|
165
|
+
// by INDEX in real date-formatting code) keeps its content instead of being
|
|
166
|
+
// eaten by the WHITESPACE rule. The inner `string` pair still surfaces.
|
|
167
|
+
string_value = ${ QUOTE ~ string ~ QUOTE }
|
|
168
|
+
// String concatenation with the link operator `<<` (manual 4.1.4): joins
|
|
169
|
+
// quoted strings, STRING variables, string functions (SPRINT/SUBSTR) and
|
|
170
|
+
// numbers (converted to text) into one string. Requires at least one `<<` so
|
|
171
|
+
// a lone string or expression still matches the simpler rules. Each operand is
|
|
172
|
+
// a quoted string or a full (numeric or string-returning) expression; `<<` is
|
|
173
|
+
// not an expression operator, so `expression` stops at the next `<<`.
|
|
174
|
+
string_expression = { concat_operand ~ ("<<" ~ concat_operand)+ }
|
|
175
|
+
concat_operand = { string_value | expression }
|
|
158
176
|
|
|
159
177
|
assignment_multi = { variable_array ~ "=" ~ (value_array | value_repeating) }
|
|
160
178
|
value_array = { "SET" ~ "(" ~ (expression | value_none) ~ ("," ~ (expression | value_none))* ~ ")" }
|
|
@@ -203,7 +221,7 @@ integer = @{ "-"? ~ ASCII_DIGIT+ ~ !ASCII_ALPHA } // Ensure integers ar
|
|
|
203
221
|
arith_fun = { arith_fun_name ~ "(" ~ function_arguments ~ ")" }
|
|
204
222
|
arith_fun_name = @{ ASCII_ALPHA ~ (ASCII_ALPHANUMERIC | "_")* }
|
|
205
223
|
|
|
206
|
-
tool_selection = { ^"T" ~ "=" ~
|
|
224
|
+
tool_selection = { ^"T" ~ "=" ~ string_value }
|
|
207
225
|
|
|
208
226
|
// Frame instructions. A single rule captures the whole family (with any
|
|
209
227
|
// number of axis assignments, including none: a bare substituting frame
|
|
@@ -216,9 +234,11 @@ frame_kw = @{
|
|
|
216
234
|
|
|
217
235
|
// function call parser
|
|
218
236
|
non_returning_function_call = { identifier ~ ("(" ~ function_arguments? ~ ")")? }
|
|
219
|
-
function_arguments = { ((expression |
|
|
220
|
-
|
|
221
|
-
|
|
237
|
+
function_arguments = { ((expression | string_value)? ~ ",")* ~ (expression | string_value) }
|
|
238
|
+
// Atomic body: a maximal run of non-quote characters, taken verbatim with no
|
|
239
|
+
// WHITESPACE skipping (may be empty for ""). Its quote wrapper (string_value)
|
|
240
|
+
// is compound-atomic, so leading/trailing/only-whitespace content survives.
|
|
241
|
+
string = @{ (!"\"" ~ ANY)* }
|
|
222
242
|
|
|
223
243
|
// builtins, globally available names
|
|
224
244
|
nc_variable = { "$" ~ identifier }
|