From ce2f3f37694914256b3505766cd263294e11c486 Mon Sep 17 00:00:00 2001 From: cnc Date: Tue, 26 May 2026 17:00:58 +0800 Subject: [PATCH] Harden LinuxCNC wasm and native probe workflows --- analyze-linuxcnc-wasm-blockers.sh | 88 + build-wasm.sh | 58 +- core/CMakeLists.txt | 137 ++ core/src/canon_event_sink.cpp | 32 +- core/src/canon_event_sink.h | 5 + core/src/cnc_sim_api.cpp | 5 + core/src/linuxcnc_rs274_backend.cpp | 13 +- core/src/smoke_gcode_parser.cpp | 1920 +++++++++++++---- core/src/smoke_gcode_parser.h | 10 + .../cnc_sim_api_linuxcnc_rs274_smoke.cpp | 41 + core/tests/cnc_sim_api_smoke.cpp | 1889 +++++++++++++++- core/tools/linuxcnc_rs274_dump.cpp | 7 + core/wasm_shims/boost/python/dict.hpp | 4 + core/wasm_shims/boost/python/extract.hpp | 4 + core/wasm_shims/boost/python/import.hpp | 4 + core/wasm_shims/boost/python/list.hpp | 4 + core/wasm_shims/boost/python/object.hpp | 211 ++ core/wasm_shims/boost/python/object_fwd.hpp | 10 + core/wasm_shims/boost/python/scope.hpp | 4 + core/wasm_shims/boost/python/str.hpp | 3 + core/wasm_shims/boost/python/tuple.hpp | 3 + core/wasm_shims/dlfcn.cc | 30 + core/wasm_shims/dlfcn.h | 18 + core/wasm_shims/emc_status_shim.cc | 4 + core/wasm_shims/gettext_shim.cc | 6 + core/wasm_shims/interp_base_probe_main.cc | 7 + core/wasm_shims/interp_find_probe_main.cc | 40 + core/wasm_shims/interp_find_probe_stubs.cc | 7 + core/wasm_shims/linuxcnc_runtime_shim.cc | 72 + core/wasm_shims/python_c_api_shim.cc | 76 + core/wasm_shims/pythonplugin/python_plugin.cc | 50 + core/wasm_shims/pythonplugin/python_plugin.hh | 60 + .../pythonplugin/python_plugin_probe_main.cc | 23 + core/wasm_shims/rs274ngc_pre_probe_main.cc | 33 + core/wasm_shims/rtapi_compat.cc | 11 + core/wasm_shims/tooldata/tooldata.hh | 58 + .../tooldata/tooldata_common_probe_main.cc | 101 + .../tooldata/tooldata_mmap_backend.cc | 127 ++ .../tooldata/tooldata_probe_main.cc | 340 +++ .../tooldata/tooldata_runtime_stubs.cc | 47 + docs/linuxcnc-porting.md | 8 +- docs/linuxcnc-rs274-source-map.md | 22 +- linuxcnc-rs274-wasm-source-files.txt | 28 + rs274ngc.var | 10 +- rs274ngc.var.bak | 119 + test-all-native.sh | 55 + test-linuxcnc-api-native.sh | 10 +- test-linuxcnc-bridge-native.sh | 10 +- test-linuxcnc-rs274-native.sh | 280 +-- test-linuxcnc-source-link.sh | 858 +++++--- test-linuxcnc-source-objects.sh | 27 +- test-linuxcnc-source-syntax.sh | 47 +- test-linuxcnc-wasm-blockers.sh | 50 + test-linuxcnc-wasm-cmake-safe-probe.sh | 260 +++ test-linuxcnc-wasm-interp-base-link.sh | 40 + test-linuxcnc-wasm-interp-find-link.sh | 73 + test-linuxcnc-wasm-python-plugin-link.sh | 35 + ...inuxcnc-wasm-rs274ngc-pre-link-blockers.sh | 167 ++ test-linuxcnc-wasm-rs274ngc-pre-link.sh | 91 + test-linuxcnc-wasm-rs274ngc-pre-object.sh | 36 + test-linuxcnc-wasm-source-objects.sh | 81 + test-linuxcnc-wasm-source-syntax.sh | 59 + test-linuxcnc-wasm-tooldata-common-link.sh | 41 + test-linuxcnc-wasm-tooldata-link.sh | 41 + test-linuxcnc-wasm-tooldata-mmap-symbols.sh | 69 + ...-linuxcnc-wasm-tooldata-runtime-symbols.sh | 60 + test-native.sh | 25 +- tests/gcode/linuxcnc_block_delete.ngc | 4 + tests/gcode/linuxcnc_canned_cycle_a_error.ngc | 2 + tests/gcode/linuxcnc_canned_cycle_b_error.ngc | 2 + tests/gcode/linuxcnc_canned_cycle_c_error.ngc | 2 + ...uxcnc_canned_cycle_g18_missing_y_error.ngc | 3 + ...nuxcnc_canned_cycle_g18_r_less_y_error.ngc | 3 + ...uxcnc_canned_cycle_g19_missing_x_error.ngc | 3 + ...nuxcnc_canned_cycle_g19_r_less_x_error.ngc | 3 + ...uxcnc_canned_cycle_g73_missing_q_error.ngc | 3 + .../linuxcnc_canned_cycle_g73_q0_error.ngc | 3 + ...uxcnc_canned_cycle_g74_clockwise_error.ngc | 4 + ...canned_cycle_g74_stopped_spindle_error.ngc | 3 + ...uxcnc_canned_cycle_g82_missing_p_error.ngc | 3 + ...uxcnc_canned_cycle_g83_missing_q_error.ngc | 3 + .../linuxcnc_canned_cycle_g83_q0_error.ngc | 3 + ...anned_cycle_g84_counterclockwise_error.ngc | 4 + ...canned_cycle_g84_stopped_spindle_error.ngc | 3 + ...uxcnc_canned_cycle_g86_missing_p_error.ngc | 3 + ...canned_cycle_g86_stopped_spindle_error.ngc | 3 + ...uxcnc_canned_cycle_g87_missing_i_error.ngc | 4 + ...uxcnc_canned_cycle_g87_missing_j_error.ngc | 4 + ...uxcnc_canned_cycle_g87_missing_k_error.ngc | 4 + ...canned_cycle_g87_stopped_spindle_error.ngc | 3 + ...uxcnc_canned_cycle_g88_missing_p_error.ngc | 4 + ...canned_cycle_g88_stopped_spindle_error.ngc | 3 + ...uxcnc_canned_cycle_g89_missing_p_error.ngc | 3 + .../gcode/linuxcnc_canned_cycle_l0_error.ngc | 3 + .../linuxcnc_canned_cycle_missing_r_error.ngc | 3 + .../linuxcnc_canned_cycle_missing_z_error.ngc | 3 + .../linuxcnc_canned_cycle_r_less_z_error.ngc | 3 + .../linuxcnc_canned_cycle_u_in_xy_error.ngc | 3 + 98 files changed, 7384 insertions(+), 877 deletions(-) create mode 100755 analyze-linuxcnc-wasm-blockers.sh create mode 100644 core/wasm_shims/boost/python/dict.hpp create mode 100644 core/wasm_shims/boost/python/extract.hpp create mode 100644 core/wasm_shims/boost/python/import.hpp create mode 100644 core/wasm_shims/boost/python/list.hpp create mode 100644 core/wasm_shims/boost/python/object.hpp create mode 100644 core/wasm_shims/boost/python/object_fwd.hpp create mode 100644 core/wasm_shims/boost/python/scope.hpp create mode 100644 core/wasm_shims/boost/python/str.hpp create mode 100644 core/wasm_shims/boost/python/tuple.hpp create mode 100644 core/wasm_shims/dlfcn.cc create mode 100644 core/wasm_shims/dlfcn.h create mode 100644 core/wasm_shims/emc_status_shim.cc create mode 100644 core/wasm_shims/gettext_shim.cc create mode 100644 core/wasm_shims/interp_base_probe_main.cc create mode 100644 core/wasm_shims/interp_find_probe_main.cc create mode 100644 core/wasm_shims/interp_find_probe_stubs.cc create mode 100644 core/wasm_shims/linuxcnc_runtime_shim.cc create mode 100644 core/wasm_shims/python_c_api_shim.cc create mode 100644 core/wasm_shims/pythonplugin/python_plugin.cc create mode 100644 core/wasm_shims/pythonplugin/python_plugin.hh create mode 100644 core/wasm_shims/pythonplugin/python_plugin_probe_main.cc create mode 100644 core/wasm_shims/rs274ngc_pre_probe_main.cc create mode 100644 core/wasm_shims/rtapi_compat.cc create mode 100644 core/wasm_shims/tooldata/tooldata.hh create mode 100644 core/wasm_shims/tooldata/tooldata_common_probe_main.cc create mode 100644 core/wasm_shims/tooldata/tooldata_mmap_backend.cc create mode 100644 core/wasm_shims/tooldata/tooldata_probe_main.cc create mode 100644 core/wasm_shims/tooldata/tooldata_runtime_stubs.cc create mode 100644 linuxcnc-rs274-wasm-source-files.txt create mode 100644 rs274ngc.var.bak create mode 100755 test-linuxcnc-wasm-blockers.sh create mode 100755 test-linuxcnc-wasm-cmake-safe-probe.sh create mode 100755 test-linuxcnc-wasm-interp-base-link.sh create mode 100755 test-linuxcnc-wasm-interp-find-link.sh create mode 100755 test-linuxcnc-wasm-python-plugin-link.sh create mode 100755 test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh create mode 100755 test-linuxcnc-wasm-rs274ngc-pre-link.sh create mode 100755 test-linuxcnc-wasm-rs274ngc-pre-object.sh create mode 100755 test-linuxcnc-wasm-source-objects.sh create mode 100755 test-linuxcnc-wasm-source-syntax.sh create mode 100755 test-linuxcnc-wasm-tooldata-common-link.sh create mode 100755 test-linuxcnc-wasm-tooldata-link.sh create mode 100755 test-linuxcnc-wasm-tooldata-mmap-symbols.sh create mode 100755 test-linuxcnc-wasm-tooldata-runtime-symbols.sh create mode 100644 tests/gcode/linuxcnc_block_delete.ngc create mode 100644 tests/gcode/linuxcnc_canned_cycle_a_error.ngc create mode 100644 tests/gcode/linuxcnc_canned_cycle_b_error.ngc create mode 100644 tests/gcode/linuxcnc_canned_cycle_c_error.ngc create mode 100644 tests/gcode/linuxcnc_canned_cycle_g18_missing_y_error.ngc create mode 100644 tests/gcode/linuxcnc_canned_cycle_g18_r_less_y_error.ngc create mode 100644 tests/gcode/linuxcnc_canned_cycle_g19_missing_x_error.ngc create mode 100644 tests/gcode/linuxcnc_canned_cycle_g19_r_less_x_error.ngc create mode 100644 tests/gcode/linuxcnc_canned_cycle_g73_missing_q_error.ngc create mode 100644 tests/gcode/linuxcnc_canned_cycle_g73_q0_error.ngc create mode 100644 tests/gcode/linuxcnc_canned_cycle_g74_clockwise_error.ngc create mode 100644 tests/gcode/linuxcnc_canned_cycle_g74_stopped_spindle_error.ngc create mode 100644 tests/gcode/linuxcnc_canned_cycle_g82_missing_p_error.ngc create mode 100644 tests/gcode/linuxcnc_canned_cycle_g83_missing_q_error.ngc create mode 100644 tests/gcode/linuxcnc_canned_cycle_g83_q0_error.ngc create mode 100644 tests/gcode/linuxcnc_canned_cycle_g84_counterclockwise_error.ngc create mode 100644 tests/gcode/linuxcnc_canned_cycle_g84_stopped_spindle_error.ngc create mode 100644 tests/gcode/linuxcnc_canned_cycle_g86_missing_p_error.ngc create mode 100644 tests/gcode/linuxcnc_canned_cycle_g86_stopped_spindle_error.ngc create mode 100644 tests/gcode/linuxcnc_canned_cycle_g87_missing_i_error.ngc create mode 100644 tests/gcode/linuxcnc_canned_cycle_g87_missing_j_error.ngc create mode 100644 tests/gcode/linuxcnc_canned_cycle_g87_missing_k_error.ngc create mode 100644 tests/gcode/linuxcnc_canned_cycle_g87_stopped_spindle_error.ngc create mode 100644 tests/gcode/linuxcnc_canned_cycle_g88_missing_p_error.ngc create mode 100644 tests/gcode/linuxcnc_canned_cycle_g88_stopped_spindle_error.ngc create mode 100644 tests/gcode/linuxcnc_canned_cycle_g89_missing_p_error.ngc create mode 100644 tests/gcode/linuxcnc_canned_cycle_l0_error.ngc create mode 100644 tests/gcode/linuxcnc_canned_cycle_missing_r_error.ngc create mode 100644 tests/gcode/linuxcnc_canned_cycle_missing_z_error.ngc create mode 100644 tests/gcode/linuxcnc_canned_cycle_r_less_z_error.ngc create mode 100644 tests/gcode/linuxcnc_canned_cycle_u_in_xy_error.ngc diff --git a/analyze-linuxcnc-wasm-blockers.sh b/analyze-linuxcnc-wasm-blockers.sh new file mode 100755 index 0000000..4af3877 --- /dev/null +++ b/analyze-linuxcnc-wasm-blockers.sh @@ -0,0 +1,88 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")" + +linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} +manifest=${1:-linuxcnc-rs274-wasm-source-files.txt} + +if [[ ! -f "$manifest" ]]; then + echo "missing manifest: $manifest" >&2 + exit 1 +fi + +if [[ ! -d "$linuxcnc_root" ]]; then + echo "missing LinuxCNC root: $linuxcnc_root" >&2 + exit 1 +fi + +python_blockers=() +dlopen_blockers=() +tooldata_blockers=() +shimmed_tooldata_users=() +native_backend_blockers=() +native_fs_blockers=() + +while IFS=: read -r group path note; do + [[ -z "$group" || "$group" == \#* ]] && continue + case "$group" in + core) + continue + ;; + blocked) + ;; + *) + echo "unknown manifest group: $group" >&2 + exit 1 + ;; + esac + + full_path="$linuxcnc_root/$path" + if [[ ! -f "$full_path" ]]; then + echo "missing manifest source: $path" >&2 + exit 1 + fi + + if [[ "$path" == src/emc/tooldata/* ]]; then + tooldata_blockers+=("$path") + fi + + if [[ "$path" == "src/emc/tooldata/tooldata_mmap.cc" ]]; then + native_backend_blockers+=("$path") + fi + + if grep -Eq 'Python\.h|boost/python|pythonplugin|PyObject|PyInit_' "$full_path"; then + python_blockers+=("$path") + fi + + if grep -Eq 'dlopen|dlsym|RTLD_' "$full_path"; then + dlopen_blockers+=("$path") + fi + + if grep -Eq 'mkstemp|mkstemps|dirent\.h|opendir|readdir|unistd\.h' "$full_path"; then + native_fs_blockers+=("$path") + fi + + if [[ "$path" != src/emc/tooldata/* ]] && grep -Eq 'tooldata/tooldata.hh|tooldata_' "$full_path"; then + shimmed_tooldata_users+=("$path") + fi +done < "$manifest" + +print_group() { + local title=$1 + shift + echo "[$title]" + if [[ $# -eq 0 ]]; then + echo "(none)" + else + printf '%s\n' "$@" | LC_ALL=C sort -u + fi + echo +} + +print_group "python" "${python_blockers[@]}" +print_group "dlopen" "${dlopen_blockers[@]}" +print_group "tooldata" "${tooldata_blockers[@]}" +print_group "tooldata-users" "${shimmed_tooldata_users[@]}" +print_group "native-backend" "${native_backend_blockers[@]}" +print_group "native-fs" "${native_fs_blockers[@]}" diff --git a/build-wasm.sh b/build-wasm.sh index 4e56ffd..7acb856 100755 --- a/build-wasm.sh +++ b/build-wasm.sh @@ -3,12 +3,68 @@ set -euo pipefail cd "$(dirname "$0")" +linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} +if [[ ! -d "$linuxcnc_root" ]]; then + echo "missing LinuxCNC root: $linuxcnc_root" >&2 + exit 1 +fi +linuxcnc_root=$(cd "$linuxcnc_root" && pwd) + +# This script writes fixed build and public artifact paths, so serialize it. +exec 9>"${TMPDIR:-/tmp}/cnc_sim_build_wasm.lock" +flock 9 + +echo "LinuxCNC wasm blocker scan" +./test-linuxcnc-wasm-blockers.sh + +echo "LinuxCNC wasm-safe source syntax probe" +./test-linuxcnc-wasm-source-syntax.sh + +echo "LinuxCNC wasm-safe source object probe" +./test-linuxcnc-wasm-source-objects.sh + +echo "LinuxCNC wasm-safe CMake probe target" +./test-linuxcnc-wasm-cmake-safe-probe.sh + +echo "LinuxCNC wasm tooldata shim link probe" +./test-linuxcnc-wasm-tooldata-link.sh + +echo "LinuxCNC wasm tooldata_common link probe" +./test-linuxcnc-wasm-tooldata-common-link.sh + +echo "LinuxCNC wasm tooldata_mmap symbol probe" +./test-linuxcnc-wasm-tooldata-mmap-symbols.sh + +echo "LinuxCNC wasm tooldata runtime symbol probe" +./test-linuxcnc-wasm-tooldata-runtime-symbols.sh + +echo "LinuxCNC wasm interp_base link probe" +./test-linuxcnc-wasm-interp-base-link.sh + +echo "LinuxCNC wasm interp_find link probe" +./test-linuxcnc-wasm-interp-find-link.sh + +echo "LinuxCNC wasm python_plugin link probe" +./test-linuxcnc-wasm-python-plugin-link.sh + +echo "LinuxCNC wasm rs274ngc_pre object probe" +./test-linuxcnc-wasm-rs274ngc-pre-object.sh + +echo "LinuxCNC wasm rs274ngc_pre link blocker scan" +./test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh + +echo "LinuxCNC wasm rs274ngc_pre link probe" +./test-linuxcnc-wasm-rs274ngc-pre-link.sh + if ! command -v emcmake >/dev/null 2>&1; then echo "Emscripten is required. Install/activate emsdk so emcmake and emcc are in PATH." >&2 exit 1 fi -emcmake cmake -S core -B build/wasm -DCMAKE_BUILD_TYPE=Release +emcmake cmake -S core -B build/wasm \ + -DCMAKE_BUILD_TYPE=Release \ + -DCNC_SIM_LINUXCNC_ROOT="$linuxcnc_root" \ + -DCNC_SIM_ENABLE_LINUXCNC_WASM_SAFE_PROBE=ON cmake --build build/wasm mkdir -p web/public diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index d97671e..7fb238b 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -3,7 +3,13 @@ project(cnc_sim_wasm_core LANGUAGES CXX) option(CNC_SIM_ENABLE_LINUXCNC_BRIDGE "Build the experimental LinuxCNC canon bridge" OFF) option(CNC_SIM_ENABLE_LINUXCNC_RS274_BACKEND "Build the native LinuxCNC librs274 backend" OFF) +option(CNC_SIM_ENABLE_LINUXCNC_WASM_SAFE_PROBE "Compile the browser-safe subset of LinuxCNC rs274 sources" OFF) set(CNC_SIM_LINUXCNC_ROOT "" CACHE PATH "LinuxCNC source root for the experimental bridge") +set(CNC_SIM_LINUXCNC_WASM_SOURCE_MANIFEST + "${CMAKE_CURRENT_SOURCE_DIR}/../linuxcnc-rs274-wasm-source-files.txt" + CACHE FILEPATH + "Manifest that partitions LinuxCNC rs274 sources into wasm-safe core and blocked groups" +) set(cnc_sim_core_sources src/canon_event_sink.cpp @@ -28,6 +34,137 @@ if(CNC_SIM_ENABLE_LINUXCNC_RS274_BACKEND) ) endif() +if(CNC_SIM_ENABLE_LINUXCNC_WASM_SAFE_PROBE) + if(NOT CNC_SIM_LINUXCNC_ROOT) + message(FATAL_ERROR "Set CNC_SIM_LINUXCNC_ROOT to the LinuxCNC source root") + endif() + get_filename_component(CNC_SIM_LINUXCNC_ROOT_ABS + "${CNC_SIM_LINUXCNC_ROOT}" + ABSOLUTE + BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/.." + ) + if(NOT IS_DIRECTORY "${CNC_SIM_LINUXCNC_ROOT_ABS}") + message(FATAL_ERROR "LinuxCNC source root does not exist: ${CNC_SIM_LINUXCNC_ROOT_ABS}") + endif() + if(NOT EXISTS "${CNC_SIM_LINUXCNC_WASM_SOURCE_MANIFEST}") + message(FATAL_ERROR "Missing LinuxCNC wasm source manifest: ${CNC_SIM_LINUXCNC_WASM_SOURCE_MANIFEST}") + endif() + if(IS_DIRECTORY "${CNC_SIM_LINUXCNC_WASM_SOURCE_MANIFEST}") + message(FATAL_ERROR "LinuxCNC wasm source manifest is not a file: ${CNC_SIM_LINUXCNC_WASM_SOURCE_MANIFEST}") + endif() + + set(linuxcnc_wasm_safe_probe_sources) + set(linuxcnc_wasm_blocked_sources) + file(STRINGS "${CNC_SIM_LINUXCNC_WASM_SOURCE_MANIFEST}" linuxcnc_wasm_manifest_lines) + foreach(manifest_line IN LISTS linuxcnc_wasm_manifest_lines) + string(STRIP "${manifest_line}" manifest_line) + if(manifest_line STREQUAL "" OR manifest_line MATCHES "^#") + continue() + endif() + if(NOT manifest_line MATCHES "^([^:]+):([^:]+):(.*)$") + message(FATAL_ERROR "Bad LinuxCNC wasm source manifest line: ${manifest_line}") + endif() + + set(manifest_group "${CMAKE_MATCH_1}") + set(manifest_path "${CMAKE_MATCH_2}") + if(NOT manifest_group STREQUAL "core" AND NOT manifest_group STREQUAL "blocked") + message(FATAL_ERROR "Unknown LinuxCNC wasm source manifest group: ${manifest_group}") + endif() + + set(manifest_source "${CNC_SIM_LINUXCNC_ROOT_ABS}/${manifest_path}") + if(NOT EXISTS "${manifest_source}") + message(FATAL_ERROR "LinuxCNC wasm manifest source does not exist: ${manifest_source}") + endif() + if(IS_DIRECTORY "${manifest_source}") + message(FATAL_ERROR "LinuxCNC wasm manifest source is not a file: ${manifest_source}") + endif() + + if(manifest_group STREQUAL "core") + list(APPEND linuxcnc_wasm_safe_probe_sources "${manifest_source}") + else() + list(APPEND linuxcnc_wasm_blocked_sources "${manifest_source}") + endif() + endforeach() + + list(LENGTH linuxcnc_wasm_safe_probe_sources linuxcnc_wasm_safe_probe_source_count) + list(LENGTH linuxcnc_wasm_blocked_sources linuxcnc_wasm_blocked_source_count) + if(linuxcnc_wasm_safe_probe_source_count EQUAL 0) + message(FATAL_ERROR "LinuxCNC wasm source manifest has no core entries") + endif() + if(linuxcnc_wasm_blocked_source_count EQUAL 0) + message(FATAL_ERROR "LinuxCNC wasm source manifest has no blocked entries") + endif() + message(STATUS "LinuxCNC wasm-safe core sources: ${linuxcnc_wasm_safe_probe_source_count}") + message(STATUS "LinuxCNC wasm blocked sources: ${linuxcnc_wasm_blocked_source_count}") + + set(linuxcnc_wasm_safe_probe_shims + ${CMAKE_CURRENT_SOURCE_DIR}/wasm_shims/dlfcn.cc + ${CMAKE_CURRENT_SOURCE_DIR}/wasm_shims/emc_status_shim.cc + ${CMAKE_CURRENT_SOURCE_DIR}/wasm_shims/gettext_shim.cc + ${CMAKE_CURRENT_SOURCE_DIR}/wasm_shims/linuxcnc_runtime_shim.cc + ${CMAKE_CURRENT_SOURCE_DIR}/wasm_shims/python_c_api_shim.cc + ${CMAKE_CURRENT_SOURCE_DIR}/wasm_shims/rtapi_compat.cc + ${CMAKE_CURRENT_SOURCE_DIR}/wasm_shims/tooldata/tooldata_mmap_backend.cc + ${CMAKE_CURRENT_SOURCE_DIR}/wasm_shims/tooldata/tooldata_runtime_stubs.cc + ${CMAKE_CURRENT_SOURCE_DIR}/wasm_shims/pythonplugin/python_plugin.cc + ) + + add_library(linuxcnc_rs274_wasm_safe_probe_objects OBJECT + ${linuxcnc_wasm_safe_probe_shims} + ${linuxcnc_wasm_safe_probe_sources} + ) + target_compile_features(linuxcnc_rs274_wasm_safe_probe_objects PUBLIC cxx_std_20) + target_compile_definitions(linuxcnc_rs274_wasm_safe_probe_objects PRIVATE ULAPI) + target_compile_options(linuxcnc_rs274_wasm_safe_probe_objects PRIVATE -include wctype.h) + target_include_directories(linuxcnc_rs274_wasm_safe_probe_objects + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${CMAKE_CURRENT_SOURCE_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR}/wasm_shims + ${CNC_SIM_LINUXCNC_ROOT_ABS}/src + ${CNC_SIM_LINUXCNC_ROOT_ABS}/src/emc + ${CNC_SIM_LINUXCNC_ROOT_ABS}/src/emc/nml_intf + ${CNC_SIM_LINUXCNC_ROOT_ABS}/src/emc/rs274ngc + ${CNC_SIM_LINUXCNC_ROOT_ABS}/src/emc/motion + ${CNC_SIM_LINUXCNC_ROOT_ABS}/include + ) + + set(linuxcnc_rs274_wasm_safe_probe_project_sources + ${CMAKE_CURRENT_SOURCE_DIR}/src/canon_event_sink.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/linuxcnc_canon_bridge.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/linuxcnc_tooldata_fixture.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/rtcp_kinematics.cpp + ) + + add_executable(linuxcnc_rs274_wasm_safe_probe EXCLUDE_FROM_ALL + $ + ${linuxcnc_rs274_wasm_safe_probe_project_sources} + ${CMAKE_CURRENT_SOURCE_DIR}/wasm_shims/rs274ngc_pre_probe_main.cc + ) + target_compile_features(linuxcnc_rs274_wasm_safe_probe PRIVATE cxx_std_20) + target_compile_definitions(linuxcnc_rs274_wasm_safe_probe PRIVATE ULAPI) + target_compile_options(linuxcnc_rs274_wasm_safe_probe PRIVATE -include wctype.h) + target_include_directories(linuxcnc_rs274_wasm_safe_probe + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${CMAKE_CURRENT_SOURCE_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR}/wasm_shims + ${CNC_SIM_LINUXCNC_ROOT_ABS}/src + ${CNC_SIM_LINUXCNC_ROOT_ABS}/src/emc + ${CNC_SIM_LINUXCNC_ROOT_ABS}/src/emc/nml_intf + ${CNC_SIM_LINUXCNC_ROOT_ABS}/src/emc/rs274ngc + ${CNC_SIM_LINUXCNC_ROOT_ABS}/src/emc/motion + ${CNC_SIM_LINUXCNC_ROOT_ABS}/include + ) + target_link_libraries(linuxcnc_rs274_wasm_safe_probe PRIVATE fmt) + + if(linuxcnc_wasm_blocked_sources) + add_custom_target(linuxcnc_rs274_wasm_blocked_sources + SOURCES ${linuxcnc_wasm_blocked_sources} + ) + endif() +endif() + add_library(cnc_sim_objects OBJECT ${cnc_sim_core_sources} ) diff --git a/core/src/canon_event_sink.cpp b/core/src/canon_event_sink.cpp index 629149f..6601668 100644 --- a/core/src/canon_event_sink.cpp +++ b/core/src/canon_event_sink.cpp @@ -39,6 +39,8 @@ void CanonEventSink::reset() { feed_hold_ = false; mist_on_ = false; flood_on_ = false; + has_pending_motion_start_ = false; + pending_motion_start_ = {}; } void CanonEventSink::set_callback(CncSimEventCallback callback, void *user_data) { @@ -55,6 +57,10 @@ bool CanonEventSink::callback_aborted() const { return callback_status_ != 0; } +void CanonEventSink::set_block_delete(bool enabled) { + block_delete_ = enabled; +} + void CanonEventSink::configure_rtcp(bool enabled, double tool_length) { rtcp_enabled_ = enabled; default_rtcp_tool_length_ = tool_length; @@ -80,6 +86,10 @@ void CanonEventSink::set_tool_length_offset(const CncSimPose &offset) { } void CanonEventSink::apply_tool_length_offset(const CncSimPose &offset) { + if (!has_pending_motion_start_) { + pending_motion_start_ = position_; + has_pending_motion_start_ = true; + } double dx = tool_length_offset_.x - offset.x; double dy = tool_length_offset_.y - offset.y; rotate_xy(&dx, &dy, -xy_rotation_degrees_); @@ -305,41 +315,45 @@ void CanonEventSink::change_tool(int line) { void CanonEventSink::straight_traverse(int line, const CncSimPose &end) { CncSimEvent event = base_event(CNC_SIM_EVENT_RAPID, line); - event.start = position_; + event.start = has_pending_motion_start_ ? pending_motion_start_ : position_; event.end = end; emit(event); - emit_rtcp_pivot(line, position_, end); + emit_rtcp_pivot(line, event.start, end); + has_pending_motion_start_ = false; position_ = end; } void CanonEventSink::straight_feed(int line, const CncSimPose &end) { CncSimEvent event = base_event(CNC_SIM_EVENT_LINEAR_FEED, line); - event.start = position_; + event.start = has_pending_motion_start_ ? pending_motion_start_ : position_; event.end = end; emit(event); - emit_rtcp_pivot(line, position_, end); + emit_rtcp_pivot(line, event.start, end); + has_pending_motion_start_ = false; position_ = end; } void CanonEventSink::arc_feed(int line, const CncSimPose &end, const CncSimPose ¢er, int turns) { CncSimEvent event = base_event(CNC_SIM_EVENT_ARC_FEED, line); - event.start = position_; + event.start = has_pending_motion_start_ ? pending_motion_start_ : position_; event.end = end; event.center = center; event.arc_turns = turns; emit(event); - emit_rtcp_pivot(line, position_, end); + emit_rtcp_pivot(line, event.start, end); + has_pending_motion_start_ = false; position_ = end; } void CanonEventSink::straight_probe(int line, const CncSimPose &end, int probe_type) { CncSimEvent event = base_event(CNC_SIM_EVENT_PROBE, line); - event.start = position_; + event.start = has_pending_motion_start_ ? pending_motion_start_ : position_; event.end = end; event.reserved = probe_type; emit(event); probe_position_ = end; probe_tripped_ = (probe_type & 2) == 0; + has_pending_motion_start_ = false; position_ = end; } @@ -446,6 +460,10 @@ bool CanonEventSink::flood_on() const { return flood_on_; } +bool CanonEventSink::block_delete() const { + return block_delete_; +} + void CanonEventSink::set_mist_on(bool enabled) { mist_on_ = enabled; } diff --git a/core/src/canon_event_sink.h b/core/src/canon_event_sink.h index bd36824..22c80d1 100644 --- a/core/src/canon_event_sink.h +++ b/core/src/canon_event_sink.h @@ -10,6 +10,7 @@ public: void set_callback(CncSimEventCallback callback, void *user_data); void clear_callback_status(); bool callback_aborted() const; + void set_block_delete(bool enabled); void configure_rtcp(bool enabled, double tool_length); void set_tool_length(int h_code, double tool_length); void set_tool_length_offset(const CncSimPose &offset); @@ -70,6 +71,7 @@ public: bool feed_hold() const; bool mist_on() const; bool flood_on() const; + bool block_delete() const; void set_mist_on(bool enabled); void set_flood_on(bool enabled); @@ -104,5 +106,8 @@ private: bool feed_hold_ = false; bool mist_on_ = false; bool flood_on_ = false; + bool block_delete_ = false; std::unordered_map tool_lengths_; + bool has_pending_motion_start_ = false; + CncSimPose pending_motion_start_{}; }; diff --git a/core/src/cnc_sim_api.cpp b/core/src/cnc_sim_api.cpp index a9c9371..b6c74b8 100644 --- a/core/src/cnc_sim_api.cpp +++ b/core/src/cnc_sim_api.cpp @@ -152,6 +152,11 @@ int apply_config(CncSimHandle *handle, const std::string &json) { if (has_rtcp_enabled || has_tool_length) { handle->sink.configure_rtcp(rtcp_enabled, tool_length); } + bool block_delete = false; + if (contains_bool_value(compact, "blockdelete", &block_delete) || + contains_bool_value(compact, "block_delete", &block_delete)) { + handle->sink.set_block_delete(block_delete); + } apply_tool_length_table(handle->sink, compact); return 0; } diff --git a/core/src/linuxcnc_rs274_backend.cpp b/core/src/linuxcnc_rs274_backend.cpp index dd18497..957ae5a 100644 --- a/core/src/linuxcnc_rs274_backend.cpp +++ b/core/src/linuxcnc_rs274_backend.cpp @@ -77,8 +77,12 @@ bool write_temp_program(const char *program, size_t program_len, std::string *path, std::string *error) { - char tmpl[] = "/tmp/cnc_sim_api_XXXXXX.ngc"; - const int fd = mkstemps(tmpl, 4); + const char *tmpdir = std::getenv("TMPDIR"); + std::string tmpl = std::string(tmpdir ? tmpdir : "/tmp") + "/cnc_sim_api_XXXXXX.ngc"; + std::vector tmpl_buffer(tmpl.begin(), tmpl.end()); + tmpl_buffer.push_back('\0'); + + const int fd = mkstemps(tmpl_buffer.data(), 4); if (fd < 0) { if (error) { *error = "failed to create temporary ngc file"; @@ -100,13 +104,13 @@ bool write_temp_program(const char *program, ok = false; } if (!ok) { - unlink(tmpl); + unlink(tmpl_buffer.data()); if (error) { *error = "failed to write temporary ngc file"; } return false; } - *path = tmpl; + *path = tmpl_buffer.data(); return true; } @@ -185,6 +189,7 @@ int parse_linuxcnc_rs274_backend(CanonEventSink &sink, cnc_sim_linuxcnc_set_canon_sink(nullptr); return -1; } + SET_BLOCK_DELETE(sink.block_delete()); if (file_mode_enabled()) { std::string temp_path; diff --git a/core/src/smoke_gcode_parser.cpp b/core/src/smoke_gcode_parser.cpp index d2ee3ef..33d3a44 100644 --- a/core/src/smoke_gcode_parser.cpp +++ b/core/src/smoke_gcode_parser.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include namespace { @@ -119,6 +120,15 @@ enum class LinuxCncReaderSymbol { LetterWord, }; +size_t find_matching_square_bracket(const std::string &text, size_t open); +bool unary_function_span(const std::string &line, size_t start, size_t *end); +std::string compact_linuxcnc_oword_line(const std::string &line); +size_t leading_oword_start(const std::string &line); +size_t leading_special_word_start(const std::string &line); +bool is_numeric_m98_subprogram_header(const std::string &line); +OwordKind oword_kind(const std::string &line); +bool integer_word_value(const std::unordered_map &words, char letter, int *value); + const std::array &linuxcnc_reader_symbols() { static const std::array table = [] { std::array out{}; @@ -496,6 +506,17 @@ std::string strip_comments(const std::string &line, std::string *comment) { return out; } +std::string remove_linuxcnc_reader_whitespace(const std::string &line) { + std::string out; + out.reserve(line.size()); + for (char ch : line) { + if (!std::isspace(static_cast(ch))) { + out.push_back(ch); + } + } + return out; +} + bool validate_linuxcnc_comments(const std::string &line, std::string *error) { bool in_paren = false; for (char raw : line) { @@ -555,6 +576,63 @@ bool parse_linuxcnc_real_value(const char *text, double *value, size_t *consumed return parse_linuxcnc_real_number(text, value, consumed); } +enum class LinuxCncIntegerReadResult { + Ok, + NoValue, + NonInteger, +}; + +LinuxCncIntegerReadResult linuxcnc_integer_from_real(double float_value, int *integer) { + int parsed_integer = static_cast(std::floor(float_value)); + const double fraction = float_value - parsed_integer; + if (fraction > 0.9999) { + parsed_integer = static_cast(std::ceil(float_value)); + } else if (fraction > 0.0001) { + return LinuxCncIntegerReadResult::NonInteger; + } + + *integer = parsed_integer; + return LinuxCncIntegerReadResult::Ok; +} + +LinuxCncIntegerReadResult read_linuxcnc_integer_value(const std::string &text, + size_t start, + int *integer, + size_t *end) { + size_t pos = start; + while (pos < text.size() && std::isspace(static_cast(text[pos]))) { + ++pos; + } + std::string compact; + std::vector compact_to_text; + compact.reserve(text.size() - pos); + compact_to_text.reserve(text.size() - pos); + for (size_t i = pos; i < text.size(); ++i) { + if (std::isspace(static_cast(text[i]))) { + continue; + } + compact_to_text.push_back(i); + compact.push_back(text[i]); + } + + double float_value = 0.0; + size_t consumed = 0; + if (compact.empty() || + !parse_linuxcnc_real_value(compact.c_str(), &float_value, &consumed) || + consumed == 0 || + consumed > compact_to_text.size()) { + return LinuxCncIntegerReadResult::NoValue; + } + + const LinuxCncIntegerReadResult result = linuxcnc_integer_from_real(float_value, integer); + if (result != LinuxCncIntegerReadResult::Ok) { + return result; + } + + *end = compact_to_text[consumed - 1] + 1; + return LinuxCncIntegerReadResult::Ok; +} + bool parse_linuxcnc_real_number(const char *text, double *value, size_t *consumed) { size_t end = 0; while (text[end] == '+' || text[end] == '-') { @@ -591,7 +669,7 @@ bool skip_linuxcnc_top_level_construct(const std::string &line, size_t *index) { return false; } const char ch = line[i]; - if (ch == '[' || ch == ']' || ch == '/') { + if (ch == '[' || ch == ']') { *index = i + 1; return true; } @@ -629,6 +707,301 @@ bool is_linuxcnc_midline_n_word(const std::string &line, size_t index) { return looks_like_real_start(line[index + 1]); } +bool raw_line_has_leading_paren_comment(const std::string &raw_line) { + const size_t first = raw_line.find_first_not_of(" \t"); + return first != std::string::npos && raw_line[first] == '('; +} + +bool validate_linuxcnc_comment_prefixed_leading_items(const std::string &raw_line, + const std::string &line, + std::string *error) { + if (!raw_line_has_leading_paren_comment(raw_line)) { + return true; + } + const size_t first = line.find_first_not_of(" \t\r"); + if (first == std::string::npos) { + return true; + } + const char ch = line[first]; + if (ch == 'N' || ch == 'n') { + size_t pos = first + 1; + while (pos < line.size() && std::isspace(static_cast(line[pos]))) { + ++pos; + } + if (pos < line.size() && looks_like_real_start(line[pos])) { + if (error) { + *error = "Bad character 'n' used"; + } + return false; + } + } + if (ch == 'O' || ch == 'o') { + size_t pos = first + 1; + while (pos < line.size() && std::isspace(static_cast(line[pos]))) { + ++pos; + } + const bool numeric_oword = pos < line.size() && + std::isdigit(static_cast(line[pos])); + const size_t suffix_start = pos; + while (pos < line.size() && std::isdigit(static_cast(line[pos]))) { + ++pos; + } + if ((pos < line.size() && line[pos] == '<') || + numeric_oword || + (numeric_oword && oword_kind(line) != OwordKind::None) || + (suffix_start < pos && oword_kind(line) != OwordKind::None)) { + if (error) { + *error = "Bad character 'o' used"; + } + return false; + } + } + if (is_numeric_m98_subprogram_header(line) || + oword_kind(line) != OwordKind::None) { + if (error) { + *error = "Bad character 'o' used"; + } + return false; + } + if (ch == 'M' || ch == 'm') { + size_t pos = first + 1; + while (pos < line.size() && std::isspace(static_cast(line[pos]))) { + ++pos; + } + if (pos + 1 < line.size() && line[pos] == '9' && line[pos + 1] == '8') { + if (error) { + *error = "Unknown m code used: M98"; + } + return false; + } + } + return true; +} + +bool validate_linuxcnc_top_level_brackets(const std::string &line, std::string *error) { + int depth = 0; + for (size_t i = 0; i < line.size(); ++i) { + const char ch = line[i]; + if (depth > 0) { + if (ch == '[') { + ++depth; + } else if (ch == ']') { + --depth; + } + continue; + } + if (ch == ']') { + if (error) { + *error = "Bad character ']' used"; + } + return false; + } + if (ch != '[') { + continue; + } + + size_t previous = i; + while (previous > 0 && std::isspace(static_cast(line[previous - 1]))) { + --previous; + } + const char before = previous > 0 ? line[previous - 1] : '\0'; + const bool value_context = std::isalpha(static_cast(before)) || + before == '#' || + before == '$' || + before == '@' || + before == '^' || + before == '+' || + before == '-' || + before == '/'; + if (!value_context) { + if (error) { + *error = "Bad character '[' used"; + } + return false; + } + depth = 1; + } + return true; +} + +bool validate_linuxcnc_top_level_item_start(const std::string &line, std::string *error) { + const size_t first = line.find_first_not_of(" \t\r"); + if (first == std::string::npos) { + return true; + } + const unsigned char ch = static_cast(line[first]); + if (!looks_like_real_start(static_cast(ch))) { + return true; + } + if (error) { + if (!std::isprint(ch) || std::isspace(ch)) { + char buffer[32]; + std::snprintf(buffer, sizeof(buffer), "Bad character '\\%03o' used", ch); + *error = buffer; + } else { + *error = "Bad character '" + std::string(1, static_cast(ch)) + "' used"; + } + } + return false; +} + +bool is_linuxcnc_value_context_char(char ch) { + return std::isalpha(static_cast(ch)) || + ch == '#' || + ch == '$' || + ch == '@' || + ch == '^' || + ch == '+' || + ch == '-' || + ch == '/' || + ch == '*' || + ch == '['; +} + +bool validate_linuxcnc_top_level_parameter_setting_items(const std::string &line, std::string *error) { + int depth = 0; + for (size_t i = 0; i < line.size(); ++i) { + const char ch = line[i]; + if (ch == '[') { + ++depth; + continue; + } + if (ch == ']') { + if (depth > 0) { + --depth; + } + continue; + } + if (depth > 0 || ch != '#') { + continue; + } + + size_t previous = i; + while (previous > 0 && std::isspace(static_cast(line[previous - 1]))) { + --previous; + } + const char before = previous > 0 ? line[previous - 1] : '\0'; + if (is_linuxcnc_value_context_char(before)) { + continue; + } + + size_t equals = i + 1; + while (equals < line.size() && line[equals] != '=' && line[equals] != ';') { + if (line[equals] == '(') { + break; + } + ++equals; + } + if (equals >= line.size() || line[equals] != '=') { + if (error) { + *error = "Equal sign missing in parameter setting"; + } + return false; + } + } + return true; +} + +bool validate_linuxcnc_top_level_unary_items(const std::string &line, std::string *error) { + int depth = 0; + for (size_t i = 0; i < line.size(); ++i) { + const char ch = line[i]; + if (ch == '[') { + ++depth; + continue; + } + if (ch == ']') { + if (depth > 0) { + --depth; + } + continue; + } + if (depth > 0 || !std::isalpha(static_cast(ch))) { + continue; + } + + size_t unary_end = 0; + if (!unary_function_span(line, i, &unary_end)) { + continue; + } + + size_t previous = i; + while (previous > 0 && std::isspace(static_cast(line[previous - 1]))) { + --previous; + } + const char before = previous > 0 ? line[previous - 1] : '\0'; + if (std::isalpha(static_cast(before)) || + before == '#' || + before == '+' || + before == '-' || + before == '/' || + before == '*') { + i = unary_end - 1; + continue; + } + + if (error) { + *error = uppercase(line.substr(i, 4)) == "ATAN" + ? "Bad character '/' used" + : "Unknown word where unary operation could be"; + } + return false; + } + return true; +} + +bool validate_linuxcnc_nonleading_oword_items(const std::string &line, std::string *error) { + const std::string compact = compact_linuxcnc_oword_line(line); + const size_t leading_start = leading_oword_start(line); + for (size_t i = 0; i < compact.size(); ++i) { + if (std::toupper(static_cast(compact[i])) != 'O') { + continue; + } + if (i > 0 && std::isalpha(static_cast(compact[i - 1]))) { + continue; + } + + size_t pos = i + 1; + bool is_oword = false; + if (pos < compact.size() && compact[pos] == '<') { + const size_t close = compact.find('>', pos + 1); + if (close != std::string::npos) { + is_oword = true; + } + } else if (pos < compact.size() && + std::isdigit(static_cast(compact[pos]))) { + is_oword = true; + } + + if (is_oword && i != leading_start) { + if (error) { + *error = "Bad character 'o' used"; + } + return false; + } + } + return true; +} + +bool validate_linuxcnc_nonleading_m98_items(const std::string &line, std::string *error) { + const std::string compact = remove_linuxcnc_reader_whitespace(line); + const size_t leading_start = leading_special_word_start(line); + for (size_t i = 0; i + 2 < compact.size(); ++i) { + if (std::toupper(static_cast(compact[i])) != 'M' || + compact[i + 1] != '9' || + compact[i + 2] != '8') { + continue; + } + if (i != leading_start) { + if (error) { + *error = "Unknown m code used: M98"; + } + return false; + } + } + return true; +} + bool validate_linuxcnc_top_level_reader_symbols(const std::string &line, std::string *error) { const auto &reader_symbols = linuxcnc_reader_symbols(); for (size_t i = 0; i < line.size();) { @@ -642,7 +1015,7 @@ bool validate_linuxcnc_top_level_reader_symbols(const std::string &line, std::st } if (is_linuxcnc_midline_n_word(line, i)) { if (error) { - *error = "Bad character 'N' used"; + *error = "Bad character 'n' used"; } return false; } @@ -707,12 +1080,6 @@ bool parse_word_list_impl(const std::string &line, ParsedWordList *parsed, std:: } return false; } - if (strict && ((radius_word && parsed->radius_flag) || (!radius_word && parsed->theta_flag))) { - if (error) { - *error = radius_word ? "multiple @ words on one line" : "multiple ^ words on one line"; - } - return false; - } if (radius_word) { parsed->radius_flag = true; parsed->radius_value = value; @@ -750,7 +1117,7 @@ bool parse_word_list_impl(const std::string &line, ParsedWordList *parsed, std:: } if (strict && parsed->dollar_flag) { if (error) { - *error = "multiple $ words on one line"; + *error = "Multiple spindle choice ($) words on one line"; } return false; } @@ -779,7 +1146,7 @@ bool parse_word_list_impl(const std::string &line, ParsedWordList *parsed, std:: } if (letter_pos != 0) { if (error) { - *error = "Bad character 'N' used"; + *error = "Bad character 'n' used"; } return false; } @@ -824,7 +1191,8 @@ bool parse_word_list_impl(const std::string &line, ParsedWordList *parsed, std:: const size_t letter_index = static_cast(letter - 'A'); if (strict && seen_letters[letter_index]) { if (error) { - *error = "multiple " + std::string(1, letter) + " words on one line"; + *error = "Multiple " + std::string(1, static_cast(std::tolower(static_cast(letter)))) + + " words on one line"; } return false; } @@ -917,33 +1285,113 @@ std::string numeric_o_identifier(int number) { return "N:" + std::to_string(number); } -std::string first_o_identifier(const std::string &line, const std::vector &word_list) { - for (size_t i = 0; i < line.size(); ++i) { - if (std::toupper(static_cast(line[i])) != 'O') { - continue; - } - size_t pos = i + 1; - while (pos < line.size() && std::isspace(static_cast(line[pos]))) { +std::string compact_linuxcnc_oword_line(const std::string &line) { + return remove_linuxcnc_reader_whitespace(line); +} + +size_t leading_oword_start(const std::string &line) { + const std::string compact = compact_linuxcnc_oword_line(line); + size_t pos = leading_special_word_start(line); + if (pos >= compact.size() || + (compact[pos] != 'O' && compact[pos] != 'o')) { + return std::string::npos; + } + return pos; +} + +size_t leading_special_word_start(const std::string &line) { + const std::string compact = compact_linuxcnc_oword_line(line); + size_t pos = 0; + if (pos < compact.size() && compact[pos] == '/') { + ++pos; + } + if (pos < compact.size() && + (compact[pos] == 'N' || compact[pos] == 'n')) { + ++pos; + const size_t digits_start = pos; + while (pos < compact.size() && std::isdigit(static_cast(compact[pos]))) { ++pos; } - if (pos >= line.size() || line[pos] != '<') { + if (pos == digits_start) { + return std::string::npos; + } + } + return pos; +} + +bool is_numeric_m98_subprogram_header(const std::string &line) { + const std::string compact = compact_linuxcnc_oword_line(line); + for (size_t start = 0; start < compact.size(); ++start) { + if (compact[start] != 'O' && compact[start] != 'o') { continue; } - const size_t close = line.find('>', pos + 1); + if (start > 0 && std::isalpha(static_cast(compact[start - 1]))) { + continue; + } + size_t pos = start + 1; + const size_t digits_start = pos; + while (pos < compact.size() && + std::isdigit(static_cast(compact[pos]))) { + ++pos; + } + if (pos > digits_start && pos == compact.size()) { + return true; + } + } + return false; +} + +std::string numeric_m98_subprogram_identifier(const std::string &line) { + const std::string compact = compact_linuxcnc_oword_line(line); + for (size_t start = 0; start < compact.size(); ++start) { + if (compact[start] != 'O' && compact[start] != 'o') { + continue; + } + if (start > 0 && std::isalpha(static_cast(compact[start - 1]))) { + continue; + } + size_t pos = start + 1; + const size_t digits_start = pos; + while (pos < compact.size() && + std::isdigit(static_cast(compact[pos]))) { + ++pos; + } + if (pos > digits_start && pos == compact.size()) { + return numeric_o_identifier(std::atoi(compact.c_str() + digits_start)); + } + } + return {}; +} + +std::string first_o_identifier(const std::string &line, const std::vector &word_list) { + (void)word_list; + const std::string compact = compact_linuxcnc_oword_line(line); + const size_t start = leading_oword_start(line); + if (start == std::string::npos || start + 1 >= compact.size()) { + return {}; + } + size_t pos = start + 1; + if (compact[pos] == '<') { + const size_t close = compact.find('>', pos + 1); if (close == std::string::npos) { - continue; + return {}; } - const std::string name = normalize_named_parameter(line.substr(pos + 1, close - pos - 1)); + const std::string name = normalize_named_parameter(compact.substr(pos + 1, close - pos - 1)); if (!name.empty()) { return "S:" + name; } + return {}; } - int number = 0; - if (first_o_number(word_list, &number)) { - return numeric_o_identifier(number); + size_t number_end = pos; + while (number_end < compact.size() && + std::isdigit(static_cast(compact[number_end]))) { + ++number_end; } - return {}; + if (number_end == pos) { + return {}; + } + return numeric_o_identifier(std::atoi(compact.c_str() + pos)); } bool has_m_code(const std::vector &word_list, int code) { @@ -973,19 +1421,19 @@ bool validate_linuxcnc_g_code_value(double value, std::string *error) { code = static_cast(std::ceil(scaled)); } else if (fraction > 0.001) { if (error) { - *error = "G code out of range"; + *error = "G-code out of range"; } return false; } if (code > 999) { if (error) { - *error = "G code out of range"; + *error = "G-code out of range"; } return false; } if (code < 0) { if (error) { - *error = "Negative G code used"; + *error = "Negative g code used"; } return false; } @@ -1065,7 +1513,7 @@ bool check_linuxcnc_modal_group_conflicts(const std::vector &word_list, st const int mode = supported_linuxcnc_g_modal_group(word.value); if (mode < 0) { if (error) { - *error = "Unknown G code used"; + *error = "Unknown g code used"; } return false; } @@ -1073,7 +1521,7 @@ bool check_linuxcnc_modal_group_conflicts(const std::vector &word_list, st const int previous_code = g_modes[static_cast(mode)]; if (previous_code != -1 && previous_code != 800 && code != 800) { if (error) { - *error = "Two G codes used from same modal group"; + *error = "Two g codes used from same modal group"; } return false; } @@ -1087,7 +1535,7 @@ bool check_linuxcnc_modal_group_conflicts(const std::vector &word_list, st } if (m_modes[static_cast(mode)]) { if (error) { - *error = "Two M codes used from same modal group"; + *error = "Two m codes used from same modal group"; } return false; } @@ -1113,88 +1561,166 @@ bool has_o_code_keyword(const std::string &line, const char *keyword) { } OwordKind oword_kind(const std::string &line) { - for (size_t i = 0; i < line.size(); ++i) { - if (std::toupper(static_cast(line[i])) != 'O') { - continue; - } - size_t pos = i + 1; - while (pos < line.size() && std::isspace(static_cast(line[pos]))) { - ++pos; - } - if (pos < line.size() && line[pos] == '<') { - const size_t close = line.find('>', pos + 1); - if (close == std::string::npos) { - return OwordKind::None; - } - pos = close + 1; - } else { - bool saw_digit = false; - while (pos < line.size() && std::isdigit(static_cast(line[pos]))) { - saw_digit = true; - ++pos; - } - if (!saw_digit) { - return OwordKind::None; - } - } - while (pos < line.size() && std::isspace(static_cast(line[pos]))) { - ++pos; - } - const size_t start = pos; - while (pos < line.size() && std::isalpha(static_cast(line[pos]))) { - ++pos; - } - const std::string keyword = lower_compact(line.substr(start, pos - start)); - if (keyword == "if") { - return OwordKind::If; - } - if (keyword == "elseif") { - return OwordKind::ElseIf; - } - if (keyword == "else") { - return OwordKind::Else; - } - if (keyword == "endif") { - return OwordKind::EndIf; - } - if (keyword == "do") { - return OwordKind::Do; - } - if (keyword == "while") { - return OwordKind::While; - } - if (keyword == "endwhile") { - return OwordKind::EndWhile; - } - if (keyword == "repeat") { - return OwordKind::Repeat; - } - if (keyword == "endrepeat") { - return OwordKind::EndRepeat; - } - if (keyword == "break") { - return OwordKind::Break; - } - if (keyword == "continue") { - return OwordKind::Continue; - } - if (keyword == "call") { - return OwordKind::Call; - } - if (keyword == "return") { - return OwordKind::Return; - } - if (keyword == "sub") { - return OwordKind::Sub; - } - if (keyword == "endsub") { - return OwordKind::EndSub; - } + const std::string compact = compact_linuxcnc_oword_line(line); + const size_t start = leading_oword_start(line); + if (start == std::string::npos) { return OwordKind::None; } + size_t pos = start + 1; + if (pos < compact.size() && compact[pos] == '<') { + const size_t close = compact.find('>', pos + 1); + if (close == std::string::npos) { + return OwordKind::None; + } + pos = close + 1; + } else { + const size_t digits_start = pos; + while (pos < compact.size() && + std::isdigit(static_cast(compact[pos]))) { + ++pos; + } + if (pos == digits_start) { + return OwordKind::None; + } + } + const std::string tail = lower_compact(compact.substr(pos)); + if (tail.rfind("elseif", 0) == 0) { + return OwordKind::ElseIf; + } + if (tail.rfind("endif", 0) == 0) { + return OwordKind::EndIf; + } + if (tail.rfind("endwhile", 0) == 0) { + return OwordKind::EndWhile; + } + if (tail.rfind("endrepeat", 0) == 0) { + return OwordKind::EndRepeat; + } + if (tail.rfind("endsub", 0) == 0) { + return OwordKind::EndSub; + } + if (tail.rfind("return", 0) == 0) { + return OwordKind::Return; + } + if (tail.rfind("repeat", 0) == 0) { + return OwordKind::Repeat; + } + if (tail.rfind("while", 0) == 0) { + return OwordKind::While; + } + if (tail.rfind("break", 0) == 0) { + return OwordKind::Break; + } + if (tail.rfind("continue", 0) == 0) { + return OwordKind::Continue; + } + if (tail.rfind("call", 0) == 0) { + return OwordKind::Call; + } + if (tail.rfind("sub", 0) == 0) { + return OwordKind::Sub; + } + if (tail.rfind("else", 0) == 0) { + return OwordKind::Else; + } + if (tail.rfind("if", 0) == 0) { + return OwordKind::If; + } + if (tail.rfind("do", 0) == 0) { + return OwordKind::Do; + } return OwordKind::None; } +bool consume_linuxcnc_oword_bracket_argument(const std::string &line, size_t *pos) { + while (*pos < line.size() && std::isspace(static_cast(line[*pos]))) { + ++(*pos); + } + if (*pos >= line.size() || line[*pos] != '[') { + return false; + } + const size_t close = find_matching_square_bracket(line, *pos); + if (close == std::string::npos) { + return false; + } + *pos = close + 1; + return true; +} + +bool validate_linuxcnc_oword_tail(const std::string &line, + OwordKind kind, + std::string *error) { + size_t pos = 0; + while (pos < line.size() && std::isspace(static_cast(line[pos]))) { + ++pos; + } + if (pos < line.size() && line[pos] == '/') { + ++pos; + } + if (pos < line.size() && (line[pos] == 'N' || line[pos] == 'n')) { + ++pos; + while (pos < line.size() && std::isdigit(static_cast(line[pos]))) { + ++pos; + } + } + while (pos < line.size() && std::isspace(static_cast(line[pos]))) { + ++pos; + } + if (pos >= line.size() || (line[pos] != 'O' && line[pos] != 'o')) { + return true; + } + ++pos; + if (pos < line.size() && line[pos] == '<') { + const size_t close = line.find('>', pos + 1); + if (close == std::string::npos) { + return true; + } + pos = close + 1; + } else { + while (pos < line.size() && std::isdigit(static_cast(line[pos]))) { + ++pos; + } + } + while (pos < line.size() && std::isspace(static_cast(line[pos]))) { + ++pos; + } + while (pos < line.size() && std::isalpha(static_cast(line[pos]))) { + ++pos; + } + + switch (kind) { + case OwordKind::If: + case OwordKind::ElseIf: + case OwordKind::While: + case OwordKind::Repeat: + if (!consume_linuxcnc_oword_bracket_argument(line, &pos)) { + break; + } + break; + case OwordKind::Call: + while (consume_linuxcnc_oword_bracket_argument(line, &pos)) { + } + break; + case OwordKind::Return: + case OwordKind::EndSub: + consume_linuxcnc_oword_bracket_argument(line, &pos); + break; + default: + break; + } + + while (pos < line.size() && std::isspace(static_cast(line[pos]))) { + ++pos; + } + if (pos != line.size()) { + if (error) { + *error = "Unexpected character after O-word"; + } + return false; + } + return true; +} + bool parameter_value(const std::vector &call_stack, const std::unordered_map ¶meters, int index, @@ -1312,7 +1838,13 @@ bool truthy(double value) { } bool is_smoke_readonly_named_parameter(const std::string &name) { - return name == "_task" || + return name == "_vmajor" || + name == "_VMAJOR" || + name == "_vminor" || + name == "_VMINOR" || + name == "_metric_machine" || + name == "_METRIC_MACHINE" || + name == "_task" || name == "_TASK" || name == "_LATHE_DIAMETER_MODE" || name == "_lathe_diameter_mode" || @@ -1458,7 +1990,19 @@ public: return ok_ && pos_ == text_.size() && std::isfinite(*value); } + const std::string &error() const { + return error_; + } + private: + double fail(const char *message) { + ok_ = false; + if (error_.empty()) { + error_ = message; + } + return 0.0; + } + double parse_logical() { double value = parse_comparison(); while (ok_) { @@ -1716,12 +2260,18 @@ private: double parse_parameter_reference() { if (consume('#')) { const double index_value = parse_parameter_reference(); - double value = 0.0; - if (!ok_ || !indexed_parameter_value(call_stack_, parameters_, index_value, &value)) { - ok_ = false; + if (!ok_) { return 0.0; } - return value; + int index = 0; + const LinuxCncIntegerReadResult integer_result = linuxcnc_integer_from_real(index_value, &index); + if (integer_result == LinuxCncIntegerReadResult::NonInteger) { + return fail("Non integer value for integer"); + } + if (integer_result != LinuxCncIntegerReadResult::Ok) { + return fail(""); + } + return parameter_reference_value(index); } if (consume('[')) { const double index_value = parse_logical(); @@ -1729,12 +2279,15 @@ private: ok_ = false; return 0.0; } - double value = 0.0; - if (!indexed_parameter_value(call_stack_, parameters_, index_value, &value)) { - ok_ = false; - return 0.0; + int index = 0; + const LinuxCncIntegerReadResult integer_result = linuxcnc_integer_from_real(index_value, &index); + if (integer_result == LinuxCncIntegerReadResult::NonInteger) { + return fail("Non integer value for integer"); } - return value; + if (integer_result != LinuxCncIntegerReadResult::Ok) { + return fail(""); + } + return parameter_reference_value(index); } if (consume('<')) { const size_t start = pos_; @@ -1759,13 +2312,13 @@ private: return value; } const double index_value = parse_primary(); - const int index = static_cast(std::floor(index_value)); - if (index_value - index > 0.9999) { - return parameter_reference_value(static_cast(std::ceil(index_value))); + int index = 0; + const LinuxCncIntegerReadResult integer_result = linuxcnc_integer_from_real(index_value, &index); + if (integer_result == LinuxCncIntegerReadResult::NonInteger) { + return fail("Non integer value for integer"); } - if (index_value - index > 0.0001) { - ok_ = false; - return 0.0; + if (integer_result != LinuxCncIntegerReadResult::Ok) { + return fail(""); } return parameter_reference_value(index); } @@ -1825,7 +2378,14 @@ private: ok_ = false; return 0.0; } - const int index = rounded_value(index_value); + int index = 0; + const LinuxCncIntegerReadResult integer_result = linuxcnc_integer_from_real(index_value, &index); + if (integer_result == LinuxCncIntegerReadResult::NonInteger) { + return fail("Non integer value for integer"); + } + if (integer_result != LinuxCncIntegerReadResult::Ok) { + return fail(""); + } return index >= 1 && index < kMaxNumberedParameter ? 1.0 : 0.0; } @@ -1883,15 +2443,29 @@ private: const std::unordered_map &named_parameters_; size_t pos_ = 0; bool ok_ = true; + std::string error_; }; bool evaluate_expression(const std::string &text, const std::vector &call_stack, const std::unordered_map ¶meters, const std::unordered_map &named_parameters, - double *value) { + double *value, + std::string *error) { ExpressionParser parser(text, call_stack, parameters, named_parameters); - return parser.parse(value); + const bool ok = parser.parse(value); + if (!ok && error && !parser.error().empty()) { + *error = parser.error(); + } + return ok; +} + +bool evaluate_expression(const std::string &text, + const std::vector &call_stack, + const std::unordered_map ¶meters, + const std::unordered_map &named_parameters, + double *value) { + return evaluate_expression(text, call_stack, parameters, named_parameters, value, nullptr); } bool evaluate_oword_expression(const std::string &line, @@ -1915,13 +2489,15 @@ bool evaluate_oword_expression(const std::string &line, return false; } double value = 0.0; + std::string expression_error; if (!evaluate_expression(line.substr(open + 1, close - open - 1), call_stack, parameters, named_parameters, - &value)) { + &value, + &expression_error)) { if (error) { - *error = "unsupported O-word expression"; + *error = expression_error.empty() ? "unsupported O-word expression" : expression_error; } return false; } @@ -1992,60 +2568,29 @@ bool is_loop_end(OwordKind kind) { } bool validate_numeric_oword_main_structure(const std::vector &lines, std::string *error) { - bool numeric_main_active = false; - bool main_terminated = false; - for (const auto &source_line : lines) { - const std::string stripped = trim(strip_comments(source_line.raw, nullptr)); - if (stripped.empty()) { - continue; - } - if (stripped == "%") { - if (numeric_main_active) { - numeric_main_active = false; - main_terminated = true; - } - continue; - } - const auto word_list = parse_word_list(stripped); - const std::string id = first_o_identifier(stripped, word_list); - const OwordKind kind = oword_kind(stripped); - if (has_m_code(word_list, 2) || has_m_code(word_list, 30)) { - numeric_main_active = false; - main_terminated = true; - continue; - } - if (!id.empty() && id.rfind("N:", 0) == 0 && - kind == OwordKind::None && - !numeric_main_active && - !main_terminated) { - numeric_main_active = true; - continue; - } - if (numeric_main_active && kind == OwordKind::Sub) { - if (error) { - *error = "numeric O-word subprogram found before main program end"; - } - return false; - } - } - if (numeric_main_active) { - if (error) { - *error = "numeric O-word main program ended without M2, M30, or %"; - } - return false; - } + (void)lines; + (void)error; return true; } bool validate_no_nested_subprogram_definitions(const std::vector &lines, std::string *error) { for (size_t i = 0; i < lines.size(); ++i) { + if (raw_line_has_leading_paren_comment(lines[i].raw)) { + continue; + } const std::string stripped = strip_comments(lines[i].raw, nullptr); const auto words = parse_word_list(stripped); const std::string id = first_o_identifier(stripped, words); - if (id.empty() || oword_kind(stripped) != OwordKind::Sub) { + const OwordKind kind = oword_kind(stripped); + const bool numeric_header = is_numeric_m98_subprogram_header(stripped); + const bool subprogram_header = (kind == OwordKind::Sub); + if (id.empty() || !subprogram_header) { continue; } for (size_t j = i + 1; j < lines.size(); ++j) { + if (raw_line_has_leading_paren_comment(lines[j].raw)) { + continue; + } const std::string nested_line = strip_comments(lines[j].raw, nullptr); const auto nested_words = parse_word_list(nested_line); const std::string nested_id = first_o_identifier(nested_line, nested_words); @@ -2055,7 +2600,7 @@ bool validate_no_nested_subprogram_definitions(const std::vector &li break; } if (nested_kind == OwordKind::Sub || - (!nested_id.empty() && nested_id.rfind("N:", 0) == 0 && nested_kind == OwordKind::None)) { + (is_numeric_m98_subprogram_header(nested_line) && !numeric_header)) { if (error) { *error = "Nested subroutine definition"; } @@ -2323,6 +2868,69 @@ bool parameter_reference_index_span(const std::string &line, size_t start, size_ return unary_function_span(line, expr_start, end); } +bool linuxcnc_real_value_span(const std::string &line, size_t start, size_t *end) { + if (start >= line.size()) { + return false; + } + const char c = line[start]; + const char c1 = start + 1 < line.size() ? line[start + 1] : '\0'; + if ((c == '+' || c == '-') && c1 != '\0' && + !std::isdigit(static_cast(c1)) && c1 != '.') { + return linuxcnc_real_value_span(line, start + 1, end); + } + if (c == '[') { + const size_t close = find_matching_square_bracket(line, start); + if (close == std::string::npos) { + return false; + } + *end = close + 1; + return true; + } + if (c == '#') { + size_t pos = start + 1; + if (pos >= line.size()) { + return false; + } + if (line[pos] == '<') { + const size_t close = line.find('>', pos + 1); + if (close == std::string::npos) { + return false; + } + *end = close + 1; + return true; + } + if (line[pos] == '[') { + const size_t close = find_matching_square_bracket(line, pos); + if (close == std::string::npos) { + return false; + } + *end = close + 1; + return true; + } + if (line[pos] == '#') { + return linuxcnc_real_value_span(line, pos, end); + } + while (pos < line.size() && std::isdigit(static_cast(line[pos]))) { + ++pos; + } + if (pos > start + 1) { + *end = pos; + return true; + } + return parameter_reference_index_span(line, start + 1, end); + } + if (std::isalpha(static_cast(c))) { + return unary_function_span(line, start, end); + } + double value = 0.0; + size_t consumed = 0; + if (!parse_linuxcnc_real_value(line.c_str() + start, &value, &consumed)) { + return false; + } + *end = start + consumed; + return true; +} + std::string expand_expressions_and_parameters(const std::string &line, const std::vector &call_stack, const std::unordered_map ¶meters, @@ -2358,13 +2966,15 @@ std::string expand_expressions_and_parameters(const std::string &line, return {}; } double value = 0.0; + std::string expression_error; if (!evaluate_expression(line.substr(value_start, close - value_start + 1), call_stack, parameters, named_parameters, - &value)) { + &value, + &expression_error)) { if (error) { - *error = "unsupported bracket expression"; + *error = expression_error.empty() ? "unsupported bracket expression" : expression_error; } return {}; } @@ -2376,13 +2986,15 @@ std::string expand_expressions_and_parameters(const std::string &line, size_t function_end = 0; if (unary_function_span(line, function_start, &function_end)) { double value = 0.0; + std::string expression_error; if (!evaluate_expression(line.substr(value_start, function_end - value_start), call_stack, parameters, named_parameters, - &value)) { + &value, + &expression_error)) { if (error) { - *error = "unsupported word unary function expression"; + *error = expression_error.empty() ? "unsupported word unary function expression" : expression_error; } return {}; } @@ -2401,9 +3013,15 @@ std::string expand_expressions_and_parameters(const std::string &line, return {}; } double value = 0.0; - if (!evaluate_expression(line.substr(i + 1, close - i - 1), call_stack, parameters, named_parameters, &value)) { + std::string expression_error; + if (!evaluate_expression(line.substr(i + 1, close - i - 1), + call_stack, + parameters, + named_parameters, + &value, + &expression_error)) { if (error) { - *error = "unsupported bracket expression"; + *error = expression_error.empty() ? "unsupported bracket expression" : expression_error; } return {}; } @@ -2440,14 +3058,34 @@ std::string expand_expressions_and_parameters(const std::string &line, } end = close + 1; } else { - while (end < line.size() && std::isdigit(static_cast(line[end]))) { - ++end; + int ignored_index = 0; + size_t index_end = end; + const LinuxCncIntegerReadResult integer_result = + read_linuxcnc_integer_value(line, end, &ignored_index, &index_end); + if (integer_result == LinuxCncIntegerReadResult::NonInteger) { + if (error) { + *error = "Non integer value for integer"; + } + return {}; + } + if (integer_result != LinuxCncIntegerReadResult::Ok) { + while (end < line.size() && std::isdigit(static_cast(line[end]))) { + ++end; + } + } else { + end = index_end; } } double value = 0.0; - if (!evaluate_expression(line.substr(i, end - i), call_stack, parameters, named_parameters, &value)) { + std::string expression_error; + if (!evaluate_expression(line.substr(i, end - i), + call_stack, + parameters, + named_parameters, + &value, + &expression_error)) { if (error) { - *error = "unsupported indirect parameter expression"; + *error = expression_error.empty() ? "unsupported indirect parameter expression" : expression_error; } return {}; } @@ -2464,18 +3102,34 @@ std::string expand_expressions_and_parameters(const std::string &line, return {}; } double index_value = 0.0; + std::string expression_error; if (!evaluate_expression(line.substr(j + 1, close - j - 1), call_stack, parameters, named_parameters, - &index_value)) { + &index_value, + &expression_error)) { + if (error) { + *error = expression_error.empty() ? "unsupported indexed parameter expression" : expression_error; + } + return {}; + } + int index = 0; + const LinuxCncIntegerReadResult integer_result = linuxcnc_integer_from_real(index_value, &index); + if (integer_result == LinuxCncIntegerReadResult::NonInteger) { + if (error) { + *error = "Non integer value for integer"; + } + return {}; + } + if (integer_result != LinuxCncIntegerReadResult::Ok) { if (error) { *error = "unsupported indexed parameter expression"; } return {}; } double value = 0.0; - if (!indexed_parameter_value(call_stack, parameters, index_value, &value)) { + if (!parameter_value(call_stack, parameters, index, &value)) { if (error) { *error = "indexed parameter is out of range"; } @@ -2520,18 +3174,34 @@ std::string expand_expressions_and_parameters(const std::string &line, return {}; } double index_value = 0.0; + std::string expression_error; if (!evaluate_expression(line.substr(j, expression_end - j), call_stack, parameters, named_parameters, - &index_value)) { + &index_value, + &expression_error)) { + if (error) { + *error = expression_error.empty() ? "unsupported parameter expression" : expression_error; + } + return {}; + } + int index = 0; + const LinuxCncIntegerReadResult integer_result = linuxcnc_integer_from_real(index_value, &index); + if (integer_result == LinuxCncIntegerReadResult::NonInteger) { + if (error) { + *error = "Non integer value for integer"; + } + return {}; + } + if (integer_result != LinuxCncIntegerReadResult::Ok) { if (error) { *error = "unsupported parameter expression"; } return {}; } double value = 0.0; - if (!indexed_parameter_value(call_stack, parameters, index_value, &value)) { + if (!parameter_value(call_stack, parameters, index, &value)) { if (error) { *error = "indexed parameter is out of range"; } @@ -2541,10 +3211,22 @@ std::string expand_expressions_and_parameters(const std::string &line, i = expression_end; continue; } - while (j < line.size() && std::isdigit(static_cast(line[j]))) { - ++j; + int index = 0; + size_t index_end = j; + const LinuxCncIntegerReadResult integer_result = + read_linuxcnc_integer_value(line, j, &index, &index_end); + if (integer_result == LinuxCncIntegerReadResult::NonInteger) { + if (error) { + *error = "Non integer value for integer"; + } + return {}; + } + if (integer_result != LinuxCncIntegerReadResult::Ok) { + if (error) { + *error = "unsupported parameter expression"; + } + return {}; } - const int index = std::atoi(line.c_str() + i + 1); if (index <= 0 || index >= kMaxNumberedParameter) { if (error) { *error = "numbered parameter is out of range"; @@ -2559,7 +3241,7 @@ std::string expand_expressions_and_parameters(const std::string &line, return {}; } out += number_to_string(value); - i = j; + i = index_end; } return out; } @@ -2638,20 +3320,28 @@ bool parse_parameter_assignment(const std::string &line, return true; } double index_value = 0.0; + std::string expression_error; if (!evaluate_expression(text.substr(pos + 1, close - pos - 1), call_stack, parameters, named_parameters, - &index_value)) { + &index_value, + &expression_error)) { if (error) { - *error = "unsupported indexed parameter assignment expression"; + *error = expression_error.empty() ? "unsupported indexed parameter assignment expression" : expression_error; } return true; } - parsed_index = rounded_value(index_value); - if (std::fabs(index_value - parsed_index) > 0.0001) { + const LinuxCncIntegerReadResult integer_result = linuxcnc_integer_from_real(index_value, &parsed_index); + if (integer_result == LinuxCncIntegerReadResult::NonInteger) { if (error) { - *error = "indexed parameter assignment must evaluate to an integer"; + *error = "Non integer value for integer"; + } + return true; + } + if (integer_result != LinuxCncIntegerReadResult::Ok) { + if (error) { + *error = "unsupported indexed parameter assignment expression"; } return true; } @@ -2664,27 +3354,56 @@ bool parse_parameter_assignment(const std::string &line, pos = close + 1; } - if (pos >= text.size() || !std::isdigit(static_cast(text[pos]))) { + if (parsed_name.empty() && parsed_index == 0) { + size_t index_end = pos; + const LinuxCncIntegerReadResult integer_result = + read_linuxcnc_integer_value(text, pos, &parsed_index, &index_end); + if (integer_result == LinuxCncIntegerReadResult::Ok) { + if (parsed_index <= 0 || parsed_index >= kMaxNumberedParameter) { + if (error) { + *error = "numbered parameter assignment is out of range"; + } + return true; + } + pos = index_end; + } else if (integer_result == LinuxCncIntegerReadResult::NonInteger) { + if (error) { + *error = "Non integer value for integer"; + } + return true; + } + } + + if (parsed_name.empty() && parsed_index == 0 && + (pos >= text.size() || !std::isdigit(static_cast(text[pos])))) { if (parsed_name.empty() && parsed_index == 0) { size_t expression_end = 0; if (!parameter_reference_index_span(text, pos, &expression_end)) { return false; } double index_value = 0.0; + std::string expression_error; if (!evaluate_expression(text.substr(pos, expression_end - pos), call_stack, parameters, named_parameters, - &index_value)) { + &index_value, + &expression_error)) { if (error) { - *error = "unsupported indexed parameter assignment expression"; + *error = expression_error.empty() ? "unsupported indexed parameter assignment expression" : expression_error; } return true; } - parsed_index = rounded_value(index_value); - if (std::fabs(index_value - parsed_index) > 0.0001) { + const LinuxCncIntegerReadResult integer_result = linuxcnc_integer_from_real(index_value, &parsed_index); + if (integer_result == LinuxCncIntegerReadResult::NonInteger) { if (error) { - *error = "indexed parameter assignment must evaluate to an integer"; + *error = "Non integer value for integer"; + } + return true; + } + if (integer_result != LinuxCncIntegerReadResult::Ok) { + if (error) { + *error = "unsupported indexed parameter assignment expression"; } return true; } @@ -2697,18 +3416,6 @@ bool parse_parameter_assignment(const std::string &line, pos = expression_end; } } - if (parsed_name.empty() && parsed_index == 0) { - while (pos < text.size() && std::isdigit(static_cast(text[pos]))) { - parsed_index = parsed_index * 10 + (text[pos] - '0'); - ++pos; - } - if (parsed_index <= 0 || parsed_index >= kMaxNumberedParameter) { - if (error) { - *error = "numbered parameter assignment is out of range"; - } - return true; - } - } while (pos < text.size() && std::isspace(static_cast(text[pos]))) { ++pos; } @@ -2736,19 +3443,38 @@ bool parse_parameter_assignment(const std::string &line, } return true; } - rhs = text.substr(pos + 1, close - pos - 1); + rhs = remove_linuxcnc_reader_whitespace(text.substr(pos + 1, close - pos - 1)); value_end = close + 1; } else { - while (value_end < text.size() && !std::isspace(static_cast(text[value_end]))) { - ++value_end; + std::string compact_rhs; + std::vector compact_to_text; + compact_rhs.reserve(text.size() - pos); + compact_to_text.reserve(text.size() - pos); + for (size_t i = pos; i < text.size(); ++i) { + if (std::isspace(static_cast(text[i]))) { + continue; + } + compact_to_text.push_back(i); + compact_rhs.push_back(text[i]); } - rhs = text.substr(pos, value_end - pos); + size_t compact_value_end = 0; + if (!linuxcnc_real_value_span(compact_rhs, 0, &compact_value_end) || + compact_value_end == 0 || + compact_value_end > compact_to_text.size()) { + if (error) { + *error = "unsupported parameter assignment expression"; + } + return true; + } + rhs = compact_rhs.substr(0, compact_value_end); + value_end = compact_to_text[compact_value_end - 1] + 1; } double parsed_value = 0.0; - if (!evaluate_expression(rhs, call_stack, parameters, named_parameters, &parsed_value)) { + std::string expression_error; + if (!evaluate_expression(rhs, call_stack, parameters, named_parameters, &parsed_value, &expression_error)) { if (error) { - *error = "unsupported parameter assignment expression"; + *error = expression_error.empty() ? "unsupported parameter assignment expression" : expression_error; } return true; } @@ -2840,17 +3566,47 @@ bool extract_parameter_assignments(const std::string &line, std::unordered_map discover_subprograms(const std::vector &lines, std::unordered_map *by_header) { std::unordered_map subprograms; + std::unordered_set referenced_numeric_subprograms; + for (const auto &source_line : lines) { + const std::string stripped = strip_comments(source_line.raw, nullptr); + const auto words = parse_word_list(stripped); + if (!has_m_code(words, 98)) { + continue; + } + int sub_number = 0; + std::unordered_map last_words = last_words_by_letter(words); + if (!integer_word_value(last_words, 'P', &sub_number)) { + continue; + } + referenced_numeric_subprograms.insert(numeric_o_identifier(sub_number)); + } + for (size_t i = 0; i < lines.size(); ++i) { const std::string stripped = strip_comments(lines[i].raw, nullptr); + const bool comment_prefixed = raw_line_has_leading_paren_comment(lines[i].raw); + if (comment_prefixed && + !is_numeric_m98_subprogram_header(stripped)) { + continue; + } const auto words = parse_word_list(stripped); - const std::string id = first_o_identifier(stripped, words); + std::string id = first_o_identifier(stripped, words); + if (id.empty() && is_numeric_m98_subprogram_header(stripped)) { + id = numeric_m98_subprogram_identifier(stripped); + } if (id.empty()) { continue; } + if (comment_prefixed && + referenced_numeric_subprograms.find(id) == referenced_numeric_subprograms.end()) { + continue; + } const OwordKind kind = oword_kind(stripped); if (kind == OwordKind::Sub) { for (size_t j = i + 1; j < lines.size(); ++j) { + if (raw_line_has_leading_paren_comment(lines[j].raw)) { + continue; + } const std::string end_line = strip_comments(lines[j].raw, nullptr); const auto end_words = parse_word_list(end_line); const std::string end_id = first_o_identifier(end_line, end_words); @@ -2865,20 +3621,32 @@ std::unordered_map discover_subprograms(const std::vect } continue; } - if (kind != OwordKind::None) { + if (!is_numeric_m98_subprogram_header(stripped)) { continue; } + bool found_numeric_end = false; for (size_t j = i + 1; j < lines.size(); ++j) { + if (raw_line_has_leading_paren_comment(lines[j].raw)) { + continue; + } const std::string end_line = strip_comments(lines[j].raw, nullptr); - if (has_m_code(parse_word_list(end_line), 99)) { + const auto end_words = parse_word_list(end_line); + const std::string end_id = first_o_identifier(end_line, end_words); + const OwordKind end_kind = oword_kind(end_line); + if (has_m_code(end_words, 99) || + ((end_kind == OwordKind::EndSub || end_kind == OwordKind::Return) && end_id == id)) { Subprogram subprogram{i, i + 1, j, false}; subprograms[id] = subprogram; (*by_header)[i] = subprogram; i = j; + found_numeric_end = true; break; } } + if (comment_prefixed && !found_numeric_end) { + continue; + } } return subprograms; } @@ -2909,6 +3677,27 @@ bool has_rotary_word(const std::unordered_map &words) { words.find('C') != words.end(); } +char first_rotary_word(const std::unordered_map &words) { + if (words.find('A') != words.end()) { + return 'A'; + } + if (words.find('B') != words.end()) { + return 'B'; + } + if (words.find('C') != words.end()) { + return 'C'; + } + return '\0'; +} + +std::string rotary_cycle_axis_error(char axis) { + if (axis == 'A') { + return "Cannot put an a in canned cycle"; + } + return "Cannot put a " + std::string(1, static_cast(std::tolower(static_cast(axis)))) + + " in canned cycle"; +} + bool has_uvw_word(const std::unordered_map &words) { return words.find('U') != words.end() || words.find('V') != words.end() || @@ -2943,6 +3732,44 @@ CycleAxes cycle_axes_for_plane(int plane) { return CycleAxes{'X', 'Y', 'Z', &CncSimPose::x, &CncSimPose::y, &CncSimPose::z}; } +const char *canned_cycle_missing_depth_error(int plane) { + switch (plane) { + case 17: + return "Z value unspecified in xy plane canned cycle"; + case 18: + return "Y value unspecified in xz plane canned cycle"; + case 19: + return "X value unspecified in yz plane canned cycle"; + case 171: + return "W value unspecified in UV plane canned cycle"; + case 181: + return "V value unspecified in UW plane canned cycle"; + case 191: + return "U value unspecified in VW plane canned cycle"; + default: + return "canned cycle requires R and Z words before motion"; + } +} + +const char *canned_cycle_r_less_than_depth_error(int plane) { + switch (plane) { + case 17: + return "R less than z in cycle in xy plane"; + case 18: + return "R less than y in cycle in xz plane"; + case 19: + return "R less than x in cycle in yz plane"; + case 171: + return "R less than W in cycle in UV plane"; + case 181: + return "R less than V in cycle in UW plane"; + case 191: + return "R less than U in cycle in VW plane"; + default: + return "canned cycle R plane must not be below Z depth"; + } +} + struct G87CycleValues { double first_offset; double second_offset; @@ -3005,17 +3832,41 @@ bool has_cycle_position_word_for_plane(const std::unordered_map &w words.find(axes.second_word) != words.end(); } -bool has_disallowed_cycle_axis_word(const std::unordered_map &words, const CycleAxes &axes) { +char disallowed_cycle_axis_word(const std::unordered_map &words, const CycleAxes &axes) { static constexpr char axes_words[] = {'X', 'Y', 'Z', 'A', 'B', 'C', 'U', 'V', 'W'}; for (char axis : axes_words) { if (axis == axes.first_word || axis == axes.second_word || axis == axes.depth_word) { continue; } if (words.find(axis) != words.end()) { - return true; + return axis; } } - return false; + return '\0'; +} + +const char *canned_cycle_plane_name(int plane) { + switch (plane) { + case 17: + return "XY"; + case 18: + return "XZ"; + case 19: + return "YZ"; + case 171: + return "UV"; + case 181: + return "UW"; + case 191: + return "VW"; + default: + return "invalid"; + } +} + +std::string disallowed_cycle_axis_error(char axis, int plane) { + return "Cannot put a " + std::string(1, axis) + + " in a canned cycle in the " + canned_cycle_plane_name(plane) + " plane"; } double pose_axis(const CncSimPose &pose, double CncSimPose::*member) { @@ -3229,12 +4080,12 @@ bool integer_word_value(const std::unordered_map &words, if (it == words.end()) { return false; } - const double rounded = std::round(it->second); - if (std::fabs(it->second - rounded) > 0.0001) { - return false; - } - *value = static_cast(rounded); - return true; + return linuxcnc_integer_from_real(it->second, value) == LinuxCncIntegerReadResult::Ok; +} + +bool linuxcnc_convert_near_int(double value, int *result) { + *result = static_cast(value + 0.5); + return std::fabs(static_cast(*result) - value) < 0.0001; } int positive_integer_word(const std::unordered_map &words, char letter, int fallback) { @@ -3337,7 +4188,7 @@ bool has_motion_g_code(const std::vector &word_list) { } const int g = rounded_value(word.value); if (g == 0 || g == 1 || g == 2 || g == 3 || - g == 33 || g == 73 || g == 76 || + g == 33 || g == 73 || g == 74 || g == 76 || (g >= 81 && g <= 89)) { return true; } @@ -3819,6 +4670,52 @@ bool modal_motion_accepts_polar_coordinates(int modal_motion) { modal_motion == 722; } +bool modal_motion_accepts_axis_words(int modal_motion) { + return modal_motion == 0 || + modal_motion == 1 || + modal_motion == 2 || + modal_motion == 3 || + modal_motion_is_threading(modal_motion) || + modal_motion_is_threading_cycle(modal_motion) || + modal_motion_is_probe(modal_motion); +} + +bool line_has_axis_word_consumer(const std::vector &word_list, + int modal_motion, + int canned_cycle) { + const bool explicit_g80 = has_g_code(word_list, 80.0); + if (has_g_code(word_list, 10.0) || + has_g_code(word_list, 28.0) || + has_g_code(word_list, 30.0) || + has_g_code(word_list, 52.0) || + has_g_code(word_list, 92.0)) { + return true; + } + if (has_motion_g_code(word_list) && + !std::all_of(word_list.begin(), word_list.end(), [](const Word &word) { + return word.letter != 'G' || g_code_is(word.value, 80.0); + })) { + return true; + } + for (const Word &word : word_list) { + if (word.letter != 'G') { + continue; + } + if (g_code_is(word.value, 5.2) || + g_code_is(word.value, 6.2) || + g_code_is(word.value, 43.1) || + g_code_is(word.value, 43.2) || + linuxcnc_g_code_is_g7x_cycle(word.value)) { + return true; + } + } + if (!explicit_g80 && + (modal_motion_accepts_axis_words(modal_motion) || canned_cycle != 0)) { + return true; + } + return false; +} + bool parsed_word_list_has_polar_coordinate(const ParsedWordList &parsed) { return parsed.radius_flag || parsed.theta_flag; } @@ -3860,47 +4757,6 @@ void emit_tool_length_offset(CanonEventSink &sink, const CncSimPose &offset, int sink.emit_raw(event); } -CncSimPose smoke_tool_table_offset(int h_code, double unit_scale) { - CncSimPose offset{}; - if (h_code == 1) { - offset.z = 12.5 * unit_scale; - } else if (h_code == 4) { - offset.z = 0.7 * unit_scale; - } - return offset; -} - -double smoke_tool_table_diameter(int tool_id, double unit_scale) { - if (tool_id == 1) { - return 6.0 * unit_scale; - } - if (tool_id == 4) { - return 1.0 * unit_scale; - } - return 0.0; -} - -double smoke_tool_table_frontangle(int tool_id) { - if (tool_id == 1) { - return 12.0; - } - return 0.0; -} - -double smoke_tool_table_backangle(int tool_id) { - if (tool_id == 1) { - return 34.0; - } - return 0.0; -} - -double smoke_tool_table_orientation(int tool_id) { - if (tool_id == 1) { - return 5.0; - } - return 0.0; -} - bool load_smoke_parameter_file(const char *path, std::unordered_map *parameters) { if (!path || !*path) { return true; @@ -4005,6 +4861,17 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string m66_result_ = 0.0; oword_return_value_ = 0.0; oword_value_returned_ = false; + tool_table_.clear(); + tool_table_[0] = {}; + tool_table_[1] = {}; + tool_table_[1].offset.z = 12.5; + tool_table_[1].diameter = 6.0; + tool_table_[1].frontangle = 12.0; + tool_table_[1].backangle = 34.0; + tool_table_[1].orientation = 5.0; + tool_table_[4] = {}; + tool_table_[4].offset.z = 0.7; + tool_table_[4].diameter = 1.0; // Match the source-linked backend's initial interpreter state publication. sink_.use_length_units(1.0, 0); @@ -4147,6 +5014,33 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string frame->modal_plane = state.plane; frame->modal_unit_scale = state.unit_scale; }; + auto scaled_tool_offset = [&](int tool_id) { + CncSimPose offset{}; + const auto it = tool_table_.find(tool_id); + if (it == tool_table_.end()) { + return offset; + } + offset = it->second.offset; + offset.x *= sink_.unit_scale(); + offset.y *= sink_.unit_scale(); + offset.z *= sink_.unit_scale(); + offset.a *= sink_.unit_scale(); + offset.b *= sink_.unit_scale(); + offset.c *= sink_.unit_scale(); + offset.u *= sink_.unit_scale(); + offset.v *= sink_.unit_scale(); + offset.w *= sink_.unit_scale(); + return offset; + }; + auto rotate_xy = [](double *x, double *y, double degrees) { + const double radians = degrees_to_radians(degrees); + const double c = std::cos(radians); + const double s = std::sin(radians); + const double rx = *x * c - *y * s; + const double ry = *x * s + *y * c; + *x = rx; + *y = ry; + }; auto refresh_readonly_named_parameters = [&](int line_number) { auto set_pose_parameters = [&](int start_index, const CncSimPose &pose) { parameters[start_index + 0] = pose.x; @@ -4159,6 +5053,10 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string parameters[start_index + 7] = pose.v; parameters[start_index + 8] = pose.w; }; + auto tool_entry = [&](int tool_id) -> const SmokeToolTableEntry * { + const auto it = tool_table_.find(tool_id); + return it == tool_table_.end() ? nullptr : &it->second; + }; named_parameters["_lathe_diameter_mode"] = lathe_diameter_mode_ ? 1.0 : 0.0; named_parameters["_lathe_radius_mode"] = lathe_diameter_mode_ ? 0.0 : 1.0; named_parameters["_LATHE_DIAMETER_MODE"] = lathe_diameter_mode_ ? 1.0 : 0.0; @@ -4235,8 +5133,14 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string named_parameters["_CALL_LEVEL"] = named_parameters["_call_level"]; named_parameters["_remap_level"] = 0.0; named_parameters["_REMAP_LEVEL"] = 0.0; - named_parameters["_task"] = 1.0; - named_parameters["_TASK"] = 1.0; + named_parameters["_vmajor"] = 2.10; + named_parameters["_VMAJOR"] = 2.10; + named_parameters["_vminor"] = 0.0; + named_parameters["_VMINOR"] = 0.0; + named_parameters["_metric_machine"] = -1.0; + named_parameters["_METRIC_MACHINE"] = -1.0; + named_parameters["_task"] = 0.0; + named_parameters["_TASK"] = 0.0; const bool has_tool_offset = tool_length_offset.x != 0.0 || tool_length_offset.y != 0.0 || tool_length_offset.z != 0.0 || @@ -4321,10 +5225,11 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string parameters[5407] = tool_length_offset.u; parameters[5408] = tool_length_offset.v; parameters[5409] = tool_length_offset.w; - parameters[5410] = smoke_tool_table_diameter(spindle_tool_id_, sink_.unit_scale()); - parameters[5411] = smoke_tool_table_frontangle(spindle_tool_id_); - parameters[5412] = smoke_tool_table_backangle(spindle_tool_id_); - parameters[5413] = smoke_tool_table_orientation(spindle_tool_id_); + const SmokeToolTableEntry *spindle_tool_entry = tool_entry(spindle_tool_id_); + parameters[5410] = spindle_tool_entry ? spindle_tool_entry->diameter * sink_.unit_scale() : 0.0; + parameters[5411] = spindle_tool_entry ? spindle_tool_entry->frontangle : 0.0; + parameters[5412] = spindle_tool_entry ? spindle_tool_entry->backangle : 0.0; + parameters[5413] = spindle_tool_entry ? spindle_tool_entry->orientation : 0.0; parameters[5420] = position.x; parameters[5421] = position.y; @@ -4358,8 +5263,18 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string ++pc; const std::string &raw_line = lines[current_pc].raw; const int line_number = lines[current_pc].number; + const size_t raw_first = raw_line.find_first_not_of(" \t"); + if (sink_.block_delete()) { + if (raw_first != std::string::npos && raw_line[raw_first] == '/') { + continue; + } + } std::string comment; std::string line = strip_comments(raw_line, &comment); + if (raw_first != std::string::npos && raw_line[raw_first] == '/') { + const size_t first = line.find_first_not_of(" \t"); + line.erase(first, 1); + } const std::string trimmed_hot_comment = trim(comment); const std::string compact_hot_comment = lower_compact(trimmed_hot_comment); const bool debug_hot_comment = compact_hot_comment.rfind("debug,", 0) == 0; @@ -4428,6 +5343,22 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string } return -1; } + if (!validate_linuxcnc_comment_prefixed_leading_items(raw_line, line, error)) { + return -1; + } + const std::string keyword_line = line; + const bool has_keyword_oword = oword_kind(keyword_line) != OwordKind::None || + !first_o_identifier(keyword_line, parse_word_list(keyword_line)).empty(); + if (!has_keyword_oword) { + if (!validate_linuxcnc_top_level_item_start(line, error) || + !validate_linuxcnc_top_level_parameter_setting_items(line, error) || + !validate_linuxcnc_top_level_unary_items(line, error) || + !validate_linuxcnc_nonleading_oword_items(line, error) || + !validate_linuxcnc_nonleading_m98_items(line, error) || + !validate_linuxcnc_top_level_brackets(line, error)) { + return -1; + } + } const std::string expanded_line = expand_expressions_and_parameters(line, call_stack, parameters, named_parameters, ¶meter_error); if (!parameter_error.empty()) { @@ -4436,8 +5367,7 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string } return -1; } - const std::string keyword_line = line; - line = expanded_line; + line = has_keyword_oword ? expanded_line : remove_linuxcnc_reader_whitespace(expanded_line); if (!validate_linuxcnc_top_level_reader_symbols(line, error)) { return -1; } @@ -4452,6 +5382,10 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string refresh_readonly_named_parameters(line_number); const std::string o_identifier = first_o_identifier(keyword_line, word_list); const bool has_o_word = !o_identifier.empty(); + const OwordKind current_oword_kind = has_o_word ? oword_kind(keyword_line) : OwordKind::None; + if (has_o_word && !validate_linuxcnc_oword_tail(keyword_line, current_oword_kind, error)) { + return -1; + } const std::string trimmed_comment = trim(comment); if (!trimmed_comment.empty()) { const std::string compact_comment = lower_compact(trimmed_comment); @@ -4507,13 +5441,13 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string if (has_h_word) { if (!integer_word_value(words, 'H', &h_word_value)) { if (error) { - *error = "H word must be an integer"; + *error = "Non integer value for integer"; } return -1; } if (h_word_value < -1) { if (error) { - *error = "Negative H word used"; + *error = "Negative h word used"; } return -1; } @@ -4541,7 +5475,7 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string !((previous_canned_cycle == 73 || previous_canned_cycle == 83) && !has_g_code(word_list, 80.0)) && !has_g_code(word_list, 96.0)) { if (error) { - *error = "D word with no G41, G41.1, G42, G42.1, G70, G71, G72, G73, G83 or G96 to use it"; + *error = "D word with no G41, G41.1, G42, G42.1, G71, G71.1, G71.2 G73, G83 or G96 to use it"; } return -1; } @@ -4593,7 +5527,14 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string if (parsed_word_list_has_polar_coordinate(parsed_word_list) && has_g_code(word_list, 80.0) && !mode_zero_uses_axes) { if (error) { - *error = "Cannot use axis values with G80"; + *error = "Cannot use axis values with g80"; + } + return -1; + } + if (has_axis_words && has_g_code(word_list, 80.0) && + !mode_zero_uses_axes && !has_non_g80_motion_g_code) { + if (error) { + *error = "Cannot use axis values with g80"; } return -1; } @@ -4623,7 +5564,16 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string if (parsed_word_list_has_polar_coordinate(parsed_word_list) && (words.count('X') || words.count('Y'))) { if (error) { - *error = "Cannot specify X or Y words with polar coordinate"; + *error = words.count('X') + ? "Cannot specify both polar coordinate and X word" + : "Cannot specify both polar coordinate and Y word"; + } + return -1; + } + if (has_axis_words && + !line_has_axis_word_consumer(word_list, modal_motion, previous_canned_cycle)) { + if (error) { + *error = "Cannot use axis values without a g code that uses them"; } return -1; } @@ -4652,7 +5602,7 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string const int p_value = rounded_word(words, 'P', 0); if (std::fabs(static_cast(p_value) - words.at('P')) > 0.001) { if (error) { - *error = "P value not an integer with M19"; + *error = "P value not an integer with M19 G2 or G3"; } return -1; } @@ -4669,15 +5619,9 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string } return -1; } - if (words.count('Q') && has_m_code(word_list, 19) && words.at('Q') <= 0.0) { - if (error) { - *error = "Q word with M19 requires a value > 0"; - } - return -1; - } if (words.count('R') && !r_word_has_linuxcnc_use(word_list, modal_motion)) { if (error) { - *error = "R word with no G2, G3, G10, G76, G81 to G89, G73, G74, G41.1, G42.1 or M19 to use it"; + *error = "R word with no g code that uses it"; } return -1; } @@ -4688,17 +5632,23 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string } return -1; } + if (words.count('Q') && has_m_code(word_list, 19) && words.at('Q') <= 0.0) { + if (error) { + *error = "Q word with M19 requires a value > 0"; + } + return -1; + } if (words.count('L')) { int l_word_value = 0; if (!integer_word_value(words, 'L', &l_word_value)) { if (error) { - *error = "Non-integer L word used"; + *error = "Non integer value for integer"; } return -1; } if (l_word_value < 0) { if (error) { - *error = "Negative L word used"; + *error = "Negative l word used"; } return -1; } @@ -4736,7 +5686,7 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string if (has_g7x_cycle) { if (!words.count('Q')) { if (error) { - *error = "G7x.x requires a Q word"; + *error = "G7x.x requires a Q word"; } return -1; } @@ -4755,11 +5705,10 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string } if (mode_zero_uses_axes && has_non_g80_motion_g_code) { if (error) { - *error = "Cannot use two G codes that both use axis values"; + *error = "Cannot use two g codes that both use axis values"; } return -1; } - const OwordKind current_oword_kind = has_o_word ? oword_kind(keyword_line) : OwordKind::None; const bool oword_call = current_oword_kind == OwordKind::Call; const bool oword_return = current_oword_kind == OwordKind::Return; const bool oword_endsub = current_oword_kind == OwordKind::EndSub; @@ -5035,27 +5984,38 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string } return -1; } - if (words.count('P') && - std::fabs(static_cast(rounded_word(words, 'P', 0)) - words.at('P')) > 0.0001) { - if (error) { - *error = "P value not an integer with G10"; + if (words.count('P')) { + const double p_value = words.at('P'); + const int p_int = p_value >= 0.0 + ? static_cast(p_value + 0.5) + : static_cast(p_value - 0.5); + if (((p_value + 0.0001) - p_int) > 0.0002) { + if (error) { + *error = "P value not an integer with G10"; + } + return -1; } - return -1; } - const int requested_p = rounded_word(words, 'P', active_g5x_index); + const int requested_p = rounded_word(words, 'P', -1); if ((l == 2 || l == 20) && (requested_p < 0 || requested_p > 9)) { if (error) { - *error = "P value out of range (0-9) with G10 L2/L20"; + *error = "P value out of range (0-9) with G10 L" + std::to_string(l); } return -1; } if ((l == 1 || l == 10 || l == 11) && requested_p < 1) { if (error) { - *error = "P value out of range with G10 L1/L10/L11"; + *error = "P value out of range with G10 L" + std::to_string(l); } return -1; } if (l == 1 || l == 10 || l == 11) { + if (!smoke_tool_number_exists(requested_p)) { + if (error) { + *error = "Requested tool " + std::to_string(requested_p) + " not found in the tool table"; + } + return -1; + } const bool has_tool_offset_word = has_axis_word(words) || words.count('R') || words.count('Q') || words.count('I') || words.count('J'); @@ -5065,9 +6025,143 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string } return -1; } + SmokeToolTableEntry &tool_entry = tool_table_[requested_p]; + if (l == 1) { + static constexpr char axes[] = {'X', 'Y', 'Z', 'A', 'B', 'C', 'U', 'V', 'W'}; + for (char axis : axes) { + if (!words.count(axis)) { + continue; + } + set_pose_axis(&tool_entry.offset, + axis, + coordinate_word_value(words, axis, sink_.unit_scale(), lathe_diameter_mode_)); + } + } else { + const bool to_g593 = l == 11; + const int destination_system = to_g593 ? 9 : active_g5x_index; + CncSimPose current = sink_.position(); + current.x += tool_length_offset.x; + current.y += tool_length_offset.y; + current.z += tool_length_offset.z; + current.a += tool_length_offset.a; + current.b += tool_length_offset.b; + current.c += tool_length_offset.c; + current.u += tool_length_offset.u; + current.v += tool_length_offset.v; + current.w += tool_length_offset.w; + if (g92_applied_) { + current.x += g92_parameters.x; + current.y += g92_parameters.y; + current.z += g92_parameters.z; + current.a += g92_parameters.a; + current.b += g92_parameters.b; + current.c += g92_parameters.c; + current.u += g92_parameters.u; + current.v += g92_parameters.v; + current.w += g92_parameters.w; + } + const double active_rotation = g5x_rotations.count(active_g5x_index) + ? g5x_rotations[active_g5x_index] + : 0.0; + rotate_xy(¤t.x, ¤t.y, active_rotation); + const CncSimPose active_offset = g5x_offsets.count(active_g5x_index) + ? g5x_offsets[active_g5x_index] + : CncSimPose{}; + current.x += active_offset.x; + current.y += active_offset.y; + current.z += active_offset.z; + current.a += active_offset.a; + current.b += active_offset.b; + current.c += active_offset.c; + current.u += active_offset.u; + current.v += active_offset.v; + current.w += active_offset.w; + + const CncSimPose destination_offset = g5x_offsets.count(destination_system) + ? g5x_offsets[destination_system] + : CncSimPose{}; + current.x -= destination_offset.x; + current.y -= destination_offset.y; + current.z -= destination_offset.z; + current.a -= destination_offset.a; + current.b -= destination_offset.b; + current.c -= destination_offset.c; + current.u -= destination_offset.u; + current.v -= destination_offset.v; + current.w -= destination_offset.w; + const double rotation = g5x_rotations.count(destination_system) + ? g5x_rotations[destination_system] + : 0.0; + rotate_xy(¤t.x, ¤t.y, -rotation); + + if (!to_g593 && g92_applied_) { + current.x -= g92_parameters.x; + current.y -= g92_parameters.y; + current.z -= g92_parameters.z; + current.a -= g92_parameters.a; + current.b -= g92_parameters.b; + current.c -= g92_parameters.c; + current.u -= g92_parameters.u; + current.v -= g92_parameters.v; + current.w -= g92_parameters.w; + } + if (words.count('X') && words.count('Y')) { + double tx = current.x - coordinate_word_value(words, 'X', sink_.unit_scale(), lathe_diameter_mode_); + double ty = current.y - coordinate_word_value(words, 'Y', sink_.unit_scale(), lathe_diameter_mode_); + rotate_xy(&tx, &ty, rotation); + tool_entry.offset.x = tx; + tool_entry.offset.y = ty; + } else if (words.count('X')) { + double ox = tool_entry.offset.x; + double oy = tool_entry.offset.y; + rotate_xy(&ox, &oy, -rotation); + ox = 0.0; + rotate_xy(&ox, &oy, rotation); + + double tx = current.x - coordinate_word_value(words, 'X', sink_.unit_scale(), lathe_diameter_mode_); + double ty = 0.0; + rotate_xy(&tx, &ty, rotation); + tool_entry.offset.x = tx + ox; + tool_entry.offset.y = ty + oy; + } else if (words.count('Y')) { + double ox = tool_entry.offset.x; + double oy = tool_entry.offset.y; + rotate_xy(&ox, &oy, -rotation); + oy = 0.0; + rotate_xy(&ox, &oy, rotation); + + double tx = 0.0; + double ty = current.y - coordinate_word_value(words, 'Y', sink_.unit_scale(), lathe_diameter_mode_); + rotate_xy(&tx, &ty, rotation); + tool_entry.offset.x = tx + ox; + tool_entry.offset.y = ty + oy; + } + static constexpr char axes[] = {'X', 'Y', 'Z', 'A', 'B', 'C', 'U', 'V', 'W'}; + for (char axis : axes) { + if (axis == 'X' || axis == 'Y') { + continue; + } + if (!words.count(axis)) { + continue; + } + set_pose_axis(&tool_entry.offset, + axis, + pose_axis(current, axis) - + coordinate_word_value(words, axis, sink_.unit_scale(), lathe_diameter_mode_)); + } + } + if (words.count('R')) { + tool_entry.diameter = words.at('R') * sink_.unit_scale() * 2.0; + } + if (words.count('I')) { + tool_entry.frontangle = words.at('I'); + } + if (words.count('J')) { + tool_entry.backangle = words.at('J'); + } if (words.count('Q')) { int orientation = 0; - if (!integer_word_value(words, 'Q', &orientation)) { + if (!linuxcnc_convert_near_int(words.at('Q'), &orientation)) { if (error) { *error = "Q number in G10 is not an integer"; } @@ -5079,6 +6173,7 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string } return -1; } + tool_entry.orientation = static_cast(orientation); } coordinate_control = true; } @@ -5093,7 +6188,7 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string } const int p = requested_p == 0 ? active_g5x_index : requested_p; if ((l == 2 || l == 20) && p > 0) { - if (l == 2 && (words.count('I') || words.count('J'))) { + if ((l == 2 || l == 20) && (words.count('I') || words.count('J'))) { if (error) { *error = "I J words not allowed with G10 L2"; } @@ -5163,14 +6258,14 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string } return -1; } - const int h_code = has_h_word ? h_word_value : sink_.selected_tool(); + const int h_code = has_h_word ? h_word_value : spindle_tool_id_; if (has_h_word && !smoke_tool_number_exists(h_code)) { if (error) { *error = "Requested tool " + std::to_string(h_code) + " not found in the tool table"; } return -1; } - tool_length_offset = smoke_tool_table_offset(h_code, sink_.unit_scale()); + tool_length_offset = scaled_tool_offset(h_code); sink_.apply_tool_length_offset(tool_length_offset); emit_tool_length_offset(sink_, tool_length_offset, line_number); if (stop_if_callback_aborted(sink_, error)) { @@ -5221,7 +6316,7 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string } return -1; } - const CncSimPose table_offset = smoke_tool_table_offset(h_word_value, sink_.unit_scale()); + const CncSimPose table_offset = scaled_tool_offset(h_word_value); offset.x += table_offset.x; offset.y += table_offset.y; offset.z += table_offset.z; @@ -5235,6 +6330,9 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string tool_length_offset = offset; sink_.apply_tool_length_offset(tool_length_offset); emit_tool_length_offset(sink_, tool_length_offset, line_number); + if (g_code_is(word.value, 43.2) && !has_h_word) { + emit_comment_state(sink_, line_number, 0); + } if (stop_if_callback_aborted(sink_, error)) { return -1; } @@ -5252,7 +6350,7 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string } if (!cutter_comp_off && !plane_ok) { if (error) { - *error = "Cutter radius compensation allowed only in XY or XZ planes"; + *error = "Cutter radius compensation allowed only in XY, XZ planes"; } return -1; } @@ -5349,7 +6447,7 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string } if (cutter_comp_on_ && selected_plane == 19) { if (error) { - *error = "Cutter radius compensation allowed only in XY or XZ planes"; + *error = "Cutter radius compensation allowed only in XY, XZ planes"; } return -1; } @@ -5744,9 +6842,10 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string clear_g5_nurbs_collection(); clear_g6_nurbs_collection(); } else if (g == 4) { - if (!words.count('P') || words.at('P') <= -1.0) { + if (!words.count('P') || + words.at('P') == -1.0) { if (error) { - *error = "Dwell time missing with G4"; + *error = "Dwell time missing with g4"; } return -1; } @@ -5819,7 +6918,7 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string } if (!has_axis_word(words)) { if (error) { - *error = "All axes missing with G52 or G92"; + *error = "All axes missing with g52 or g92"; } return -1; } @@ -5876,7 +6975,7 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string } if (!has_axis_word(words)) { if (error) { - *error = "All axes missing with G52 or G92"; + *error = "All axes missing with g52 or g92"; } return -1; } @@ -5905,7 +7004,7 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string } else if (g == 80) { if (has_axis_words && !mode_zero_uses_axes && !has_non_g80_motion_g_code) { if (error) { - *error = "Cannot use axis values with G80"; + *error = "Cannot use axis values with g80"; } return -1; } @@ -5938,25 +7037,25 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string std::unordered_map single_m_word{{'M', word.value}}; if (!integer_word_value(single_m_word, 'M', &m_code)) { if (error) { - *error = "Non-integer M code used"; + *error = "Non integer value for integer"; } return -1; } if (m_code < 0) { if (error) { - *error = "Negative M code used"; + *error = "Negative m code used"; } return -1; } if (m_code > 199 && m_code != 428 && m_code != 429 && m_code != 430) { if (error) { - *error = "M code greater than 199"; + *error = "M-code greater than 199: M" + std::to_string(m_code); } return -1; } if (!smoke_m_code_has_linuxcnc_or_simulator_use(m_code)) { if (error) { - *error = "Unknown M code used"; + *error = "Unknown m code used: M" + std::to_string(m_code); } return -1; } @@ -5965,21 +7064,21 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string } if (m_codes.size() > 4) { if (error) { - *error = "Too many M codes on line"; + *error = "Too many m codes on line"; } return -1; } if (modal_motion_is_threading(modal_motion) && words.count('F')) { if (error) { - *error = "F word used with G33/G33.1"; + *error = "F word used with a g33/g33.1"; } return -1; } if (words.count('F')) { if (words.at('F') < 0.0) { if (error) { - *error = "Negative F word used"; + *error = "Negative f word used"; } return -1; } @@ -6011,13 +7110,13 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string int tool_id = 0; if (!integer_word_value(words, 'T', &tool_id)) { if (error) { - *error = "Non-integer value for T word"; + *error = "Non integer value for integer"; } return -1; } if (tool_id < 0) { if (error) { - *error = "Negative tool ID used"; + *error = "Negative tool id (tool not found)"; } return -1; } @@ -6077,7 +7176,9 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string } else if (oword_return || oword_endsub) { if (call_stack.empty() || !call_stack.back().oword) { if (error) { - *error = "O-word return/endsub without active O-word call"; + *error = (!o_identifier.empty() && o_identifier.rfind("N:", 0) == 0) + ? "Fanuc 'O....' subroutine definition must end with 'M99'" + : "O-word return/endsub without active O-word call"; } return -1; } @@ -6147,7 +7248,7 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string } else if (m == 6) { if (!tool_selected_) { if (error) { - *error = "Txx missing for M6"; + *error = "Need tool prepared -Txx- for toolchange"; } return -1; } @@ -6315,15 +7416,18 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string } else if (m == 62 || m == 63 || m == 64 || m == 65) { if (cutter_comp_on_) { if (error) { - *error = (m == 62 || m == 63) - ? "Cannot set motion output with cutter radius compensation on" - : "Cannot set auxiliary digital output with cutter radius compensation on"; + *error = m == 62 ? "Cannot set motion output with cutter radius compensation on" + : m == 63 ? "Cannot set motion digital output with cutter radius compensation on" + : "Cannot set auxiliary digital output with cutter radius compensation on"; } return -1; } if (!words.count('P')) { if (error) { - *error = "M62-M65 requires a P output index"; + *error = m == 62 ? "No valid P word with M62" + : m == 63 ? "No valid P word with M63" + : m == 64 ? "No valid P word with M64" + : "No valid P word with M65"; } return -1; } @@ -6346,13 +7450,26 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string } if (words.count('P') && words.count('E')) { if (error) { - *error = "M66 cannot select both P digital and E analog inputs"; + *error = "Invalid to select both a digital and an analog input with M66"; + } + return -1; + } + const int wait_type = rounded_word(words, 'L', 0); + if (wait_type > 0 && (!words.count('Q') || words.at('Q') <= 0.0)) { + if (error) { + *error = "Zero timeout with wait type != immediate return"; + } + return -1; + } + if (words.count('E') && words.count('L') && rounded_word(words, 'L', 0) != 0) { + if (error) { + *error = "Can't select analog input with wait type != immediate return"; } return -1; } if (!words.count('P') && !words.count('E')) { if (error) { - *error = "M66 requires a P digital or E analog input index"; + *error = "Need to have either a valid P or a valid E word with M66"; } return -1; } @@ -6360,21 +7477,7 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string const int input_index = rounded_word(words, words.count('P') ? 'P' : 'E', 0); if (input_index < 0) { if (error) { - *error = input_type == 1 ? "Digital input index out of bounds with M66" - : "Analog input index out of bounds with M66"; - } - return -1; - } - const int wait_type = input_type == 0 ? 0 : rounded_word(words, 'L', 0); - if (input_type == 0 && words.count('L') && rounded_word(words, 'L', 0) != 0) { - if (error) { - *error = "M66 analog input only permits L0 immediate mode"; - } - return -1; - } - if (wait_type > 0 && (!words.count('Q') || words.at('Q') <= 0.0)) { - if (error) { - *error = "M66 non-immediate wait requires Q timeout greater than zero"; + *error = "Need to have either a valid P or a valid E word with M66"; } return -1; } @@ -6391,7 +7494,8 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string } } else if (m == 19) { const int spindle = selected_spindle; - if (parsed_word_list.dollar_flag && spindle < 0) { + if (parsed_word_list.dollar_flag && + !smoke_spindle_selector_valid_for_mode(parsed_word_list.dollar_value)) { if (error) { *error = "Spindle ($) number out of range in M19 Command"; } @@ -6422,7 +7526,7 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string } if (!words.count('E')) { if (error) { - *error = "M67/M68 requires an E analog output index"; + *error = m == 67 ? "Invalid analog index with M67" : "Invalid analog index with M68"; } return -1; } @@ -6459,9 +7563,15 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string deferred_program_stops.push_back(1); } else if (m == 98) { int sub_number = -1; + if (!words.count('P')) { + if (error) { + *error = "Found 'm98' code with no P-word"; + } + return -1; + } if (!integer_word_value(words, 'P', &sub_number)) { if (error) { - *error = "M98 requires an integer P subprogram number"; + *error = "Non integer value for integer"; } return -1; } @@ -6495,7 +7605,11 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string subprogram_control = true; break; } else if (m == 99) { - if (call_stack.empty() || call_stack.back().oword) { + if (call_stack.empty()) { + program_end_m_code = 99; + break; + } + if (call_stack.back().oword) { if (error) { *error = "M99 without active M98 call"; } @@ -6541,13 +7655,13 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string } if (machine_coordinate_move && !absolute_) { if (error) { - *error = "Cannot use G53 incremental"; + *error = "Cannot use g53 incremental"; } return -1; } if (machine_coordinate_move && modal_motion != 0 && modal_motion != 1) { if (error) { - *error = "Must use G0 or G1 with G53"; + *error = "Must use g0 or g1 with g53"; } return -1; } @@ -6618,6 +7732,13 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string words.count(cycle_axes.depth_word) || words.count('R'); const bool cycle_context = cycle_requested || previous_canned_cycle != 0; + const char rotary_axis = first_rotary_word(words); + if (cycle_context && rotary_axis != '\0') { + if (error) { + *error = rotary_cycle_axis_error(rotary_axis); + } + return -1; + } if (cycle_context && sink_.feed_rate() == 0.0) { if (error) { *error = "Cannot feed with zero feed rate"; @@ -6636,15 +7757,10 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string } return -1; } - if (cycle_context && has_rotary_word(words)) { + const char disallowed_axis = disallowed_cycle_axis_word(words, cycle_axes); + if (cycle_context && disallowed_axis != '\0') { if (error) { - *error = "rotary axis word is not allowed in canned cycle"; - } - return -1; - } - if (cycle_context && has_disallowed_cycle_axis_word(words, cycle_axes)) { - if (error) { - *error = "axis word is not allowed in the selected canned cycle plane"; + *error = disallowed_cycle_axis_error(disallowed_axis, sink_.plane()); } return -1; } @@ -6652,18 +7768,22 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string const bool current_line_has_r = words.count('R') != 0; const bool current_line_has_z = words.count(cycle_axes.depth_word) != 0; if (cycle_requested && - ((!current_line_has_r && !same_canned_cycle_mode) || - (!current_line_has_z && !same_canned_cycle_mode) || - !canned_has_r_ || - !canned_has_z_)) { + ((!current_line_has_r && !same_canned_cycle_mode) || !canned_has_r_)) { if (error) { - *error = "canned cycle requires R and Z words before motion"; + *error = "R clearance plane unspecified in cycle"; + } + return -1; + } + if (cycle_requested && + ((!current_line_has_z && !same_canned_cycle_mode) || !canned_has_z_)) { + if (error) { + *error = canned_cycle_missing_depth_error(sink_.plane()); } return -1; } if (cycle_requested && canned_r_ < canned_z_) { if (error) { - *error = "canned cycle R plane must not be below Z depth"; + *error = canned_cycle_r_less_than_depth_error(sink_.plane()); } return -1; } @@ -6671,9 +7791,17 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string const bool current_line_has_q = words.count('Q') != 0; if (cycle_requested && (canned_cycle_ == 73 || canned_cycle_ == 83) && - ((!current_line_has_q && !same_canned_cycle_mode) || !canned_has_q_ || canned_q_ <= 0.0)) { + ((!current_line_has_q && !same_canned_cycle_mode) || !canned_has_q_)) { if (error) { - *error = canned_cycle_ == 73 ? "G73 canned cycle requires positive Q word" : "G83 canned cycle requires positive Q word"; + *error = canned_cycle_ == 73 ? "Q word missing with g73" : "Q word missing with g83"; + } + return -1; + } + if (cycle_requested && + (canned_cycle_ == 73 || canned_cycle_ == 83) && + canned_q_ <= 0.0) { + if (error) { + *error = "Negative or zero q value used"; } return -1; } @@ -6682,32 +7810,36 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string ((!current_line_has_p && !same_canned_cycle_mode) || !canned_has_p_)) { if (error) { if (canned_cycle_ == 82) { - *error = "G82 canned cycle requires P dwell word"; + *error = "Dwell time p word missing with g82"; } else { *error = canned_cycle_ == 86 - ? "G86 canned cycle requires P dwell word" + ? "Dwell time p word missing with g86" : (canned_cycle_ == 88 - ? "G88 canned cycle requires P dwell word" - : "G89 canned cycle requires P dwell word"); + ? "Dwell time p word missing with g88" + : "Dwell time p word missing with g89"); } } return -1; } if (cycle_requested && canned_cycle_ == 74 && sink_.spindle_direction() != 2) { if (error) { - *error = "G74 canned cycle requires a counterclockwise running spindle"; + *error = sink_.spindle_direction() == 1 + ? "Spindle turning clockwise in G74" + : "Spindle not turning in G74"; } return -1; } if (cycle_requested && canned_cycle_ == 84 && sink_.spindle_direction() != 1) { if (error) { - *error = "G84 canned cycle requires a clockwise running spindle"; + *error = sink_.spindle_direction() == 2 + ? "Spindle turning counterclockwise in G84" + : "Spindle not turning in G84"; } return -1; } if (cycle_requested && canned_cycle_ == 86 && sink_.spindle_direction() == 0) { if (error) { - *error = "G86 canned cycle requires a running spindle"; + *error = "Spindle not turning in g86"; } return -1; } @@ -6722,26 +7854,32 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string !canned_has_j_ || !canned_has_k_)) { if (error) { - *error = "G87 canned cycle requires I, J, and K words"; + if ((!current_line_has_i && !same_canned_cycle_mode) || !canned_has_i_) { + *error = "I word missing with g87"; + } else if ((!current_line_has_j && !same_canned_cycle_mode) || !canned_has_j_) { + *error = "J word missing with g87"; + } else { + *error = "K word missing with g87"; + } } return -1; } if (cycle_requested && canned_cycle_ == 87 && sink_.spindle_direction() == 0) { if (error) { - *error = "G87 canned cycle requires a running spindle"; + *error = "Spindle not turning in g87"; } return -1; } if (cycle_requested && canned_cycle_ == 88 && sink_.spindle_direction() == 0) { if (error) { - *error = "G88 canned cycle requires a running spindle"; + *error = "Spindle not turning in g88"; } return -1; } const int repeat_count = positive_integer_word(words, 'L', 1); if (cycle_requested && repeat_count < 1) { if (error) { - *error = "canned cycle L word must be a positive integer"; + *error = "Cannot do zero repeats of cycle"; } return -1; } @@ -7065,7 +8203,7 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string } if (modal_motion_is_threading(modal_motion) && !words.count('K')) { if (error) { - *error = "K word missing with G33/G33.1"; + *error = "K word missing with g33/g33.1"; } return -1; } @@ -7145,7 +8283,13 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string } return -1; } - if ((modal_motion == 1 || modal_motion == 2 || modal_motion == 3) && + if (modal_motion == 1 && sink_.feed_rate() == 0.0) { + if (error) { + *error = "Cannot do g1 with zero feed rate"; + } + return -1; + } + if ((modal_motion == 2 || modal_motion == 3) && sink_.feed_rate() == 0.0) { if (error) { *error = "Cannot do feed motion with zero feed rate"; diff --git a/core/src/smoke_gcode_parser.h b/core/src/smoke_gcode_parser.h index 5dc4de1..b2d7649 100644 --- a/core/src/smoke_gcode_parser.h +++ b/core/src/smoke_gcode_parser.h @@ -3,6 +3,7 @@ #include "canon_event_sink.h" #include +#include #include class SmokeGcodeParser { @@ -12,6 +13,14 @@ public: int parse(const char *program, size_t program_len, std::string *error); private: + struct SmokeToolTableEntry { + CncSimPose offset{}; + double diameter = 0.0; + double frontangle = 0.0; + double backangle = 0.0; + double orientation = 0.0; + }; + CanonEventSink &sink_; bool absolute_ = true; bool arc_absolute_ = false; @@ -55,4 +64,5 @@ private: double oword_return_value_ = 0.0; bool oword_value_returned_ = false; int selected_tool_id_ = 0; + std::unordered_map tool_table_; }; diff --git a/core/tests/cnc_sim_api_linuxcnc_rs274_smoke.cpp b/core/tests/cnc_sim_api_linuxcnc_rs274_smoke.cpp index 0b88e19..56ed5e3 100644 --- a/core/tests/cnc_sim_api_linuxcnc_rs274_smoke.cpp +++ b/core/tests/cnc_sim_api_linuxcnc_rs274_smoke.cpp @@ -1200,6 +1200,47 @@ int main() { ok &= expect(saw_g52_restore, "expected LinuxCNC G52/G92 shared offset to restore with G92.3"); ok &= expect(saw_g52_end_line, "expected LinuxCNC G52 program end line number"); + const char block_delete_program[] = + "G21 G90 G17 F100\n" + " /N10 G1 X5\n" + "G1 X2\n" + "M30\n"; + const char block_delete_on_config[] = "{\"backend\":\"linuxcnc-rs274\",\"blockDelete\":true}"; + events.clear(); + ok &= expect(cnc_sim_load_config_json(sim, block_delete_on_config, sizeof(block_delete_on_config) - 1) == 0, + cnc_sim_last_error(sim)); + ok &= expect(cnc_sim_parse_program(sim, block_delete_program, sizeof(block_delete_program) - 1) == 0, + cnc_sim_last_error(sim)); + bool saw_block_delete_skipped_line = false; + bool saw_block_delete_followup_line = false; + for (const auto &event : events) { + saw_block_delete_skipped_line = saw_block_delete_skipped_line || + (event.type == CNC_SIM_EVENT_LINEAR_FEED && + event.line == 2 && + event.end.x == 5.0); + saw_block_delete_followup_line = saw_block_delete_followup_line || + (event.type == CNC_SIM_EVENT_LINEAR_FEED && + event.line == 3 && + event.end.x == 2.0); + } + ok &= expect(!saw_block_delete_skipped_line, "expected LinuxCNC block-delete line to be skipped"); + ok &= expect(saw_block_delete_followup_line, "expected LinuxCNC block-delete followup line to execute"); + + const char block_delete_off_config[] = "{\"backend\":\"linuxcnc-rs274\",\"blockDelete\":false}"; + events.clear(); + ok &= expect(cnc_sim_load_config_json(sim, block_delete_off_config, sizeof(block_delete_off_config) - 1) == 0, + cnc_sim_last_error(sim)); + ok &= expect(cnc_sim_parse_program(sim, block_delete_program, sizeof(block_delete_program) - 1) == 0, + cnc_sim_last_error(sim)); + bool saw_block_delete_disabled_line = false; + for (const auto &event : events) { + saw_block_delete_disabled_line = saw_block_delete_disabled_line || + (event.type == CNC_SIM_EVENT_LINEAR_FEED && + event.line == 2 && + event.end.x == 5.0); + } + ok &= expect(saw_block_delete_disabled_line, "expected LinuxCNC disabled block-delete line to execute"); + cnc_sim_destroy(sim); int abort_count = 0; diff --git a/core/tests/cnc_sim_api_smoke.cpp b/core/tests/cnc_sim_api_smoke.cpp index f2b275b..19bc350 100644 --- a/core/tests/cnc_sim_api_smoke.cpp +++ b/core/tests/cnc_sim_api_smoke.cpp @@ -2,10 +2,12 @@ #include #include +#include #include #include #include #include +#include namespace { @@ -52,6 +54,34 @@ private: std::string old_; }; +class ScopedTempDir { +public: + explicit ScopedTempDir(const char *prefix) { + const char *tmpdir = std::getenv("TMPDIR"); + std::string tmpl = std::string(tmpdir ? tmpdir : "/tmp") + "/" + prefix + ".XXXXXX"; + std::vector buffer(tmpl.begin(), tmpl.end()); + buffer.push_back('\0'); + char *dir = mkdtemp(buffer.data()); + if (!dir) { + std::perror("mkdtemp"); + std::exit(1); + } + path_ = dir; + } + + ~ScopedTempDir() { + std::error_code error; + std::filesystem::remove_all(path_, error); + } + + std::string file(const char *name) const { + return path_ + "/" + name; + } + +private: + std::string path_; +}; + bool saw_comment_state(const std::vector &events, int line, int reserved, @@ -76,6 +106,8 @@ bool saw_comment_state(const std::vector &events, } // namespace int main() { + ScopedTempDir temp_dir("cnc_sim_api_smoke"); + const char program[] = "G21 G90 G17\n" "T1 M6\n" @@ -182,6 +214,7 @@ int main() { bool saw_g432_tool_length = false; bool saw_g432_h_tool_length = false; bool saw_g49_tool_length_clear = false; + bool saw_g432_followup_comment = false; bool saw_g81_feed = false; bool saw_g81_g98_return = false; bool saw_g82_feed = false; @@ -1264,6 +1297,10 @@ int main() { event.reserved == 430 && event.start.x == 0.0 && event.start.z == 0.0); + saw_g432_followup_comment = saw_g432_followup_comment || + (event.type == CNC_SIM_EVENT_COMMENT && + event.line == 3 && + event.reserved == 0); saw_g432_h_tool_length = saw_g432_h_tool_length || (event.type == CNC_SIM_EVENT_COMMENT && event.line == 4 && @@ -1275,6 +1312,7 @@ int main() { ok &= expect(saw_g432_tool_length, "expected smoke G43.2 additive tool length event"); ok &= expect(saw_g432_h_tool_length, "expected smoke G43.2 H tool-table additive event"); ok &= expect(saw_g49_tool_length_clear, "expected smoke G49 tool length clear event"); + ok &= expect(saw_g432_followup_comment, "expected LinuxCNC G43.2 axis tool offset follow-up comment event"); const char immediate_g431_named_position_program[] = "G21 G90 G17\n" @@ -1369,10 +1407,10 @@ int main() { (event.type == CNC_SIM_EVENT_COMMENT && event.line == 5 && event.reserved == 430 && - event.start.z == 12.5); + event.start.z == 0.0); } ok &= expect(saw_g43_h_tool_length, "expected smoke G43 H1 tool-table offset"); - ok &= expect(saw_g43_current_tool_length, "expected smoke G43 to use current selected tool"); + ok &= expect(saw_g43_current_tool_length, "expected LinuxCNC bare G43 to use the spindle tool slot in the nonrandom fixture"); const char immediate_g43_named_position_program[] = "G21 G90 G17\n" @@ -1397,6 +1435,281 @@ int main() { ok &= expect(saw_immediate_g43_named_position, "expected G43 to update readonly position named parameters before the next motion"); + const char g43_start_position_program[] = + "G21 G90 G17\n" + "G43.1 X2 Y1\n" + "G1 Z1 F100\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + g43_start_position_program, + sizeof(g43_start_position_program) - 1) == 0, + cnc_sim_last_error(sim)); + bool saw_g43_start_position = false; + for (const auto &event : events) { + saw_g43_start_position = saw_g43_start_position || + (event.type == CNC_SIM_EVENT_LINEAR_FEED && + event.line == 3 && + near(event.start.x, 0.0) && + near(event.start.y, 0.0) && + near(event.start.z, 0.0) && + near(event.end.x, -2.0) && + near(event.end.y, -1.0) && + near(event.end.z, 1.0)); + } + ok &= expect(saw_g43_start_position, + "expected LinuxCNC G43.1 to keep the pre-offset position as the next motion start"); + + const char g432_after_g431_start_position_program[] = + "G21 G90 G17\n" + "G43.1 X2\n" + "G43.2 H1\n" + "G1 Z1 F100\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + g432_after_g431_start_position_program, + sizeof(g432_after_g431_start_position_program) - 1) == 0, + cnc_sim_last_error(sim)); + bool saw_g432_after_g431_start_position = false; + for (const auto &event : events) { + saw_g432_after_g431_start_position = saw_g432_after_g431_start_position || + (event.type == CNC_SIM_EVENT_LINEAR_FEED && + event.line == 4 && + near(event.start.x, 0.0) && + near(event.start.y, 0.0) && + near(event.start.z, 0.0) && + near(event.end.x, -2.0) && + near(event.end.y, 0.0) && + near(event.end.z, 1.0)); + } + ok &= expect(saw_g432_after_g431_start_position, + "expected LinuxCNC to preserve the original motion start across consecutive G43.1 and G43.2 offsets"); + + const char g432_axis_after_g43_start_position_program[] = + "G21 G90 G17\n" + "G43 H1\n" + "G43.2 X1\n" + "G1 Z1 F100\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + g432_axis_after_g43_start_position_program, + sizeof(g432_axis_after_g43_start_position_program) - 1) == 0, + cnc_sim_last_error(sim)); + bool saw_g432_axis_after_g43_start_position = false; + for (const auto &event : events) { + saw_g432_axis_after_g43_start_position = saw_g432_axis_after_g43_start_position || + (event.type == CNC_SIM_EVENT_LINEAR_FEED && + event.line == 4 && + near(event.start.x, 0.0) && + near(event.start.y, 0.0) && + near(event.start.z, 0.0) && + near(event.end.x, -1.0) && + near(event.end.y, 0.0) && + near(event.end.z, 1.0)); + } + ok &= expect(saw_g432_axis_after_g43_start_position, + "expected LinuxCNC to preserve the original motion start across G43 and additive G43.2 axis offsets"); + + const char g49_after_g43_start_position_program[] = + "G21 G90 G17\n" + "G43 H1\n" + "G49\n" + "G1 Z1 F100\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + g49_after_g43_start_position_program, + sizeof(g49_after_g43_start_position_program) - 1) == 0, + cnc_sim_last_error(sim)); + bool saw_g49_after_g43_start_position = false; + for (const auto &event : events) { + saw_g49_after_g43_start_position = saw_g49_after_g43_start_position || + (event.type == CNC_SIM_EVENT_LINEAR_FEED && + event.line == 4 && + near(event.start.x, 0.0) && + near(event.start.y, 0.0) && + near(event.start.z, 0.0) && + near(event.end.x, 0.0) && + near(event.end.y, 0.0) && + near(event.end.z, 1.0)); + } + ok &= expect(saw_g49_after_g43_start_position, + "expected LinuxCNC G49 to preserve the original motion start after clearing tool length offsets"); + + const char g49_after_g431_start_position_program[] = + "G21 G90 G17\n" + "G43.1 X2\n" + "G49\n" + "G1 Z1 F100\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + g49_after_g431_start_position_program, + sizeof(g49_after_g431_start_position_program) - 1) == 0, + cnc_sim_last_error(sim)); + bool saw_g49_after_g431_start_position = false; + for (const auto &event : events) { + saw_g49_after_g431_start_position = saw_g49_after_g431_start_position || + (event.type == CNC_SIM_EVENT_LINEAR_FEED && + event.line == 4 && + near(event.start.x, 0.0) && + near(event.start.y, 0.0) && + near(event.start.z, 0.0) && + near(event.end.x, 0.0) && + near(event.end.y, 0.0) && + near(event.end.z, 1.0)); + } + ok &= expect(saw_g49_after_g431_start_position, + "expected LinuxCNC G49 to preserve the original motion start after clearing dynamic tool offsets"); + + const char g10_l10_tool_length_program[] = + "G21 G90 G17\n" + "G0 Z20\n" + "G10 L10 P1 Z10\n" + "G43 H1\n" + "O10 if [[#5403 EQ 10] AND [#5422 EQ 10]]\n" + "G1 X1 F100\n" + "O10 endif\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + g10_l10_tool_length_program, + sizeof(g10_l10_tool_length_program) - 1) == 0, + cnc_sim_last_error(sim)); + bool saw_g10_l10_tool_length = false; + for (const auto &event : events) { + saw_g10_l10_tool_length = saw_g10_l10_tool_length || + (event.type == CNC_SIM_EVENT_LINEAR_FEED && + event.line == 6 && + near(event.end.x, 1.0) && + near(event.end.z, 10.0)); + } + ok &= expect(saw_g10_l10_tool_length, + "expected LinuxCNC G10 L10 to update the tool table used by G43"); + + const char g10_l11_tool_length_program[] = + "G21 G90 G17\n" + "G0 Z20\n" + "G10 L11 P1 Z10\n" + "G43 H1\n" + "O10 if [[#5403 EQ 10] AND [#5422 EQ 10]]\n" + "G1 X1 F100\n" + "O10 endif\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + g10_l11_tool_length_program, + sizeof(g10_l11_tool_length_program) - 1) == 0, + cnc_sim_last_error(sim)); + bool saw_g10_l11_tool_length = false; + for (const auto &event : events) { + saw_g10_l11_tool_length = saw_g10_l11_tool_length || + (event.type == CNC_SIM_EVENT_LINEAR_FEED && + event.line == 6 && + near(event.end.x, 1.0) && + near(event.end.z, 10.0)); + } + ok &= expect(saw_g10_l11_tool_length, + "expected LinuxCNC G10 L11 to update the tool table used by G43"); + + const char g10_l10_rotated_xy_tool_offset_program[] = + "G21 G90 G17\n" + "G10 L2 P1 X0 Y0 R90\n" + "G0 X10 Y20\n" + "G10 L10 P1 X-20 Y10\n" + "G43 H1\n" + "O10 if [[#5401 EQ -10] AND [#5402 EQ 30] AND [#5420 EQ -20] AND [#5421 EQ 10]]\n" + "G1 Z1 F100\n" + "O10 endif\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + g10_l10_rotated_xy_tool_offset_program, + sizeof(g10_l10_rotated_xy_tool_offset_program) - 1) == 0, + cnc_sim_last_error(sim)); + bool saw_g10_l10_rotated_xy_tool_offset = false; + for (const auto &event : events) { + saw_g10_l10_rotated_xy_tool_offset = saw_g10_l10_rotated_xy_tool_offset || + (event.type == CNC_SIM_EVENT_LINEAR_FEED && + event.line == 7 && + near(event.end.x, -20.0) && + near(event.end.y, 10.0) && + near(event.end.z, 1.0)); + } + ok &= expect(saw_g10_l10_rotated_xy_tool_offset, + "expected LinuxCNC G10 L10 to preserve rotated XY components when updating the tool table"); + + const char g10_l10_rotated_xonly_tool_offset_program[] = + "G21 G90 G17\n" + "G10 L2 P1 X0 Y0 R90\n" + "G0 X10 Y20\n" + "G10 L10 P1 X-20\n" + "G43 H1\n" + "O10 if [[#5401 EQ 0] AND [#5402 EQ 30]]\n" + "G1 Z1 F100\n" + "O10 endif\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + g10_l10_rotated_xonly_tool_offset_program, + sizeof(g10_l10_rotated_xonly_tool_offset_program) - 1) == 0, + cnc_sim_last_error(sim)); + bool saw_g10_l10_rotated_xonly_tool_offset = false; + for (const auto &event : events) { + saw_g10_l10_rotated_xonly_tool_offset = saw_g10_l10_rotated_xonly_tool_offset || + (event.type == CNC_SIM_EVENT_LINEAR_FEED && + event.line == 7 && + near(event.end.x, -20.0) && + near(event.end.y, 20.0) && + near(event.end.z, 1.0)); + } + ok &= expect(saw_g10_l10_rotated_xonly_tool_offset, + "expected LinuxCNC G10 L10 rotated X-only update to preserve the orthogonal XY component"); + + const char g10_l10_rotated_yonly_tool_offset_program[] = + "G21 G90 G17\n" + "G10 L2 P1 X0 Y0 R90\n" + "G0 X10 Y20\n" + "G10 L10 P1 Y10\n" + "G43 H1\n" + "O10 if [[#5401 EQ -10] AND [#5402 EQ 0]]\n" + "G1 Z1 F100\n" + "O10 endif\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + g10_l10_rotated_yonly_tool_offset_program, + sizeof(g10_l10_rotated_yonly_tool_offset_program) - 1) == 0, + cnc_sim_last_error(sim)); + bool saw_g10_l10_rotated_yonly_tool_offset = false; + for (const auto &event : events) { + saw_g10_l10_rotated_yonly_tool_offset = saw_g10_l10_rotated_yonly_tool_offset || + (event.type == CNC_SIM_EVENT_LINEAR_FEED && + event.line == 7 && + near(event.end.x, 10.0) && + near(event.end.y, 10.0) && + near(event.end.z, 1.0)); + } + ok &= expect(saw_g10_l10_rotated_yonly_tool_offset, + "expected LinuxCNC G10 L10 rotated Y-only update to preserve the orthogonal XY component"); + + const char g10_l11_g92_rotated_xonly_tool_offset_program[] = + "G21 G90 G17\n" + "G10 L2 P9 X0 Y0 R90\n" + "G92 X3 Y4\n" + "G0 X10 Y20\n" + "G10 L11 P1 X-20\n" + "G43 H1\n" + "O10 if [[#5401 EQ 0] AND [#5402 EQ 36]]\n" + "G1 Z1 F100\n" + "O10 endif\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + g10_l11_g92_rotated_xonly_tool_offset_program, + sizeof(g10_l11_g92_rotated_xonly_tool_offset_program) - 1) == 0, + cnc_sim_last_error(sim)); + bool saw_g10_l11_g92_rotated_xonly_tool_offset = false; + for (const auto &event : events) { + saw_g10_l11_g92_rotated_xonly_tool_offset = saw_g10_l11_g92_rotated_xonly_tool_offset || + (event.type == CNC_SIM_EVENT_LINEAR_FEED && + event.line == 8 && + near(event.end.z, 1.0)); + } + ok &= expect(saw_g10_l11_g92_rotated_xonly_tool_offset, + "expected LinuxCNC G10 L11 to ignore active G92 when updating rotated X-only tool offsets"); + const char missing_g43_h_tool_program[] = "G21 G90 G17\n" "G43 H3\n"; @@ -1423,6 +1736,30 @@ int main() { fractional_h_word_program, sizeof(fractional_h_word_program) - 1) != 0, "expected fractional H word to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Non integer value for integer", + "expected LinuxCNC fractional-H error text"); + + const char boundary_noninteger_h_word_program[] = + "G21 G90 G17\n" + "G43 H1.9999\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + boundary_noninteger_h_word_program, + sizeof(boundary_noninteger_h_word_program) - 1) != 0, + "expected LinuxCNC to reject H word exactly at the read_integer_value upper tolerance boundary"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Non integer value for integer", + "expected LinuxCNC boundary-H error text"); + + const char negative_h_word_program[] = + "G21 G90 G17\n" + "G43 H-2\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + negative_h_word_program, + sizeof(negative_h_word_program) - 1) != 0, + "expected LinuxCNC to reject H words less than -1"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Negative h word used", + "expected LinuxCNC negative-H error text"); const char bare_d_word_program[] = "G21 G90 G17\n" @@ -1432,6 +1769,9 @@ int main() { bare_d_word_program, sizeof(bare_d_word_program) - 1) != 0, "expected bare D word to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == + "D word with no G41, G41.1, G42, G42.1, G71, G71.1, G71.2 G73, G83 or G96 to use it", + "expected LinuxCNC bare-D error text"); const char modal_motion_d_word_program[] = "G21 G90 G17\n" @@ -1441,6 +1781,9 @@ int main() { modal_motion_d_word_program, sizeof(modal_motion_d_word_program) - 1) != 0, "expected D word with unrelated motion to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == + "D word with no G41, G41.1, G42, G42.1, G71, G71.1, G71.2 G73, G83 or G96 to use it", + "expected LinuxCNC modal-motion-D error text"); const char cutter_comp_program[] = "G21 G90 G17\n" @@ -1470,6 +1813,9 @@ int main() { invalid_cutter_plane_program, sizeof(invalid_cutter_plane_program) - 1) != 0, "expected cutter compensation to reject G19"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == + "Cutter radius compensation allowed only in XY, XZ planes", + "expected LinuxCNC cutter-comp-plane error text"); const char invalid_cutter_g53_program[] = "G21 G90 G17\n" @@ -1541,6 +1887,19 @@ int main() { sizeof(invalid_cutter_digital_output_program) - 1) != 0, "expected digital outputs to reject cutter compensation"); + const char invalid_cutter_motion_digital_output_program[] = + "G21 G90 G17\n" + "G41 D1\n" + "M63 P1\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + invalid_cutter_motion_digital_output_program, + sizeof(invalid_cutter_motion_digital_output_program) - 1) != 0, + "expected motion digital outputs to reject cutter compensation"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == + "Cannot set motion digital output with cutter radius compensation on", + "expected LinuxCNC M63 cutter-comp error text"); + const char invalid_cutter_analog_output_program[] = "G21 G90 G17\n" "G41 D1\n" @@ -1590,6 +1949,8 @@ int main() { missing_t_for_m6_program, sizeof(missing_t_for_m6_program) - 1) != 0, "expected M6 to reject missing T word"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Need tool prepared -Txx- for toolchange", + "expected LinuxCNC missing-M6-T error text"); const char invalid_cutter_wait_input_program[] = "G21 G90 G17\n" @@ -2225,8 +2586,7 @@ int main() { "G83 X3 Z-3 R1 Q1\n" "G98 G85 X4 Z-4 R0.5\n" "G89 X5 Z-5 R0.25 P0.15\n" - "G80\n" - "X6\n"; + "G80\n"; events.clear(); ok &= expect(cnc_sim_parse_program(sim, canned_cycle_program, sizeof(canned_cycle_program) - 1) == 0, cnc_sim_last_error(sim)); @@ -3357,10 +3717,13 @@ int main() { const char invalid_cycle_l_program[] = "G21 G90 G17\n" + "F100\n" "G81 X1 Z-1 R1 L0\n"; events.clear(); ok &= expect(cnc_sim_parse_program(sim, invalid_cycle_l_program, sizeof(invalid_cycle_l_program) - 1) != 0, "expected canned cycle L0 to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Cannot do zero repeats of cycle", + cnc_sim_last_error(sim)); const char missing_cycle_feed_program[] = "G21 G90 G17\n" @@ -3369,6 +3732,26 @@ int main() { ok &= expect(cnc_sim_parse_program(sim, missing_cycle_feed_program, sizeof(missing_cycle_feed_program) - 1) != 0, "expected G81 without feed rate to fail"); + const char missing_cycle_r_program[] = + "G21 G90 G17\n" + "F100\n" + "G81 X1 Z-1\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, missing_cycle_r_program, sizeof(missing_cycle_r_program) - 1) != 0, + "expected G81 without R clearance plane to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "R clearance plane unspecified in cycle", + cnc_sim_last_error(sim)); + + const char missing_cycle_z_program[] = + "G21 G90 G17\n" + "F100\n" + "G81 X1 R1\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, missing_cycle_z_program, sizeof(missing_cycle_z_program) - 1) != 0, + "expected G81 without Z depth to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Z value unspecified in xy plane canned cycle", + cnc_sim_last_error(sim)); + const char inverse_time_cycle_program[] = "G21 G90 G17\n" "G93\n" @@ -3389,10 +3772,43 @@ int main() { const char missing_g73_q_program[] = "G21 G90 G17\n" + "F100\n" "G73 X1 Z-1 R1\n"; events.clear(); ok &= expect(cnc_sim_parse_program(sim, missing_g73_q_program, sizeof(missing_g73_q_program) - 1) != 0, "expected G73 without Q peck to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Q word missing with g73", + cnc_sim_last_error(sim)); + + const char missing_g83_q_program[] = + "G21 G90 G17\n" + "F100\n" + "G83 X1 Z-1 R1\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, missing_g83_q_program, sizeof(missing_g83_q_program) - 1) != 0, + "expected G83 without Q peck to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Q word missing with g83", + cnc_sim_last_error(sim)); + + const char zero_g73_q_program[] = + "G21 G90 G17\n" + "F100\n" + "G73 X1 Z-1 R1 Q0\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, zero_g73_q_program, sizeof(zero_g73_q_program) - 1) != 0, + "expected G73 with zero Q peck to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Negative or zero q value used", + cnc_sim_last_error(sim)); + + const char zero_g83_q_program[] = + "G21 G90 G17\n" + "F100\n" + "G83 X1 Z-1 R1 Q0\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, zero_g83_q_program, sizeof(zero_g83_q_program) - 1) != 0, + "expected G83 with zero Q peck to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Negative or zero q value used", + cnc_sim_last_error(sim)); const char cross_cycle_q_program[] = "G21 G90 G17\n" @@ -3404,32 +3820,66 @@ int main() { const char stopped_g86_program[] = "G21 G90 G17\n" + "F100\n" "G86 X1 Z-1 R1 P0.1\n"; events.clear(); ok &= expect(cnc_sim_parse_program(sim, stopped_g86_program, sizeof(stopped_g86_program) - 1) != 0, "expected G86 with stopped spindle to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Spindle not turning in g86", + cnc_sim_last_error(sim)); const char stopped_g88_program[] = "G21 G90 G17\n" + "F100\n" "G88 X1 Z-1 R1 P0.1 F70\n"; events.clear(); ok &= expect(cnc_sim_parse_program(sim, stopped_g88_program, sizeof(stopped_g88_program) - 1) != 0, "expected G88 with stopped spindle to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Spindle not turning in g88", + cnc_sim_last_error(sim)); const char stopped_g87_program[] = "G21 G90 G17\n" + "F100\n" "G87 X1 Z-1 R1 I0.1 J0.1 K0 F80\n"; events.clear(); ok &= expect(cnc_sim_parse_program(sim, stopped_g87_program, sizeof(stopped_g87_program) - 1) != 0, "expected G87 with stopped spindle to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Spindle not turning in g87", + cnc_sim_last_error(sim)); const char missing_g87_ijk_program[] = "G21 G90 G17\n" + "F100\n" "S800 M3\n" "G87 X1 Z-1 R1 I0.1 J0.1 F80\n"; events.clear(); ok &= expect(cnc_sim_parse_program(sim, missing_g87_ijk_program, sizeof(missing_g87_ijk_program) - 1) != 0, "expected G87 without I/J/K to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "K word missing with g87", + cnc_sim_last_error(sim)); + + const char missing_g87_i_program[] = + "G21 G90 G17\n" + "F100\n" + "S800 M3\n" + "G87 X1 Z-1 R1 J0.1 K0 F80\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, missing_g87_i_program, sizeof(missing_g87_i_program) - 1) != 0, + "expected G87 without I to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "I word missing with g87", + cnc_sim_last_error(sim)); + + const char missing_g87_j_program[] = + "G21 G90 G17\n" + "F100\n" + "S800 M3\n" + "G87 X1 Z-1 R1 I0.1 K0 F80\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, missing_g87_j_program, sizeof(missing_g87_j_program) - 1) != 0, + "expected G87 without J to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "J word missing with g87", + cnc_sim_last_error(sim)); const char reverse_g84_program[] = "G21 G90 G17\n" @@ -3438,6 +3888,8 @@ int main() { events.clear(); ok &= expect(cnc_sim_parse_program(sim, reverse_g84_program, sizeof(reverse_g84_program) - 1) != 0, "expected G84 with non-clockwise spindle to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Spindle turning counterclockwise in G84", + cnc_sim_last_error(sim)); const char forward_g74_program[] = "G21 G90 G17\n" @@ -3446,13 +3898,69 @@ int main() { events.clear(); ok &= expect(cnc_sim_parse_program(sim, forward_g74_program, sizeof(forward_g74_program) - 1) != 0, "expected G74 with non-counterclockwise spindle to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Spindle turning clockwise in G74", + cnc_sim_last_error(sim)); + + const char stopped_g74_program[] = + "G21 G90 G17\n" + "F100\n" + "G74 X1 Z-1 R1\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, stopped_g74_program, sizeof(stopped_g74_program) - 1) != 0, + "expected G74 with stopped spindle to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Spindle not turning in G74", + cnc_sim_last_error(sim)); + + const char stopped_g84_program[] = + "G21 G90 G17\n" + "F100\n" + "G84 X1 Z-1 R1\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, stopped_g84_program, sizeof(stopped_g84_program) - 1) != 0, + "expected G84 with stopped spindle to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Spindle not turning in G84", + cnc_sim_last_error(sim)); + + const char missing_g82_p_program[] = + "G21 G90 G17\n" + "F100\n" + "G82 X1 Z-1 R1\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, missing_g82_p_program, sizeof(missing_g82_p_program) - 1) != 0, + "expected G82 without P dwell to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Dwell time p word missing with g82", + cnc_sim_last_error(sim)); + + const char missing_g86_p_program[] = + "G21 G90 G17\n" + "F100\n" + "G86 X1 Z-1 R1\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, missing_g86_p_program, sizeof(missing_g86_p_program) - 1) != 0, + "expected G86 without P dwell to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Dwell time p word missing with g86", + cnc_sim_last_error(sim)); + + const char missing_g88_p_program[] = + "G21 G90 G17\n" + "F100\n" + "S100 M3\n" + "G88 X1 Z-1 R1\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, missing_g88_p_program, sizeof(missing_g88_p_program) - 1) != 0, + "expected G88 without P dwell to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Dwell time p word missing with g88", + cnc_sim_last_error(sim)); const char missing_g89_p_program[] = "G21 G90 G17\n" + "F100\n" "G89 X1 Z-1 R1\n"; events.clear(); ok &= expect(cnc_sim_parse_program(sim, missing_g89_p_program, sizeof(missing_g89_p_program) - 1) != 0, "expected G89 without P dwell to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Dwell time p word missing with g89", + cnc_sim_last_error(sim)); const char cross_cycle_p_program[] = "G21 G90 G17\n" @@ -3485,27 +3993,86 @@ int main() { events.clear(); ok &= expect(cnc_sim_parse_program(sim, rotary_cycle_program, sizeof(rotary_cycle_program) - 1) != 0, "expected rotary axis word in canned cycle to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Cannot put an a in canned cycle", + cnc_sim_last_error(sim)); + + const char rotary_b_cycle_program[] = + "G21 G90 G17\n" + "G81 X1 B10 Z-1 R1\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, rotary_b_cycle_program, sizeof(rotary_b_cycle_program) - 1) != 0, + "expected B-axis word in canned cycle to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Cannot put a b in canned cycle", + cnc_sim_last_error(sim)); + + const char rotary_c_cycle_program[] = + "G21 G90 G17\n" + "G81 X1 C10 Z-1 R1\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, rotary_c_cycle_program, sizeof(rotary_c_cycle_program) - 1) != 0, + "expected C-axis word in canned cycle to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Cannot put a c in canned cycle", + cnc_sim_last_error(sim)); const char g18_missing_depth_cycle_program[] = "G21 G90 G18\n" + "F100\n" "G81 X1 Z-1 R1\n"; events.clear(); ok &= expect(cnc_sim_parse_program(sim, g18_missing_depth_cycle_program, sizeof(g18_missing_depth_cycle_program) - 1) != 0, "expected G18 canned cycle to require Y depth word"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Y value unspecified in xz plane canned cycle", + cnc_sim_last_error(sim)); + + const char g19_missing_depth_cycle_program[] = + "G21 G90 G19\n" + "F100\n" + "G81 Y1 Z-1 R1\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, g19_missing_depth_cycle_program, sizeof(g19_missing_depth_cycle_program) - 1) != 0, + "expected G19 canned cycle to require X depth word"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "X value unspecified in yz plane canned cycle", + cnc_sim_last_error(sim)); const char uvw_cycle_program[] = "G21 G90 G17\n" + "F100\n" "G81 X1 U2 Z-1 R1\n"; events.clear(); ok &= expect(cnc_sim_parse_program(sim, uvw_cycle_program, sizeof(uvw_cycle_program) - 1) != 0, "expected UVW axis word in G17 canned cycle to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Cannot put a U in a canned cycle in the XY plane", + cnc_sim_last_error(sim)); const char invalid_cycle_r_program[] = "G21 G90 G17\n" + "F100\n" "G81 X1 Z2 R1\n"; events.clear(); ok &= expect(cnc_sim_parse_program(sim, invalid_cycle_r_program, sizeof(invalid_cycle_r_program) - 1) != 0, "expected canned cycle R below Z to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "R less than z in cycle in xy plane", + cnc_sim_last_error(sim)); + + const char g18_invalid_cycle_r_program[] = + "G21 G90 G18\n" + "F100\n" + "G81 X1 Y2 Z-1 R1\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, g18_invalid_cycle_r_program, sizeof(g18_invalid_cycle_r_program) - 1) != 0, + "expected G18 canned cycle R below Y depth to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "R less than y in cycle in xz plane", + cnc_sim_last_error(sim)); + + const char g19_invalid_cycle_r_program[] = + "G21 G90 G19\n" + "F100\n" + "G81 X2 Y1 Z-1 R1\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, g19_invalid_cycle_r_program, sizeof(g19_invalid_cycle_r_program) - 1) != 0, + "expected G19 canned cycle R below X depth to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "R less than x in cycle in yz plane", + cnc_sim_last_error(sim)); const char incremental_g53_program[] = "G21 G91\n" @@ -3513,6 +4080,18 @@ int main() { events.clear(); ok &= expect(cnc_sim_parse_program(sim, incremental_g53_program, sizeof(incremental_g53_program) - 1) != 0, "expected G53 in incremental distance mode to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Cannot use g53 incremental", + "expected LinuxCNC incremental-G53 error text"); + + const char g53_without_g0_g1_program[] = + "G21 G90\n" + "F100\n" + "G53 G2 X1 Y0 I0 J0\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, g53_without_g0_g1_program, sizeof(g53_without_g0_g1_program) - 1) != 0, + "expected LinuxCNC to reject G53 without G0 or G1"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Must use g0 or g1 with g53", + "expected LinuxCNC G53-motion error text"); const char subprogram_program[] = "G21 G90\n" @@ -3560,6 +4139,22 @@ int main() { noninteger_m98_p_program, sizeof(noninteger_m98_p_program) - 1) != 0, "expected LinuxCNC to reject non-integer M98 P word"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Non integer value for integer", + "expected LinuxCNC non-integer M98 P error text"); + + const char missing_m98_p_program[] = + "G21 G90\n" + "M98\n" + "M30\n" + "O100\n" + "M99\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + missing_m98_p_program, + sizeof(missing_m98_p_program) - 1) != 0, + "expected LinuxCNC to reject M98 with no P word"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Found 'm98' code with no P-word", + "expected LinuxCNC missing-M98-P error text"); const char missing_subprogram[] = "G21\n" @@ -3568,12 +4163,34 @@ int main() { ok &= expect(cnc_sim_parse_program(sim, missing_subprogram, sizeof(missing_subprogram) - 1) != 0, "expected unknown M98 subprogram to fail"); + const char noninteger_m98_l_program[] = + "G21 G90\n" + "M98 P100 L1.5\n" + "M30\n" + "O100\n" + "M99\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + noninteger_m98_l_program, + sizeof(noninteger_m98_l_program) - 1) != 0, + "expected LinuxCNC to reject non-integer M98 L word"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Non integer value for integer", + "expected LinuxCNC non-integer M98 L error text"); + const char stray_m99_program[] = "G21\n" "M99\n"; events.clear(); - ok &= expect(cnc_sim_parse_program(sim, stray_m99_program, sizeof(stray_m99_program) - 1) != 0, - "expected M99 without M98 to fail"); + ok &= expect(cnc_sim_parse_program(sim, stray_m99_program, sizeof(stray_m99_program) - 1) == 0, + cnc_sim_last_error(sim)); + bool saw_m99_final_end = false; + for (const auto &event : events) { + saw_m99_final_end = saw_m99_final_end || + (event.type == CNC_SIM_EVENT_PROGRAM_END && + event.line == 2 && + event.reserved == 0); + } + ok &= expect(saw_m99_final_end, "expected main-program M99 final program end event"); const char recursive_subprogram[] = "G21\n" @@ -3586,6 +4203,77 @@ int main() { ok &= expect(cnc_sim_parse_program(sim, recursive_subprogram, sizeof(recursive_subprogram) - 1) != 0, "expected recursive M98 nesting limit to fail"); + const char fanuc_subprogram_endsub_program[] = + "G21\n" + "F100\n" + "M98 P100\n" + "M30\n" + "O100\n" + "G1 X1\n" + "O100 endsub\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + fanuc_subprogram_endsub_program, + sizeof(fanuc_subprogram_endsub_program) - 1) != 0, + "expected LinuxCNC to reject Fanuc subprogram ending with O endsub"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == + "Fanuc 'O....' subroutine definition must end with 'M99'", + "expected LinuxCNC Fanuc-sub endsub error text"); + + const char fanuc_subprogram_return_program[] = + "G21\n" + "F100\n" + "M98 P100\n" + "M30\n" + "O100\n" + "G1 X1\n" + "O100 return\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + fanuc_subprogram_return_program, + sizeof(fanuc_subprogram_return_program) - 1) != 0, + "expected LinuxCNC to reject Fanuc subprogram ending with O return"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == + "Fanuc 'O....' subroutine definition must end with 'M99'", + "expected LinuxCNC Fanuc-sub return error text"); + + const char fanuc_comment_prefixed_header_program[] = + "G21\n" + "F100\n" + "M98 P100\n" + "M30\n" + "(comment) O100\n" + "G1 X1\n" + "M99\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + fanuc_comment_prefixed_header_program, + sizeof(fanuc_comment_prefixed_header_program) - 1) == 0, + cnc_sim_last_error(sim)); + + const char fanuc_midline_header_program[] = + "G21\n" + "F100\n" + "M98 P100\n" + "M30\n" + "G0 X0 O100\n" + "G1 X1\n" + "M99\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + fanuc_midline_header_program, + sizeof(fanuc_midline_header_program) - 1) == 0, + cnc_sim_last_error(sim)); + bool saw_fanuc_midline_sub_motion = false; + for (const auto &event : events) { + saw_fanuc_midline_sub_motion = saw_fanuc_midline_sub_motion || + (event.type == CNC_SIM_EVENT_LINEAR_FEED && + event.line == 6 && + event.end.x == 1.0); + } + ok &= expect(saw_fanuc_midline_sub_motion, + "expected LinuxCNC midline Fanuc numeric header to define an M98 subprogram"); + const char oword_subprogram[] = "G21 G90\n" "G0 X0 Y0 Z0\n" @@ -3785,6 +4473,46 @@ int main() { ok &= expect(saw_spaced_expression_parameter_index, "expected LinuxCNC to ignore whitespace after # inside an expression parameter index"); + const char spaced_numbered_parameter_assignment_program[] = + "G21 G90\n" + "F100\n" + "#1 2 = 7\n" + "G1 X#12\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + spaced_numbered_parameter_assignment_program, + sizeof(spaced_numbered_parameter_assignment_program) - 1) == 0, + cnc_sim_last_error(sim)); + bool saw_spaced_numbered_parameter_assignment = false; + for (const auto &event : events) { + saw_spaced_numbered_parameter_assignment = saw_spaced_numbered_parameter_assignment || + (event.type == CNC_SIM_EVENT_LINEAR_FEED && + event.line == 4 && + event.end.x == 7.0); + } + ok &= expect(saw_spaced_numbered_parameter_assignment, + "expected LinuxCNC to ignore whitespace inside numbered parameter assignment index"); + + const char spaced_parameter_assignment_value_program[] = + "G21 G90\n" + "F100\n" + "#1 = 2 3\n" + "G1 X#1\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + spaced_parameter_assignment_value_program, + sizeof(spaced_parameter_assignment_value_program) - 1) == 0, + cnc_sim_last_error(sim)); + bool saw_spaced_parameter_assignment_value = false; + for (const auto &event : events) { + saw_spaced_parameter_assignment_value = saw_spaced_parameter_assignment_value || + (event.type == CNC_SIM_EVENT_LINEAR_FEED && + event.line == 4 && + event.end.x == 23.0); + } + ok &= expect(saw_spaced_parameter_assignment_value, + "expected LinuxCNC to ignore whitespace inside parameter assignment values"); + const char parallel_parameter_assignment_program[] = "G21 G90\n" "F100\n" @@ -4146,8 +4874,8 @@ int main() { events.clear(); ok &= expect(cnc_sim_parse_program(sim, numeric_oword_main_eof_program, - sizeof(numeric_oword_main_eof_program) - 1) != 0, - "expected LinuxCNC to reject numeric O-word main program ending at EOF"); + sizeof(numeric_oword_main_eof_program) - 1) == 0, + cnc_sim_last_error(sim)); const char numeric_oword_sub_illegal_location_program[] = "O1\n" @@ -4160,8 +4888,21 @@ int main() { events.clear(); ok &= expect(cnc_sim_parse_program(sim, numeric_oword_sub_illegal_location_program, - sizeof(numeric_oword_sub_illegal_location_program) - 1) != 0, - "expected LinuxCNC to reject numeric O-word subprogram before main program end"); + sizeof(numeric_oword_sub_illegal_location_program) - 1) == 0, + cnc_sim_last_error(sim)); + + const char comment_prefixed_numeric_main_program[] = + "(comment) O1\n" + "M30\n" + "O2 sub\n" + "M99\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + comment_prefixed_numeric_main_program, + sizeof(comment_prefixed_numeric_main_program) - 1) != 0, + "expected LinuxCNC to reject comment-prefixed numeric O-word main header"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Bad character 'o' used", + "expected LinuxCNC comment-prefixed numeric O-word error text"); const char canned_cycle_rotary_followup_program[] = "G81 X0 Y0 Z-1 R0 F12\n" @@ -4171,6 +4912,8 @@ int main() { canned_cycle_rotary_followup_program, sizeof(canned_cycle_rotary_followup_program) - 1) != 0, "expected LinuxCNC to reject rotary axis word during active canned cycle"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Cannot put an a in canned cycle", + cnc_sim_last_error(sim)); const char nested_subroutine_definition_program[] = "O sub\n" @@ -4734,12 +5477,12 @@ int main() { ok &= expect(saw_current_position_numbered_params, "expected LinuxCNC numbered current-position parameters from parameter table"); - const char *persistent_var_path = "/tmp/cnc_sim_smoke_persistent.var"; + const std::string persistent_var_path = temp_dir.file("persistent.var"); { std::ofstream var_file(persistent_var_path); } { - ScopedEnv var_env("CNC_SIM_RS274_VAR", persistent_var_path); + ScopedEnv var_env("CNC_SIM_RS274_VAR", persistent_var_path.c_str()); const char persistent_parameters_set_program[] = "G21 G90 G17\n" "#5001 = 23\n" @@ -4778,12 +5521,12 @@ int main() { "expected LinuxCNC persistent numbered parameters to reload from parameter file"); } - const char *g54_persistent_var_path = "/tmp/cnc_sim_smoke_g54_persistent.var"; + const std::string g54_persistent_var_path = temp_dir.file("g54_persistent.var"); { std::ofstream var_file(g54_persistent_var_path); } { - ScopedEnv var_env("CNC_SIM_RS274_VAR", g54_persistent_var_path); + ScopedEnv var_env("CNC_SIM_RS274_VAR", g54_persistent_var_path.c_str()); const char g54_persistent_set_program[] = "G21 G90 G17\n" "G10 L2 P1 X1 Y2 Z3 A4 B5 C6 U7 V8 W9 R11\n" @@ -4815,12 +5558,12 @@ int main() { "expected LinuxCNC G54 persistent numbered parameters to reload from parameter file"); } - const char *user_persistent_var_path = "/tmp/cnc_sim_smoke_user_persistent.var"; + const std::string user_persistent_var_path = temp_dir.file("user_persistent.var"); { std::ofstream var_file(user_persistent_var_path); } { - ScopedEnv var_env("CNC_SIM_RS274_VAR", user_persistent_var_path); + ScopedEnv var_env("CNC_SIM_RS274_VAR", user_persistent_var_path.c_str()); const char user_persistent_set_program[] = "#5001 = 23\n" "M30\n"; @@ -4850,13 +5593,13 @@ int main() { "expected LinuxCNC #5001 to remain absent from an empty parameter file"); } - const char *user_existing_persistent_var_path = "/tmp/cnc_sim_smoke_user_existing_persistent.var"; + const std::string user_existing_persistent_var_path = temp_dir.file("user_existing_persistent.var"); { std::ofstream var_file(user_existing_persistent_var_path); var_file << "5001\t17.000000\n"; } { - ScopedEnv var_env("CNC_SIM_RS274_VAR", user_existing_persistent_var_path); + ScopedEnv var_env("CNC_SIM_RS274_VAR", user_existing_persistent_var_path.c_str()); const char user_existing_persistent_set_program[] = "#5001 = 23\n" "M30\n"; @@ -4886,13 +5629,13 @@ int main() { "expected LinuxCNC #5001 to persist when the parameter already exists in the file"); } - const char *user_boundary_persistent_var_path = "/tmp/cnc_sim_smoke_user_boundary_persistent.var"; + const std::string user_boundary_persistent_var_path = temp_dir.file("user_boundary_persistent.var"); { std::ofstream var_file(user_boundary_persistent_var_path); var_file << "5001\t17.000000\n5030\t18.000000\n5060\t19.000000\n"; } { - ScopedEnv var_env("CNC_SIM_RS274_VAR", user_boundary_persistent_var_path); + ScopedEnv var_env("CNC_SIM_RS274_VAR", user_boundary_persistent_var_path.c_str()); const char user_boundary_persistent_set_program[] = "#5000 = 11\n" "#5001 = 22\n" @@ -4925,12 +5668,12 @@ int main() { "expected LinuxCNC persistent-user parameter boundary behavior for #5000/#5001/#5060"); } - const char *rotary_persistent_var_path = "/tmp/cnc_sim_smoke_rotary_persistent.var"; + const std::string rotary_persistent_var_path = temp_dir.file("rotary_persistent.var"); { std::ofstream var_file(rotary_persistent_var_path); } { - ScopedEnv var_env("CNC_SIM_RS274_VAR", rotary_persistent_var_path); + ScopedEnv var_env("CNC_SIM_RS274_VAR", rotary_persistent_var_path.c_str()); const char rotary_persistent_set_program[] = "G21 G90 G17\n" "G0 A1 B2 C3 U4 V5 W6\n" @@ -5063,13 +5806,13 @@ int main() { ok &= expect(saw_named_parameter_normalized_exists, "expected EXISTS to use normalized named parameter names"); - const char *ini_path = "/tmp/cnc_sim_smoke_named_ini.ini"; + const std::string ini_path = temp_dir.file("named_ini.ini"); { std::ofstream ini(ini_path); ini << "[SETUP]\nXPOS = 3.25\n"; } { - ScopedEnv ini_env("INI_FILE_NAME", ini_path); + ScopedEnv ini_env("INI_FILE_NAME", ini_path.c_str()); const char ini_named_parameter_program[] = "G21 G90\n" "F100\n" @@ -5096,19 +5839,19 @@ int main() { ok &= expect(saw_ini_named_parameter_motion, "expected #<_ini[section]key> named parameter lookup"); ok &= expect(saw_ini_named_parameter_exists_motion, "expected EXISTS for #<_ini[section]key>"); - const char *hal_path = "/tmp/cnc_sim_smoke_hal_pins.txt"; + const std::string hal_path = temp_dir.file("hal_pins.txt"); { std::ofstream hal(hal_path); hal << "motion-controller.time = 42\n"; } { - ScopedEnv hal_env("HAL_PIN_FILE", hal_path); + ScopedEnv hal_env("HAL_PIN_FILE", hal_path.c_str()); const char hal_named_parameter_program[] = "G21 G90\n" "F100\n" "G1 X#<_hal[motion-controller.time]>\n" "O10 if [EXISTS[#<_hal[motion-controller.time]>]]\n" - "G1 Y#<_task>\n" + "G1 Y[1 + #<_task>]\n" "O10 endif\n"; events.clear(); ok &= expect(cnc_sim_parse_program(sim, @@ -5129,6 +5872,26 @@ int main() { ok &= expect(saw_hal_named_parameter_motion, "expected #<_hal[HAL item]> named parameter lookup"); ok &= expect(saw_hal_named_parameter_exists_motion, "expected EXISTS for #<_hal[HAL item]>"); + const char version_named_parameter_program[] = + "G21 G90\n" + "F100\n" + "G1 X#<_vmajor> Y[1 + #<_vminor>]\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + version_named_parameter_program, + sizeof(version_named_parameter_program) - 1) == 0, + cnc_sim_last_error(sim)); + bool saw_version_named_parameters = false; + for (const auto &event : events) { + saw_version_named_parameters = saw_version_named_parameters || + (event.type == CNC_SIM_EVENT_LINEAR_FEED && + event.line == 3 && + near(event.end.x, 2.10) && + near(event.end.y, 1.0)); + } + ok &= expect(saw_version_named_parameters, + "expected LinuxCNC predefined _vmajor/_vminor named parameters"); + const char override_named_parameter_program[] = "G21 G90\n" "M7\n" @@ -5416,6 +6179,25 @@ int main() { ok &= expect(saw_signed_spaced_bracket_word_motion, "expected LinuxCNC word bracket expressions to allow whitespace after a minus sign"); + const char ignored_space_inside_number_program[] = + "G21 G90\n" + "F100\n" + "G1 X1 2\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + ignored_space_inside_number_program, + sizeof(ignored_space_inside_number_program) - 1) == 0, + cnc_sim_last_error(sim)); + bool saw_ignored_space_inside_number_motion = false; + for (const auto &event : events) { + saw_ignored_space_inside_number_motion = saw_ignored_space_inside_number_motion || + (event.type == CNC_SIM_EVENT_LINEAR_FEED && + event.line == 3 && + event.end.x == 12.0); + } + ok &= expect(saw_ignored_space_inside_number_motion, + "expected LinuxCNC to ignore spaces inside numeric words"); + const char parenthesized_expression_program[] = "G21 G90\n" "F100\n" @@ -5552,6 +6334,17 @@ int main() { sizeof(invalid_m19_p_word_program) - 1) != 0, "expected LinuxCNC to reject M19 P values outside 0, 1, or 2"); + const char noninteger_m19_p_word_program[] = + "G21 G90\n" + "M19 R45 P1.5 Q1\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + noninteger_m19_p_word_program, + sizeof(noninteger_m19_p_word_program) - 1) != 0, + "expected LinuxCNC to reject non-integer M19 P values"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "P value not an integer with M19 G2 or G3", + "expected LinuxCNC non-integer M19 P error text"); + const char unused_q_word_program[] = "G21 G90\n" "F100\n" @@ -5571,6 +6364,8 @@ int main() { unused_r_word_program, sizeof(unused_r_word_program) - 1) != 0, "expected LinuxCNC to reject R word with no code to use it"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "R word with no g code that uses it", + "expected LinuxCNC unused-R-word error text"); const char unused_l_word_program[] = "G21 G90\n" @@ -5601,6 +6396,8 @@ int main() { g70_is_not_units_program, sizeof(g70_is_not_units_program) - 1) != 0, "expected LinuxCNC to treat G70 as a lathe cycle, not inch units"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "G7x.x requires a Q word", + "expected LinuxCNC missing-G7x-Q error text"); const char g71_requires_g18_program[] = "G21 G90\n" @@ -5638,6 +6435,8 @@ int main() { noninteger_l_word_program, sizeof(noninteger_l_word_program) - 1) != 0, "expected LinuxCNC to reject non-integer L word"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Non integer value for integer", + "expected LinuxCNC non-integer-L error text"); const char invalid_g10_l_word_program[] = "G21 G90\n" @@ -5678,6 +6477,17 @@ int main() { sizeof(out_of_range_g10_l1_p_word_program) - 1) != 0, "expected LinuxCNC to reject G10 L1 P values less than 1"); + const char missing_g10_l1_p_word_program[] = + "G21 G90\n" + "G10 L1 X0\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + missing_g10_l1_p_word_program, + sizeof(missing_g10_l1_p_word_program) - 1) != 0, + "expected LinuxCNC to reject G10 L1 without a P tool number"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "P value out of range with G10 L1", + "expected LinuxCNC missing-G10-L1-P error text"); + const char g10_l1_without_offsets_program[] = "G21 G90\n" "G10 L1 P1\n"; @@ -5687,6 +6497,17 @@ int main() { sizeof(g10_l1_without_offsets_program) - 1) != 0, "expected LinuxCNC to reject G10 L1 without offsets"); + const char missing_g10_tool_program[] = + "G21 G90\n" + "G10 L1 P999 X0\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + missing_g10_tool_program, + sizeof(missing_g10_tool_program) - 1) != 0, + "expected LinuxCNC G10 L1 to reject tool numbers missing from the tool table"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Requested tool 999 not found in the tool table", + "expected LinuxCNC missing-G10-tool error text"); + const char noninteger_g10_tool_q_program[] = "G21 G90\n" "G10 L1 P1 Q1.5\n"; @@ -5696,6 +6517,17 @@ int main() { sizeof(noninteger_g10_tool_q_program) - 1) != 0, "expected LinuxCNC to reject non-integer G10 tool Q word"); + const char negative_g10_tool_q_program[] = + "G21 G90\n" + "G10 L1 P1 Q-2\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + negative_g10_tool_q_program, + sizeof(negative_g10_tool_q_program) - 1) != 0, + "expected LinuxCNC G10 tool Q integer check to reject negative values"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Q number in G10 is not an integer", + "expected LinuxCNC negative-G10-Q error text"); + const char out_of_range_g10_tool_q_program[] = "G21 G90\n" "G10 L1 P1 Q10\n"; @@ -5746,12 +6578,14 @@ int main() { const char too_many_m_codes_program[] = "G21 G90\n" - "M3 M7 M8 M48 M50\n"; + "M3 M6 M7 M48 M62\n"; events.clear(); ok &= expect(cnc_sim_parse_program(sim, too_many_m_codes_program, sizeof(too_many_m_codes_program) - 1) != 0, "expected LinuxCNC to reject more than four M codes on one line"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Too many m codes on line", + "expected LinuxCNC too-many-M-codes error text"); const char missing_g96_s_word_program[] = "G21 G90\n" @@ -5771,6 +6605,17 @@ int main() { sizeof(invalid_m19_q_word_program) - 1) != 0, "expected LinuxCNC to reject M19 Q values less than or equal to zero"); + const char invalid_m19_rq_priority_program[] = + "G21 G90\n" + "M19 R-1 Q0\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + invalid_m19_rq_priority_program, + sizeof(invalid_m19_rq_priority_program) - 1) != 0, + "expected LinuxCNC to prioritize invalid M19 R over invalid Q"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "R value must be within 0..360 with M19", + "expected LinuxCNC M19 R/Q error priority text"); + const char invalid_m67_e_word_program[] = "G21 G90\n" "M67 E-1 Q1\n"; @@ -5779,6 +6624,30 @@ int main() { invalid_m67_e_word_program, sizeof(invalid_m67_e_word_program) - 1) != 0, "expected LinuxCNC to reject negative E analog index with M67"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Invalid analog index with M67", + "expected LinuxCNC invalid-M67-E error text"); + + const char missing_m67_e_word_program[] = + "G21 G90\n" + "M67 Q1\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + missing_m67_e_word_program, + sizeof(missing_m67_e_word_program) - 1) != 0, + "expected LinuxCNC to reject missing E analog index with M67"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Invalid analog index with M67", + "expected LinuxCNC missing-M67-E error text"); + + const char missing_m68_e_word_program[] = + "G21 G90\n" + "M68 Q1\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + missing_m68_e_word_program, + sizeof(missing_m68_e_word_program) - 1) != 0, + "expected LinuxCNC to reject missing E analog index with M68"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Invalid analog index with M68", + "expected LinuxCNC missing-M68-E error text"); const char invalid_m66_p_word_program[] = "G21 G90\n" @@ -5788,6 +6657,117 @@ int main() { invalid_m66_p_word_program, sizeof(invalid_m66_p_word_program) - 1) != 0, "expected LinuxCNC to reject negative P digital input index with M66"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == + "Need to have either a valid P or a valid E word with M66", + "expected LinuxCNC negative-P M66 error text"); + + const char invalid_m66_e_word_program[] = + "G21 G90\n" + "M66 E-1 L0\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + invalid_m66_e_word_program, + sizeof(invalid_m66_e_word_program) - 1) != 0, + "expected LinuxCNC to reject negative E analog input index with M66"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == + "Need to have either a valid P or a valid E word with M66", + "expected LinuxCNC negative-E M66 error text"); + + const char missing_m66_input_program[] = + "G21 G90\n" + "M66\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + missing_m66_input_program, + sizeof(missing_m66_input_program) - 1) != 0, + "expected LinuxCNC to reject M66 missing both P and E words"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == + "Need to have either a valid P or a valid E word with M66", + "expected LinuxCNC missing-M66-input error text"); + + const char dual_input_m66_program[] = + "G21 G90\n" + "M66 P0 E1\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + dual_input_m66_program, + sizeof(dual_input_m66_program) - 1) != 0, + "expected LinuxCNC to reject M66 selecting both digital and analog inputs"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == + "Invalid to select both a digital and an analog input with M66", + "expected LinuxCNC dual-input M66 error text"); + + const char analog_wait_type_m66_program[] = + "G21 G90\n" + "M66 E0 L1\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + analog_wait_type_m66_program, + sizeof(analog_wait_type_m66_program) - 1) != 0, + "expected LinuxCNC to reject non-immediate analog M66 wait"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == + "Zero timeout with wait type != immediate return", + "expected LinuxCNC analog-wait-type M66 error text"); + + const char analog_wait_type_with_timeout_m66_program[] = + "G21 G90\n" + "M66 E0 L1 Q1\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + analog_wait_type_with_timeout_m66_program, + sizeof(analog_wait_type_with_timeout_m66_program) - 1) != 0, + "expected LinuxCNC to reject analog M66 wait types other than immediate"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == + "Can't select analog input with wait type != immediate return", + "expected LinuxCNC analog-wait-type-with-timeout M66 error text"); + + const char missing_timeout_m66_program[] = + "G21 G90\n" + "M66 P0 L3\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + missing_timeout_m66_program, + sizeof(missing_timeout_m66_program) - 1) != 0, + "expected LinuxCNC to reject non-immediate M66 with zero timeout"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == + "Zero timeout with wait type != immediate return", + "expected LinuxCNC zero-timeout M66 error text"); + + const char noninteger_l_m66_program[] = + "G21 G90\n" + "M66 P0 L1.5 Q1\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + noninteger_l_m66_program, + sizeof(noninteger_l_m66_program) - 1) != 0, + "expected LinuxCNC to reject non-integer L word with M66"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Non integer value for integer", + "expected LinuxCNC non-integer-L M66 error text"); + + const char negative_l_m66_program[] = + "G21 G90\n" + "M66 P0 L-1 Q1\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + negative_l_m66_program, + sizeof(negative_l_m66_program) - 1) != 0, + "expected LinuxCNC to reject negative L word with M66"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Negative l word used", + "expected LinuxCNC negative-L M66 error text"); + + const char negative_l_m98_program[] = + "G21 G90\n" + "M98 P100 L-1\n" + "M30\n" + "O100\n" + "M99\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + negative_l_m98_program, + sizeof(negative_l_m98_program) - 1) != 0, + "expected LinuxCNC to reject negative L word with M98"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Negative l word used", + "expected LinuxCNC negative-L M98 error text"); const char negative_m62_p_word_program[] = "G21 G90\n" @@ -5808,6 +6788,50 @@ int main() { ok &= expect(saw_negative_m62_output, "expected LinuxCNC to allow M62 P-1 and pass the rounded output index"); + const char missing_m62_p_word_program[] = + "G21 G90\n" + "M62\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + missing_m62_p_word_program, + sizeof(missing_m62_p_word_program) - 1) != 0, + "expected LinuxCNC to reject M62 with no P word"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "No valid P word with M62", + "expected LinuxCNC missing-M62-P error text"); + + const char missing_m63_p_word_program[] = + "G21 G90\n" + "M63\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + missing_m63_p_word_program, + sizeof(missing_m63_p_word_program) - 1) != 0, + "expected LinuxCNC to reject M63 with no P word"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "No valid P word with M63", + "expected LinuxCNC missing-M63-P error text"); + + const char missing_m64_p_word_program[] = + "G21 G90\n" + "M64\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + missing_m64_p_word_program, + sizeof(missing_m64_p_word_program) - 1) != 0, + "expected LinuxCNC to reject M64 with no P word"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "No valid P word with M64", + "expected LinuxCNC missing-M64-P error text"); + + const char missing_m65_p_word_program[] = + "G21 G90\n" + "M65\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + missing_m65_p_word_program, + sizeof(missing_m65_p_word_program) - 1) != 0, + "expected LinuxCNC to reject M65 with no P word"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "No valid P word with M65", + "expected LinuxCNC missing-M65-P error text"); + const char invalid_g10_l20_r_word_program[] = "G21 G90\n" "G10 L20 P1 X1 R30\n"; @@ -5816,6 +6840,8 @@ int main() { invalid_g10_l20_r_word_program, sizeof(invalid_g10_l20_r_word_program) - 1) != 0, "expected LinuxCNC to reject R word with G10 L20"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "R not allowed in G10 L20", + "expected LinuxCNC G10 L20 R-word error text"); const char invalid_g10_l2_i_word_program[] = "G21 G90\n" @@ -5825,6 +6851,19 @@ int main() { invalid_g10_l2_i_word_program, sizeof(invalid_g10_l2_i_word_program) - 1) != 0, "expected LinuxCNC to reject I word with G10 L2"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "I J words not allowed with G10 L2", + "expected LinuxCNC G10 L2 I-word error text"); + + const char invalid_g10_l20_i_word_program[] = + "G21 G90\n" + "G10 L20 P1 I2\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + invalid_g10_l20_i_word_program, + sizeof(invalid_g10_l20_i_word_program) - 1) != 0, + "expected LinuxCNC to reject I word with G10 L20"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "I J words not allowed with G10 L2", + "expected LinuxCNC G10 L20 I-word error text"); const char probe_feed_per_rev_program[] = "G21 G90\n" @@ -5864,6 +6903,44 @@ int main() { negative_g4_p_word_program, sizeof(negative_g4_p_word_program) - 1) != 0, "expected LinuxCNC to reject G4 P-1 as a missing dwell time"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Dwell time missing with g4", + "expected LinuxCNC negative G4 P error text"); + + const char less_than_negative_one_g4_p_word_program[] = + "G21 G90\n" + "G4 P-1.001\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + less_than_negative_one_g4_p_word_program, + sizeof(less_than_negative_one_g4_p_word_program) - 1) == 0, + cnc_sim_last_error(sim)); + bool saw_less_than_negative_one_g4_dwell = false; + for (const auto &event : events) { + saw_less_than_negative_one_g4_dwell = saw_less_than_negative_one_g4_dwell || + (event.type == CNC_SIM_EVENT_DWELL && + event.line == 2 && + near(event.dwell_seconds, -1.001)); + } + ok &= expect(saw_less_than_negative_one_g4_dwell, + "expected LinuxCNC to execute G4 with P less than -1"); + + const char near_negative_one_g4_p_word_program[] = + "G21 G90\n" + "G4 P-0.9999\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + near_negative_one_g4_p_word_program, + sizeof(near_negative_one_g4_p_word_program) - 1) == 0, + cnc_sim_last_error(sim)); + bool saw_near_negative_one_g4_dwell = false; + for (const auto &event : events) { + saw_near_negative_one_g4_dwell = saw_near_negative_one_g4_dwell || + (event.type == CNC_SIM_EVENT_DWELL && + event.line == 2 && + near(event.dwell_seconds, -0.9999)); + } + ok &= expect(saw_near_negative_one_g4_dwell, + "expected LinuxCNC to execute G4 with P just above -1"); const char g4_with_arc_program[] = "G21 G90 G17\n" @@ -5881,8 +6958,17 @@ int main() { events.clear(); ok &= expect(cnc_sim_parse_program(sim, noninteger_g10_p_word_program, - sizeof(noninteger_g10_p_word_program) - 1) != 0, - "expected LinuxCNC to reject non-integer P word with G10"); + sizeof(noninteger_g10_p_word_program) - 1) == 0, + cnc_sim_last_error(sim)); + + const char noninteger_g10_l20_p_word_program[] = + "G21 G90\n" + "G10 L20 P1.5 X1\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + noninteger_g10_l20_p_word_program, + sizeof(noninteger_g10_l20_p_word_program) - 1) == 0, + cnc_sim_last_error(sim)); const char near_noninteger_g10_p_word_program[] = "G21 G90\n" @@ -5901,6 +6987,74 @@ int main() { out_of_range_g10_p_word_program, sizeof(out_of_range_g10_p_word_program) - 1) != 0, "expected LinuxCNC to reject G10 L2 P values outside 0..9"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "P value out of range (0-9) with G10 L2", + "expected LinuxCNC G10 L2 P-range error text"); + + const char missing_g10_l2_p_word_program[] = + "G21 G90\n" + "G10 L2 X1\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + missing_g10_l2_p_word_program, + sizeof(missing_g10_l2_p_word_program) - 1) != 0, + "expected LinuxCNC to reject G10 L2 without a P word"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "P value out of range (0-9) with G10 L2", + "expected LinuxCNC missing-G10-L2-P error text"); + + const char out_of_range_g10_l20_p_word_program[] = + "G21 G90\n" + "G10 L20 P10 X1\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + out_of_range_g10_l20_p_word_program, + sizeof(out_of_range_g10_l20_p_word_program) - 1) != 0, + "expected LinuxCNC to reject G10 L20 P values outside 0..9"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "P value out of range (0-9) with G10 L20", + "expected LinuxCNC G10 L20 P-range error text"); + + const char missing_g10_l20_p_word_program[] = + "G21 G90\n" + "G10 L20 X1\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + missing_g10_l20_p_word_program, + sizeof(missing_g10_l20_p_word_program) - 1) != 0, + "expected LinuxCNC to reject G10 L20 without a P word"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "P value out of range (0-9) with G10 L20", + "expected LinuxCNC missing-G10-L20-P error text"); + + const char out_of_range_g10_l1_p_word_text_program[] = + "G21 G90\n" + "G10 L1 P0 X0\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + out_of_range_g10_l1_p_word_text_program, + sizeof(out_of_range_g10_l1_p_word_text_program) - 1) != 0, + "expected LinuxCNC to reject G10 L1 P values below 1"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "P value out of range with G10 L1", + "expected LinuxCNC G10 L1 P-range error text"); + + const char out_of_range_g10_l10_p_word_program[] = + "G21 G90\n" + "G10 L10 P0 X0\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + out_of_range_g10_l10_p_word_program, + sizeof(out_of_range_g10_l10_p_word_program) - 1) != 0, + "expected LinuxCNC to reject G10 L10 P values below 1"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "P value out of range with G10 L10", + "expected LinuxCNC G10 L10 P-range error text"); + + const char out_of_range_g10_l11_p_word_program[] = + "G21 G90\n" + "G10 L11 P0 X0\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + out_of_range_g10_l11_p_word_program, + sizeof(out_of_range_g10_l11_p_word_program) - 1) != 0, + "expected LinuxCNC to reject G10 L11 P values below 1"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "P value out of range with G10 L11", + "expected LinuxCNC G10 L11 P-range error text"); const char g1_without_feed_program[] = "G21 G90\n" @@ -5910,6 +7064,8 @@ int main() { g1_without_feed_program, sizeof(g1_without_feed_program) - 1) != 0, "expected LinuxCNC to reject G1 with zero feed rate"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Cannot do g1 with zero feed rate", + "expected LinuxCNC zero-feed-G1 error text"); const char negative_feed_word_program[] = "G21 G90\n" @@ -5919,6 +7075,8 @@ int main() { negative_feed_word_program, sizeof(negative_feed_word_program) - 1) != 0, "expected LinuxCNC to reject negative F word"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Negative f word used", + "expected LinuxCNC negative-F error text"); const char negative_spindle_speed_program[] = "G21 G90\n" @@ -5937,6 +7095,8 @@ int main() { negative_tool_id_program, sizeof(negative_tool_id_program) - 1) != 0, "expected LinuxCNC to reject negative T word"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Negative tool id (tool not found)", + "expected LinuxCNC negative-T error text"); const char missing_tool_id_program[] = "G21 G90\n" @@ -5955,6 +7115,19 @@ int main() { noninteger_tool_id_program, sizeof(noninteger_tool_id_program) - 1) != 0, "expected LinuxCNC to reject non-integer T word"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Non integer value for integer", + "expected LinuxCNC non-integer-T error text"); + + const char boundary_noninteger_tool_id_program[] = + "G21 G90\n" + "T1.9999\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + boundary_noninteger_tool_id_program, + sizeof(boundary_noninteger_tool_id_program) - 1) != 0, + "expected LinuxCNC to reject T word exactly at the read_integer_value upper tolerance boundary"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Non integer value for integer", + "expected LinuxCNC boundary-T error text"); const char negative_m_code_program[] = "G21 G90\n" @@ -5964,6 +7137,8 @@ int main() { negative_m_code_program, sizeof(negative_m_code_program) - 1) != 0, "expected LinuxCNC to reject negative M code"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Negative m code used", + "expected LinuxCNC negative-M error text"); const char noninteger_m_code_program[] = "G21 G90\n" @@ -5973,6 +7148,8 @@ int main() { noninteger_m_code_program, sizeof(noninteger_m_code_program) - 1) != 0, "expected LinuxCNC to reject non-integer M code"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Non integer value for integer", + "expected LinuxCNC non-integer-M error text"); const char upper_tolerance_noninteger_m_code_program[] = "G21 G90\n" @@ -5991,6 +7168,8 @@ int main() { unknown_m_code_program, sizeof(unknown_m_code_program) - 1) != 0, "expected LinuxCNC to reject unknown M code"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Unknown m code used: M54", + "expected LinuxCNC unknown-M error text"); const char out_of_range_m_code_program[] = "G21 G90\n" @@ -6000,6 +7179,8 @@ int main() { out_of_range_m_code_program, sizeof(out_of_range_m_code_program) - 1) != 0, "expected LinuxCNC to reject M code greater than 199"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "M-code greater than 199: M200", + "expected LinuxCNC out-of-range-M error text"); const char g33_with_f_word_program[] = "G21 G90\n" @@ -6010,6 +7191,8 @@ int main() { g33_with_f_word_program, sizeof(g33_with_f_word_program) - 1) != 0, "expected LinuxCNC to reject F word with G33"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "F word used with a g33/g33.1", + "expected LinuxCNC G33-F error text"); const char g33_without_k_word_program[] = "G21 G90\n" @@ -6020,6 +7203,8 @@ int main() { g33_without_k_word_program, sizeof(g33_without_k_word_program) - 1) != 0, "expected LinuxCNC to reject G33 without K word"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "K word missing with g33/g33.1", + "expected LinuxCNC missing-G33-K error text"); const char g331_without_k_word_program[] = "G21 G90\n" @@ -6030,6 +7215,8 @@ int main() { g331_without_k_word_program, sizeof(g331_without_k_word_program) - 1) != 0, "expected LinuxCNC to reject G33.1 without K word"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "K word missing with g33/g33.1", + "expected LinuxCNC missing-G33.1-K error text"); const char g33_without_axis_program[] = "G21 G90\n" @@ -6049,6 +7236,8 @@ int main() { g52_without_axis_program, sizeof(g52_without_axis_program) - 1) != 0, "expected LinuxCNC to reject G52 without axis words"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "All axes missing with g52 or g92", + "expected LinuxCNC missing-G52-axis error text"); const char g92_without_axis_program[] = "G21 G90\n" @@ -6058,6 +7247,74 @@ int main() { g92_without_axis_program, sizeof(g92_without_axis_program) - 1) != 0, "expected LinuxCNC to reject G92 without axis words"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "All axes missing with g52 or g92", + "expected LinuxCNC missing-G92-axis error text"); + + const char g921_with_axis_program[] = + "G21 G90\n" + "G92.1 X1\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + g921_with_axis_program, + sizeof(g921_with_axis_program) - 1) != 0, + "expected LinuxCNC to reject axis words with G92.1"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Cannot use axis values without a g code that uses them", + "expected LinuxCNC G92.1 axis-word error text"); + + const char g922_with_axis_program[] = + "G21 G90\n" + "G92.2 X1\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + g922_with_axis_program, + sizeof(g922_with_axis_program) - 1) != 0, + "expected LinuxCNC to reject axis words with G92.2"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Cannot use axis values without a g code that uses them", + "expected LinuxCNC G92.2 axis-word error text"); + + const char g923_with_axis_program[] = + "G21 G90\n" + "G92.3 X1\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + g923_with_axis_program, + sizeof(g923_with_axis_program) - 1) != 0, + "expected LinuxCNC to reject axis words with G92.3"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Cannot use axis values without a g code that uses them", + "expected LinuxCNC G92.3 axis-word error text"); + + const char g49_with_axis_program[] = + "G21 G90\n" + "G49 X1\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + g49_with_axis_program, + sizeof(g49_with_axis_program) - 1) != 0, + "expected LinuxCNC to reject axis words with G49"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Cannot use axis values without a g code that uses them", + "expected LinuxCNC G49 axis-word error text"); + + const char m0_with_axis_program[] = + "G21 G90\n" + "M0 X1\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + m0_with_axis_program, + sizeof(m0_with_axis_program) - 1) != 0, + "expected LinuxCNC to reject axis words with M0 and no motion mode"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Cannot use axis values without a g code that uses them", + "expected LinuxCNC M0 axis-word error text"); + + const char feed_with_axis_program[] = + "G21 G90\n" + "F200 X1\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + feed_with_axis_program, + sizeof(feed_with_axis_program) - 1) != 0, + "expected LinuxCNC to reject axis words with F and no motion mode"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Cannot use axis values without a g code that uses them", + "expected LinuxCNC F axis-word error text"); const char g80_with_axis_program[] = "G21 G90\n" @@ -6067,6 +7324,8 @@ int main() { g80_with_axis_program, sizeof(g80_with_axis_program) - 1) != 0, "expected LinuxCNC to reject axis words with G80"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Cannot use axis values with g80", + "expected LinuxCNC G80 axis-word error text"); const char group0_and_motion_axis_program[] = "G21 G90\n" @@ -6077,6 +7336,8 @@ int main() { group0_and_motion_axis_program, sizeof(group0_and_motion_axis_program) - 1) != 0, "expected LinuxCNC to reject group 0 and motion G codes that both use axis words"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Cannot use two g codes that both use axis values", + "expected LinuxCNC dual-axis-G-code error text"); const char duplicate_axis_word_program[] = "G21 G90\n" @@ -6087,6 +7348,19 @@ int main() { duplicate_axis_word_program, sizeof(duplicate_axis_word_program) - 1) != 0, "expected LinuxCNC to reject multiple X words on one line"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Multiple x words on one line", + "expected LinuxCNC duplicate-X error text"); + + const char duplicate_spindle_selector_program[] = + "G21 G90\n" + "M3 $0 $1\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + duplicate_spindle_selector_program, + sizeof(duplicate_spindle_selector_program) - 1) != 0, + "expected LinuxCNC to reject multiple spindle selector words on one line"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Multiple spindle choice ($) words on one line", + "expected LinuxCNC duplicate-$ error text"); const char same_modal_group_g_program[] = "G21 G90\n" @@ -6097,6 +7371,8 @@ int main() { same_modal_group_g_program, sizeof(same_modal_group_g_program) - 1) != 0, "expected LinuxCNC to reject two G codes from the same modal group"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Two g codes used from same modal group", + "expected LinuxCNC same-modal-group-G error text"); const char same_modal_group_uv_plane_program[] = "G21 G90\n" @@ -6210,6 +7486,8 @@ int main() { same_modal_group_m_program, sizeof(same_modal_group_m_program) - 1) != 0, "expected LinuxCNC to reject two M codes from the same modal group"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Two m codes used from same modal group", + "expected LinuxCNC same-modal-group-M error text"); const char same_modal_group_output_m_program[] = "G21 G90\n" @@ -6228,6 +7506,8 @@ int main() { negative_g_code_program, sizeof(negative_g_code_program) - 1) != 0, "expected LinuxCNC to reject negative G code"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Negative g code used", + "expected LinuxCNC negative-G error text"); const char out_of_range_g_code_program[] = "G21 G90\n" @@ -6237,6 +7517,8 @@ int main() { out_of_range_g_code_program, sizeof(out_of_range_g_code_program) - 1) != 0, "expected LinuxCNC to reject G code greater than 99.9"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "G-code out of range", + "expected LinuxCNC out-of-range-G error text"); const char noninitial_line_number_program[] = "G21 G90\n" @@ -6291,6 +7573,8 @@ int main() { unknown_g_code_program, sizeof(unknown_g_code_program) - 1) != 0, "expected LinuxCNC to reject unknown G code"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Unknown g code used", + "expected LinuxCNC unknown-G error text"); const char invalid_g50_code_program[] = "G21 G90\n" @@ -6373,6 +7657,17 @@ int main() { ok &= expect(saw_m19_dollar_orient, "expected LinuxCNC M19 $ spindle orient state"); ok &= expect(saw_m19_dollar_wait, "expected LinuxCNC M19 $ spindle orient wait state"); + const char m19_dollar_word_out_of_range_program[] = + "G21 G90\n" + "M19 R45 $2 Q1\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + m19_dollar_word_out_of_range_program, + sizeof(m19_dollar_word_out_of_range_program) - 1) != 0, + "expected LinuxCNC to reject out-of-range M19 $ spindle selector"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Spindle ($) number out of range in M19 Command", + "expected LinuxCNC out-of-range M19 $ spindle error text"); + const char spindle_dollar_program[] = "G21\n" "S1200 M3 $0\n" @@ -6666,6 +7961,46 @@ int main() { } ok &= expect(saw_polar_absolute_motion, "expected LinuxCNC polar absolute XY motion"); + const char polar_duplicate_radius_program[] = + "G21 G90 G17\n" + "F100\n" + "G1 @1 @2 ^0\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + polar_duplicate_radius_program, + sizeof(polar_duplicate_radius_program) - 1) == 0, + cnc_sim_last_error(sim)); + bool saw_polar_duplicate_radius_motion = false; + for (const auto &event : events) { + saw_polar_duplicate_radius_motion = saw_polar_duplicate_radius_motion || + (event.type == CNC_SIM_EVENT_LINEAR_FEED && + event.line == 3 && + near(event.end.x, 2.0) && + near(event.end.y, 0.0)); + } + ok &= expect(saw_polar_duplicate_radius_motion, + "expected LinuxCNC duplicate polar radius words to use the last value"); + + const char polar_duplicate_theta_program[] = + "G21 G90 G17\n" + "F100\n" + "G1 @1 ^0 ^90\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + polar_duplicate_theta_program, + sizeof(polar_duplicate_theta_program) - 1) == 0, + cnc_sim_last_error(sim)); + bool saw_polar_duplicate_theta_motion = false; + for (const auto &event : events) { + saw_polar_duplicate_theta_motion = saw_polar_duplicate_theta_motion || + (event.type == CNC_SIM_EVENT_LINEAR_FEED && + event.line == 3 && + near(event.end.x, 0.0) && + near(event.end.y, 1.0)); + } + ok &= expect(saw_polar_duplicate_theta_motion, + "expected LinuxCNC duplicate polar theta words to use the last value"); + const char polar_theta_absolute_program[] = "G21 G90 G17\n" "F100\n" @@ -6726,6 +8061,30 @@ int main() { sizeof(polar_non_g17_program) - 1) != 0, "expected LinuxCNC to reject polar coordinates outside G17"); + const char polar_with_x_program[] = + "G21 G90 G17\n" + "F100\n" + "G1 @1 X2\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + polar_with_x_program, + sizeof(polar_with_x_program) - 1) != 0, + "expected LinuxCNC to reject polar coordinate with X word"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Cannot specify both polar coordinate and X word", + "expected LinuxCNC polar/X conflict error text"); + + const char polar_with_y_program[] = + "G21 G90 G17\n" + "F100\n" + "G1 @1 Y2\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + polar_with_y_program, + sizeof(polar_with_y_program) - 1) != 0, + "expected LinuxCNC to reject polar coordinate with Y word"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Cannot specify both polar coordinate and Y word", + "expected LinuxCNC polar/Y conflict error text"); + const char polar_incremental_radius_program[] = "G21 G91 G17\n" "F100\n" @@ -7048,6 +8407,18 @@ int main() { sizeof(g52_missing_y_control_point_program) - 1) != 0, "expected LinuxCNC to reject G5.2 control point with X but no Y in XY plane"); + const char g52_noninteger_l_word_program[] = + "G21 G90 G17\n" + "F100\n" + "G5.2 X1 Y1 P1 L1.9999\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + g52_noninteger_l_word_program, + sizeof(g52_noninteger_l_word_program) - 1) != 0, + "expected LinuxCNC to reject non-integer G5.2 L word"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Non integer value for integer", + "expected LinuxCNC non-integer G5.2 L error text"); + const char g52_nonpositive_subsequent_weight_program[] = "G21 G90 G17\n" "F100\n" @@ -7580,6 +8951,21 @@ int main() { ok &= expect(saw_exists_spaced_parameter_motion, "expected LinuxCNC EXISTS to ignore whitespace after # in a parameter reference"); + const char non_integer_exists_numbered_parameter_program[] = + "#1 = 1\n" + "G21 G90\n" + "F100\n" + "O10 if [EXISTS[#1.0002]]\n" + "G1 X1\n" + "O10 endif\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + non_integer_exists_numbered_parameter_program, + sizeof(non_integer_exists_numbered_parameter_program) - 1) != 0, + "expected LinuxCNC to reject non-integer decimal numbered parameter references in EXISTS"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Non integer value for integer", + "expected LinuxCNC non-integer decimal EXISTS parameter error text"); + const char missing_named_parameter_program[] = "G21 G90\n" "F100\n" @@ -7625,6 +9011,149 @@ int main() { ok &= expect(saw_indirect_parameter_motion, "expected ##n indirect parameter support"); ok &= expect(saw_indexed_parameter_condition_motion, "expected indirect parameter support in O-word condition"); + const char decimal_numbered_parameter_program[] = + "#1.00009 = 12\n" + "G21 G90\n" + "F100\n" + "G1 X#1.00009\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + decimal_numbered_parameter_program, + sizeof(decimal_numbered_parameter_program) - 1) == 0, + cnc_sim_last_error(sim)); + bool saw_decimal_numbered_parameter_motion = false; + for (const auto &event : events) { + saw_decimal_numbered_parameter_motion = saw_decimal_numbered_parameter_motion || + (event.type == CNC_SIM_EVENT_LINEAR_FEED && + event.line == 4 && + event.end.x == 12.0); + } + ok &= expect(saw_decimal_numbered_parameter_motion, + "expected LinuxCNC read_integer_value tolerance for decimal numbered parameters"); + + const char decimal_indirect_numbered_parameter_program[] = + "#1 = 2\n" + "#2 = 13\n" + "G21 G90\n" + "F100\n" + "G1 X##1.00009\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + decimal_indirect_numbered_parameter_program, + sizeof(decimal_indirect_numbered_parameter_program) - 1) == 0, + cnc_sim_last_error(sim)); + bool saw_decimal_indirect_numbered_parameter_motion = false; + for (const auto &event : events) { + saw_decimal_indirect_numbered_parameter_motion = saw_decimal_indirect_numbered_parameter_motion || + (event.type == CNC_SIM_EVENT_LINEAR_FEED && + event.line == 5 && + event.end.x == 13.0); + } + ok &= expect(saw_decimal_indirect_numbered_parameter_motion, + "expected LinuxCNC read_integer_value tolerance for decimal indirect numbered parameters"); + + const char decimal_indexed_numbered_parameter_program[] = + "#1 = 15\n" + "G21 G90\n" + "F100\n" + "G1 X#[1.00009]\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + decimal_indexed_numbered_parameter_program, + sizeof(decimal_indexed_numbered_parameter_program) - 1) == 0, + cnc_sim_last_error(sim)); + bool saw_decimal_indexed_numbered_parameter_motion = false; + for (const auto &event : events) { + saw_decimal_indexed_numbered_parameter_motion = saw_decimal_indexed_numbered_parameter_motion || + (event.type == CNC_SIM_EVENT_LINEAR_FEED && + event.line == 4 && + event.end.x == 15.0); + } + ok &= expect(saw_decimal_indexed_numbered_parameter_motion, + "expected LinuxCNC read_integer_value tolerance for decimal indexed numbered parameters"); + + const char decimal_indexed_parameter_assignment_program[] = + "#[1.00009] = 14\n" + "G21 G90\n" + "F100\n" + "G1 X#1\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + decimal_indexed_parameter_assignment_program, + sizeof(decimal_indexed_parameter_assignment_program) - 1) == 0, + cnc_sim_last_error(sim)); + bool saw_decimal_indexed_parameter_assignment_motion = false; + for (const auto &event : events) { + saw_decimal_indexed_parameter_assignment_motion = saw_decimal_indexed_parameter_assignment_motion || + (event.type == CNC_SIM_EVENT_LINEAR_FEED && + event.line == 4 && + event.end.x == 14.0); + } + ok &= expect(saw_decimal_indexed_parameter_assignment_motion, + "expected LinuxCNC read_integer_value tolerance for decimal indexed parameter assignments"); + + const char non_integer_numbered_parameter_program[] = + "#1 = 12\n" + "G21 G90\n" + "F100\n" + "G1 X#1.0002\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + non_integer_numbered_parameter_program, + sizeof(non_integer_numbered_parameter_program) - 1) != 0, + "expected LinuxCNC to reject non-integer decimal numbered parameter references"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Non integer value for integer", + "expected LinuxCNC non-integer decimal numbered parameter error text"); + + const char non_integer_indirect_numbered_parameter_program[] = + "#1 = 2\n" + "#2 = 13\n" + "G21 G90\n" + "F100\n" + "G1 X##1.0002\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + non_integer_indirect_numbered_parameter_program, + sizeof(non_integer_indirect_numbered_parameter_program) - 1) != 0, + "expected LinuxCNC to reject non-integer decimal indirect numbered parameter references"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Non integer value for integer", + "expected LinuxCNC non-integer decimal indirect numbered parameter error text"); + + const char non_integer_indirect_parameter_expression_program[] = + "#1 = 2\n" + "#2 = 13\n" + "#3 = [##1.0002]\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + non_integer_indirect_parameter_expression_program, + sizeof(non_integer_indirect_parameter_expression_program) - 1) != 0, + "expected LinuxCNC to reject non-integer decimal indirect numbered parameter references in expressions"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Non integer value for integer", + "expected LinuxCNC non-integer decimal indirect expression parameter error text"); + + const char non_integer_indexed_numbered_parameter_program[] = + "#1 = 15\n" + "G21 G90\n" + "F100\n" + "G1 X#[1.0002]\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + non_integer_indexed_numbered_parameter_program, + sizeof(non_integer_indexed_numbered_parameter_program) - 1) != 0, + "expected LinuxCNC to reject non-integer decimal indexed numbered parameter references"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Non integer value for integer", + "expected LinuxCNC non-integer decimal indexed numbered parameter error text"); + + const char non_integer_indexed_parameter_assignment_program[] = + "#[1.0002] = 14\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + non_integer_indexed_parameter_assignment_program, + sizeof(non_integer_indexed_parameter_assignment_program) - 1) != 0, + "expected LinuxCNC to reject non-integer decimal indexed parameter assignments"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Non integer value for integer", + "expected LinuxCNC non-integer decimal indexed assignment error text"); + const char oword_conditional_program[] = "G21 G90\n" "F100\n" @@ -7918,6 +9447,306 @@ int main() { ok &= expect(cnc_sim_parse_program(sim, mixed_subprogram_styles, sizeof(mixed_subprogram_styles) - 1) != 0, "expected M98 calling O-word subprogram to fail"); + const char block_delete_program[] = + "G21 G90 G17 F100\n" + " /N10 G1 X5\n" + "G1 X2\n" + "M30\n"; + const char block_delete_on_config[] = "{\"backend\":\"smoke\",\"blockDelete\":true}"; + config_rc = cnc_sim_load_config_json(sim, block_delete_on_config, sizeof(block_delete_on_config) - 1); + events.clear(); + ok &= expect(config_rc == 0, cnc_sim_last_error(sim)); + ok &= expect(cnc_sim_parse_program(sim, block_delete_program, sizeof(block_delete_program) - 1) == 0, + cnc_sim_last_error(sim)); + bool saw_block_delete_skipped_line = false; + bool saw_block_delete_followup_line = false; + for (const auto &event : events) { + saw_block_delete_skipped_line = saw_block_delete_skipped_line || + (event.type == CNC_SIM_EVENT_LINEAR_FEED && + event.line == 2 && + event.end.x == 5.0); + saw_block_delete_followup_line = saw_block_delete_followup_line || + (event.type == CNC_SIM_EVENT_LINEAR_FEED && + event.line == 3 && + event.end.x == 2.0); + } + ok &= expect(!saw_block_delete_skipped_line, "expected smoke block-delete line to be skipped"); + ok &= expect(saw_block_delete_followup_line, "expected smoke block-delete followup line to execute"); + + const char block_delete_off_config[] = "{\"backend\":\"smoke\",\"blockDelete\":false}"; + config_rc = cnc_sim_load_config_json(sim, block_delete_off_config, sizeof(block_delete_off_config) - 1); + events.clear(); + ok &= expect(config_rc == 0, cnc_sim_last_error(sim)); + ok &= expect(cnc_sim_parse_program(sim, block_delete_program, sizeof(block_delete_program) - 1) == 0, + cnc_sim_last_error(sim)); + bool saw_block_delete_disabled_line = false; + for (const auto &event : events) { + saw_block_delete_disabled_line = saw_block_delete_disabled_line || + (event.type == CNC_SIM_EVENT_LINEAR_FEED && + event.line == 2 && + event.end.x == 5.0); + } + ok &= expect(saw_block_delete_disabled_line, "expected smoke disabled block-delete line to execute"); + + const char comment_then_slash_program[] = + "G21 G90 F100\n" + "(comment) /G1 X5\n" + "M30\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, comment_then_slash_program, sizeof(comment_then_slash_program) - 1) != 0, + "expected smoke non-leading slash after comment to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Bad character '/' used", + "expected smoke non-leading slash error text"); + + const char top_level_bracket_program[] = + "G21 G90 F100\n" + "[1] G1 X5\n" + "M30\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, top_level_bracket_program, sizeof(top_level_bracket_program) - 1) != 0, + "expected smoke top-level bracket expression to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Bad character '[' used", + "expected smoke top-level bracket error text"); + + const char top_level_right_bracket_program[] = + "G21 G90 F100\n" + "] G1 X5\n" + "M30\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + top_level_right_bracket_program, + sizeof(top_level_right_bracket_program) - 1) != 0, + "expected smoke top-level right bracket to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Bad character ']' used", + "expected smoke top-level right bracket error text"); + + const char midline_bracket_program[] = + "G21 G90 F100\n" + "G1 X5 [1]\n" + "M30\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, midline_bracket_program, sizeof(midline_bracket_program) - 1) != 0, + "expected smoke midline top-level bracket to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Bad character '[' used", + "expected smoke midline top-level bracket error text"); + + const char midline_right_bracket_program[] = + "G21 G90 F100\n" + "G1 X5 ]\n" + "M30\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + midline_right_bracket_program, + sizeof(midline_right_bracket_program) - 1) != 0, + "expected smoke midline top-level right bracket to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Bad character ']' used", + "expected smoke midline top-level right bracket error text"); + + const char top_level_number_program[] = + "G21 G90 F100\n" + "1 G1 X5\n" + "M30\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, top_level_number_program, sizeof(top_level_number_program) - 1) != 0, + "expected smoke top-level naked number to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Bad character '1' used", + "expected smoke top-level naked number error text"); + + const char top_level_parameter_setting_program[] = + "G21 G90 F100\n" + "#1\n" + "M30\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + top_level_parameter_setting_program, + sizeof(top_level_parameter_setting_program) - 1) != 0, + "expected smoke top-level bare parameter item to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Equal sign missing in parameter setting", + "expected smoke top-level bare parameter error text"); + + const char midline_parameter_setting_program[] = + "G21 G90 F100\n" + "G1 X5 #1\n" + "M30\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + midline_parameter_setting_program, + sizeof(midline_parameter_setting_program) - 1) != 0, + "expected smoke midline bare parameter item to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Equal sign missing in parameter setting", + "expected smoke midline bare parameter error text"); + + const char top_level_unary_item_program[] = + "G21 G90 F100\n" + "ABS[1]\n" + "M30\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + top_level_unary_item_program, + sizeof(top_level_unary_item_program) - 1) != 0, + "expected smoke top-level bare unary item to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Unknown word where unary operation could be", + "expected smoke top-level bare unary error text"); + + const char midline_unary_item_program[] = + "G21 G90 F100\n" + "G1 X5 ABS[1]\n" + "M30\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + midline_unary_item_program, + sizeof(midline_unary_item_program) - 1) != 0, + "expected smoke midline bare unary item to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Unknown word where unary operation could be", + "expected smoke midline bare unary error text"); + + const char top_level_atan_item_program[] = + "G21 G90 F100\n" + "ATAN[1]/[1]\n" + "M30\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + top_level_atan_item_program, + sizeof(top_level_atan_item_program) - 1) != 0, + "expected smoke top-level bare ATAN item to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Bad character '/' used", + "expected smoke top-level bare ATAN error text"); + + const char midline_atan_item_program[] = + "G21 G90 F100\n" + "G1 X5 ATAN[1]/[1]\n" + "M30\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + midline_atan_item_program, + sizeof(midline_atan_item_program) - 1) != 0, + "expected smoke midline bare ATAN item to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Bad character '/' used", + "expected smoke midline bare ATAN error text"); + + const char midline_numeric_oword_program[] = + "G21 G90 F100\n" + "G1 X5 O100\n" + "M30\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + midline_numeric_oword_program, + sizeof(midline_numeric_oword_program) - 1) != 0, + "expected smoke midline numeric O-word to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Bad character 'o' used", + "expected smoke midline numeric O-word error text"); + + const char midline_named_oword_program[] = + "G21 G90 F100\n" + "G1 X5 O\n" + "M30\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + midline_named_oword_program, + sizeof(midline_named_oword_program) - 1) != 0, + "expected smoke midline named O-word to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Bad character 'o' used", + "expected smoke midline named O-word error text"); + + const char comment_then_n_word_program[] = + "G21 G90 F100\n" + "(comment) N10 G1 X5\n" + "M30\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + comment_then_n_word_program, + sizeof(comment_then_n_word_program) - 1) != 0, + "expected smoke comment-prefixed N word to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Bad character 'n' used", + "expected smoke comment-prefixed N word error text"); + + const char comment_then_spaced_n_word_program[] = + "G21 G90 F100\n" + "(comment) N 10\n" + "M30\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + comment_then_spaced_n_word_program, + sizeof(comment_then_spaced_n_word_program) - 1) != 0, + "expected smoke comment-prefixed spaced N word to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Bad character 'n' used", + "expected smoke comment-prefixed spaced N word error text"); + + const char comment_then_oword_program[] = + "G21 G90 F100\n" + "(comment) O100\n" + "M30\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + comment_then_oword_program, + sizeof(comment_then_oword_program) - 1) != 0, + "expected smoke comment-prefixed O-word to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Bad character 'o' used", + "expected smoke comment-prefixed O-word error text"); + + const char midline_m98_program[] = + "G21\n" + "F100\n" + "G1 X5 M98 P100\n" + "M30\n" + "O100\n" + "M99\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + midline_m98_program, + sizeof(midline_m98_program) - 1) != 0, + "expected smoke midline M98 to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Unknown m code used: M98", + "expected smoke midline M98 error text"); + + const char comment_then_m98_program[] = + "G21\n" + "F100\n" + "(comment) M98 P100\n" + "M30\n" + "O100\n" + "M99\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + comment_then_m98_program, + sizeof(comment_then_m98_program) - 1) != 0, + "expected smoke comment-prefixed M98 to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Unknown m code used: M98", + "expected smoke comment-prefixed M98 error text"); + + const char oword_if_extra_gcode_program[] = + "O100 if [1] G1 X1\n" + "O100 endif\n" + "M30\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + oword_if_extra_gcode_program, + sizeof(oword_if_extra_gcode_program) - 1) != 0, + "expected smoke O-word if with trailing G-code to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Unexpected character after O-word", + "expected smoke O-word trailing-character error text"); + + const char numeric_subprogram_endsub_tail_program[] = + "O100 sub\n" + "O100 endsub X1\n" + "M30\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + numeric_subprogram_endsub_tail_program, + sizeof(numeric_subprogram_endsub_tail_program) - 1) == 0, + cnc_sim_last_error(sim)); + + const char numeric_subprogram_return_tail_program[] = + "O100 sub\n" + "O100 return X1\n" + "O100 endsub\n" + "M30\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + numeric_subprogram_return_tail_program, + sizeof(numeric_subprogram_return_tail_program) - 1) == 0, + cnc_sim_last_error(sim)); + const char linuxcnc_config[] = "{\"backend\":\"linuxcnc-rs274\"}"; config_rc = cnc_sim_load_config_json(sim, linuxcnc_config, sizeof(linuxcnc_config) - 1); rc = cnc_sim_parse_program(sim, program, sizeof(program) - 1); diff --git a/core/tools/linuxcnc_rs274_dump.cpp b/core/tools/linuxcnc_rs274_dump.cpp index 30f6b5d..00b74ec 100644 --- a/core/tools/linuxcnc_rs274_dump.cpp +++ b/core/tools/linuxcnc_rs274_dump.cpp @@ -128,6 +128,11 @@ bool trace_enabled() { return std::getenv("CNC_SIM_TRACE_RS274") != nullptr; } +bool block_delete_enabled() { + const char *value = std::getenv("CNC_SIM_BLOCK_DELETE"); + return value && std::string(value) != "0" && std::string(value) != "false"; +} + bool execute_line(InterpBase *interp, CanonEventSink &sink, const std::string &line, @@ -251,6 +256,7 @@ int main(int argc, char **argv) { cnc_sim_init_minimal_linuxcnc_tooldata(); CanonEventSink sink; + sink.set_block_delete(block_delete_enabled()); bool first = true; sink.set_callback(print_event, &first); cnc_sim_linuxcnc_set_canon_sink(&sink); @@ -271,6 +277,7 @@ int main(int argc, char **argv) { if (trace_enabled()) { std::cerr << "rs274:init done\n"; } + SET_BLOCK_DELETE(sink.block_delete()); bool ok = true; if (use_file_mode) { diff --git a/core/wasm_shims/boost/python/dict.hpp b/core/wasm_shims/boost/python/dict.hpp new file mode 100644 index 0000000..db5b5b5 --- /dev/null +++ b/core/wasm_shims/boost/python/dict.hpp @@ -0,0 +1,4 @@ +#pragma once + +#include "boost/python/object.hpp" + diff --git a/core/wasm_shims/boost/python/extract.hpp b/core/wasm_shims/boost/python/extract.hpp new file mode 100644 index 0000000..db5b5b5 --- /dev/null +++ b/core/wasm_shims/boost/python/extract.hpp @@ -0,0 +1,4 @@ +#pragma once + +#include "boost/python/object.hpp" + diff --git a/core/wasm_shims/boost/python/import.hpp b/core/wasm_shims/boost/python/import.hpp new file mode 100644 index 0000000..db5b5b5 --- /dev/null +++ b/core/wasm_shims/boost/python/import.hpp @@ -0,0 +1,4 @@ +#pragma once + +#include "boost/python/object.hpp" + diff --git a/core/wasm_shims/boost/python/list.hpp b/core/wasm_shims/boost/python/list.hpp new file mode 100644 index 0000000..db5b5b5 --- /dev/null +++ b/core/wasm_shims/boost/python/list.hpp @@ -0,0 +1,4 @@ +#pragma once + +#include "boost/python/object.hpp" + diff --git a/core/wasm_shims/boost/python/object.hpp b/core/wasm_shims/boost/python/object.hpp new file mode 100644 index 0000000..eee5374 --- /dev/null +++ b/core/wasm_shims/boost/python/object.hpp @@ -0,0 +1,211 @@ +#pragma once + +#include + +extern "C" { + +struct _typeobject { + const char *tp_name; +}; +using PyTypeObject = _typeobject; + +struct _object { + PyTypeObject *ob_type; +}; +using PyObject = _object; + +struct _inittab { + const char *name; + PyObject *(*initfunc)(void); +}; + +int Py_IsInitialized(void); +PyObject *PyErr_Occurred(void); +void PyErr_Clear(void); +int PyErr_ExceptionMatches(PyObject *); +int PyCallable_Check(PyObject *); +void PyErr_Fetch(PyObject **, PyObject **, PyObject **); +void PyErr_NormalizeException(PyObject **, PyObject **, PyObject **); +void PyErr_Print(void); +int PyUnicode_Check(PyObject *); +int PyLong_Check(PyObject *); +int PyFloat_Check(PyObject *); +int PyGen_Check(PyObject *); +PyObject *PyObject_Str(PyObject *); +const char *PyUnicode_AsUTF8(PyObject *); +void Py_DecRef(PyObject *); +extern PyObject *PyExc_KeyError; +extern PyObject *PyExc_StopIteration; +extern PyObject *_Py_NoneStructPtr; + +#define Py_None (_Py_NoneStructPtr) +#define Py_TYPE(ob) ((ob)->ob_type) +#define Py_XDECREF(ob) \ + do { \ + if (ob) { \ + Py_DecRef(ob); \ + } \ + } while (0) + +} + +namespace boost { + +template +const T &cref(const T &value) { + return value; +} + +namespace python { + +class error_already_set {}; + +class object { +public: + object() = default; + template + object(const T &) { + } + ~object() = default; + + class attr_proxy { + public: + template + attr_proxy &operator=(const T &) { + return *this; + } + + operator object() const { + return object(); + } + }; + + attr_proxy attr(const char *) const { + return {}; + } + + object operator[](const char *) const { + return {}; + } + + object operator[](const std::string &) const { + return {}; + } + + object operator[](int) const { + return {}; + } + + PyObject *ptr() const { + return nullptr; + } + + template + object operator()(Args &&...) const { + return {}; + } +}; + +inline object getattr(const object &, const char *) { + return {}; +} + +class list : public object { +public: + template + void append(const T &) { + } +}; + +class dict : public object { +public: + list keys() const { + return {}; + } +}; + +class tuple : public object { +public: + tuple() = default; + tuple(const list &) { + } +}; + +class scope { +public: + explicit scope(const object &) { + } + + object::attr_proxy attr(const char *) const { + return {}; + } +}; + +template +struct extract { + explicit extract(const object &) { + } + + operator T() const { + return T(); + } +}; + +inline object import(const char *) { + return {}; +} + +inline int len(const list &) { + return 0; +} + +inline void handle_exception() { +} + +template +struct borrowed_result { + explicit borrowed_result(T *) { + } +}; + +template +struct allow_null_result { + explicit allow_null_result(T *) { + } +}; + +template +borrowed_result borrowed(T *value) { + return borrowed_result(value); +} + +template +allow_null_result allow_null(T *value) { + return allow_null_result(value); +} + +template +class handle { +public: + template + explicit handle(const U &) { + } + + explicit operator bool() const { + return false; + } +}; + +class str : public object { +public: + template + explicit str(const T &) { + } + + object join(const object &) const { + return {}; + } +}; + +} // namespace python +} // namespace boost diff --git a/core/wasm_shims/boost/python/object_fwd.hpp b/core/wasm_shims/boost/python/object_fwd.hpp new file mode 100644 index 0000000..18a176a --- /dev/null +++ b/core/wasm_shims/boost/python/object_fwd.hpp @@ -0,0 +1,10 @@ +#pragma once + +namespace boost { +namespace python { + +class object; + +} // namespace python +} // namespace boost + diff --git a/core/wasm_shims/boost/python/scope.hpp b/core/wasm_shims/boost/python/scope.hpp new file mode 100644 index 0000000..db5b5b5 --- /dev/null +++ b/core/wasm_shims/boost/python/scope.hpp @@ -0,0 +1,4 @@ +#pragma once + +#include "boost/python/object.hpp" + diff --git a/core/wasm_shims/boost/python/str.hpp b/core/wasm_shims/boost/python/str.hpp new file mode 100644 index 0000000..a330665 --- /dev/null +++ b/core/wasm_shims/boost/python/str.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "boost/python/object.hpp" diff --git a/core/wasm_shims/boost/python/tuple.hpp b/core/wasm_shims/boost/python/tuple.hpp new file mode 100644 index 0000000..a330665 --- /dev/null +++ b/core/wasm_shims/boost/python/tuple.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "boost/python/object.hpp" diff --git a/core/wasm_shims/dlfcn.cc b/core/wasm_shims/dlfcn.cc new file mode 100644 index 0000000..d9fbf25 --- /dev/null +++ b/core/wasm_shims/dlfcn.cc @@ -0,0 +1,30 @@ +#include "dlfcn.h" + +#include + +namespace { + +thread_local char last_error[128] = "dynamic loading is unavailable in wasm-safe probe"; + +} // namespace + +extern "C" { + +void *dlopen(const char *, int) { + return nullptr; +} + +void *dlsym(void *, const char *) { + return nullptr; +} + +char *dlerror(void) { + return last_error; +} + +int dlclose(void *) { + return 0; +} + +} // extern "C" + diff --git a/core/wasm_shims/dlfcn.h b/core/wasm_shims/dlfcn.h new file mode 100644 index 0000000..99185ed --- /dev/null +++ b/core/wasm_shims/dlfcn.h @@ -0,0 +1,18 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#define RTLD_GLOBAL 0x00100 +#define RTLD_NOW 0x00002 + +void *dlopen(const char *filename, int flags); +void *dlsym(void *handle, const char *symbol); +char *dlerror(void); +int dlclose(void *handle); + +#ifdef __cplusplus +} +#endif + diff --git a/core/wasm_shims/emc_status_shim.cc b/core/wasm_shims/emc_status_shim.cc new file mode 100644 index 0000000..0544faf --- /dev/null +++ b/core/wasm_shims/emc_status_shim.cc @@ -0,0 +1,4 @@ +#include "nml_intf/emc_nml.hh" + +EMC_STAT *emcStatus = nullptr; + diff --git a/core/wasm_shims/gettext_shim.cc b/core/wasm_shims/gettext_shim.cc new file mode 100644 index 0000000..e035b46 --- /dev/null +++ b/core/wasm_shims/gettext_shim.cc @@ -0,0 +1,6 @@ +#include + +extern "C" char *gettext(const char *msgid) { + return const_cast(msgid ? msgid : ""); +} + diff --git a/core/wasm_shims/interp_base_probe_main.cc b/core/wasm_shims/interp_base_probe_main.cc new file mode 100644 index 0000000..649fbc4 --- /dev/null +++ b/core/wasm_shims/interp_base_probe_main.cc @@ -0,0 +1,7 @@ +#include "interp_base.hh" + +int main() { + InterpBase *interp = interp_from_shlib("nonexistent-interpreter.so"); + return interp == nullptr ? 0 : 1; +} + diff --git a/core/wasm_shims/interp_find_probe_main.cc b/core/wasm_shims/interp_find_probe_main.cc new file mode 100644 index 0000000..534b158 --- /dev/null +++ b/core/wasm_shims/interp_find_probe_main.cc @@ -0,0 +1,40 @@ +#include "tooldata/tooldata.hh" +#include "rs274ngc/rs274ngc_interp.hh" + +int main() { + if (tool_mmap_creator(nullptr, 1) != 0) { + return 7; + } + tooldata_reset(); + + CANON_TOOL_TABLE spindle = tooldata_entry_init(); + spindle.toolno = 0; + spindle.pocketno = 0; + if (tooldata_put(spindle, 0) != IDX_OK) { + return 1; + } + + CANON_TOOL_TABLE tool = tooldata_entry_init(); + tool.toolno = 7; + tool.pocketno = 12; + if (tooldata_put(tool, 12) != IDX_NEW) { + return 2; + } + + int idx = -1; + int pocket = -1; + auto *interp = reinterpret_cast(0x1); + if (interp->find_tool_index(nullptr, 7, &idx) != INTERP_OK) { + return 3; + } + if (idx != 12) { + return 4; + } + if (interp->find_tool_pocket(nullptr, 7, &pocket) != INTERP_OK) { + return 5; + } + if (pocket != 12) { + return 6; + } + return 0; +} diff --git a/core/wasm_shims/interp_find_probe_stubs.cc b/core/wasm_shims/interp_find_probe_stubs.cc new file mode 100644 index 0000000..afc539a --- /dev/null +++ b/core/wasm_shims/interp_find_probe_stubs.cc @@ -0,0 +1,7 @@ +#include "rs274ngc/rs274ngc_interp.hh" + +#include + +void Interp::setError(const char *, ...) { +} + diff --git a/core/wasm_shims/linuxcnc_runtime_shim.cc b/core/wasm_shims/linuxcnc_runtime_shim.cc new file mode 100644 index 0000000..4676d0e --- /dev/null +++ b/core/wasm_shims/linuxcnc_runtime_shim.cc @@ -0,0 +1,72 @@ +#include "boost/python/object.hpp" +#include "hal.h" + +int _task = 0; + +extern "C" PyObject *PyInit_interpreter(void) { + return nullptr; +} + +extern "C" PyObject *PyInit_emccanon(void) { + return nullptr; +} + +extern "C" { +struct _inittab builtin_modules[] = { + {"interpreter", PyInit_interpreter}, + {"emccanon", PyInit_emccanon}, + {nullptr, nullptr}, +}; +} + +extern "C" int hal_init(const char *) { + return 1; +} + +extern "C" int hal_ready(int) { + return 0; +} + +extern "C" int hal_get_pin_value_by_name(const char *, + hal_type_t *type, + hal_data_u **data, + bool *connected) { + if (type) { + *type = HAL_TYPE_UNINITIALIZED; + } + if (data) { + *data = nullptr; + } + if (connected) { + *connected = false; + } + return -1; +} + +extern "C" int hal_get_signal_value_by_name(const char *, + hal_type_t *type, + hal_data_u **data, + bool *has_writers) { + if (type) { + *type = HAL_TYPE_UNINITIALIZED; + } + if (data) { + *data = nullptr; + } + if (has_writers) { + *has_writers = false; + } + return -1; +} + +extern "C" int hal_get_param_value_by_name(const char *, + hal_type_t *type, + hal_data_u **data) { + if (type) { + *type = HAL_TYPE_UNINITIALIZED; + } + if (data) { + *data = nullptr; + } + return -1; +} diff --git a/core/wasm_shims/python_c_api_shim.cc b/core/wasm_shims/python_c_api_shim.cc new file mode 100644 index 0000000..396a32e --- /dev/null +++ b/core/wasm_shims/python_c_api_shim.cc @@ -0,0 +1,76 @@ +#include "boost/python/object.hpp" + +extern "C" { + +static PyTypeObject none_type = {"NoneType"}; +static PyObject none_object = {&none_type}; + +PyObject *PyExc_KeyError = reinterpret_cast(1); +PyObject *PyExc_StopIteration = reinterpret_cast(2); +PyObject *_Py_NoneStructPtr = &none_object; + +int Py_IsInitialized(void) { + return 1; +} + +PyObject *PyErr_Occurred(void) { + return nullptr; +} + +void PyErr_Clear(void) { +} + +int PyErr_ExceptionMatches(PyObject *) { + return 0; +} + +int PyCallable_Check(PyObject *) { + return 0; +} + +void PyErr_Fetch(PyObject **exc, PyObject **val, PyObject **tb) { + if (exc) { + *exc = nullptr; + } + if (val) { + *val = nullptr; + } + if (tb) { + *tb = nullptr; + } +} + +void PyErr_NormalizeException(PyObject **, PyObject **, PyObject **) { +} + +void PyErr_Print(void) { +} + +int PyUnicode_Check(PyObject *) { + return 0; +} + +int PyLong_Check(PyObject *) { + return 0; +} + +int PyFloat_Check(PyObject *) { + return 0; +} + +int PyGen_Check(PyObject *) { + return 0; +} + +PyObject *PyObject_Str(PyObject *) { + return &none_object; +} + +const char *PyUnicode_AsUTF8(PyObject *) { + return ""; +} + +void Py_DecRef(PyObject *) { +} + +} diff --git a/core/wasm_shims/pythonplugin/python_plugin.cc b/core/wasm_shims/pythonplugin/python_plugin.cc new file mode 100644 index 0000000..dfde789 --- /dev/null +++ b/core/wasm_shims/pythonplugin/python_plugin.cc @@ -0,0 +1,50 @@ +#include "pythonplugin/python_plugin.hh" + +__attribute__((weak)) std::string handle_pyerror() { + return "python disabled in wasm-safe probe"; +} + +PythonPlugin *python_plugin = nullptr; + +PythonPlugin *PythonPlugin::instantiate(struct _inittab *) { + static PythonPlugin plugin; + python_plugin = &plugin; + python_plugin->status = PLUGIN_OK; + return python_plugin; +} + +int PythonPlugin::configure(const char *, const char *) { + status = PLUGIN_OK; + return status; +} + +bool PythonPlugin::is_callable(const char *, const char *) { + return false; +} + +int PythonPlugin::call(const char *, + const char *, + boost::python::object, + boost::python::object, + boost::python::object &retval) { + retval = boost::python::object(); + status = PLUGIN_NO_CALLABLE; + return status; +} + +int PythonPlugin::run_string(const char *, boost::python::object &retval, bool) { + retval = boost::python::object(); + status = PLUGIN_OK; + return status; +} + +int PythonPlugin::call_method(boost::python::object, boost::python::object &retval) { + retval = boost::python::object(); + status = PLUGIN_OK; + return status; +} + +int PythonPlugin::initialize() { + status = PLUGIN_OK; + return status; +} diff --git a/core/wasm_shims/pythonplugin/python_plugin.hh b/core/wasm_shims/pythonplugin/python_plugin.hh new file mode 100644 index 0000000..d36b8f1 --- /dev/null +++ b/core/wasm_shims/pythonplugin/python_plugin.hh @@ -0,0 +1,60 @@ +#pragma once + +#include +#include +#include + +#include "boost/python/object.hpp" + +extern std::string handle_pyerror(); + +enum pp_status { + PLUGIN_NO_SECTION = -1, + PLUGIN_NO_INIFILE = -2, + PLUGIN_BAD_INIFILE = -3, + PLUGIN_NO_TOPLEVEL = -4, + PLUGIN_BAD_PATH = -5, + PLUGIN_STAT_FAILED = -6, + PLUGIN_INITTAB_FAILED = -7, + PLUGIN_PYTHON_ALREADY_INITIALIZED = -8, + PLUGIN_EXCEPTION_DURING_PATH_PREPEND = -9, + PLUGIN_EXCEPTION_DURING_PATH_APPEND = -10, + PLUGIN_INIT_EXCEPTION = -11, + PLUGIN_PYTHON_NOT_INITIALIZED = -12, + PLUGIN_PATH_TOO_LONG = -13, + PLUGIN_OK = 0, + PLUGIN_NO_CALLABLE = 1, + PLUGIN_EXCEPTION = 2 +}; + +class PythonPlugin { +public: + static PythonPlugin *instantiate(struct _inittab *inittab = nullptr); + int configure(const char *iniFilename = nullptr, const char *section = nullptr); + bool is_callable(const char *module, const char *funcname); + int call(const char *module, + const char *callable, + boost::python::object tupleargs, + boost::python::object kwargs, + boost::python::object &retval); + int run_string(const char *cmd, boost::python::object &retval, bool as_file = false); + int call_method(boost::python::object method, boost::python::object &retval); + + int plugin_status() { return status; } + bool usable() { return status >= PLUGIN_OK; } + int initialize(); + const std::string &last_exception() { return exception_msg; } + const std::string &last_errmsg() { return error_msg; } + boost::python::object main_namespace; + +private: + PythonPlugin() = default; + + int status = PLUGIN_OK; + std::string exception_msg; + std::string error_msg; + int log_level = 0; +}; + +extern PythonPlugin *python_plugin; + diff --git a/core/wasm_shims/pythonplugin/python_plugin_probe_main.cc b/core/wasm_shims/pythonplugin/python_plugin_probe_main.cc new file mode 100644 index 0000000..a95197b --- /dev/null +++ b/core/wasm_shims/pythonplugin/python_plugin_probe_main.cc @@ -0,0 +1,23 @@ +#include "pythonplugin/python_plugin.hh" + +int main() { + auto *plugin = PythonPlugin::instantiate(nullptr); + if (!plugin) { + return 1; + } + if (!plugin->usable()) { + return 2; + } + boost::python::object retval; + if (plugin->run_string("noop", retval, false) != PLUGIN_OK) { + return 3; + } + if (plugin->is_callable(nullptr, "noop")) { + return 4; + } + if (handle_pyerror().empty()) { + return 5; + } + return 0; +} + diff --git a/core/wasm_shims/rs274ngc_pre_probe_main.cc b/core/wasm_shims/rs274ngc_pre_probe_main.cc new file mode 100644 index 0000000..0fc8896 --- /dev/null +++ b/core/wasm_shims/rs274ngc_pre_probe_main.cc @@ -0,0 +1,33 @@ +#include "canon_event_sink.h" +#include "linuxcnc_canon_bridge.h" +#include "linuxcnc_tooldata_fixture.h" +#include "rs274ngc_interp.hh" + +#include + +int main() { + cnc_sim_init_minimal_linuxcnc_tooldata(); + + CanonEventSink sink; + cnc_sim_linuxcnc_set_canon_sink(&sink); + + Interp interp; + if (interp.init() != INTERP_OK) { + return 1; + } + if (interp.sequence_number() != 0) { + return 2; + } + + const double start_x = sink.position().x; + cnc_sim_linuxcnc_set_current_line(1); + const int read_rc = interp.read("G91 G0 X1"); + if (read_rc != INTERP_OK) { + return 3; + } + const int execute_rc = interp.execute(); + if (execute_rc != INTERP_OK && execute_rc != INTERP_EXECUTE_FINISH) { + return 4; + } + return std::fabs(sink.position().x - (start_x + 1.0)) < 1e-9 ? 0 : 5; +} diff --git a/core/wasm_shims/rtapi_compat.cc b/core/wasm_shims/rtapi_compat.cc new file mode 100644 index 0000000..541ed38 --- /dev/null +++ b/core/wasm_shims/rtapi_compat.cc @@ -0,0 +1,11 @@ +#include +#include + +extern "C" int rtapi_snprintf(char *buf, unsigned long size, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + const int rc = std::vsnprintf(buf, static_cast(size), fmt, ap); + va_end(ap); + return rc; +} + diff --git a/core/wasm_shims/tooldata/tooldata.hh b/core/wasm_shims/tooldata/tooldata.hh new file mode 100644 index 0000000..d27bad8 --- /dev/null +++ b/core/wasm_shims/tooldata/tooldata.hh @@ -0,0 +1,58 @@ +#pragma once + +#include "nml_intf/canon.hh" +#include "nml_intf/emc_nml.hh" + +extern "C" { + +typedef enum { + IDX_OK = 0, + IDX_NEW, + IDX_FAIL, +} toolidx_t; + +typedef enum { + DB_NOTUSED = 0, + DB_ACTIVE, +} tooldb_t; + +typedef enum { + SPINDLE_LOAD, + SPINDLE_UNLOAD, + TOOL_OFFSET, +} tool_notify_t; + +struct CANON_TOOL_TABLE tooldata_entry_init(void); +toolidx_t tooldata_put(struct CANON_TOOL_TABLE tdata, int idx); +toolidx_t tooldata_get(CANON_TOOL_TABLE *pdata, int idx); + +void tooldata_init(bool random_tool_changer); +void tooldata_reset(void); +void tooldata_last_index_set(int idx); +int tooldata_last_index_get(void); +int tooldata_find_index_for_tool(int toolno); + +void tooldata_format_toolline(int idx, + bool ignore_zero_values, + CANON_TOOL_TABLE tdata, + char formatted_line[CANON_TOOL_ENTRY_LEN]); + +void tooldata_add_init(int nonrandom_start_idx); +int tooldata_read_entry(const char *input_line); + +void tooldata_set_db(tooldb_t mode); +int tooldata_load(const char *filename); +int tooldata_save(const char *filename); +int tool_mmap_creator(EMC_TOOL_STAT const *ptr, int random_toolchanger); +int tool_mmap_user(void); +void tool_mmap_close(void); +bool tool_mmap_is_random_toolchanger(void); +int tool_nml_register(CANON_TOOL_TABLE *tblptr); +int tooldata_db_init(char progname_plus_args[], int random_toolchanger); +int tooldata_db_notify(tool_notify_t ntype, + int toolno, + int pocketno, + CANON_TOOL_TABLE tdata); +int tooldata_db_getall(void); + +} // extern "C" diff --git a/core/wasm_shims/tooldata/tooldata_common_probe_main.cc b/core/wasm_shims/tooldata/tooldata_common_probe_main.cc new file mode 100644 index 0000000..eb4c30c --- /dev/null +++ b/core/wasm_shims/tooldata/tooldata_common_probe_main.cc @@ -0,0 +1,101 @@ +#include "tooldata.hh" + +#include +#include +#include +#include +#include +#include + +namespace { + +std::string make_temp_template(const char *name) { + const char *tmpdir = std::getenv("TMPDIR"); + std::string tmpl = std::string(tmpdir ? tmpdir : "/tmp") + "/" + name; + return tmpl; +} + +} // namespace + +int main() { + if (tool_mmap_user() != -1) { + return 11; + } + if (tool_mmap_creator(nullptr, 1) != 0) { + return 12; + } + if (tool_mmap_user() != 0) { + return 13; + } + if (!tool_mmap_is_random_toolchanger()) { + return 14; + } + + tooldata_init(true); + tooldata_reset(); + tooldata_add_init(0); + + if (tooldata_read_entry("T7 P12 Z3.5 D8 ;probe\n") != 12) { + return 1; + } + + CANON_TOOL_TABLE loaded = tooldata_entry_init(); + if (tooldata_get(&loaded, 12) != IDX_OK) { + return 2; + } + if (loaded.toolno != 7 || loaded.pocketno != 12 || + loaded.offset.tran.z != 3.5 || loaded.diameter != 8.0 || + std::strcmp(loaded.comment, "probe") != 0) { + return 3; + } + + char line[CANON_TOOL_ENTRY_LEN] = {}; + tooldata_format_toolline(12, true, loaded, line); + if (!std::strstr(line, "T7") || !std::strstr(line, "P12") || + !std::strstr(line, "D+8.000000") || !std::strstr(line, ";probe")) { + return 4; + } + + std::string path_template = make_temp_template("cnc_tooldata_common_probe_XXXXXX"); + std::vector path(path_template.begin(), path_template.end()); + path.push_back('\0'); + const int fd = mkstemp(path.data()); + if (fd < 0) { + return 5; + } + close(fd); + + if (tooldata_save(path.data()) != 0) { + std::remove(path.data()); + return 6; + } + + tooldata_reset(); + if (tooldata_find_index_for_tool(7) != -1) { + std::remove(path.data()); + return 7; + } + if (tooldata_load(path.data()) != 0) { + std::remove(path.data()); + return 8; + } + std::remove(path.data()); + + if (tooldata_get(&loaded, 12) != IDX_OK || loaded.toolno != 7 || + loaded.pocketno != 12 || loaded.offset.tran.z != 3.5 || + loaded.diameter != 8.0) { + return 9; + } + + tooldata_set_db(DB_ACTIVE); + if (tooldata_load(path.data()) != -1) { + return 10; + } + + tool_mmap_close(); + if (tool_mmap_user() != -1) { + return 15; + } + + return 0; +} diff --git a/core/wasm_shims/tooldata/tooldata_mmap_backend.cc b/core/wasm_shims/tooldata/tooldata_mmap_backend.cc new file mode 100644 index 0000000..fffb05c --- /dev/null +++ b/core/wasm_shims/tooldata/tooldata_mmap_backend.cc @@ -0,0 +1,127 @@ +#include "tooldata.hh" + +#include +#include +#include + +namespace { + +CANON_TOOL_TABLE tool_table[CANON_POCKETS_MAX]; +EMC_TOOL_STAT const *toolstat = nullptr; +int last_index = 0; +bool mmap_created = false; +bool mmap_random_toolchanger = false; +bool mmap_creator_called = false; + +} // namespace + +extern "C" { + +toolidx_t tooldata_put(CANON_TOOL_TABLE tdata, int idx) { + if (!mmap_created) { + return IDX_FAIL; + } + if (idx < 0 || idx >= CANON_POCKETS_MAX) { + return IDX_FAIL; + } + + const toolidx_t result = idx > last_index ? IDX_NEW : IDX_OK; + tool_table[idx] = tdata; + if (idx > last_index) { + last_index = idx; + } + if (idx == 0 && toolstat) { + *(CANON_TOOL_TABLE *)(&toolstat->toolTableCurrent) = tdata; + } + return result; +} + +toolidx_t tooldata_get(CANON_TOOL_TABLE *pdata, int idx) { + if (!mmap_created) { + std::fprintf(stderr, "%5d tooldata_get() not mmapped BYE\n", getpid()); + std::exit(EXIT_FAILURE); + } + if (!pdata || idx < 0 || idx >= CANON_POCKETS_MAX) { + return IDX_FAIL; + } + *pdata = tool_table[idx]; + return IDX_OK; +} + +void tooldata_reset(void) { + if (!mmap_created) { + return; + } + + const CANON_TOOL_TABLE empty = tooldata_entry_init(); + for (int idx = 0; idx < CANON_POCKETS_MAX; ++idx) { + tool_table[idx] = empty; + } +} + +void tooldata_last_index_set(int idx) { + if (!mmap_created) { + return; + } + if (idx < 0 || idx >= CANON_POCKETS_MAX) { + idx = 0; + } + last_index = idx; +} + +int tooldata_last_index_get(void) { + if (!mmap_created) { + return -1; + } + return last_index; +} + +int tooldata_find_index_for_tool(int toolno) { + if (!mmap_created || toolno == -1) { + return -1; + } + if (!mmap_random_toolchanger && toolno == 0) { + return 0; + } + + int found = -1; + for (int idx = 0; idx <= last_index && idx < CANON_POCKETS_MAX; ++idx) { + if (tool_table[idx].toolno == toolno) { + found = idx; + if (idx != 0) { + break; + } + } + } + return found; +} + +int tool_mmap_creator(EMC_TOOL_STAT const *ptr, int random_toolchanger) { + if (mmap_creator_called) { + std::fprintf(stderr, "Error: tool_mmap_creator already called BYE\n"); + std::exit(EXIT_FAILURE); + } + toolstat = ptr; + mmap_creator_called = true; + mmap_created = true; + mmap_random_toolchanger = random_toolchanger; + last_index = 0; + return 0; +} + +int tool_mmap_user(void) { + return mmap_created ? 0 : -1; +} + +void tool_mmap_close(void) { + mmap_created = false; + mmap_random_toolchanger = false; + toolstat = nullptr; + last_index = 0; +} + +bool tool_mmap_is_random_toolchanger(void) { + return mmap_random_toolchanger; +} + +} // extern "C" diff --git a/core/wasm_shims/tooldata/tooldata_probe_main.cc b/core/wasm_shims/tooldata/tooldata_probe_main.cc new file mode 100644 index 0000000..c86dfc1 --- /dev/null +++ b/core/wasm_shims/tooldata/tooldata_probe_main.cc @@ -0,0 +1,340 @@ +#include "tooldata/tooldata.hh" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +std::string make_temp_path(const char *name) { + const char *tmpdir = std::getenv("TMPDIR"); + return std::string(tmpdir ? tmpdir : "/tmp") + "/" + name; +} + +class ScopedTempWorkdir { +public: + explicit ScopedTempWorkdir(const char *name_template) { + cwd_fd_ = open(".", O_RDONLY); + if (cwd_fd_ < 0) { + return; + } + std::string path_template = make_temp_path(name_template); + dir_buffer_.assign(path_template.begin(), path_template.end()); + dir_buffer_.push_back('\0'); + dir_ = mkdtemp(dir_buffer_.data()); + if (!dir_) { + return; + } + if (chdir(dir_) != 0) { + dir_ = nullptr; + return; + } + ok_ = true; + } + + ~ScopedTempWorkdir() { + if (cwd_fd_ >= 0) { + if (ok_) { + fchdir(cwd_fd_); + } + close(cwd_fd_); + } + if (dir_) { + rmdir(dir_); + } + } + + bool ok() const { + return ok_; + } + +private: + int cwd_fd_ = -1; + std::vector dir_buffer_; + char *dir_ = nullptr; + bool ok_ = false; +}; + +} // namespace + +int main() { + const pid_t creator_child = fork(); + if (creator_child < 0) { + return 41; + } + if (creator_child == 0) { + if (tool_mmap_creator(nullptr, 1) != 0) { + _exit(42); + } + tool_mmap_creator(nullptr, 0); + _exit(43); + } + int creator_status = 0; + if (waitpid(creator_child, &creator_status, 0) != creator_child) { + return 44; + } + if (!WIFEXITED(creator_status) || WEXITSTATUS(creator_status) == 0) { + return 45; + } + + if (tool_mmap_user() != -1) { + return 25; + } + CANON_TOOL_TABLE uncreated_tool = tooldata_entry_init(); + if (tooldata_put(uncreated_tool, 0) != IDX_FAIL) { + return 27; + } + + const pid_t get_child = fork(); + if (get_child < 0) { + return 46; + } + if (get_child == 0) { + tooldata_get(&uncreated_tool, 0); + _exit(47); + } + int get_status = 0; + if (waitpid(get_child, &get_status, 0) != get_child) { + return 48; + } + if (!WIFEXITED(get_status) || WEXITSTATUS(get_status) == 0) { + return 38; + } + if (tooldata_last_index_get() != -1) { + return 40; + } + if (tooldata_db_getall() != -1) { + return 28; + } + std::string missing_db_program_with_arg_string = + make_temp_path("cnc_tooldata_probe_missing_db_program arg"); + std::vector missing_db_program_with_arg( + missing_db_program_with_arg_string.begin(), + missing_db_program_with_arg_string.end()); + missing_db_program_with_arg.push_back('\0'); + if (tooldata_db_init(missing_db_program_with_arg.data(), 1) != -1) { + return 55; + } + const auto separator = missing_db_program_with_arg_string.find(' '); + if (separator == std::string::npos || missing_db_program_with_arg[separator] != 0) { + return 56; + } + std::string missing_db_program_string = make_temp_path("cnc_tooldata_probe_missing_db_program"); + std::vector missing_db_program( + missing_db_program_string.begin(), + missing_db_program_string.end()); + missing_db_program.push_back('\0'); + if (tooldata_db_init(missing_db_program.data(), 1) != -1) { + return 31; + } + char too_many_db_args[] = "/bin/true a b c d e f g h i"; + if (tooldata_db_init(too_many_db_args, 1) != -1) { + return 32; + } + char non_db_program[] = "/bin/true"; + if (tooldata_db_init(non_db_program, 1) != -1) { + return 33; + } + if (tool_nml_register(nullptr) != -1) { + return 29; + } + alignas(EMC_TOOL_STAT) unsigned char toolstat_storage[sizeof(EMC_TOOL_STAT)] = {}; + auto *toolstat = reinterpret_cast(toolstat_storage); + if (tool_mmap_creator(toolstat, 1) != 0) { + return 15; + } + if (tool_mmap_user() != 0) { + return 26; + } + if (!tool_mmap_is_random_toolchanger()) { + return 16; + } + tooldata_set_db(DB_ACTIVE); + const std::string unavailable_db_path = make_temp_path("cnc_tooldata_probe_db_unavailable.tbl"); + if (tooldata_load(unavailable_db_path.c_str()) != -1) { + return 30; + } + tooldata_set_db(DB_ACTIVE); + tooldata_init(false); + if (tooldata_save(nullptr) != 0) { + return 34; + } + tooldata_set_db(DB_NOTUSED); + tooldata_init(true); + tooldata_reset(); + + CANON_TOOL_TABLE spindle = tooldata_entry_init(); + spindle.toolno = 0; + spindle.pocketno = 0; + if (tooldata_put(spindle, 0) != IDX_OK) { + return 1; + } + if (toolstat->toolTableCurrent.toolno != 0 || toolstat->toolTableCurrent.pocketno != 0) { + return 49; + } + + CANON_TOOL_TABLE tool = tooldata_entry_init(); + tool.toolno = 7; + tool.pocketno = 12; + tool.offset.tran.z = 3.5; + tool.diameter = 8.0; + std::strncpy(tool.comment, "probe", CANON_TOOL_COMMENT_SIZE - 1); + if (tooldata_put(tool, 12) != IDX_NEW) { + return 2; + } + + CANON_TOOL_TABLE loaded = tooldata_entry_init(); + if (tooldata_get(&loaded, 12) != IDX_OK) { + return 3; + } + if (loaded.toolno != 7 || loaded.pocketno != 12 || loaded.offset.tran.z != 3.5 || loaded.diameter != 8.0) { + return 4; + } + char db_spindle_line[CANON_TOOL_ENTRY_LEN] = {}; + { + ScopedTempWorkdir db_spindle_workdir("cnc_tooldata_probe_workdir.XXXXXX"); + if (!db_spindle_workdir.ok()) { + return 35; + } + std::remove("./db_spindle.tbl"); + tooldata_set_db(DB_ACTIVE); + if (tooldata_save(nullptr) != 0) { + return 36; + } + FILE *db_spindle_fp = std::fopen("./db_spindle.tbl", "r"); + if (!db_spindle_fp) { + return 37; + } + std::fread(db_spindle_line, 1, sizeof(db_spindle_line) - 1, db_spindle_fp); + std::fclose(db_spindle_fp); + std::remove("./db_spindle.tbl"); + tooldata_set_db(DB_NOTUSED); + } + if (!std::strstr(db_spindle_line, "T0") || std::strstr(db_spindle_line, "T7")) { + return 38; + } + if (tooldata_find_index_for_tool(7) != 12) { + return 5; + } + if (tooldata_find_index_for_tool(0) != 0) { + return 6; + } + if (tooldata_find_index_for_tool(-1) != -1) { + return 17; + } + + tooldata_init(false); + if (tooldata_find_index_for_tool(0) != 0) { + return 18; + } + tooldata_init(true); + + tooldata_last_index_set(12); + if (tooldata_last_index_get() != 12) { + return 7; + } + if (tooldata_put(tool, -1) != IDX_FAIL) { + return 50; + } + if (tooldata_put(tool, CANON_POCKETS_MAX) != IDX_FAIL) { + return 51; + } + if (tooldata_last_index_get() != 12) { + return 52; + } + if (tooldata_get(&loaded, -1) != IDX_FAIL) { + return 53; + } + if (tooldata_get(&loaded, CANON_POCKETS_MAX) != IDX_FAIL) { + return 54; + } + tooldata_last_index_set(CANON_POCKETS_MAX); + if (tooldata_last_index_get() != 0) { + return 24; + } + tooldata_last_index_set(12); + + char line[CANON_TOOL_ENTRY_LEN] = {}; + tooldata_format_toolline(12, true, loaded, line); + if (!std::strstr(line, "T7") || !std::strstr(line, "P12") || !std::strstr(line, "D+8.000000")) { + return 8; + } + + std::string path_template = make_temp_path("cnc_tooldata_probe_XXXXXX"); + std::vector path(path_template.begin(), path_template.end()); + path.push_back('\0'); + const int fd = mkstemp(path.data()); + if (fd < 0) { + return 9; + } + close(fd); + tooldata_last_index_set(0); + if (tooldata_save(path.data()) != 0) { + std::remove(path.data()); + return 10; + } + + tooldata_reset(); + if (tooldata_find_index_for_tool(7) != -1) { + std::remove(path.data()); + return 11; + } + if (tooldata_load(path.data()) != 0) { + std::remove(path.data()); + return 12; + } + std::remove(path.data()); + + CANON_TOOL_TABLE reloaded = tooldata_entry_init(); + if (tooldata_get(&reloaded, 12) != IDX_OK) { + return 13; + } + if (reloaded.toolno != 7 || reloaded.pocketno != 12 || + reloaded.offset.tran.z != 3.5 || reloaded.diameter != 8.0) { + return 14; + } + + tooldata_init(false); + std::string nonrandom_path_template = make_temp_path("cnc_tooldata_nonrandom_probe_XXXXXX"); + std::vector nonrandom_path(nonrandom_path_template.begin(), nonrandom_path_template.end()); + nonrandom_path.push_back('\0'); + const int nonrandom_fd = mkstemp(nonrandom_path.data()); + if (nonrandom_fd < 0) { + return 19; + } + FILE *nonrandom_fp = fdopen(nonrandom_fd, "w"); + if (!nonrandom_fp) { + close(nonrandom_fd); + std::remove(nonrandom_path.data()); + return 20; + } + std::fputs("T-1 P5 Z4.25\n", nonrandom_fp); + std::fclose(nonrandom_fp); + + if (tooldata_load(nonrandom_path.data()) != 0) { + std::remove(nonrandom_path.data()); + return 21; + } + std::remove(nonrandom_path.data()); + + CANON_TOOL_TABLE nonrandom_spindle = tooldata_entry_init(); + if (tooldata_get(&nonrandom_spindle, 0) != IDX_OK) { + return 22; + } + if (nonrandom_spindle.toolno != -1 || nonrandom_spindle.pocketno != 5 || + nonrandom_spindle.offset.tran.z != 4.25) { + return 23; + } + + tool_mmap_close(); + if (tool_mmap_user() != -1) { + return 39; + } + + return 0; +} diff --git a/core/wasm_shims/tooldata/tooldata_runtime_stubs.cc b/core/wasm_shims/tooldata/tooldata_runtime_stubs.cc new file mode 100644 index 0000000..7ef3644 --- /dev/null +++ b/core/wasm_shims/tooldata/tooldata_runtime_stubs.cc @@ -0,0 +1,47 @@ +#include "tooldata.hh" + +#include +#include + +namespace { + +constexpr int MAX_DB_PROGRAM_ARGS = 10; +bool db_live = false; + +} // namespace + +extern "C" { + +int tool_nml_register(CANON_TOOL_TABLE *tblptr) { + return tblptr ? 0 : -1; +} + +int tooldata_db_init(char progname_plus_args[], int) { + if (!progname_plus_args) { + return -1; + } + + char *saveptr = nullptr; + char *program = strtok_r(progname_plus_args, " ", &saveptr); + int argc = 0; + for (char *token = program; token; token = strtok_r(nullptr, " ", &saveptr)) { + ++argc; + if (argc >= MAX_DB_PROGRAM_ARGS) { + return -1; + } + } + if (!program || access(program, X_OK) != 0) { + return -1; + } + return -1; +} + +int tooldata_db_notify(tool_notify_t, int, int, CANON_TOOL_TABLE) { + return 0; +} + +int tooldata_db_getall(void) { + return db_live ? 0 : -1; +} + +} // extern "C" diff --git a/docs/linuxcnc-porting.md b/docs/linuxcnc-porting.md index 4106327..147a438 100644 --- a/docs/linuxcnc-porting.md +++ b/docs/linuxcnc-porting.md @@ -11,8 +11,8 @@ The browser calls only `cnc_sim_*` functions from `core/include/cnc_sim_api.h`. Test: ```bash -g++ -std=c++17 -I core/include core/src/cnc_sim_api.cpp core/tests/cnc_sim_api_smoke.cpp -o /tmp/cnc_sim_api_smoke -/tmp/cnc_sim_api_smoke +g++ -std=c++17 -I core/include core/src/cnc_sim_api.cpp core/tests/cnc_sim_api_smoke.cpp -o ./cnc_sim_api_smoke +./cnc_sim_api_smoke ``` ## Step 1: Temporary event parser @@ -102,8 +102,8 @@ Before porting `rs274ngc` sources to wasm, use the already-built native LinuxCNC This builds `core/tools/linuxcnc_rs274_dump.cpp`, links LinuxCNC `librs274`, and writes: -- `/tmp/cnc_sim_linuxcnc_basic_motion.json` -- `/tmp/cnc_sim_linuxcnc_basic_mill.json` +- run-local output files under a per-run temporary `build_dir/outputs/` +- example files: `cnc_sim_linuxcnc_basic_motion.json`, `cnc_sim_linuxcnc_basic_mill.json` This is a native-only stepping stone. Once stable, the same event sink is used by the wasm build. diff --git a/docs/linuxcnc-rs274-source-map.md b/docs/linuxcnc-rs274-source-map.md index 4ed293c..8b972c1 100644 --- a/docs/linuxcnc-rs274-source-map.md +++ b/docs/linuxcnc-rs274-source-map.md @@ -43,6 +43,7 @@ These are the first group to keep compiling while we peel away native-only depen - `interp_remap.cc` - `interp_setup.cc` - `interp_write.cc` +- `inifile.cc` - `modal_state.cc` - `nurbs_additional_functions.cc` - `rs274ngc_pre.cc` @@ -64,7 +65,7 @@ These are not needed for the browser simulator ABI and should not be part of the - Python/Boost.Python remap and named parameter hooks. - `dlopen`/`dlsym` interpreter loading in `interp_base.cc`. -- mmap-backed `tooldata_mmap.cc`. +- native mmap/filesystem storage in `tooldata_mmap.cc`. - persistent parameter file writes. - dynamic INI/HAL queries. @@ -92,7 +93,9 @@ The next boundary is linking. Expected link risks: - `PythonPlugin` and Boost.Python symbols from remap/named parameter paths. - Python module initialization symbols if the source backend reuses the existing builtin module setup. -- `tooldata_*` implementations, currently mmap-backed in native LinuxCNC and unsuitable for wasm. +- `tooldata_mmap.cc` native mmap/filesystem storage. `tooldata_common.cc` now + remains in the wasm-safe core and links against browser-safe tooldata backend + shims. - dynamic loader code in `interp_base.cc`. - parameter file persistence in `rs274ngc_pre.cc`. @@ -112,13 +115,26 @@ and libraries for the dependencies that have not been replaced yet: - `libpyplugin` for Python remap and named-parameter hooks. - `liblinuxcncini` and `liblinuxcnchal` for INI/HAL named parameter paths. - `liblinuxcnc-uspace-posix` for `rtapi_*` user-space helpers. -- `libtooldata` for the current mmap-backed native tool table. +- `libtooldata` for the native source-link runner. The wasm-safe source probes + instead compile LinuxCNC `tooldata_common.cc` with `tooldata_mmap_backend.cc` + and `tooldata_runtime_stubs.cc`. This is not wasm-ready yet, but it proves the simulator can own and compile the RS274 interpreter core sources directly. The next source-port boundary is to replace each support dependency with browser-safe shims instead of pulling in the LinuxCNC task, motion, HAL, and Python runtime layers. +The wasm-safe source manifest is `linuxcnc-rs274-wasm-source-files.txt`. The +`rs274ngc_pre` wasm link and undefined-symbol probes read their LinuxCNC source +list from that manifest so `inifile.cc`, `tooldata_common.cc`, and the +interpreter sources stay on one source of truth. + +When `CNC_SIM_ENABLE_LINUXCNC_WASM_SAFE_PROBE=ON`, CMake also exposes +`linuxcnc_rs274_wasm_safe_probe_objects` and the `EXCLUDE_FROM_ALL` +`linuxcnc_rs274_wasm_safe_probe` executable. The executable mirrors the +`rs274ngc_pre` shell link probe and is built only when explicitly requested, so +the normal browser wasm artifact does not accidentally link the probe runtime. + The native runner used by this probe now recognizes simulator-owned control lines before handing code to LinuxCNC: `M428`, `M429`, `M430`, `G43.4`, `G43.5`, and `G49`. This keeps the direct runner aligned with the public diff --git a/linuxcnc-rs274-wasm-source-files.txt b/linuxcnc-rs274-wasm-source-files.txt new file mode 100644 index 0000000..b42e38b --- /dev/null +++ b/linuxcnc-rs274-wasm-source-files.txt @@ -0,0 +1,28 @@ +# LinuxCNC rs274 source subset for browser-safe source-link probing. +# Format: group:path:note +core:src/emc/rs274ngc/interp_arc.cc:arc geometry and canonical arc conversion +core:src/emc/rs274ngc/interp_array.cc:block array initialization +core:src/emc/rs274ngc/interp_base.cc:base interpreter interface; compile-safe with wasm dlfcn shim, runtime replacement still required +core:src/emc/rs274ngc/interp_check.cc:error text and block checks +core:src/emc/rs274ngc/interp_convert.cc:block-to-canon conversion +core:src/emc/rs274ngc/interp_cycles.cc:canned cycles +core:src/emc/rs274ngc/interp_execute.cc:execute parsed blocks +core:src/emc/rs274ngc/interp_find.cc:geometry and tool lookup helpers; still depends on tooldata declarations +core:src/emc/rs274ngc/interp_g7x.cc:lathe roughing cycles +core:src/emc/rs274ngc/interp_inspection.cc:inspection helpers +core:src/emc/rs274ngc/interp_internal.cc:internal setup and utility logic +core:src/emc/rs274ngc/interp_inverse.cc:inverse time/feed helpers +core:src/emc/rs274ngc/interp_queue.cc:canonical command queue +core:src/emc/rs274ngc/interp_read.cc:line lexer/parser +core:src/emc/rs274ngc/interp_setup.cc:setup initialization helpers; satisfied by wasm Boost.Python shim +core:src/emc/rs274ngc/interp_write.cc:modal state writers +core:src/emc/rs274ngc/modal_state.cc:state tag modal representation +core:src/emc/rs274ngc/nurbs_additional_functions.cc:NURBS helpers +core:src/emc/rs274ngc/interp_namedparams.cc:Python named-parameter hooks satisfied by wasm Boost.Python/Python C API shims +core:src/emc/rs274ngc/interp_o_word.cc:O-word control flow and directory traversal; compile-safe with wasm shims +core:src/emc/rs274ngc/interp_python.cc:Python remap support satisfied by wasm Python plugin shim +core:src/emc/rs274ngc/interp_remap.cc:Python remap support satisfied by wasm Python plugin shim +core:src/emc/rs274ngc/rs274ngc_pre.cc:Interp implementation covered by rs274ngc_pre link probe +core:src/emc/tooldata/tooldata_common.cc:shared tool table parser/format logic linked with wasm-safe mmap backend +core:src/emc/ini/inifile.cc:INI reader needed by rs274ngc_pre source-link probe +blocked:src/emc/tooldata/tooldata_mmap.cc:native mmap implementation diff --git a/rs274ngc.var b/rs274ngc.var index 1776377..2c7b8bc 100644 --- a/rs274ngc.var +++ b/rs274ngc.var @@ -16,8 +16,8 @@ 5187 0.000000 5188 0.000000 5189 0.000000 -5210 0.000000 -5211 0.000000 +5210 1.000000 +5211 -1.000000 5212 0.000000 5213 0.000000 5214 0.000000 @@ -27,8 +27,8 @@ 5218 0.000000 5219 0.000000 5220 1.000000 -5221 0.000000 -5222 0.000000 +5221 0.015192 +5222 -1.041888 5223 0.000000 5224 0.000000 5225 0.000000 @@ -36,7 +36,7 @@ 5227 0.000000 5228 0.000000 5229 0.000000 -5230 0.000000 +5230 30.000000 5241 0.000000 5242 0.000000 5243 0.000000 diff --git a/rs274ngc.var.bak b/rs274ngc.var.bak new file mode 100644 index 0000000..2c7b8bc --- /dev/null +++ b/rs274ngc.var.bak @@ -0,0 +1,119 @@ +5161 0.000000 +5162 0.000000 +5163 0.000000 +5164 0.000000 +5165 0.000000 +5166 0.000000 +5167 0.000000 +5168 0.000000 +5169 0.000000 +5181 0.000000 +5182 0.000000 +5183 0.000000 +5184 0.000000 +5185 0.000000 +5186 0.000000 +5187 0.000000 +5188 0.000000 +5189 0.000000 +5210 1.000000 +5211 -1.000000 +5212 0.000000 +5213 0.000000 +5214 0.000000 +5215 0.000000 +5216 0.000000 +5217 0.000000 +5218 0.000000 +5219 0.000000 +5220 1.000000 +5221 0.015192 +5222 -1.041888 +5223 0.000000 +5224 0.000000 +5225 0.000000 +5226 0.000000 +5227 0.000000 +5228 0.000000 +5229 0.000000 +5230 30.000000 +5241 0.000000 +5242 0.000000 +5243 0.000000 +5244 0.000000 +5245 0.000000 +5246 0.000000 +5247 0.000000 +5248 0.000000 +5249 0.000000 +5250 0.000000 +5261 0.000000 +5262 0.000000 +5263 0.000000 +5264 0.000000 +5265 0.000000 +5266 0.000000 +5267 0.000000 +5268 0.000000 +5269 0.000000 +5270 0.000000 +5281 0.000000 +5282 0.000000 +5283 0.000000 +5284 0.000000 +5285 0.000000 +5286 0.000000 +5287 0.000000 +5288 0.000000 +5289 0.000000 +5290 0.000000 +5301 0.000000 +5302 0.000000 +5303 0.000000 +5304 0.000000 +5305 0.000000 +5306 0.000000 +5307 0.000000 +5308 0.000000 +5309 0.000000 +5310 0.000000 +5321 0.000000 +5322 0.000000 +5323 0.000000 +5324 0.000000 +5325 0.000000 +5326 0.000000 +5327 0.000000 +5328 0.000000 +5329 0.000000 +5330 0.000000 +5341 0.000000 +5342 0.000000 +5343 0.000000 +5344 0.000000 +5345 0.000000 +5346 0.000000 +5347 0.000000 +5348 0.000000 +5349 0.000000 +5350 0.000000 +5361 0.000000 +5362 0.000000 +5363 0.000000 +5364 0.000000 +5365 0.000000 +5366 0.000000 +5367 0.000000 +5368 0.000000 +5369 0.000000 +5370 0.000000 +5381 0.000000 +5382 0.000000 +5383 0.000000 +5384 0.000000 +5385 0.000000 +5386 0.000000 +5387 0.000000 +5388 0.000000 +5389 0.000000 +5390 0.000000 diff --git a/test-all-native.sh b/test-all-native.sh index caa236b..0d2dbf3 100755 --- a/test-all-native.sh +++ b/test-all-native.sh @@ -3,6 +3,61 @@ set -euo pipefail cd "$(dirname "$0")" +missing_root=/tmp/does-not-exist-linuxcnc +missing_root_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_native_missing_root.XXXXXX.log") +grep -F 'exec 9>"${TMPDIR:-/tmp}/cnc_sim_rs274_source_link.lock"' test-linuxcnc-source-link.sh >/dev/null +grep -F "flock 9" test-linuxcnc-source-link.sh >/dev/null +grep -F 'exec 9>"${TMPDIR:-/tmp}/cnc_sim_linuxcnc_rs274_native.lock"' test-linuxcnc-rs274-native.sh >/dev/null +grep -F "flock 9" test-linuxcnc-rs274-native.sh >/dev/null +for script in \ + test-linuxcnc-bridge-native.sh \ + test-linuxcnc-rs274-native.sh \ + test-linuxcnc-api-native.sh; do + if LINUXCNC_ROOT="$missing_root" "./$script" >"$missing_root_log" 2>&1; then + echo "$script accepted missing LinuxCNC root: $missing_root" >&2 + exit 1 + fi + if ! grep -F "missing LinuxCNC root: $missing_root" "$missing_root_log" >/dev/null; then + echo "$script did not report missing LinuxCNC root clearly" >&2 + sed -n '1,20p' "$missing_root_log" >&2 + exit 1 + fi +done +rm -f "$missing_root_log" + +unknown_group_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_native_unknown_manifest_group.XXXXXX.txt") +unknown_group_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_native_unknown_manifest_group.XXXXXX.log") +printf 'bogus:src/emc/rs274ngc/interp_arc.cc:test unknown group\n' >"$unknown_group_manifest" +for script in \ + test-linuxcnc-source-syntax.sh \ + test-linuxcnc-source-objects.sh \ + test-linuxcnc-source-link.sh; do + if "./$script" "$unknown_group_manifest" >"$unknown_group_log" 2>&1; then + echo "$script accepted unknown manifest group" >&2 + exit 1 + fi + if ! grep -F "unknown manifest group: bogus" "$unknown_group_log" >/dev/null; then + echo "$script did not report unknown manifest group clearly" >&2 + sed -n '1,20p' "$unknown_group_log" >&2 + exit 1 + fi +done +rm -f "$unknown_group_manifest" "$unknown_group_log" + +missing_source_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_native_syntax_missing_source_manifest.XXXXXX.txt") +missing_source_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_native_syntax_missing_source.XXXXXX.log") +printf 'core:src/emc/rs274ngc/does_not_exist.cc:test missing source\n' >"$missing_source_manifest" +if ./test-linuxcnc-source-syntax.sh "$missing_source_manifest" >"$missing_source_log" 2>&1; then + echo "test-linuxcnc-source-syntax.sh accepted missing manifest file" >&2 + exit 1 +fi +if ! grep -F "missing manifest file: src/emc/rs274ngc/does_not_exist.cc" "$missing_source_log" >/dev/null; then + echo "test-linuxcnc-source-syntax.sh did not report missing manifest file clearly" >&2 + sed -n '1,20p' "$missing_source_log" >&2 + exit 1 +fi +rm -f "$missing_source_manifest" "$missing_source_log" + ./test-native.sh ./test-linuxcnc-source-syntax.sh ./test-linuxcnc-source-objects.sh diff --git a/test-linuxcnc-api-native.sh b/test-linuxcnc-api-native.sh index 05becba..08f82fb 100755 --- a/test-linuxcnc-api-native.sh +++ b/test-linuxcnc-api-native.sh @@ -5,8 +5,14 @@ cd "$(dirname "$0")" linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} cxx=${CXX:-g++} -build_dir=${TMPDIR:-/tmp}/cnc_sim_native -mkdir -p "$build_dir" +build_dir=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_native.XXXXXX") + +if [[ ! -d "$linuxcnc_root" ]]; then + echo "missing LinuxCNC root: $linuxcnc_root" >&2 + exit 1 +fi + +trap 'rm -rf "$build_dir"' EXIT var_file="$build_dir/rs274ngc-api.var" cp "$linuxcnc_root/tests/halui/jogging/sim.var" "$var_file" diff --git a/test-linuxcnc-bridge-native.sh b/test-linuxcnc-bridge-native.sh index 8bb82b2..7cbc5fc 100755 --- a/test-linuxcnc-bridge-native.sh +++ b/test-linuxcnc-bridge-native.sh @@ -5,8 +5,14 @@ cd "$(dirname "$0")" linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} cxx=${CXX:-g++} -build_dir=${TMPDIR:-/tmp}/cnc_sim_native -mkdir -p "$build_dir" +build_dir=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_native.XXXXXX") + +if [[ ! -d "$linuxcnc_root" ]]; then + echo "missing LinuxCNC root: $linuxcnc_root" >&2 + exit 1 +fi + +trap 'rm -rf "$build_dir"' EXIT "$cxx" -std=c++17 \ -I core/include \ diff --git a/test-linuxcnc-rs274-native.sh b/test-linuxcnc-rs274-native.sh index a6b9aba..64a38ca 100755 --- a/test-linuxcnc-rs274-native.sh +++ b/test-linuxcnc-rs274-native.sh @@ -5,11 +5,23 @@ cd "$(dirname "$0")" linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} cxx=${CXX:-g++} -build_dir=${TMPDIR:-/tmp}/cnc_sim_native -mkdir -p "$build_dir" +build_dir=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_native.XXXXXX") + +if [[ ! -d "$linuxcnc_root" ]]; then + echo "missing LinuxCNC root: $linuxcnc_root" >&2 + exit 1 +fi + +trap 'rm -rf "$build_dir"' EXIT +# The LinuxCNC rs274 backend path can bus-error when two dumps run concurrently. +exec 9>"${TMPDIR:-/tmp}/cnc_sim_linuxcnc_rs274_native.lock" +flock 9 + var_file="$build_dir/rs274ngc.var" base_var_file="$build_dir/rs274ngc-base.var" ini_named_parameter_file="$(pwd)/tests/gcode/linuxcnc_ini_named_parameter.ini" +output_dir="$build_dir/outputs" +mkdir -p "$output_dir" cp "$linuxcnc_root/tests/halui/jogging/sim.var" "$base_var_file" cp "$base_var_file" "$var_file" @@ -36,62 +48,65 @@ cp "$base_var_file" "$var_file" -lpython3.13 \ -o "$build_dir/linuxcnc_rs274_dump" -CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_basic_motion.ngc >/tmp/cnc_sim_linuxcnc_basic_motion.json -CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/basic_mill.ngc >/tmp/cnc_sim_linuxcnc_basic_mill.json -CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_rtcp_controls.ngc >/tmp/cnc_sim_linuxcnc_rtcp_controls.json -CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_spindle_direction.ngc >/tmp/cnc_sim_linuxcnc_spindle_direction.json -CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_spindle_modes.ngc >/tmp/cnc_sim_linuxcnc_spindle_modes.json -CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_spindle_orient.ngc >/tmp/cnc_sim_linuxcnc_spindle_orient.json -CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_io_controls.ngc >/tmp/cnc_sim_linuxcnc_io_controls.json -CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_override_controls.ngc >/tmp/cnc_sim_linuxcnc_override_controls.json -CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_modal_state.ngc >/tmp/cnc_sim_linuxcnc_modal_state.json -CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_modal_autorestore.ngc >/tmp/cnc_sim_linuxcnc_modal_autorestore.json -if CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_modal_invalidate.ngc >/tmp/cnc_sim_linuxcnc_modal_invalidate.json 2>/tmp/cnc_sim_linuxcnc_modal_invalidate.err; then +CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_basic_motion.ngc >"$output_dir/cnc_sim_linuxcnc_basic_motion.json" +CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/basic_mill.ngc >"$output_dir/cnc_sim_linuxcnc_basic_mill.json" +CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_rtcp_controls.ngc >"$output_dir/cnc_sim_linuxcnc_rtcp_controls.json" +CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_spindle_direction.ngc >"$output_dir/cnc_sim_linuxcnc_spindle_direction.json" +CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_spindle_modes.ngc >"$output_dir/cnc_sim_linuxcnc_spindle_modes.json" +CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_spindle_orient.ngc >"$output_dir/cnc_sim_linuxcnc_spindle_orient.json" +CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_io_controls.ngc >"$output_dir/cnc_sim_linuxcnc_io_controls.json" +CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_override_controls.ngc >"$output_dir/cnc_sim_linuxcnc_override_controls.json" +CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_modal_state.ngc >"$output_dir/cnc_sim_linuxcnc_modal_state.json" +CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_modal_autorestore.ngc >"$output_dir/cnc_sim_linuxcnc_modal_autorestore.json" +if CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_modal_invalidate.ngc >"$output_dir/cnc_sim_linuxcnc_modal_invalidate.json" 2>"$output_dir/cnc_sim_linuxcnc_modal_invalidate.err"; then echo "expected linuxcnc_modal_invalidate.ngc to fail" >&2 exit 1 fi -CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_threading_sync.ngc >/tmp/cnc_sim_linuxcnc_threading_sync.json -CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_threading_cycle.ngc >/tmp/cnc_sim_linuxcnc_threading_cycle.json -CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_tool_number.ngc >/tmp/cnc_sim_linuxcnc_tool_number.json -CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_program_stops.ngc >/tmp/cnc_sim_linuxcnc_program_stops.json -CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_coolant.ngc >/tmp/cnc_sim_linuxcnc_coolant.json -CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_feed_modes.ngc >/tmp/cnc_sim_linuxcnc_feed_modes.json -CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_motion_modes.ngc >/tmp/cnc_sim_linuxcnc_motion_modes.json -CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_tool_length.ngc >/tmp/cnc_sim_linuxcnc_tool_length.json -CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_dynamic_tool_length.ngc >/tmp/cnc_sim_linuxcnc_dynamic_tool_length.json -CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_canned_cycle.ngc >/tmp/cnc_sim_linuxcnc_canned_cycle.json -CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_canned_cycles_extended.ngc >/tmp/cnc_sim_linuxcnc_canned_cycles_extended.json -CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_canned_cycle_planes.ngc >/tmp/cnc_sim_linuxcnc_canned_cycle_planes.json -CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_arc_planes.ngc >/tmp/cnc_sim_linuxcnc_arc_planes.json -CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_arc_distance_modes.ngc >/tmp/cnc_sim_linuxcnc_arc_distance_modes.json -CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_predefined_positions.ngc >/tmp/cnc_sim_linuxcnc_predefined_positions.json -CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_machine_coordinates.ngc >/tmp/cnc_sim_linuxcnc_machine_coordinates.json -CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_cutter_comp.ngc >/tmp/cnc_sim_linuxcnc_cutter_comp.json -CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_comments.ngc >/tmp/cnc_sim_linuxcnc_comments.json -CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_probe_no_error.ngc >/tmp/cnc_sim_linuxcnc_probe_no_error.json -CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/smoke_oword_subprogram.ngc >/tmp/cnc_sim_linuxcnc_oword_subprogram.json -CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_named_oword_subprogram.ngc >/tmp/cnc_sim_linuxcnc_named_oword_subprogram.json -CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_oword_control_flow.ngc >/tmp/cnc_sim_linuxcnc_oword_control_flow.json -CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_named_parameter_exists.ngc >/tmp/cnc_sim_linuxcnc_named_parameter_exists.json -CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_indexed_parameters.ngc >/tmp/cnc_sim_linuxcnc_indexed_parameters.json -CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_named_parameter_normalization.ngc >/tmp/cnc_sim_linuxcnc_named_parameter_normalization.json -INI_FILE_NAME="$ini_named_parameter_file" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_ini_named_parameter.ngc >/tmp/cnc_sim_linuxcnc_ini_named_parameter.json +CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_threading_sync.ngc >"$output_dir/cnc_sim_linuxcnc_threading_sync.json" +CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_threading_cycle.ngc >"$output_dir/cnc_sim_linuxcnc_threading_cycle.json" +CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_tool_number.ngc >"$output_dir/cnc_sim_linuxcnc_tool_number.json" +CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_program_stops.ngc >"$output_dir/cnc_sim_linuxcnc_program_stops.json" +CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_coolant.ngc >"$output_dir/cnc_sim_linuxcnc_coolant.json" +CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_feed_modes.ngc >"$output_dir/cnc_sim_linuxcnc_feed_modes.json" +CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_motion_modes.ngc >"$output_dir/cnc_sim_linuxcnc_motion_modes.json" +CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_tool_length.ngc >"$output_dir/cnc_sim_linuxcnc_tool_length.json" +CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_dynamic_tool_length.ngc >"$output_dir/cnc_sim_linuxcnc_dynamic_tool_length.json" +CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_canned_cycle.ngc >"$output_dir/cnc_sim_linuxcnc_canned_cycle.json" +CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_canned_cycles_extended.ngc >"$output_dir/cnc_sim_linuxcnc_canned_cycles_extended.json" +CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_canned_cycle_planes.ngc >"$output_dir/cnc_sim_linuxcnc_canned_cycle_planes.json" +CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_arc_planes.ngc >"$output_dir/cnc_sim_linuxcnc_arc_planes.json" +CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_arc_distance_modes.ngc >"$output_dir/cnc_sim_linuxcnc_arc_distance_modes.json" +CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_predefined_positions.ngc >"$output_dir/cnc_sim_linuxcnc_predefined_positions.json" +CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_machine_coordinates.ngc >"$output_dir/cnc_sim_linuxcnc_machine_coordinates.json" +CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_cutter_comp.ngc >"$output_dir/cnc_sim_linuxcnc_cutter_comp.json" +CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_comments.ngc >"$output_dir/cnc_sim_linuxcnc_comments.json" +CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_probe_no_error.ngc >"$output_dir/cnc_sim_linuxcnc_probe_no_error.json" +CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/smoke_oword_subprogram.ngc >"$output_dir/cnc_sim_linuxcnc_oword_subprogram.json" +CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_named_oword_subprogram.ngc >"$output_dir/cnc_sim_linuxcnc_named_oword_subprogram.json" +CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_oword_control_flow.ngc >"$output_dir/cnc_sim_linuxcnc_oword_control_flow.json" +CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_named_parameter_exists.ngc >"$output_dir/cnc_sim_linuxcnc_named_parameter_exists.json" +CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_indexed_parameters.ngc >"$output_dir/cnc_sim_linuxcnc_indexed_parameters.json" +CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_named_parameter_normalization.ngc >"$output_dir/cnc_sim_linuxcnc_named_parameter_normalization.json" +INI_FILE_NAME="$ini_named_parameter_file" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_ini_named_parameter.ngc >"$output_dir/cnc_sim_linuxcnc_ini_named_parameter.json" cp "$base_var_file" "$var_file" -CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_coordinate_offsets.ngc >/tmp/cnc_sim_linuxcnc_coordinate_offsets.json +CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_coordinate_offsets.ngc >"$output_dir/cnc_sim_linuxcnc_coordinate_offsets.json" cp "$base_var_file" "$var_file" -CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_coordinate_l20.ngc >/tmp/cnc_sim_linuxcnc_coordinate_l20.json +CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_coordinate_l20.ngc >"$output_dir/cnc_sim_linuxcnc_coordinate_l20.json" cp "$base_var_file" "$var_file" -CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_coordinate_p0.ngc >/tmp/cnc_sim_linuxcnc_coordinate_p0.json +CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_coordinate_p0.ngc >"$output_dir/cnc_sim_linuxcnc_coordinate_p0.json" cp "$base_var_file" "$var_file" -CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_coordinate_select_all.ngc >/tmp/cnc_sim_linuxcnc_coordinate_select_all.json +CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_coordinate_select_all.ngc >"$output_dir/cnc_sim_linuxcnc_coordinate_select_all.json" cp "$base_var_file" "$var_file" -CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_g92_restore.ngc >/tmp/cnc_sim_linuxcnc_g92_restore.json +CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_g92_restore.ngc >"$output_dir/cnc_sim_linuxcnc_g92_restore.json" cp "$base_var_file" "$var_file" -CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_g52_offset.ngc >/tmp/cnc_sim_linuxcnc_g52_offset.json -python3 - <<'PY' +CNC_SIM_RS274_VAR="$var_file" "$build_dir/linuxcnc_rs274_dump" tests/gcode/linuxcnc_g52_offset.ngc >"$output_dir/cnc_sim_linuxcnc_g52_offset.json" +CNC_SIM_RS274_NATIVE_OUTPUT_DIR="$output_dir" python3 - <<'PY' import json +import os from pathlib import Path +OUTPUT_DIR = Path(os.environ["CNC_SIM_RS274_NATIVE_OUTPUT_DIR"]) + def check(path, required): events = json.loads(Path(path).read_text()) types = [event["type"] for event in events] @@ -103,11 +118,14 @@ def check(path, required): raise SystemExit(f"{path}: unexpected final XY position") return events -required_motion = {"set-units", "set-plane", "set-spindle", "rapid", "set-feed", "linear-feed", "arc-feed", "dwell", "program-end"} -check("/tmp/cnc_sim_linuxcnc_basic_motion.json", required_motion) -check("/tmp/cnc_sim_linuxcnc_basic_mill.json", required_motion | {"tool-change"}) +def load(name): + return json.loads((OUTPUT_DIR / name).read_text()) -controls = json.loads(Path("/tmp/cnc_sim_linuxcnc_rtcp_controls.json").read_text()) +required_motion = {"set-units", "set-plane", "set-spindle", "rapid", "set-feed", "linear-feed", "arc-feed", "dwell", "program-end"} +check(OUTPUT_DIR / "cnc_sim_linuxcnc_basic_motion.json", required_motion) +check(OUTPUT_DIR / "cnc_sim_linuxcnc_basic_mill.json", required_motion | {"tool-change"}) + +controls = load("cnc_sim_linuxcnc_rtcp_controls.json") kin = [event for event in controls if event["type"] == "kinematics-switch"] if [(event["reserved"], event["feed"]) for event in kin] != [(1, 1), (0, 0), (2, 1)]: raise SystemExit("unexpected kinematics switch sequence") @@ -115,7 +133,7 @@ rtcp = [event for event in controls if event["type"] == "rtcp-state"] if [(event["line"], event["tool"], event["feed"]) for event in rtcp] != [(3, 0, 0), (4, 7, 1)]: raise SystemExit("unexpected RTCP state sequence") -spindle = json.loads(Path("/tmp/cnc_sim_linuxcnc_spindle_direction.json").read_text()) +spindle = load("cnc_sim_linuxcnc_spindle_direction.json") spindle_states = [ (event["line"], event["spindle"], event["reserved"]) for event in spindle @@ -128,7 +146,7 @@ if (3, 1200, 2) not in spindle_states: if (4, 0, 0) not in spindle_states: raise SystemExit("missing M5 stopped spindle state") -spindle_modes = json.loads(Path("/tmp/cnc_sim_linuxcnc_spindle_modes.json").read_text()) +spindle_modes = load("cnc_sim_linuxcnc_spindle_modes.json") spindle_mode_states = [ (event["line"], event["reserved"], event["feed"], event["tool"]) for event in spindle_modes @@ -142,7 +160,7 @@ expected_spindle_mode_states = [ if spindle_mode_states != expected_spindle_mode_states: raise SystemExit(f"unexpected G96/G97 spindle mode states: {spindle_mode_states!r}") -spindle_orient = json.loads(Path("/tmp/cnc_sim_linuxcnc_spindle_orient.json").read_text()) +spindle_orient = load("cnc_sim_linuxcnc_spindle_orient.json") if not any( event["type"] == "comment" and event["line"] == 2 and @@ -163,7 +181,7 @@ if not any( ): raise SystemExit("missing M19 spindle orient wait state") -io_controls = json.loads(Path("/tmp/cnc_sim_linuxcnc_io_controls.json").read_text()) +io_controls = load("cnc_sim_linuxcnc_io_controls.json") io_states = [ (event["line"], event["reserved"], event["tool"], event["feed"], event["arcTurns"], event["dwellSeconds"]) for event in io_controls @@ -182,7 +200,7 @@ expected_io_states = [ if io_states != expected_io_states: raise SystemExit(f"unexpected M62-M68 I/O states: {io_states!r}") -override_controls = json.loads(Path("/tmp/cnc_sim_linuxcnc_override_controls.json").read_text()) +override_controls = load("cnc_sim_linuxcnc_override_controls.json") override_states = [ (event["line"], event["reserved"], event["tool"], event["feed"]) for event in override_controls @@ -206,7 +224,7 @@ expected_override_states = [ if override_states != expected_override_states: raise SystemExit(f"unexpected M48-M53 override states: {override_states!r}") -modal_state = json.loads(Path("/tmp/cnc_sim_linuxcnc_modal_state.json").read_text()) +modal_state = load("cnc_sim_linuxcnc_modal_state.json") if not any( event["type"] == "rapid" and event["line"] == 7 and @@ -226,7 +244,7 @@ if not any( ): raise SystemExit("missing M72 restored XY plane arc") -modal_autorestore = json.loads(Path("/tmp/cnc_sim_linuxcnc_modal_autorestore.json").read_text()) +modal_autorestore = load("cnc_sim_linuxcnc_modal_autorestore.json") if not any( event["type"] == "rapid" and event["line"] == 9 and @@ -243,7 +261,7 @@ if not any( ): raise SystemExit("missing M73 autorestored incremental caller move") -threading_sync = json.loads(Path("/tmp/cnc_sim_linuxcnc_threading_sync.json").read_text()) +threading_sync = load("cnc_sim_linuxcnc_threading_sync.json") sync_states = [ (event["line"], event["reserved"], event["feed"], event["tool"]) for event in threading_sync @@ -263,7 +281,7 @@ if not any( ): raise SystemExit("missing G33.1 rigid tap motion line") -threading_cycle = json.loads(Path("/tmp/cnc_sim_linuxcnc_threading_cycle.json").read_text()) +threading_cycle = load("cnc_sim_linuxcnc_threading_cycle.json") if not any( event["type"] == "comment" and event["line"] == 4 and @@ -282,7 +300,7 @@ if not any( if not any(event["type"] == "program-end" and event["line"] == 5 for event in threading_cycle): raise SystemExit("missing G76 program end line number") -tool_number = json.loads(Path("/tmp/cnc_sim_linuxcnc_tool_number.json").read_text()) +tool_number = load("cnc_sim_linuxcnc_tool_number.json") if not any( event["type"] == "tool-change" and event["line"] == 2 and @@ -291,7 +309,7 @@ if not any( ): raise SystemExit("missing M61 Q1 tool-number event line") -stops = json.loads(Path("/tmp/cnc_sim_linuxcnc_program_stops.json").read_text()) +stops = load("cnc_sim_linuxcnc_program_stops.json") stop_states = [ (event["line"], event["reserved"]) for event in stops @@ -301,7 +319,7 @@ for expected in [(2, 1), (3, 2), (4, 3), (4, 1), (5, 0)]: if expected not in stop_states: raise SystemExit(f"missing program stop state {expected!r}") -coolant = json.loads(Path("/tmp/cnc_sim_linuxcnc_coolant.json").read_text()) +coolant = load("cnc_sim_linuxcnc_coolant.json") coolant_states = [ (event["line"], event["reserved"]) for event in coolant @@ -311,7 +329,7 @@ for expected in [(2, 11), (3, 21), (4, 10), (4, 20)]: if expected not in coolant_states: raise SystemExit(f"missing coolant state {expected!r}") -feed_modes = json.loads(Path("/tmp/cnc_sim_linuxcnc_feed_modes.json").read_text()) +feed_modes = load("cnc_sim_linuxcnc_feed_modes.json") feed_mode_comments = [ (event["line"], event["reserved"]) for event in feed_modes @@ -329,7 +347,7 @@ for expected in [(3, 0), (4, 0)]: if expected not in feed_mode_events: raise SystemExit(f"missing feed mode event {expected!r}") -motion_modes = json.loads(Path("/tmp/cnc_sim_linuxcnc_motion_modes.json").read_text()) +motion_modes = load("cnc_sim_linuxcnc_motion_modes.json") motion_mode_states = [ (event["line"], event["reserved"], event["feed"]) for event in motion_modes @@ -339,7 +357,7 @@ for expected in [(2, 611, 0), (3, 612, 0), (4, 640, 0.05)]: if expected not in motion_mode_states: raise SystemExit(f"missing motion mode state {expected!r}") -tool_length = json.loads(Path("/tmp/cnc_sim_linuxcnc_tool_length.json").read_text()) +tool_length = load("cnc_sim_linuxcnc_tool_length.json") if not any( event["type"] == "comment" and event["line"] == 2 and @@ -357,7 +375,7 @@ if not any( ): raise SystemExit("missing G49 tool length clear event") -dynamic_tool_length = json.loads(Path("/tmp/cnc_sim_linuxcnc_dynamic_tool_length.json").read_text()) +dynamic_tool_length = load("cnc_sim_linuxcnc_dynamic_tool_length.json") dynamic_offsets = [ (event["line"], event["start"]["x"], event["start"]["z"]) for event in dynamic_tool_length @@ -367,7 +385,7 @@ expected_dynamic_offsets = [(2, 1, 2), (3, 1.25, 2.5), (4, 1.25, 15), (5, 0, 0)] if dynamic_offsets != expected_dynamic_offsets: raise SystemExit(f"unexpected G43.1/G43.2 tool length offsets: {dynamic_offsets!r}") -cycle = json.loads(Path("/tmp/cnc_sim_linuxcnc_canned_cycle.json").read_text()) +cycle = load("cnc_sim_linuxcnc_canned_cycle.json") motions = [event for event in cycle if event["type"] in {"rapid", "linear-feed"}] expected_cycle = [ ("rapid", 0, 0, 5), @@ -380,7 +398,7 @@ actual_cycle = [(event["type"], event["end"]["x"], event["end"]["y"], event["end if actual_cycle != expected_cycle: raise SystemExit(f"unexpected G81/G80 expansion: {actual_cycle!r}") -cycle_planes = json.loads(Path("/tmp/cnc_sim_linuxcnc_canned_cycle_planes.json").read_text()) +cycle_planes = load("cnc_sim_linuxcnc_canned_cycle_planes.json") if not any( event["type"] == "linear-feed" and event["line"] == 4 and @@ -526,7 +544,7 @@ expected_plane_g87_sequence = [ if plane_g87_sequence != expected_plane_g87_sequence: raise SystemExit(f"unexpected G19 G87 plane sequence: {plane_g87_sequence!r}") -extended_cycle = json.loads(Path("/tmp/cnc_sim_linuxcnc_canned_cycles_extended.json").read_text()) +extended_cycle = load("cnc_sim_linuxcnc_canned_cycles_extended.json") if not any( event["type"] == "linear-feed" and event["line"] == 3 and @@ -653,7 +671,7 @@ expected_g87_sequence = [ if g87_sequence != expected_g87_sequence: raise SystemExit(f"unexpected G87 back-boring sequence: {g87_sequence!r}") -arc_planes = json.loads(Path("/tmp/cnc_sim_linuxcnc_arc_planes.json").read_text()) +arc_planes = load("cnc_sim_linuxcnc_arc_planes.json") if not any( event["type"] == "arc-feed" and event["line"] == 5 and @@ -683,7 +701,7 @@ if not any( ): raise SystemExit("missing G19 YZ plane arc-feed mapping") -arc_distance = json.loads(Path("/tmp/cnc_sim_linuxcnc_arc_distance_modes.json").read_text()) +arc_distance = load("cnc_sim_linuxcnc_arc_distance_modes.json") arc_centers = [ (event["line"], event["center"]["x"], event["center"]["y"]) for event in arc_distance @@ -693,7 +711,7 @@ expected_arc_centers = [(5, 5, 0), (7, 15, 0), (9, 25, 0)] if arc_centers != expected_arc_centers: raise SystemExit(f"unexpected G90.1/G91.1 arc centers: {arc_centers!r}") -predefined = json.loads(Path("/tmp/cnc_sim_linuxcnc_predefined_positions.json").read_text()) +predefined = load("cnc_sim_linuxcnc_predefined_positions.json") predefined_rapids = [ ( event["line"], @@ -712,7 +730,7 @@ expected_predefined_rapids = [ if predefined_rapids != expected_predefined_rapids: raise SystemExit(f"unexpected G28/G30 rapid sequence: {predefined_rapids!r}") -machine_coords = json.loads(Path("/tmp/cnc_sim_linuxcnc_machine_coordinates.json").read_text()) +machine_coords = load("cnc_sim_linuxcnc_machine_coordinates.json") machine_moves = [ ( event["type"], event["line"], @@ -730,7 +748,7 @@ expected_machine_moves = [ if machine_moves != expected_machine_moves: raise SystemExit(f"unexpected G53 machine-coordinate moves: {machine_moves!r}") -cutter_comp = json.loads(Path("/tmp/cnc_sim_linuxcnc_cutter_comp.json").read_text()) +cutter_comp = load("cnc_sim_linuxcnc_cutter_comp.json") cutter_comp_motions = [ event for event in cutter_comp @@ -758,11 +776,11 @@ actual_cutter_comp = [ if actual_cutter_comp != expected_cutter_comp: raise SystemExit(f"unexpected cutter compensation path: {actual_cutter_comp!r}") -comments = json.loads(Path("/tmp/cnc_sim_linuxcnc_comments.json").read_text()) +comments = load("cnc_sim_linuxcnc_comments.json") if not any(event["type"] == "comment" and event["line"] == 2 for event in comments): raise SystemExit("missing LinuxCNC comment line event") -probe = json.loads(Path("/tmp/cnc_sim_linuxcnc_probe_no_error.json").read_text()) +probe = load("cnc_sim_linuxcnc_probe_no_error.json") probe_events = [ (event["line"], event["reserved"], event["start"]["z"], event["end"]["z"]) for event in probe @@ -787,7 +805,7 @@ if not any( ): raise SystemExit("missing #5070 probe-tripped conditional motion") -oword = json.loads(Path("/tmp/cnc_sim_linuxcnc_oword_subprogram.json").read_text()) +oword = load("cnc_sim_linuxcnc_oword_subprogram.json") if not any( event["type"] == "linear-feed" and event["line"] == 8 and @@ -812,7 +830,7 @@ if any( ): raise SystemExit("LinuxCNC O-word return should skip remaining subprogram body") -named_oword = json.loads(Path("/tmp/cnc_sim_linuxcnc_named_oword_subprogram.json").read_text()) +named_oword = load("cnc_sim_linuxcnc_named_oword_subprogram.json") if not any( event["type"] == "linear-feed" and event["line"] == 9 and @@ -845,7 +863,7 @@ if any( ): raise SystemExit("LinuxCNC named O-word return should skip remaining subprogram body") -oword_flow = json.loads(Path("/tmp/cnc_sim_linuxcnc_oword_control_flow.json").read_text()) +oword_flow = load("cnc_sim_linuxcnc_oword_control_flow.json") if not any(event["type"] == "linear-feed" and event["line"] == 7 and event["end"]["x"] == 2 for event in oword_flow): raise SystemExit("missing LinuxCNC O-word elseif branch motion") if any( @@ -910,7 +928,7 @@ if not any( ): raise SystemExit("missing LinuxCNC O-word do/continue skipped-body counter motion") -exists = json.loads(Path("/tmp/cnc_sim_linuxcnc_named_parameter_exists.json").read_text()) +exists = load("cnc_sim_linuxcnc_named_parameter_exists.json") exists_moves = [ (event["line"], event["end"]["x"], event["end"]["y"]) for event in exists @@ -920,7 +938,7 @@ expected_exists_moves = [(5, 1, 0), (8, 1, 1)] if exists_moves != expected_exists_moves: raise SystemExit(f"unexpected LinuxCNC EXISTS named parameter branch motions: {exists_moves!r}") -indexed = json.loads(Path("/tmp/cnc_sim_linuxcnc_indexed_parameters.json").read_text()) +indexed = load("cnc_sim_linuxcnc_indexed_parameters.json") indexed_moves = [ ( event["line"], @@ -947,7 +965,7 @@ expected_indexed_moves = [ if indexed_moves != expected_indexed_moves: raise SystemExit(f"unexpected LinuxCNC indexed/indirect parameter motions: {indexed_moves!r}") -normalized = json.loads(Path("/tmp/cnc_sim_linuxcnc_named_parameter_normalization.json").read_text()) +normalized = load("cnc_sim_linuxcnc_named_parameter_normalization.json") normalized_moves = [ (event["line"], event["end"]["x"], event["end"]["y"]) for event in normalized @@ -957,7 +975,7 @@ expected_normalized_moves = [(4, 12, 0), (6, 12, 1)] if normalized_moves != expected_normalized_moves: raise SystemExit(f"unexpected LinuxCNC normalized named parameter motions: {normalized_moves!r}") -ini_named = json.loads(Path("/tmp/cnc_sim_linuxcnc_ini_named_parameter.json").read_text()) +ini_named = load("cnc_sim_linuxcnc_ini_named_parameter.json") ini_named_moves = [ (event["line"], event["end"]["x"], event["end"]["y"]) for event in ini_named @@ -967,7 +985,7 @@ expected_ini_named_moves = [(3, 3.25, 0), (5, 3.25, 1)] if ini_named_moves != expected_ini_named_moves: raise SystemExit(f"unexpected LinuxCNC INI named parameter motions: {ini_named_moves!r}") -coords = json.loads(Path("/tmp/cnc_sim_linuxcnc_coordinate_offsets.json").read_text()) +coords = load("cnc_sim_linuxcnc_coordinate_offsets.json") g5x = [event for event in coords if event["type"] == "set-g5x-offset"] g92 = [event for event in coords if event["type"] == "set-g92-offset"] rot = [event for event in coords if event["type"] == "set-xy-rotation"] @@ -993,7 +1011,7 @@ if not any( if not any(event["type"] == "program-end" and event["line"] == 5 for event in coords): raise SystemExit("missing coordinate program end line number") -l20 = json.loads(Path("/tmp/cnc_sim_linuxcnc_coordinate_l20.json").read_text()) +l20 = load("cnc_sim_linuxcnc_coordinate_l20.json") if not any( event["type"] == "set-g5x-offset" and event["line"] == 4 and @@ -1007,7 +1025,7 @@ if not any( if not any(event["type"] == "program-end" and event["line"] == 5 for event in l20): raise SystemExit("missing G10 L20 program end line number") -p0 = json.loads(Path("/tmp/cnc_sim_linuxcnc_coordinate_p0.json").read_text()) +p0 = load("cnc_sim_linuxcnc_coordinate_p0.json") if not any( event["type"] == "set-g5x-offset" and event["line"] == 3 and @@ -1021,7 +1039,7 @@ if not any( if not any(event["type"] == "program-end" and event["line"] == 4 for event in p0): raise SystemExit("missing G10 P0 program end line number") -select_all = json.loads(Path("/tmp/cnc_sim_linuxcnc_coordinate_select_all.json").read_text()) +select_all = load("cnc_sim_linuxcnc_coordinate_select_all.json") select_events = [ (event["line"], event["tool"], event["start"]["x"]) for event in select_all @@ -1041,7 +1059,7 @@ expected_select_events = [ if select_events != expected_select_events: raise SystemExit(f"unexpected G54-G59.3 selection events: {select_events!r}") -g92_restore = json.loads(Path("/tmp/cnc_sim_linuxcnc_g92_restore.json").read_text()) +g92_restore = load("cnc_sim_linuxcnc_g92_restore.json") g92_states = [ (event["line"], event["start"]["x"], event["start"]["y"], event["start"]["z"]) for event in g92_restore @@ -1059,7 +1077,7 @@ if g92_states != expected_g92_states: if not any(event["type"] == "program-end" and event["line"] == 7 for event in g92_restore): raise SystemExit("missing G92 restore program end line number") -g52_offset = json.loads(Path("/tmp/cnc_sim_linuxcnc_g52_offset.json").read_text()) +g52_offset = load("cnc_sim_linuxcnc_g52_offset.json") g52_states = [ (event["line"], event["start"]["x"], event["start"]["y"], event["start"]["z"]) for event in g52_offset @@ -1079,45 +1097,45 @@ if not any(event["type"] == "program-end" and event["line"] == 7 for event in g5 PY echo "linuxcnc rs274 native smoke passed" -echo "dumped /tmp/cnc_sim_linuxcnc_basic_motion.json" -echo "dumped /tmp/cnc_sim_linuxcnc_basic_mill.json" -echo "dumped /tmp/cnc_sim_linuxcnc_rtcp_controls.json" -echo "dumped /tmp/cnc_sim_linuxcnc_spindle_direction.json" -echo "dumped /tmp/cnc_sim_linuxcnc_spindle_modes.json" -echo "dumped /tmp/cnc_sim_linuxcnc_spindle_orient.json" -echo "dumped /tmp/cnc_sim_linuxcnc_io_controls.json" -echo "dumped /tmp/cnc_sim_linuxcnc_override_controls.json" -echo "dumped /tmp/cnc_sim_linuxcnc_modal_state.json" -echo "dumped /tmp/cnc_sim_linuxcnc_modal_autorestore.json" -echo "dumped /tmp/cnc_sim_linuxcnc_threading_sync.json" -echo "dumped /tmp/cnc_sim_linuxcnc_threading_cycle.json" -echo "dumped /tmp/cnc_sim_linuxcnc_tool_number.json" -echo "dumped /tmp/cnc_sim_linuxcnc_program_stops.json" -echo "dumped /tmp/cnc_sim_linuxcnc_coolant.json" -echo "dumped /tmp/cnc_sim_linuxcnc_feed_modes.json" -echo "dumped /tmp/cnc_sim_linuxcnc_motion_modes.json" -echo "dumped /tmp/cnc_sim_linuxcnc_tool_length.json" -echo "dumped /tmp/cnc_sim_linuxcnc_dynamic_tool_length.json" -echo "dumped /tmp/cnc_sim_linuxcnc_canned_cycle.json" -echo "dumped /tmp/cnc_sim_linuxcnc_canned_cycles_extended.json" -echo "dumped /tmp/cnc_sim_linuxcnc_canned_cycle_planes.json" -echo "dumped /tmp/cnc_sim_linuxcnc_arc_planes.json" -echo "dumped /tmp/cnc_sim_linuxcnc_arc_distance_modes.json" -echo "dumped /tmp/cnc_sim_linuxcnc_predefined_positions.json" -echo "dumped /tmp/cnc_sim_linuxcnc_machine_coordinates.json" -echo "dumped /tmp/cnc_sim_linuxcnc_cutter_comp.json" -echo "dumped /tmp/cnc_sim_linuxcnc_comments.json" -echo "dumped /tmp/cnc_sim_linuxcnc_probe_no_error.json" -echo "dumped /tmp/cnc_sim_linuxcnc_oword_subprogram.json" -echo "dumped /tmp/cnc_sim_linuxcnc_named_oword_subprogram.json" -echo "dumped /tmp/cnc_sim_linuxcnc_oword_control_flow.json" -echo "dumped /tmp/cnc_sim_linuxcnc_named_parameter_exists.json" -echo "dumped /tmp/cnc_sim_linuxcnc_indexed_parameters.json" -echo "dumped /tmp/cnc_sim_linuxcnc_named_parameter_normalization.json" -echo "dumped /tmp/cnc_sim_linuxcnc_ini_named_parameter.json" -echo "dumped /tmp/cnc_sim_linuxcnc_coordinate_offsets.json" -echo "dumped /tmp/cnc_sim_linuxcnc_coordinate_l20.json" -echo "dumped /tmp/cnc_sim_linuxcnc_coordinate_p0.json" -echo "dumped /tmp/cnc_sim_linuxcnc_coordinate_select_all.json" -echo "dumped /tmp/cnc_sim_linuxcnc_g92_restore.json" -echo "dumped /tmp/cnc_sim_linuxcnc_g52_offset.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_basic_motion.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_basic_mill.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_rtcp_controls.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_spindle_direction.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_spindle_modes.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_spindle_orient.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_io_controls.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_override_controls.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_modal_state.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_modal_autorestore.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_threading_sync.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_threading_cycle.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_tool_number.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_program_stops.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_coolant.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_feed_modes.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_motion_modes.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_tool_length.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_dynamic_tool_length.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_canned_cycle.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_canned_cycles_extended.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_canned_cycle_planes.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_arc_planes.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_arc_distance_modes.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_predefined_positions.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_machine_coordinates.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_cutter_comp.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_comments.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_probe_no_error.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_oword_subprogram.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_named_oword_subprogram.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_oword_control_flow.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_named_parameter_exists.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_indexed_parameters.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_named_parameter_normalization.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_ini_named_parameter.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_coordinate_offsets.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_coordinate_l20.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_coordinate_p0.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_coordinate_select_all.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_g92_restore.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_g52_offset.json" diff --git a/test-linuxcnc-source-link.sh b/test-linuxcnc-source-link.sh index 0c65ff6..4ca79c8 100755 --- a/test-linuxcnc-source-link.sh +++ b/test-linuxcnc-source-link.sh @@ -5,11 +5,24 @@ cd "$(dirname "$0")" linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} cxx=${CXX:-g++} -build_dir=${TMPDIR:-/tmp}/cnc_sim_rs274_source_link +build_dir=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_rs274_source_link.XXXXXX") python_includes=$(python3.13-config --includes 2>/dev/null || python3-config --includes) +manifest=${1:-linuxcnc-rs274-source-files.txt} -rm -rf "$build_dir" -mkdir -p "$build_dir" +if [[ ! -f "$manifest" ]]; then + echo "missing manifest: $manifest" >&2 + exit 1 +fi + +if [[ ! -d "$linuxcnc_root" ]]; then + echo "missing LinuxCNC root: $linuxcnc_root" >&2 + exit 1 +fi + +trap 'rm -rf "$build_dir"' EXIT +# The LinuxCNC source-linked dump path is not concurrency-safe under parallel execution. +exec 9>"${TMPDIR:-/tmp}/cnc_sim_rs274_source_link.lock" +flock 9 common_flags=( -std=c++17 @@ -26,13 +39,15 @@ common_flags=( -I "$linuxcnc_root/include" ) -objects=() +linuxcnc_sources=() while IFS=: read -r group path note; do case "$group" in core) - obj="$build_dir/$(basename "$path" .cc).o" - "$cxx" "${common_flags[@]}" -c "$linuxcnc_root/$path" -o "$obj" - objects+=("$obj") + if [[ ! -f "$linuxcnc_root/$path" ]]; then + echo "missing manifest file: $path" >&2 + exit 1 + fi + linuxcnc_sources+=("$path") ;; ""|\#*|binding|tooldata) ;; @@ -41,8 +56,18 @@ while IFS=: read -r group path note; do exit 1 ;; esac -done < linuxcnc-rs274-source-files.txt +done < "$manifest" +objects=() +linuxcnc_index=0 +for source in "${linuxcnc_sources[@]}"; do + obj="$build_dir/linuxcnc_${linuxcnc_index}.o" + "$cxx" "${common_flags[@]}" -c "$linuxcnc_root/$source" -o "$obj" + objects+=("$obj") + linuxcnc_index=$((linuxcnc_index + 1)) +done + +project_index=0 for source in \ core/src/canon_event_sink.cpp \ core/src/linuxcnc_canon_bridge.cpp \ @@ -50,9 +75,10 @@ for source in \ core/src/rtcp_kinematics.cpp \ core/src/simulator_gcode_controls.cpp \ core/tools/linuxcnc_rs274_dump.cpp; do - obj="$build_dir/$(basename "$source" .cpp).o" + obj="$build_dir/project_${project_index}.o" "$cxx" "${common_flags[@]}" -c "$source" -o "$obj" objects+=("$obj") + project_index=$((project_index + 1)) done support_objects=( @@ -95,397 +121,648 @@ done var_file="$build_dir/rs274ngc-source.var" base_var_file="$build_dir/rs274ngc-source-base.var" ini_named_parameter_file="$(pwd)/tests/gcode/linuxcnc_ini_named_parameter.ini" +output_dir="$build_dir/outputs" +mkdir -p "$output_dir" cp "$linuxcnc_root/tests/halui/jogging/sim.var" "$base_var_file" cp "$base_var_file" "$var_file" CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/basic_mill.ngc \ - >/tmp/cnc_sim_linuxcnc_source_basic_mill.json + >"$output_dir/cnc_sim_linuxcnc_source_basic_mill.json" +CNC_SIM_RS274_VAR="$var_file" \ + "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_block_delete.ngc \ + >"$output_dir/cnc_sim_linuxcnc_source_block_delete_off.json" +CNC_SIM_BLOCK_DELETE=1 CNC_SIM_RS274_VAR="$var_file" \ + "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_block_delete.ngc \ + >"$output_dir/cnc_sim_linuxcnc_source_block_delete_on.json" CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_rtcp_controls.ngc \ - >/tmp/cnc_sim_linuxcnc_source_rtcp_controls.json + >"$output_dir/cnc_sim_linuxcnc_source_rtcp_controls.json" CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_spindle_direction.ngc \ - >/tmp/cnc_sim_linuxcnc_source_spindle_direction.json + >"$output_dir/cnc_sim_linuxcnc_source_spindle_direction.json" CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_spindle_modes.ngc \ - >/tmp/cnc_sim_linuxcnc_source_spindle_modes.json + >"$output_dir/cnc_sim_linuxcnc_source_spindle_modes.json" CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_spindle_orient.ngc \ - >/tmp/cnc_sim_linuxcnc_source_spindle_orient.json + >"$output_dir/cnc_sim_linuxcnc_source_spindle_orient.json" CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_io_controls.ngc \ - >/tmp/cnc_sim_linuxcnc_source_io_controls.json + >"$output_dir/cnc_sim_linuxcnc_source_io_controls.json" CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_override_controls.ngc \ - >/tmp/cnc_sim_linuxcnc_source_override_controls.json + >"$output_dir/cnc_sim_linuxcnc_source_override_controls.json" CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_modal_state.ngc \ - >/tmp/cnc_sim_linuxcnc_source_modal_state.json + >"$output_dir/cnc_sim_linuxcnc_source_modal_state.json" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_modal_autorestore.ngc \ - >/tmp/cnc_sim_linuxcnc_source_modal_autorestore.json + >"$output_dir/cnc_sim_linuxcnc_source_modal_autorestore.json" if CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_modal_invalidate.ngc \ - >/tmp/cnc_sim_linuxcnc_source_modal_invalidate.json 2>/tmp/cnc_sim_linuxcnc_source_modal_invalidate.err; then + >"$output_dir/cnc_sim_linuxcnc_source_modal_invalidate.json" 2>"$output_dir/cnc_sim_linuxcnc_source_modal_invalidate.err"; then echo "expected linuxcnc_modal_invalidate.ngc to fail" >&2 exit 1 fi CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_threading_sync.ngc \ - >/tmp/cnc_sim_linuxcnc_source_threading_sync.json + >"$output_dir/cnc_sim_linuxcnc_source_threading_sync.json" CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_threading_cycle.ngc \ - >/tmp/cnc_sim_linuxcnc_source_threading_cycle.json + >"$output_dir/cnc_sim_linuxcnc_source_threading_cycle.json" CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_tool_number.ngc \ - >/tmp/cnc_sim_linuxcnc_source_tool_number.json + >"$output_dir/cnc_sim_linuxcnc_source_tool_number.json" CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_program_stops.ngc \ - >/tmp/cnc_sim_linuxcnc_source_program_stops.json + >"$output_dir/cnc_sim_linuxcnc_source_program_stops.json" CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_coolant.ngc \ - >/tmp/cnc_sim_linuxcnc_source_coolant.json + >"$output_dir/cnc_sim_linuxcnc_source_coolant.json" CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_feed_modes.ngc \ - >/tmp/cnc_sim_linuxcnc_source_feed_modes.json + >"$output_dir/cnc_sim_linuxcnc_source_feed_modes.json" CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_motion_modes.ngc \ - >/tmp/cnc_sim_linuxcnc_source_motion_modes.json + >"$output_dir/cnc_sim_linuxcnc_source_motion_modes.json" CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_tool_length.ngc \ - >/tmp/cnc_sim_linuxcnc_source_tool_length.json + >"$output_dir/cnc_sim_linuxcnc_source_tool_length.json" CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_dynamic_tool_length.ngc \ - >/tmp/cnc_sim_linuxcnc_source_dynamic_tool_length.json + >"$output_dir/cnc_sim_linuxcnc_source_dynamic_tool_length.json" CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_canned_cycle.ngc \ - >/tmp/cnc_sim_linuxcnc_source_canned_cycle.json + >"$output_dir/cnc_sim_linuxcnc_source_canned_cycle.json" +if CNC_SIM_RS274_VAR="$var_file" \ + "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_canned_cycle_l0_error.ngc \ + >"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_l0_error.json" \ + 2>"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_l0_error.err"; then + echo "expected linuxcnc_canned_cycle_l0_error.ngc to fail" >&2 + exit 1 +fi +grep -q "Cannot do zero repeats of cycle" "$output_dir/cnc_sim_linuxcnc_source_canned_cycle_l0_error.err" +if CNC_SIM_RS274_VAR="$var_file" \ + "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_canned_cycle_missing_r_error.ngc \ + >"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_missing_r_error.json" \ + 2>"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_missing_r_error.err"; then + echo "expected linuxcnc_canned_cycle_missing_r_error.ngc to fail" >&2 + exit 1 +fi +grep -q "R clearance plane unspecified in cycle" "$output_dir/cnc_sim_linuxcnc_source_canned_cycle_missing_r_error.err" +if CNC_SIM_RS274_VAR="$var_file" \ + "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_canned_cycle_missing_z_error.ngc \ + >"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_missing_z_error.json" \ + 2>"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_missing_z_error.err"; then + echo "expected linuxcnc_canned_cycle_missing_z_error.ngc to fail" >&2 + exit 1 +fi +grep -q "Z value unspecified in xy plane canned cycle" "$output_dir/cnc_sim_linuxcnc_source_canned_cycle_missing_z_error.err" +if CNC_SIM_RS274_VAR="$var_file" \ + "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_canned_cycle_g18_missing_y_error.ngc \ + >"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g18_missing_y_error.json" \ + 2>"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g18_missing_y_error.err"; then + echo "expected linuxcnc_canned_cycle_g18_missing_y_error.ngc to fail" >&2 + exit 1 +fi +grep -q "Y value unspecified in xz plane canned cycle" "$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g18_missing_y_error.err" +if CNC_SIM_RS274_VAR="$var_file" \ + "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_canned_cycle_g19_missing_x_error.ngc \ + >"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g19_missing_x_error.json" \ + 2>"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g19_missing_x_error.err"; then + echo "expected linuxcnc_canned_cycle_g19_missing_x_error.ngc to fail" >&2 + exit 1 +fi +grep -q "X value unspecified in yz plane canned cycle" "$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g19_missing_x_error.err" +if CNC_SIM_RS274_VAR="$var_file" \ + "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_canned_cycle_r_less_z_error.ngc \ + >"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_r_less_z_error.json" \ + 2>"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_r_less_z_error.err"; then + echo "expected linuxcnc_canned_cycle_r_less_z_error.ngc to fail" >&2 + exit 1 +fi +grep -q "R less than z in cycle in xy plane" "$output_dir/cnc_sim_linuxcnc_source_canned_cycle_r_less_z_error.err" +if CNC_SIM_RS274_VAR="$var_file" \ + "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_canned_cycle_g18_r_less_y_error.ngc \ + >"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g18_r_less_y_error.json" \ + 2>"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g18_r_less_y_error.err"; then + echo "expected linuxcnc_canned_cycle_g18_r_less_y_error.ngc to fail" >&2 + exit 1 +fi +grep -q "R less than y in cycle in xz plane" "$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g18_r_less_y_error.err" +if CNC_SIM_RS274_VAR="$var_file" \ + "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_canned_cycle_g19_r_less_x_error.ngc \ + >"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g19_r_less_x_error.json" \ + 2>"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g19_r_less_x_error.err"; then + echo "expected linuxcnc_canned_cycle_g19_r_less_x_error.ngc to fail" >&2 + exit 1 +fi +grep -q "R less than x in cycle in yz plane" "$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g19_r_less_x_error.err" +if CNC_SIM_RS274_VAR="$var_file" \ + "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_canned_cycle_u_in_xy_error.ngc \ + >"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_u_in_xy_error.json" \ + 2>"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_u_in_xy_error.err"; then + echo "expected linuxcnc_canned_cycle_u_in_xy_error.ngc to fail" >&2 + exit 1 +fi +grep -q "Cannot put a U in a canned cycle in the XY plane" "$output_dir/cnc_sim_linuxcnc_source_canned_cycle_u_in_xy_error.err" +if CNC_SIM_RS274_VAR="$var_file" \ + "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_canned_cycle_a_error.ngc \ + >"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_a_error.json" \ + 2>"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_a_error.err"; then + echo "expected linuxcnc_canned_cycle_a_error.ngc to fail" >&2 + exit 1 +fi +grep -q "Cannot put an a in canned cycle" "$output_dir/cnc_sim_linuxcnc_source_canned_cycle_a_error.err" +if CNC_SIM_RS274_VAR="$var_file" \ + "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_canned_cycle_b_error.ngc \ + >"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_b_error.json" \ + 2>"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_b_error.err"; then + echo "expected linuxcnc_canned_cycle_b_error.ngc to fail" >&2 + exit 1 +fi +grep -q "Cannot put a b in canned cycle" "$output_dir/cnc_sim_linuxcnc_source_canned_cycle_b_error.err" +if CNC_SIM_RS274_VAR="$var_file" \ + "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_canned_cycle_c_error.ngc \ + >"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_c_error.json" \ + 2>"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_c_error.err"; then + echo "expected linuxcnc_canned_cycle_c_error.ngc to fail" >&2 + exit 1 +fi +grep -q "Cannot put a c in canned cycle" "$output_dir/cnc_sim_linuxcnc_source_canned_cycle_c_error.err" +if CNC_SIM_RS274_VAR="$var_file" \ + "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_canned_cycle_g73_missing_q_error.ngc \ + >"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g73_missing_q_error.json" \ + 2>"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g73_missing_q_error.err"; then + echo "expected linuxcnc_canned_cycle_g73_missing_q_error.ngc to fail" >&2 + exit 1 +fi +grep -q "Q word missing with g73" "$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g73_missing_q_error.err" +if CNC_SIM_RS274_VAR="$var_file" \ + "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_canned_cycle_g83_missing_q_error.ngc \ + >"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g83_missing_q_error.json" \ + 2>"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g83_missing_q_error.err"; then + echo "expected linuxcnc_canned_cycle_g83_missing_q_error.ngc to fail" >&2 + exit 1 +fi +grep -q "Q word missing with g83" "$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g83_missing_q_error.err" +if CNC_SIM_RS274_VAR="$var_file" \ + "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_canned_cycle_g73_q0_error.ngc \ + >"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g73_q0_error.json" \ + 2>"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g73_q0_error.err"; then + echo "expected linuxcnc_canned_cycle_g73_q0_error.ngc to fail" >&2 + exit 1 +fi +grep -q "Negative or zero q value used" "$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g73_q0_error.err" +if CNC_SIM_RS274_VAR="$var_file" \ + "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_canned_cycle_g83_q0_error.ngc \ + >"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g83_q0_error.json" \ + 2>"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g83_q0_error.err"; then + echo "expected linuxcnc_canned_cycle_g83_q0_error.ngc to fail" >&2 + exit 1 +fi +grep -q "Negative or zero q value used" "$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g83_q0_error.err" +if CNC_SIM_RS274_VAR="$var_file" \ + "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_canned_cycle_g82_missing_p_error.ngc \ + >"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g82_missing_p_error.json" \ + 2>"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g82_missing_p_error.err"; then + echo "expected linuxcnc_canned_cycle_g82_missing_p_error.ngc to fail" >&2 + exit 1 +fi +grep -q "Dwell time p word missing with g82" "$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g82_missing_p_error.err" +if CNC_SIM_RS274_VAR="$var_file" \ + "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_canned_cycle_g86_missing_p_error.ngc \ + >"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g86_missing_p_error.json" \ + 2>"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g86_missing_p_error.err"; then + echo "expected linuxcnc_canned_cycle_g86_missing_p_error.ngc to fail" >&2 + exit 1 +fi +grep -q "Dwell time p word missing with g86" "$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g86_missing_p_error.err" +if CNC_SIM_RS274_VAR="$var_file" \ + "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_canned_cycle_g88_missing_p_error.ngc \ + >"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g88_missing_p_error.json" \ + 2>"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g88_missing_p_error.err"; then + echo "expected linuxcnc_canned_cycle_g88_missing_p_error.ngc to fail" >&2 + exit 1 +fi +grep -q "Dwell time p word missing with g88" "$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g88_missing_p_error.err" +if CNC_SIM_RS274_VAR="$var_file" \ + "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_canned_cycle_g89_missing_p_error.ngc \ + >"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g89_missing_p_error.json" \ + 2>"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g89_missing_p_error.err"; then + echo "expected linuxcnc_canned_cycle_g89_missing_p_error.ngc to fail" >&2 + exit 1 +fi +grep -q "Dwell time p word missing with g89" "$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g89_missing_p_error.err" +if CNC_SIM_RS274_VAR="$var_file" \ + "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_canned_cycle_g74_clockwise_error.ngc \ + >"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g74_clockwise_error.json" \ + 2>"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g74_clockwise_error.err"; then + echo "expected linuxcnc_canned_cycle_g74_clockwise_error.ngc to fail" >&2 + exit 1 +fi +grep -q "Spindle turning clockwise in G74" "$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g74_clockwise_error.err" +if CNC_SIM_RS274_VAR="$var_file" \ + "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_canned_cycle_g84_counterclockwise_error.ngc \ + >"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g84_counterclockwise_error.json" \ + 2>"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g84_counterclockwise_error.err"; then + echo "expected linuxcnc_canned_cycle_g84_counterclockwise_error.ngc to fail" >&2 + exit 1 +fi +grep -q "Spindle turning counterclockwise in G84" "$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g84_counterclockwise_error.err" +if CNC_SIM_RS274_VAR="$var_file" \ + "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_canned_cycle_g74_stopped_spindle_error.ngc \ + >"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g74_stopped_spindle_error.json" \ + 2>"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g74_stopped_spindle_error.err"; then + echo "expected linuxcnc_canned_cycle_g74_stopped_spindle_error.ngc to fail" >&2 + exit 1 +fi +grep -q "Spindle not turning in G74" "$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g74_stopped_spindle_error.err" +if CNC_SIM_RS274_VAR="$var_file" \ + "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_canned_cycle_g84_stopped_spindle_error.ngc \ + >"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g84_stopped_spindle_error.json" \ + 2>"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g84_stopped_spindle_error.err"; then + echo "expected linuxcnc_canned_cycle_g84_stopped_spindle_error.ngc to fail" >&2 + exit 1 +fi +grep -q "Spindle not turning in G84" "$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g84_stopped_spindle_error.err" +if CNC_SIM_RS274_VAR="$var_file" \ + "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_canned_cycle_g86_stopped_spindle_error.ngc \ + >"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g86_stopped_spindle_error.json" \ + 2>"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g86_stopped_spindle_error.err"; then + echo "expected linuxcnc_canned_cycle_g86_stopped_spindle_error.ngc to fail" >&2 + exit 1 +fi +grep -q "Spindle not turning in g86" "$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g86_stopped_spindle_error.err" +if CNC_SIM_RS274_VAR="$var_file" \ + "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_canned_cycle_g88_stopped_spindle_error.ngc \ + >"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g88_stopped_spindle_error.json" \ + 2>"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g88_stopped_spindle_error.err"; then + echo "expected linuxcnc_canned_cycle_g88_stopped_spindle_error.ngc to fail" >&2 + exit 1 +fi +grep -q "Spindle not turning in g88" "$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g88_stopped_spindle_error.err" +if CNC_SIM_RS274_VAR="$var_file" \ + "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_canned_cycle_g87_stopped_spindle_error.ngc \ + >"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g87_stopped_spindle_error.json" \ + 2>"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g87_stopped_spindle_error.err"; then + echo "expected linuxcnc_canned_cycle_g87_stopped_spindle_error.ngc to fail" >&2 + exit 1 +fi +grep -q "Spindle not turning in g87" "$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g87_stopped_spindle_error.err" +if CNC_SIM_RS274_VAR="$var_file" \ + "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_canned_cycle_g87_missing_k_error.ngc \ + >"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g87_missing_k_error.json" \ + 2>"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g87_missing_k_error.err"; then + echo "expected linuxcnc_canned_cycle_g87_missing_k_error.ngc to fail" >&2 + exit 1 +fi +grep -q "K word missing with g87" "$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g87_missing_k_error.err" +if CNC_SIM_RS274_VAR="$var_file" \ + "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_canned_cycle_g87_missing_i_error.ngc \ + >"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g87_missing_i_error.json" \ + 2>"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g87_missing_i_error.err"; then + echo "expected linuxcnc_canned_cycle_g87_missing_i_error.ngc to fail" >&2 + exit 1 +fi +grep -q "I word missing with g87" "$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g87_missing_i_error.err" +if CNC_SIM_RS274_VAR="$var_file" \ + "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_canned_cycle_g87_missing_j_error.ngc \ + >"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g87_missing_j_error.json" \ + 2>"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g87_missing_j_error.err"; then + echo "expected linuxcnc_canned_cycle_g87_missing_j_error.ngc to fail" >&2 + exit 1 +fi +grep -q "J word missing with g87" "$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g87_missing_j_error.err" CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_canned_cycles_extended.ngc \ - >/tmp/cnc_sim_linuxcnc_source_canned_cycles_extended.json + >"$output_dir/cnc_sim_linuxcnc_source_canned_cycles_extended.json" CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_canned_cycle_planes.ngc \ - >/tmp/cnc_sim_linuxcnc_source_canned_cycle_planes.json + >"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_planes.json" CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_extended_plane_cycle.ngc \ - >/tmp/cnc_sim_linuxcnc_source_extended_plane_cycle.json + >"$output_dir/cnc_sim_linuxcnc_source_extended_plane_cycle.json" CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_arc_planes.ngc \ - >/tmp/cnc_sim_linuxcnc_source_arc_planes.json + >"$output_dir/cnc_sim_linuxcnc_source_arc_planes.json" CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_arc_distance_modes.ngc \ - >/tmp/cnc_sim_linuxcnc_source_arc_distance_modes.json + >"$output_dir/cnc_sim_linuxcnc_source_arc_distance_modes.json" CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_predefined_positions.ngc \ - >/tmp/cnc_sim_linuxcnc_source_predefined_positions.json + >"$output_dir/cnc_sim_linuxcnc_source_predefined_positions.json" CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_machine_coordinates.ngc \ - >/tmp/cnc_sim_linuxcnc_source_machine_coordinates.json + >"$output_dir/cnc_sim_linuxcnc_source_machine_coordinates.json" CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_cutter_comp.ngc \ - >/tmp/cnc_sim_linuxcnc_source_cutter_comp.json + >"$output_dir/cnc_sim_linuxcnc_source_cutter_comp.json" CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_probe_no_error.ngc \ - >/tmp/cnc_sim_linuxcnc_source_probe_no_error.json + >"$output_dir/cnc_sim_linuxcnc_source_probe_no_error.json" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/smoke_oword_subprogram.ngc \ - >/tmp/cnc_sim_linuxcnc_source_oword_subprogram.json + >"$output_dir/cnc_sim_linuxcnc_source_oword_subprogram.json" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_named_oword_subprogram.ngc \ - >/tmp/cnc_sim_linuxcnc_source_named_oword_subprogram.json + >"$output_dir/cnc_sim_linuxcnc_source_named_oword_subprogram.json" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_oword_control_flow.ngc \ - >/tmp/cnc_sim_linuxcnc_source_oword_control_flow.json + >"$output_dir/cnc_sim_linuxcnc_source_oword_control_flow.json" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_named_parameter_exists.ngc \ - >/tmp/cnc_sim_linuxcnc_source_named_parameter_exists.json + >"$output_dir/cnc_sim_linuxcnc_source_named_parameter_exists.json" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_indexed_parameters.ngc \ - >/tmp/cnc_sim_linuxcnc_source_indexed_parameters.json + >"$output_dir/cnc_sim_linuxcnc_source_indexed_parameters.json" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_named_parameter_normalization.ngc \ - >/tmp/cnc_sim_linuxcnc_source_named_parameter_normalization.json + >"$output_dir/cnc_sim_linuxcnc_source_named_parameter_normalization.json" INI_FILE_NAME="$ini_named_parameter_file" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_ini_named_parameter.ngc \ - >/tmp/cnc_sim_linuxcnc_source_ini_named_parameter.json + >"$output_dir/cnc_sim_linuxcnc_source_ini_named_parameter.json" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_readonly_named_parameters_extra.ngc \ - >/tmp/cnc_sim_linuxcnc_source_readonly_named_parameters_extra.json + >"$output_dir/cnc_sim_linuxcnc_source_readonly_named_parameters_extra.json" if CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_readonly_named_parameter_assign_error.ngc \ - >/tmp/cnc_sim_linuxcnc_source_readonly_named_parameter_assign_error.json 2>/tmp/cnc_sim_linuxcnc_source_readonly_named_parameter_assign_error.err; then + >"$output_dir/cnc_sim_linuxcnc_source_readonly_named_parameter_assign_error.json" 2>"$output_dir/cnc_sim_linuxcnc_source_readonly_named_parameter_assign_error.err"; then echo "expected linuxcnc_readonly_named_parameter_assign_error.ngc to fail" >&2 exit 1 fi if CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_negative_sqrt_error.ngc \ - >/tmp/cnc_sim_linuxcnc_source_negative_sqrt_error.json 2>/tmp/cnc_sim_linuxcnc_source_negative_sqrt_error.err; then + >"$output_dir/cnc_sim_linuxcnc_source_negative_sqrt_error.json" 2>"$output_dir/cnc_sim_linuxcnc_source_negative_sqrt_error.err"; then echo "expected linuxcnc_negative_sqrt_error.ngc to fail" >&2 exit 1 fi if CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_infinite_expression_error.ngc \ - >/tmp/cnc_sim_linuxcnc_source_infinite_expression_error.json 2>/tmp/cnc_sim_linuxcnc_source_infinite_expression_error.err; then + >"$output_dir/cnc_sim_linuxcnc_source_infinite_expression_error.json" 2>"$output_dir/cnc_sim_linuxcnc_source_infinite_expression_error.err"; then echo "expected linuxcnc_infinite_expression_error.ngc to fail" >&2 exit 1 fi CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_value_returned.ngc \ - >/tmp/cnc_sim_linuxcnc_source_value_returned.json + >"$output_dir/cnc_sim_linuxcnc_source_value_returned.json" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_call_level.ngc \ - >/tmp/cnc_sim_linuxcnc_source_call_level.json + >"$output_dir/cnc_sim_linuxcnc_source_call_level.json" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_exists_word_value.ngc \ - >/tmp/cnc_sim_linuxcnc_source_exists_word_value.json + >"$output_dir/cnc_sim_linuxcnc_source_exists_word_value.json" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_lowercase_numeric_oword.ngc \ - >/tmp/cnc_sim_linuxcnc_source_lowercase_numeric_oword.json + >"$output_dir/cnc_sim_linuxcnc_source_lowercase_numeric_oword.json" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_parameter_expression_assignment.ngc \ - >/tmp/cnc_sim_linuxcnc_source_parameter_expression_assignment.json + >"$output_dir/cnc_sim_linuxcnc_source_parameter_expression_assignment.json" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_word_bracket_whitespace.ngc \ - >/tmp/cnc_sim_linuxcnc_source_word_bracket_whitespace.json + >"$output_dir/cnc_sim_linuxcnc_source_word_bracket_whitespace.json" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_m98_mixed_styles_variables.ngc \ - >/tmp/cnc_sim_linuxcnc_source_m98_mixed_styles_variables.json + >"$output_dir/cnc_sim_linuxcnc_source_m98_mixed_styles_variables.json" if CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_numeric_oword_main_eof_error.ngc \ - >/tmp/cnc_sim_linuxcnc_source_numeric_oword_main_eof_error.json 2>/tmp/cnc_sim_linuxcnc_source_numeric_oword_main_eof_error.err; then + >"$output_dir/cnc_sim_linuxcnc_source_numeric_oword_main_eof_error.json" 2>"$output_dir/cnc_sim_linuxcnc_source_numeric_oword_main_eof_error.err"; then echo "expected linuxcnc_numeric_oword_main_eof_error.ngc to fail" >&2 exit 1 fi if CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_numeric_oword_sub_illegal_location.ngc \ - >/tmp/cnc_sim_linuxcnc_source_numeric_oword_sub_illegal_location.json 2>/tmp/cnc_sim_linuxcnc_source_numeric_oword_sub_illegal_location.err; then + >"$output_dir/cnc_sim_linuxcnc_source_numeric_oword_sub_illegal_location.json" 2>"$output_dir/cnc_sim_linuxcnc_source_numeric_oword_sub_illegal_location.err"; then echo "expected linuxcnc_numeric_oword_sub_illegal_location.ngc to fail" >&2 exit 1 fi if CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_canned_cycle_reject_rotary_followup.ngc \ - >/tmp/cnc_sim_linuxcnc_source_canned_cycle_reject_rotary_followup.json 2>/tmp/cnc_sim_linuxcnc_source_canned_cycle_reject_rotary_followup.err; then + >"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_reject_rotary_followup.json" 2>"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_reject_rotary_followup.err"; then echo "expected linuxcnc_canned_cycle_reject_rotary_followup.ngc to fail" >&2 exit 1 fi if CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_nested_subroutine_definition_error.ngc \ - >/tmp/cnc_sim_linuxcnc_source_nested_subroutine_definition_error.json 2>/tmp/cnc_sim_linuxcnc_source_nested_subroutine_definition_error.err; then + >"$output_dir/cnc_sim_linuxcnc_source_nested_subroutine_definition_error.json" 2>"$output_dir/cnc_sim_linuxcnc_source_nested_subroutine_definition_error.err"; then echo "expected linuxcnc_nested_subroutine_definition_error.ngc to fail" >&2 exit 1 fi if CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_ccomp_arcexit_error.ngc \ - >/tmp/cnc_sim_linuxcnc_source_ccomp_arcexit_error.json 2>/tmp/cnc_sim_linuxcnc_source_ccomp_arcexit_error.err; then + >"$output_dir/cnc_sim_linuxcnc_source_ccomp_arcexit_error.json" 2>"$output_dir/cnc_sim_linuxcnc_source_ccomp_arcexit_error.err"; then echo "expected linuxcnc_ccomp_arcexit_error.ngc to fail" >&2 exit 1 fi if CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_abort_hot_comment.ngc \ - >/tmp/cnc_sim_linuxcnc_source_abort_hot_comment.json 2>/tmp/cnc_sim_linuxcnc_source_abort_hot_comment.err; then + >"$output_dir/cnc_sim_linuxcnc_source_abort_hot_comment.json" 2>"$output_dir/cnc_sim_linuxcnc_source_abort_hot_comment.err"; then echo "expected linuxcnc_abort_hot_comment.ngc to fail" >&2 exit 1 fi if CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_ccomp_gouging_error.ngc \ - >/tmp/cnc_sim_linuxcnc_source_ccomp_gouging_error.json 2>/tmp/cnc_sim_linuxcnc_source_ccomp_gouging_error.err; then + >"$output_dir/cnc_sim_linuxcnc_source_ccomp_gouging_error.json" 2>"$output_dir/cnc_sim_linuxcnc_source_ccomp_gouging_error.err"; then echo "expected linuxcnc_ccomp_gouging_error.ngc to fail" >&2 exit 1 fi cp "$base_var_file" "$var_file" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_numbered_parameters.ngc \ - >/tmp/cnc_sim_linuxcnc_source_numbered_parameters.json + >"$output_dir/cnc_sim_linuxcnc_source_numbered_parameters.json" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_rotary_probe_numbered_parameters.ngc \ - >/tmp/cnc_sim_linuxcnc_source_rotary_probe_numbered_parameters.json + >"$output_dir/cnc_sim_linuxcnc_source_rotary_probe_numbered_parameters.json" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_multi_rotary_probe_numbered_parameters.ngc \ - >/tmp/cnc_sim_linuxcnc_source_multi_rotary_probe_numbered_parameters.json + >"$output_dir/cnc_sim_linuxcnc_source_multi_rotary_probe_numbered_parameters.json" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_tool_info_zero_parameter.ngc \ - >/tmp/cnc_sim_linuxcnc_source_tool_info_zero_parameter.json + >"$output_dir/cnc_sim_linuxcnc_source_tool_info_zero_parameter.json" CNC_SIM_TOOL_IN_SPINDLE=1 CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_spindle_tool_info_parameter.ngc \ - >/tmp/cnc_sim_linuxcnc_source_spindle_tool_info_parameter.json + >"$output_dir/cnc_sim_linuxcnc_source_spindle_tool_info_parameter.json" if CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_readonly_numbered_parameter_assign_error.ngc \ - >/tmp/cnc_sim_linuxcnc_source_readonly_numbered_parameter_assign_error.json 2>/tmp/cnc_sim_linuxcnc_source_readonly_numbered_parameter_assign_error.err; then + >"$output_dir/cnc_sim_linuxcnc_source_readonly_numbered_parameter_assign_error.json" 2>"$output_dir/cnc_sim_linuxcnc_source_readonly_numbered_parameter_assign_error.err"; then echo "expected linuxcnc_readonly_numbered_parameter_assign_error.ngc to fail" >&2 exit 1 fi CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_g28_g30_numbered_parameters.ngc \ - >/tmp/cnc_sim_linuxcnc_source_g28_g30_numbered_parameters.json + >"$output_dir/cnc_sim_linuxcnc_source_g28_g30_numbered_parameters.json" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_rotary_g28_g30_numbered_parameters.ngc \ - >/tmp/cnc_sim_linuxcnc_source_rotary_g28_g30_numbered_parameters.json + >"$output_dir/cnc_sim_linuxcnc_source_rotary_g28_g30_numbered_parameters.json" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_g92_numbered_parameters.ngc \ - >/tmp/cnc_sim_linuxcnc_source_g92_numbered_parameters.json + >"$output_dir/cnc_sim_linuxcnc_source_g92_numbered_parameters.json" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_rotary_g92_numbered_parameters.ngc \ - >/tmp/cnc_sim_linuxcnc_source_rotary_g92_numbered_parameters.json + >"$output_dir/cnc_sim_linuxcnc_source_rotary_g92_numbered_parameters.json" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_g52_g92_numbered_interaction.ngc \ - >/tmp/cnc_sim_linuxcnc_source_g52_g92_numbered_interaction.json + >"$output_dir/cnc_sim_linuxcnc_source_g52_g92_numbered_interaction.json" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_g5x_numbered_parameters.ngc \ - >/tmp/cnc_sim_linuxcnc_source_g5x_numbered_parameters.json + >"$output_dir/cnc_sim_linuxcnc_source_g5x_numbered_parameters.json" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_g59_3_numbered_parameters.ngc \ - >/tmp/cnc_sim_linuxcnc_source_g59_3_numbered_parameters.json + >"$output_dir/cnc_sim_linuxcnc_source_g59_3_numbered_parameters.json" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_g59_3_rotary_numbered_parameters.ngc \ - >/tmp/cnc_sim_linuxcnc_source_g59_3_rotary_numbered_parameters.json + >"$output_dir/cnc_sim_linuxcnc_source_g59_3_rotary_numbered_parameters.json" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_g5x_rotary_numbered_parameters.ngc \ - >/tmp/cnc_sim_linuxcnc_source_g5x_rotary_numbered_parameters.json + >"$output_dir/cnc_sim_linuxcnc_source_g5x_rotary_numbered_parameters.json" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_g54_rotary_numbered_parameters.ngc \ - >/tmp/cnc_sim_linuxcnc_source_g54_rotary_numbered_parameters.json + >"$output_dir/cnc_sim_linuxcnc_source_g54_rotary_numbered_parameters.json" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_debug_level_parameter.ngc \ - >/tmp/cnc_sim_linuxcnc_source_debug_level_parameter.json + >"$output_dir/cnc_sim_linuxcnc_source_debug_level_parameter.json" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_m66_result_parameter.ngc \ - >/tmp/cnc_sim_linuxcnc_source_m66_result_parameter.json + >"$output_dir/cnc_sim_linuxcnc_source_m66_result_parameter.json" cp "$base_var_file" "$var_file" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_tool_offset_numbered_parameters.ngc \ - >/tmp/cnc_sim_linuxcnc_source_tool_offset_numbered_parameters.json + >"$output_dir/cnc_sim_linuxcnc_source_tool_offset_numbered_parameters.json" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_rotary_tool_offset_numbered_parameters.ngc \ - >/tmp/cnc_sim_linuxcnc_source_rotary_tool_offset_numbered_parameters.json + >"$output_dir/cnc_sim_linuxcnc_source_rotary_tool_offset_numbered_parameters.json" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_current_position_numbered_parameters.ngc \ - >/tmp/cnc_sim_linuxcnc_source_current_position_numbered_parameters.json + >"$output_dir/cnc_sim_linuxcnc_source_current_position_numbered_parameters.json" cp "$base_var_file" "$var_file" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_persistent_parameters_set.ngc \ - >/tmp/cnc_sim_linuxcnc_source_persistent_parameters_set.json + >"$output_dir/cnc_sim_linuxcnc_source_persistent_parameters_set.json" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_persistent_parameters_check.ngc \ - >/tmp/cnc_sim_linuxcnc_source_persistent_parameters_check.json + >"$output_dir/cnc_sim_linuxcnc_source_persistent_parameters_check.json" rotary_persistent_var_file="$build_dir/rs274ngc-rotary-persistent.var" : > "$rotary_persistent_var_file" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$rotary_persistent_var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_rotary_persistent_parameters_set.ngc \ - >/tmp/cnc_sim_linuxcnc_source_rotary_persistent_parameters_set.json + >"$output_dir/cnc_sim_linuxcnc_source_rotary_persistent_parameters_set.json" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$rotary_persistent_var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_rotary_persistent_parameters_check.ngc \ - >/tmp/cnc_sim_linuxcnc_source_rotary_persistent_parameters_check.json + >"$output_dir/cnc_sim_linuxcnc_source_rotary_persistent_parameters_check.json" g54_persistent_var_file="$build_dir/rs274ngc-g54-persistent.var" : > "$g54_persistent_var_file" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$g54_persistent_var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_g54_persistent_parameters_set.ngc \ - >/tmp/cnc_sim_linuxcnc_source_g54_persistent_parameters_set.json + >"$output_dir/cnc_sim_linuxcnc_source_g54_persistent_parameters_set.json" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$g54_persistent_var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_g54_persistent_parameters_check.ngc \ - >/tmp/cnc_sim_linuxcnc_source_g54_persistent_parameters_check.json + >"$output_dir/cnc_sim_linuxcnc_source_g54_persistent_parameters_check.json" empty_var_file="$build_dir/rs274ngc-empty.var" : > "$empty_var_file" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$empty_var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_user_persistent_parameter_set.ngc \ - >/tmp/cnc_sim_linuxcnc_source_user_persistent_parameter_set.json + >"$output_dir/cnc_sim_linuxcnc_source_user_persistent_parameter_set.json" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$empty_var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_user_persistent_parameter_check.ngc \ - >/tmp/cnc_sim_linuxcnc_source_user_persistent_parameter_check.json + >"$output_dir/cnc_sim_linuxcnc_source_user_persistent_parameter_check.json" existing_user_var_file="$build_dir/rs274ngc-existing-user.var" printf '5001\t17.000000\n' > "$existing_user_var_file" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$existing_user_var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_user_persistent_parameter_set.ngc \ - >/tmp/cnc_sim_linuxcnc_source_user_existing_persistent_parameter_set.json + >"$output_dir/cnc_sim_linuxcnc_source_user_existing_persistent_parameter_set.json" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$existing_user_var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_user_persistent_parameter_existing_check.ngc \ - >/tmp/cnc_sim_linuxcnc_source_user_existing_persistent_parameter_check.json + >"$output_dir/cnc_sim_linuxcnc_source_user_existing_persistent_parameter_check.json" boundary_user_var_file="$build_dir/rs274ngc-boundary-user.var" printf '5001\t17.000000\n5030\t18.000000\n5060\t19.000000\n' > "$boundary_user_var_file" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$boundary_user_var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_user_persistent_parameter_boundary_set.ngc \ - >/tmp/cnc_sim_linuxcnc_source_user_persistent_parameter_boundary_set.json + >"$output_dir/cnc_sim_linuxcnc_source_user_persistent_parameter_boundary_set.json" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$boundary_user_var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_user_persistent_parameter_boundary_check.ngc \ - >/tmp/cnc_sim_linuxcnc_source_user_persistent_parameter_boundary_check.json + >"$output_dir/cnc_sim_linuxcnc_source_user_persistent_parameter_boundary_check.json" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_volatile_global_parameter_set.ngc \ - >/tmp/cnc_sim_linuxcnc_source_volatile_global_parameter_set.json + >"$output_dir/cnc_sim_linuxcnc_source_volatile_global_parameter_set.json" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_volatile_global_parameter_check.ngc \ - >/tmp/cnc_sim_linuxcnc_source_volatile_global_parameter_check.json + >"$output_dir/cnc_sim_linuxcnc_source_volatile_global_parameter_check.json" CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_arc_center_tolerance_good_imperial.ngc \ - >/tmp/cnc_sim_linuxcnc_source_arc_center_tolerance_good_imperial.json + >"$output_dir/cnc_sim_linuxcnc_source_arc_center_tolerance_good_imperial.json" if CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_arc_center_tolerance_bad_metric.ngc \ - >/tmp/cnc_sim_linuxcnc_source_arc_center_tolerance_bad_metric.json 2>/tmp/cnc_sim_linuxcnc_source_arc_center_tolerance_bad_metric.err; then + >"$output_dir/cnc_sim_linuxcnc_source_arc_center_tolerance_bad_metric.json" 2>"$output_dir/cnc_sim_linuxcnc_source_arc_center_tolerance_bad_metric.err"; then echo "expected linuxcnc_arc_center_tolerance_bad_metric.ngc to fail" >&2 exit 1 fi if CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_extended_plane_arc_error.ngc \ - >/tmp/cnc_sim_linuxcnc_source_extended_plane_arc_error.json 2>/tmp/cnc_sim_linuxcnc_source_extended_plane_arc_error.err; then + >"$output_dir/cnc_sim_linuxcnc_source_extended_plane_arc_error.json" 2>"$output_dir/cnc_sim_linuxcnc_source_extended_plane_arc_error.err"; then echo "expected linuxcnc_extended_plane_arc_error.ngc to fail" >&2 exit 1 fi -if ! grep -q "Cannot do an arc in planes G17.1, G18.1, or G19.1" /tmp/cnc_sim_linuxcnc_source_extended_plane_arc_error.err; then +if ! grep -q "Cannot do an arc in planes G17.1, G18.1, or G19.1" "$output_dir/cnc_sim_linuxcnc_source_extended_plane_arc_error.err"; then echo "unexpected linuxcnc_extended_plane_arc_error.ngc error" >&2 - cat /tmp/cnc_sim_linuxcnc_source_extended_plane_arc_error.err >&2 + cat "$output_dir/cnc_sim_linuxcnc_source_extended_plane_arc_error.err" >&2 exit 1 fi if CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_g62_extended_plane_error.ngc \ - >/tmp/cnc_sim_linuxcnc_source_g62_extended_plane_error.json 2>/tmp/cnc_sim_linuxcnc_source_g62_extended_plane_error.err; then + >"$output_dir/cnc_sim_linuxcnc_source_g62_extended_plane_error.json" 2>"$output_dir/cnc_sim_linuxcnc_source_g62_extended_plane_error.err"; then echo "expected linuxcnc_g62_extended_plane_error.ngc to fail" >&2 exit 1 fi -if ! grep -q "one plane must be selected for nurbs: XY, YZ, ZX" /tmp/cnc_sim_linuxcnc_source_g62_extended_plane_error.err; then +if ! grep -q "one plane must be selected for nurbs: XY, YZ, ZX" "$output_dir/cnc_sim_linuxcnc_source_g62_extended_plane_error.err"; then echo "unexpected linuxcnc_g62_extended_plane_error.ngc error" >&2 - cat /tmp/cnc_sim_linuxcnc_source_g62_extended_plane_error.err >&2 + cat "$output_dir/cnc_sim_linuxcnc_source_g62_extended_plane_error.err" >&2 exit 1 fi CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_g76only.ngc \ - >/tmp/cnc_sim_linuxcnc_source_g76only.json + >"$output_dir/cnc_sim_linuxcnc_source_g76only.json" cp "$base_var_file" "$var_file" CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_coordinate_offsets.ngc \ - >/tmp/cnc_sim_linuxcnc_source_coordinate_offsets.json + >"$output_dir/cnc_sim_linuxcnc_source_coordinate_offsets.json" cp "$base_var_file" "$var_file" CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_coordinate_l20.ngc \ - >/tmp/cnc_sim_linuxcnc_source_coordinate_l20.json + >"$output_dir/cnc_sim_linuxcnc_source_coordinate_l20.json" cp "$base_var_file" "$var_file" CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_coordinate_p0.ngc \ - >/tmp/cnc_sim_linuxcnc_source_coordinate_p0.json + >"$output_dir/cnc_sim_linuxcnc_source_coordinate_p0.json" cp "$base_var_file" "$var_file" CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_coordinate_select_all.ngc \ - >/tmp/cnc_sim_linuxcnc_source_coordinate_select_all.json + >"$output_dir/cnc_sim_linuxcnc_source_coordinate_select_all.json" cp "$base_var_file" "$var_file" CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_g92_restore.ngc \ - >/tmp/cnc_sim_linuxcnc_source_g92_restore.json + >"$output_dir/cnc_sim_linuxcnc_source_g92_restore.json" cp "$base_var_file" "$var_file" CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_g52_offset.ngc \ - >/tmp/cnc_sim_linuxcnc_source_g52_offset.json + >"$output_dir/cnc_sim_linuxcnc_source_g52_offset.json" cp "$base_var_file" "$var_file" CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_g52_xz_nurbs.ngc \ - >/tmp/cnc_sim_linuxcnc_source_g52_xz_nurbs.json + >"$output_dir/cnc_sim_linuxcnc_source_g52_xz_nurbs.json" cp "$base_var_file" "$var_file" CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_g62_xz_nurbs.ngc \ - >/tmp/cnc_sim_linuxcnc_source_g62_xz_nurbs.json + >"$output_dir/cnc_sim_linuxcnc_source_g62_xz_nurbs.json" -python3 - <<'PY' +CNC_SIM_RS274_SOURCE_OUTPUT_DIR="$output_dir" python3 - <<'PY' import json +import os from pathlib import Path -events = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_basic_mill.json").read_text()) +OUTPUT_DIR = Path(os.environ["CNC_SIM_RS274_SOURCE_OUTPUT_DIR"]) + +events = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_basic_mill.json").read_text()) types = {event["type"] for event in events} required = { "set-units", @@ -506,7 +783,26 @@ final_motion = [event for event in events if event["type"] in {"rapid", "linear- if final_motion["end"]["x"] != 0 or final_motion["end"]["y"] != 0: raise SystemExit("unexpected source-linked final XY position") -controls = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_rtcp_controls.json").read_text()) +block_delete_off = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_block_delete_off.json").read_text()) +if not any( + event["type"] == "linear-feed" and event["line"] == 2 and event["end"]["x"] == 5 + for event in block_delete_off +): + raise SystemExit("expected source-linked disabled block-delete line to execute") + +block_delete_on = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_block_delete_on.json").read_text()) +if any( + event["type"] == "linear-feed" and event["line"] == 2 and event["end"]["x"] == 5 + for event in block_delete_on +): + raise SystemExit("expected source-linked block-delete line to be skipped") +if not any( + event["type"] == "linear-feed" and event["line"] == 3 and event["end"]["x"] == 2 + for event in block_delete_on +): + raise SystemExit("expected source-linked block-delete followup line to execute") + +controls = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_rtcp_controls.json").read_text()) kin = [event for event in controls if event["type"] == "kinematics-switch"] if [(event["reserved"], event["feed"]) for event in kin] != [(1, 1), (0, 0), (2, 1)]: raise SystemExit("unexpected source-linked kinematics switch sequence") @@ -514,7 +810,7 @@ rtcp = [event for event in controls if event["type"] == "rtcp-state"] if [(event["line"], event["tool"], event["feed"]) for event in rtcp] != [(3, 0, 0), (4, 7, 1)]: raise SystemExit("unexpected source-linked RTCP state sequence") -spindle = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_spindle_direction.json").read_text()) +spindle = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_spindle_direction.json").read_text()) spindle_states = [ (event["line"], event["spindle"], event["reserved"]) for event in spindle @@ -527,7 +823,7 @@ if (3, 1200, 2) not in spindle_states: if (4, 0, 0) not in spindle_states: raise SystemExit("missing source-linked M5 stopped spindle state") -spindle_modes = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_spindle_modes.json").read_text()) +spindle_modes = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_spindle_modes.json").read_text()) spindle_mode_states = [ (event["line"], event["reserved"], event["feed"], event["tool"]) for event in spindle_modes @@ -541,7 +837,7 @@ expected_spindle_mode_states = [ if spindle_mode_states != expected_spindle_mode_states: raise SystemExit(f"unexpected source-linked G96/G97 spindle mode states: {spindle_mode_states!r}") -spindle_orient = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_spindle_orient.json").read_text()) +spindle_orient = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_spindle_orient.json").read_text()) if not any( event["type"] == "comment" and event["line"] == 2 and @@ -562,7 +858,7 @@ if not any( ): raise SystemExit("missing source-linked M19 spindle orient wait state") -io_controls = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_io_controls.json").read_text()) +io_controls = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_io_controls.json").read_text()) io_states = [ (event["line"], event["reserved"], event["tool"], event["feed"], event["arcTurns"], event["dwellSeconds"]) for event in io_controls @@ -581,7 +877,7 @@ expected_io_states = [ if io_states != expected_io_states: raise SystemExit(f"unexpected source-linked M62-M68 I/O states: {io_states!r}") -override_controls = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_override_controls.json").read_text()) +override_controls = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_override_controls.json").read_text()) override_states = [ (event["line"], event["reserved"], event["tool"], event["feed"]) for event in override_controls @@ -605,7 +901,7 @@ expected_override_states = [ if override_states != expected_override_states: raise SystemExit(f"unexpected source-linked M48-M53 override states: {override_states!r}") -modal_state = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_modal_state.json").read_text()) +modal_state = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_modal_state.json").read_text()) if not any( event["type"] == "rapid" and event["line"] == 7 and @@ -625,7 +921,7 @@ if not any( ): raise SystemExit("missing source-linked M72 restored XY plane arc") -modal_autorestore = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_modal_autorestore.json").read_text()) +modal_autorestore = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_modal_autorestore.json").read_text()) if not any( event["type"] == "rapid" and event["line"] == 9 and @@ -642,7 +938,7 @@ if not any( ): raise SystemExit("missing source-linked M73 autorestored incremental caller move") -threading_sync = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_threading_sync.json").read_text()) +threading_sync = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_threading_sync.json").read_text()) sync_states = [ (event["line"], event["reserved"], event["feed"], event["tool"]) for event in threading_sync @@ -662,7 +958,7 @@ if not any( ): raise SystemExit("missing source-linked G33.1 rigid tap motion line") -threading_cycle = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_threading_cycle.json").read_text()) +threading_cycle = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_threading_cycle.json").read_text()) if not any( event["type"] == "comment" and event["line"] == 4 and @@ -681,7 +977,7 @@ if not any( if not any(event["type"] == "program-end" and event["line"] == 5 for event in threading_cycle): raise SystemExit("missing source-linked G76 program end line number") -tool_number = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_tool_number.json").read_text()) +tool_number = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_tool_number.json").read_text()) if not any( event["type"] == "tool-change" and event["line"] == 2 and @@ -690,7 +986,7 @@ if not any( ): raise SystemExit("missing source-linked M61 Q1 tool-number event line") -stops = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_program_stops.json").read_text()) +stops = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_program_stops.json").read_text()) stop_states = [ (event["line"], event["reserved"]) for event in stops @@ -700,7 +996,7 @@ for expected in [(2, 1), (3, 2), (4, 3), (4, 1), (5, 0)]: if expected not in stop_states: raise SystemExit(f"missing source-linked program stop state {expected!r}") -coolant = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_coolant.json").read_text()) +coolant = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_coolant.json").read_text()) coolant_states = [ (event["line"], event["reserved"]) for event in coolant @@ -710,7 +1006,7 @@ for expected in [(2, 11), (3, 21), (4, 10), (4, 20)]: if expected not in coolant_states: raise SystemExit(f"missing source-linked coolant state {expected!r}") -feed_modes = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_feed_modes.json").read_text()) +feed_modes = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_feed_modes.json").read_text()) feed_mode_comments = [ (event["line"], event["reserved"]) for event in feed_modes @@ -728,7 +1024,7 @@ for expected in [(3, 0), (4, 0)]: if expected not in feed_mode_events: raise SystemExit(f"missing source-linked feed mode event {expected!r}") -motion_modes = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_motion_modes.json").read_text()) +motion_modes = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_motion_modes.json").read_text()) motion_mode_states = [ (event["line"], event["reserved"], event["feed"]) for event in motion_modes @@ -738,7 +1034,7 @@ for expected in [(2, 611, 0), (3, 612, 0), (4, 640, 0.05)]: if expected not in motion_mode_states: raise SystemExit(f"missing source-linked motion mode state {expected!r}") -tool_length = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_tool_length.json").read_text()) +tool_length = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_tool_length.json").read_text()) if not any( event["type"] == "comment" and event["line"] == 2 and @@ -756,7 +1052,7 @@ if not any( ): raise SystemExit("missing source-linked G49 tool length clear event") -dynamic_tool_length = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_dynamic_tool_length.json").read_text()) +dynamic_tool_length = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_dynamic_tool_length.json").read_text()) dynamic_offsets = [ (event["line"], event["start"]["x"], event["start"]["z"]) for event in dynamic_tool_length @@ -766,7 +1062,7 @@ expected_dynamic_offsets = [(2, 1, 2), (3, 1.25, 2.5), (4, 1.25, 15), (5, 0, 0)] if dynamic_offsets != expected_dynamic_offsets: raise SystemExit(f"unexpected source-linked G43.1/G43.2 tool length offsets: {dynamic_offsets!r}") -cycle = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_canned_cycle.json").read_text()) +cycle = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_canned_cycle.json").read_text()) motions = [event for event in cycle if event["type"] in {"rapid", "linear-feed"}] expected_cycle = [ ("rapid", 0, 0, 5), @@ -779,7 +1075,7 @@ actual_cycle = [(event["type"], event["end"]["x"], event["end"]["y"], event["end if actual_cycle != expected_cycle: raise SystemExit(f"unexpected source-linked G81/G80 expansion: {actual_cycle!r}") -cycle_planes = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_canned_cycle_planes.json").read_text()) +cycle_planes = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_canned_cycle_planes.json").read_text()) if not any( event["type"] == "linear-feed" and event["line"] == 4 and @@ -925,7 +1221,7 @@ expected_plane_g87_sequence = [ if plane_g87_sequence != expected_plane_g87_sequence: raise SystemExit(f"unexpected source-linked G19 G87 plane sequence: {plane_g87_sequence!r}") -extended_plane_cycle = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_extended_plane_cycle.json").read_text()) +extended_plane_cycle = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_extended_plane_cycle.json").read_text()) if not any( event["type"] == "linear-feed" and event["line"] == 3 and @@ -950,7 +1246,7 @@ if not any( ): raise SystemExit("missing source-linked cutter-comp-active G17.1 plane selection") -extended_cycle = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_canned_cycles_extended.json").read_text()) +extended_cycle = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_canned_cycles_extended.json").read_text()) if not any( event["type"] == "linear-feed" and event["line"] == 3 and @@ -1077,7 +1373,7 @@ expected_g87_sequence = [ if g87_sequence != expected_g87_sequence: raise SystemExit(f"unexpected source-linked G87 back-boring sequence: {g87_sequence!r}") -arc_planes = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_arc_planes.json").read_text()) +arc_planes = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_arc_planes.json").read_text()) if not any( event["type"] == "arc-feed" and event["line"] == 5 and @@ -1107,7 +1403,7 @@ if not any( ): raise SystemExit("missing source-linked G19 YZ plane arc-feed mapping") -arc_distance = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_arc_distance_modes.json").read_text()) +arc_distance = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_arc_distance_modes.json").read_text()) arc_centers = [ (event["line"], event["center"]["x"], event["center"]["y"]) for event in arc_distance @@ -1117,7 +1413,7 @@ expected_arc_centers = [(5, 5, 0), (7, 15, 0), (9, 25, 0)] if arc_centers != expected_arc_centers: raise SystemExit(f"unexpected source-linked G90.1/G91.1 arc centers: {arc_centers!r}") -predefined = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_predefined_positions.json").read_text()) +predefined = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_predefined_positions.json").read_text()) predefined_rapids = [ ( event["line"], @@ -1136,7 +1432,7 @@ expected_predefined_rapids = [ if predefined_rapids != expected_predefined_rapids: raise SystemExit(f"unexpected source-linked G28/G30 rapid sequence: {predefined_rapids!r}") -machine_coords = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_machine_coordinates.json").read_text()) +machine_coords = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_machine_coordinates.json").read_text()) machine_moves = [ ( event["type"], event["line"], @@ -1154,7 +1450,7 @@ expected_machine_moves = [ if machine_moves != expected_machine_moves: raise SystemExit(f"unexpected source-linked G53 machine-coordinate moves: {machine_moves!r}") -cutter_comp = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_cutter_comp.json").read_text()) +cutter_comp = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_cutter_comp.json").read_text()) cutter_comp_motions = [ event for event in cutter_comp @@ -1182,7 +1478,7 @@ actual_cutter_comp = [ if actual_cutter_comp != expected_cutter_comp: raise SystemExit(f"unexpected source-linked cutter compensation path: {actual_cutter_comp!r}") -probe = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_probe_no_error.json").read_text()) +probe = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_probe_no_error.json").read_text()) probe_events = [ (event["line"], event["reserved"], event["start"]["z"], event["end"]["z"]) for event in probe @@ -1207,7 +1503,7 @@ if not any( ): raise SystemExit("missing source-linked #5070 probe-tripped conditional motion") -oword = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_oword_subprogram.json").read_text()) +oword = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_oword_subprogram.json").read_text()) if not any( event["type"] == "linear-feed" and event["line"] == 8 and @@ -1232,7 +1528,7 @@ if any( ): raise SystemExit("source-linked O-word return should skip remaining subprogram body") -named_oword = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_named_oword_subprogram.json").read_text()) +named_oword = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_named_oword_subprogram.json").read_text()) if not any( event["type"] == "linear-feed" and event["line"] == 9 and @@ -1265,7 +1561,7 @@ if any( ): raise SystemExit("source-linked named O-word return should skip remaining subprogram body") -oword_flow = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_oword_control_flow.json").read_text()) +oword_flow = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_oword_control_flow.json").read_text()) if not any(event["type"] == "linear-feed" and event["line"] == 7 and event["end"]["x"] == 2 for event in oword_flow): raise SystemExit("missing source-linked O-word elseif branch motion") if any( @@ -1330,7 +1626,7 @@ if not any( ): raise SystemExit("missing source-linked O-word do/continue skipped-body counter motion") -exists = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_named_parameter_exists.json").read_text()) +exists = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_named_parameter_exists.json").read_text()) exists_moves = [ (event["line"], event["end"]["x"], event["end"]["y"]) for event in exists @@ -1340,7 +1636,7 @@ expected_exists_moves = [(5, 1, 0), (8, 1, 1)] if exists_moves != expected_exists_moves: raise SystemExit(f"unexpected source-linked EXISTS named parameter branch motions: {exists_moves!r}") -indexed = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_indexed_parameters.json").read_text()) +indexed = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_indexed_parameters.json").read_text()) indexed_moves = [ ( event["line"], @@ -1367,7 +1663,7 @@ expected_indexed_moves = [ if indexed_moves != expected_indexed_moves: raise SystemExit(f"unexpected source-linked indexed/indirect parameter motions: {indexed_moves!r}") -normalized = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_named_parameter_normalization.json").read_text()) +normalized = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_named_parameter_normalization.json").read_text()) normalized_moves = [ (event["line"], event["end"]["x"], event["end"]["y"]) for event in normalized @@ -1377,7 +1673,7 @@ expected_normalized_moves = [(4, 12, 0), (6, 12, 1)] if normalized_moves != expected_normalized_moves: raise SystemExit(f"unexpected source-linked normalized named parameter motions: {normalized_moves!r}") -ini_named = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_ini_named_parameter.json").read_text()) +ini_named = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_ini_named_parameter.json").read_text()) ini_named_moves = [ (event["line"], event["end"]["x"], event["end"]["y"]) for event in ini_named @@ -1387,7 +1683,7 @@ expected_ini_named_moves = [(3, 3.25, 0), (5, 3.25, 1)] if ini_named_moves != expected_ini_named_moves: raise SystemExit(f"unexpected source-linked INI named parameter motions: {ini_named_moves!r}") -readonly_named = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_readonly_named_parameters_extra.json").read_text()) +readonly_named = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_readonly_named_parameters_extra.json").read_text()) if not any( event["type"] == "linear-feed" and event["line"] == 4 and @@ -1417,7 +1713,7 @@ if not any( ): raise SystemExit("missing source-linked #<_spindle_rpm_mode> / #<_spindle_css_mode> G96 branch motion") -value_returned = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_value_returned.json").read_text()) +value_returned = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_value_returned.json").read_text()) if not any( event["type"] == "linear-feed" and event["line"] == 11 and @@ -1447,7 +1743,7 @@ if not any( ): raise SystemExit("missing source-linked #<_value_returned> return[value] branch motion") -call_level = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_call_level.json").read_text()) +call_level = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_call_level.json").read_text()) if not any( event["type"] == "linear-feed" and event["line"] == 15 and @@ -1470,7 +1766,7 @@ if not any( ): raise SystemExit("missing source-linked inner O-word #<_call_level>/#<_remap_level> branch motion") -exists_word_value = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_exists_word_value.json").read_text()) +exists_word_value = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_exists_word_value.json").read_text()) if not any( event["type"] == "rapid" and event["line"] == 2 and @@ -1491,7 +1787,7 @@ if not any( ): raise SystemExit("missing source-linked indirect EXISTS word-value motion") -lowercase_numeric_oword = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_lowercase_numeric_oword.json").read_text()) +lowercase_numeric_oword = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_lowercase_numeric_oword.json").read_text()) if not any( event["type"] == "linear-feed" and event["line"] == 9 and @@ -1507,7 +1803,7 @@ if not any( ): raise SystemExit("missing source-linked lowercase numeric O-word endsub[value] motion") -parameter_expression_assignment = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_parameter_expression_assignment.json").read_text()) +parameter_expression_assignment = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_parameter_expression_assignment.json").read_text()) if not any( event["type"] == "rapid" and event["line"] == 5 and @@ -1560,7 +1856,7 @@ if not any( ): raise SystemExit("missing source-linked whitespace-insensitive word value motion") -word_bracket_whitespace = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_word_bracket_whitespace.json").read_text()) +word_bracket_whitespace = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_word_bracket_whitespace.json").read_text()) if not any( event["type"] == "linear-feed" and event["line"] == 3 and @@ -1583,7 +1879,7 @@ if not any( ): raise SystemExit("missing source-linked minus-spaced bracket word X motion") -m98_mixed_styles = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_m98_mixed_styles_variables.json").read_text()) +m98_mixed_styles = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_m98_mixed_styles_variables.json").read_text()) if not any( event["type"] == "linear-feed" and event["line"] == 5 and @@ -1603,7 +1899,7 @@ if not any( ): raise SystemExit("missing source-linked O-word mixed-style local-parameter motion") -arc_center_tolerance_good_imperial = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_arc_center_tolerance_good_imperial.json").read_text()) +arc_center_tolerance_good_imperial = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_arc_center_tolerance_good_imperial.json").read_text()) if not any( event["type"] == "arc-feed" and event["line"] == 4 and @@ -1612,7 +1908,7 @@ if not any( ): raise SystemExit("missing source-linked small imperial center-format arc within tolerance") -g76only = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_g76only.json").read_text()) +g76only = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_g76only.json").read_text()) if not any( event["type"] == "comment" and event["line"] == 8 and @@ -1627,7 +1923,7 @@ if not any( ): raise SystemExit("missing source-linked G76-only program end") -numbered_parameters = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_numbered_parameters.json").read_text()) +numbered_parameters = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_numbered_parameters.json").read_text()) if not any( event["type"] == "linear-feed" and event["line"] == 6 and @@ -1643,7 +1939,7 @@ if not any( ): raise SystemExit("missing source-linked numbered probe parameter branch motion") -rotary_probe_numbered = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_rotary_probe_numbered_parameters.json").read_text()) +rotary_probe_numbered = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_rotary_probe_numbered_parameters.json").read_text()) if not any( event["type"] == "linear-feed" and event["line"] == 5 and @@ -1653,7 +1949,7 @@ if not any( ): raise SystemExit("missing source-linked numbered rotary probe parameter branch motion") -multi_rotary_probe_numbered = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_multi_rotary_probe_numbered_parameters.json").read_text()) +multi_rotary_probe_numbered = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_multi_rotary_probe_numbered_parameters.json").read_text()) if not any( event["type"] == "linear-feed" and event["line"] == 5 and @@ -1668,7 +1964,7 @@ if not any( ): raise SystemExit("missing source-linked numbered multi-axis rotary probe parameter branch motion") -tool_info_zero_parameter = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_tool_info_zero_parameter.json").read_text()) +tool_info_zero_parameter = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_tool_info_zero_parameter.json").read_text()) if not any( event["type"] == "linear-feed" and event["line"] == 5 and @@ -1677,7 +1973,7 @@ if not any( ): raise SystemExit("missing source-linked zeroed #5400/#5410/#5411/#5412/#5413 branch motion") -spindle_tool_info_parameter = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_spindle_tool_info_parameter.json").read_text()) +spindle_tool_info_parameter = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_spindle_tool_info_parameter.json").read_text()) if not any( event["type"] == "linear-feed" and event["line"] == 4 and @@ -1686,7 +1982,7 @@ if not any( ): raise SystemExit("missing source-linked preloaded spindle-tool #5400/#5410/#5411/#5412/#5413 branch motion") -g28_g30_numbered = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_g28_g30_numbered_parameters.json").read_text()) +g28_g30_numbered = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_g28_g30_numbered_parameters.json").read_text()) if not any( event["type"] == "linear-feed" and event["line"] == 7 and @@ -1695,7 +1991,7 @@ if not any( ): raise SystemExit("missing source-linked G28/G30 numbered parameter branch motion") -rotary_g28_g30_numbered = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_rotary_g28_g30_numbered_parameters.json").read_text()) +rotary_g28_g30_numbered = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_rotary_g28_g30_numbered_parameters.json").read_text()) if not any( event["type"] == "linear-feed" and event["line"] == 7 and @@ -1710,7 +2006,7 @@ if not any( ): raise SystemExit("missing source-linked rotary G28/G30 numbered parameter branch motion") -g92_numbered = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_g92_numbered_parameters.json").read_text()) +g92_numbered = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_g92_numbered_parameters.json").read_text()) if not any( event["type"] == "linear-feed" and event["line"] == 4 and @@ -1719,7 +2015,7 @@ if not any( ): raise SystemExit("missing source-linked G92 numbered parameter branch motion") -rotary_g92_numbered = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_rotary_g92_numbered_parameters.json").read_text()) +rotary_g92_numbered = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_rotary_g92_numbered_parameters.json").read_text()) if not any( event["type"] == "linear-feed" and event["line"] == 4 and @@ -1728,7 +2024,7 @@ if not any( ): raise SystemExit("missing source-linked rotary G92 numbered parameter branch motion") -g52_g92_numbered = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_g52_g92_numbered_interaction.json").read_text()) +g52_g92_numbered = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_g52_g92_numbered_interaction.json").read_text()) if not any( event["type"] == "linear-feed" and event["line"] == 6 and @@ -1737,7 +2033,7 @@ if not any( ): raise SystemExit("missing source-linked G52 X0 Y0 #5210/#5211/#5212 branch motion") -g5x_numbered = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_g5x_numbered_parameters.json").read_text()) +g5x_numbered = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_g5x_numbered_parameters.json").read_text()) if not any( event["type"] == "linear-feed" and event["line"] == 5 and @@ -1746,7 +2042,7 @@ if not any( ): raise SystemExit("missing source-linked G5X numbered parameter branch motion") -g593_numbered = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_g59_3_numbered_parameters.json").read_text()) +g593_numbered = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_g59_3_numbered_parameters.json").read_text()) if not any( event["type"] == "linear-feed" and event["line"] == 5 and @@ -1755,7 +2051,7 @@ if not any( ): raise SystemExit("missing source-linked G59.3 numbered parameter branch motion") -g593_rotary_numbered = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_g59_3_rotary_numbered_parameters.json").read_text()) +g593_rotary_numbered = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_g59_3_rotary_numbered_parameters.json").read_text()) if not any( event["type"] == "linear-feed" and event["line"] == 5 and @@ -1764,7 +2060,7 @@ if not any( ): raise SystemExit("missing source-linked rotary G59.3 numbered parameter branch motion") -g5x_rotary_numbered = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_g5x_rotary_numbered_parameters.json").read_text()) +g5x_rotary_numbered = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_g5x_rotary_numbered_parameters.json").read_text()) if not any( event["type"] == "linear-feed" and event["line"] == 5 and @@ -1773,7 +2069,7 @@ if not any( ): raise SystemExit("missing source-linked rotary G5X numbered parameter branch motion") -g54_rotary_numbered = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_g54_rotary_numbered_parameters.json").read_text()) +g54_rotary_numbered = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_g54_rotary_numbered_parameters.json").read_text()) if not any( event["type"] == "linear-feed" and event["line"] == 4 and @@ -1782,7 +2078,7 @@ if not any( ): raise SystemExit("missing source-linked rotary G54 numbered parameter branch motion") -debug_level_parameter = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_debug_level_parameter.json").read_text()) +debug_level_parameter = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_debug_level_parameter.json").read_text()) if not any( event["type"] == "linear-feed" and event["line"] == 3 and @@ -1803,7 +2099,7 @@ if any( ): raise SystemExit("unexpected source-linked generic comment event for DEBUG hot comment") -m66_result_parameter = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_m66_result_parameter.json").read_text()) +m66_result_parameter = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_m66_result_parameter.json").read_text()) if not any( event["type"] == "linear-feed" and event["line"] == 4 and @@ -1819,7 +2115,7 @@ if not any( ): raise SystemExit("missing source-linked #5399 analog M66 result branch motion") -tool_offset_numbered = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_tool_offset_numbered_parameters.json").read_text()) +tool_offset_numbered = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_tool_offset_numbered_parameters.json").read_text()) if not any( event["type"] == "linear-feed" and event["line"] == 4 and @@ -1830,7 +2126,7 @@ if not any( ): raise SystemExit("missing source-linked numbered tool-offset/current-position parameter branch motion") -rotary_tool_offset_numbered = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_rotary_tool_offset_numbered_parameters.json").read_text()) +rotary_tool_offset_numbered = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_rotary_tool_offset_numbered_parameters.json").read_text()) if not any( event["type"] == "linear-feed" and event["line"] == 4 and @@ -1845,7 +2141,7 @@ if not any( ): raise SystemExit("missing source-linked numbered rotary tool-offset/current-position parameter branch motion") -current_position_numbered = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_current_position_numbered_parameters.json").read_text()) +current_position_numbered = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_current_position_numbered_parameters.json").read_text()) if not any( event["type"] == "linear-feed" and event["line"] == 5 and @@ -1862,7 +2158,7 @@ if not any( ): raise SystemExit("missing source-linked numbered current-position parameter branch motion") -persistent_parameters_check = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_persistent_parameters_check.json").read_text()) +persistent_parameters_check = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_persistent_parameters_check.json").read_text()) if not any( event["type"] == "linear-feed" and event["line"] == 5 and @@ -1871,7 +2167,7 @@ if not any( ): raise SystemExit("missing source-linked persistent parameter reload branch motion") -rotary_persistent_parameters_check = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_rotary_persistent_parameters_check.json").read_text()) +rotary_persistent_parameters_check = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_rotary_persistent_parameters_check.json").read_text()) if not any( event["type"] == "linear-feed" and event["line"] == 5 and @@ -1880,7 +2176,7 @@ if not any( ): raise SystemExit("missing source-linked rotary persistent parameter reload branch motion") -g54_persistent_parameters_check = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_g54_persistent_parameters_check.json").read_text()) +g54_persistent_parameters_check = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_g54_persistent_parameters_check.json").read_text()) if not any( event["type"] == "linear-feed" and event["line"] == 4 and @@ -1889,7 +2185,7 @@ if not any( ): raise SystemExit("missing source-linked G54 persistent parameter reload branch motion") -user_persistent_parameter_check = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_user_persistent_parameter_check.json").read_text()) +user_persistent_parameter_check = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_user_persistent_parameter_check.json").read_text()) if not any( event["type"] == "linear-feed" and event["line"] == 3 and @@ -1898,7 +2194,7 @@ if not any( ): raise SystemExit("missing source-linked empty-var #5001 non-persistence branch motion") -user_existing_persistent_parameter_check = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_user_existing_persistent_parameter_check.json").read_text()) +user_existing_persistent_parameter_check = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_user_existing_persistent_parameter_check.json").read_text()) if not any( event["type"] == "linear-feed" and event["line"] == 3 and @@ -1907,7 +2203,7 @@ if not any( ): raise SystemExit("missing source-linked existing-var #5001 persistence branch motion") -user_persistent_parameter_boundary_check = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_user_persistent_parameter_boundary_check.json").read_text()) +user_persistent_parameter_boundary_check = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_user_persistent_parameter_boundary_check.json").read_text()) if not any( event["type"] == "linear-feed" and event["line"] == 3 and @@ -1916,7 +2212,7 @@ if not any( ): raise SystemExit("missing source-linked #5000/#5001/#5030/#5060 persistence boundary branch motion") -volatile_global_parameter_check = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_volatile_global_parameter_check.json").read_text()) +volatile_global_parameter_check = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_volatile_global_parameter_check.json").read_text()) if not any( event["type"] == "linear-feed" and event["line"] == 3 and @@ -1925,7 +2221,7 @@ if not any( ): raise SystemExit("missing source-linked volatile global #31 reset branch motion") -coords = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_coordinate_offsets.json").read_text()) +coords = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_coordinate_offsets.json").read_text()) g5x = [event for event in coords if event["type"] == "set-g5x-offset"] g92 = [event for event in coords if event["type"] == "set-g92-offset"] rot = [event for event in coords if event["type"] == "set-xy-rotation"] @@ -1951,7 +2247,7 @@ if not any( if not any(event["type"] == "program-end" and event["line"] == 5 for event in coords): raise SystemExit("missing source-linked coordinate program end line number") -l20 = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_coordinate_l20.json").read_text()) +l20 = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_coordinate_l20.json").read_text()) if not any( event["type"] == "set-g5x-offset" and event["line"] == 4 and @@ -1965,7 +2261,7 @@ if not any( if not any(event["type"] == "program-end" and event["line"] == 5 for event in l20): raise SystemExit("missing source-linked G10 L20 program end line number") -p0 = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_coordinate_p0.json").read_text()) +p0 = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_coordinate_p0.json").read_text()) if not any( event["type"] == "set-g5x-offset" and event["line"] == 3 and @@ -1979,7 +2275,7 @@ if not any( if not any(event["type"] == "program-end" and event["line"] == 4 for event in p0): raise SystemExit("missing source-linked G10 P0 program end line number") -select_all = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_coordinate_select_all.json").read_text()) +select_all = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_coordinate_select_all.json").read_text()) select_events = [ (event["line"], event["tool"], event["start"]["x"]) for event in select_all @@ -1999,7 +2295,7 @@ expected_select_events = [ if select_events != expected_select_events: raise SystemExit(f"unexpected source-linked G54-G59.3 selection events: {select_events!r}") -g92_restore = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_g92_restore.json").read_text()) +g92_restore = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_g92_restore.json").read_text()) g92_states = [ (event["line"], event["start"]["x"], event["start"]["y"], event["start"]["z"]) for event in g92_restore @@ -2017,7 +2313,7 @@ if g92_states != expected_g92_states: if not any(event["type"] == "program-end" and event["line"] == 7 for event in g92_restore): raise SystemExit("missing source-linked G92 restore program end line number") -g52_offset = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_g52_offset.json").read_text()) +g52_offset = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_g52_offset.json").read_text()) g52_states = [ (event["line"], event["start"]["x"], event["start"]["y"], event["start"]["z"]) for event in g52_offset @@ -2035,7 +2331,7 @@ if g52_states != expected_g52_states: if not any(event["type"] == "program-end" and event["line"] == 7 for event in g52_offset): raise SystemExit("missing source-linked G52 offset program end line number") -g52_xz_nurbs = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_g52_xz_nurbs.json").read_text()) +g52_xz_nurbs = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_g52_xz_nurbs.json").read_text()) if not any( event["type"] == "linear-feed" and event["line"] == 6 and @@ -2047,7 +2343,7 @@ if not any( ): raise SystemExit("missing source-linked G18/XZ NURBS endpoint mapped to X/Z axes") -g62_xz_nurbs = json.loads(Path("/tmp/cnc_sim_linuxcnc_source_g62_xz_nurbs.json").read_text()) +g62_xz_nurbs = json.loads((OUTPUT_DIR / "cnc_sim_linuxcnc_source_g62_xz_nurbs.json").read_text()) if not any( event["type"] == "linear-feed" and event["line"] == 16 and @@ -2061,88 +2357,88 @@ if not any( PY echo "linuxcnc rs274 source link smoke passed (${#objects[@]} local objects)" -echo "dumped /tmp/cnc_sim_linuxcnc_source_basic_mill.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_rtcp_controls.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_spindle_direction.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_spindle_modes.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_spindle_orient.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_io_controls.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_override_controls.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_modal_state.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_modal_autorestore.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_threading_sync.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_threading_cycle.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_tool_number.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_program_stops.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_coolant.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_feed_modes.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_motion_modes.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_tool_length.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_dynamic_tool_length.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_canned_cycle.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_canned_cycles_extended.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_canned_cycle_planes.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_extended_plane_cycle.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_arc_planes.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_arc_distance_modes.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_predefined_positions.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_machine_coordinates.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_cutter_comp.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_probe_no_error.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_oword_subprogram.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_named_oword_subprogram.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_oword_control_flow.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_named_parameter_exists.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_indexed_parameters.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_named_parameter_normalization.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_ini_named_parameter.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_readonly_named_parameters_extra.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_value_returned.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_call_level.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_exists_word_value.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_lowercase_numeric_oword.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_parameter_expression_assignment.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_word_bracket_whitespace.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_m98_mixed_styles_variables.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_arc_center_tolerance_good_imperial.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_g76only.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_numbered_parameters.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_rotary_probe_numbered_parameters.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_multi_rotary_probe_numbered_parameters.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_tool_info_zero_parameter.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_spindle_tool_info_parameter.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_g28_g30_numbered_parameters.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_rotary_g28_g30_numbered_parameters.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_g92_numbered_parameters.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_rotary_g92_numbered_parameters.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_g52_g92_numbered_interaction.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_g5x_numbered_parameters.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_g59_3_numbered_parameters.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_g59_3_rotary_numbered_parameters.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_g5x_rotary_numbered_parameters.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_g54_rotary_numbered_parameters.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_debug_level_parameter.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_m66_result_parameter.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_tool_offset_numbered_parameters.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_rotary_tool_offset_numbered_parameters.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_current_position_numbered_parameters.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_persistent_parameters_set.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_persistent_parameters_check.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_rotary_persistent_parameters_set.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_rotary_persistent_parameters_check.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_g54_persistent_parameters_set.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_g54_persistent_parameters_check.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_user_persistent_parameter_set.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_user_persistent_parameter_check.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_user_existing_persistent_parameter_set.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_user_existing_persistent_parameter_check.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_user_persistent_parameter_boundary_set.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_user_persistent_parameter_boundary_check.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_coordinate_offsets.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_coordinate_l20.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_coordinate_p0.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_coordinate_select_all.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_g92_restore.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_g52_offset.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_g52_xz_nurbs.json" -echo "dumped /tmp/cnc_sim_linuxcnc_source_g62_xz_nurbs.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_basic_mill.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_rtcp_controls.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_spindle_direction.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_spindle_modes.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_spindle_orient.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_io_controls.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_override_controls.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_modal_state.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_modal_autorestore.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_threading_sync.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_threading_cycle.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_tool_number.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_program_stops.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_coolant.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_feed_modes.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_motion_modes.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_tool_length.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_dynamic_tool_length.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_canned_cycle.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_canned_cycles_extended.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_canned_cycle_planes.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_extended_plane_cycle.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_arc_planes.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_arc_distance_modes.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_predefined_positions.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_machine_coordinates.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_cutter_comp.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_probe_no_error.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_oword_subprogram.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_named_oword_subprogram.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_oword_control_flow.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_named_parameter_exists.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_indexed_parameters.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_named_parameter_normalization.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_ini_named_parameter.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_readonly_named_parameters_extra.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_value_returned.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_call_level.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_exists_word_value.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_lowercase_numeric_oword.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_parameter_expression_assignment.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_word_bracket_whitespace.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_m98_mixed_styles_variables.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_arc_center_tolerance_good_imperial.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_g76only.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_numbered_parameters.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_rotary_probe_numbered_parameters.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_multi_rotary_probe_numbered_parameters.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_tool_info_zero_parameter.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_spindle_tool_info_parameter.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_g28_g30_numbered_parameters.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_rotary_g28_g30_numbered_parameters.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_g92_numbered_parameters.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_rotary_g92_numbered_parameters.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_g52_g92_numbered_interaction.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_g5x_numbered_parameters.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_g59_3_numbered_parameters.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_g59_3_rotary_numbered_parameters.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_g5x_rotary_numbered_parameters.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_g54_rotary_numbered_parameters.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_debug_level_parameter.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_m66_result_parameter.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_tool_offset_numbered_parameters.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_rotary_tool_offset_numbered_parameters.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_current_position_numbered_parameters.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_persistent_parameters_set.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_persistent_parameters_check.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_rotary_persistent_parameters_set.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_rotary_persistent_parameters_check.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_g54_persistent_parameters_set.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_g54_persistent_parameters_check.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_user_persistent_parameter_set.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_user_persistent_parameter_check.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_user_existing_persistent_parameter_set.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_user_existing_persistent_parameter_check.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_user_persistent_parameter_boundary_set.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_user_persistent_parameter_boundary_check.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_coordinate_offsets.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_coordinate_l20.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_coordinate_p0.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_coordinate_select_all.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_g92_restore.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_g52_offset.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_g52_xz_nurbs.json" +echo "dumped $output_dir/cnc_sim_linuxcnc_source_g62_xz_nurbs.json" diff --git a/test-linuxcnc-source-objects.sh b/test-linuxcnc-source-objects.sh index aabb3e4..4027adb 100755 --- a/test-linuxcnc-source-objects.sh +++ b/test-linuxcnc-source-objects.sh @@ -5,11 +5,21 @@ cd "$(dirname "$0")" linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} cxx=${CXX:-g++} -build_dir=${TMPDIR:-/tmp}/cnc_sim_rs274_objects +build_dir=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_rs274_objects.XXXXXX") python_includes=$(python3.13-config --includes 2>/dev/null || python3-config --includes) +manifest=${1:-linuxcnc-rs274-source-files.txt} -rm -rf "$build_dir" -mkdir -p "$build_dir" +if [[ ! -f "$manifest" ]]; then + echo "missing manifest: $manifest" >&2 + exit 1 +fi + +if [[ ! -d "$linuxcnc_root" ]]; then + echo "missing LinuxCNC root: $linuxcnc_root" >&2 + exit 1 +fi + +trap 'rm -rf "$build_dir"' EXIT common_flags=( -std=c++17 @@ -27,6 +37,10 @@ sources=() while IFS=: read -r group path note; do case "$group" in core) + if [[ ! -f "$linuxcnc_root/$path" ]]; then + echo "missing manifest file: $path" >&2 + exit 1 + fi sources+=("$path") ;; ""|\#*|binding|tooldata) @@ -36,14 +50,15 @@ while IFS=: read -r group path note; do exit 1 ;; esac -done < linuxcnc-rs274-source-files.txt +done < "$manifest" +source_index=0 for source in "${sources[@]}"; do - obj="$build_dir/$(basename "$source" .cc).o" + obj="$build_dir/linuxcnc_${source_index}.o" # shellcheck disable=SC2086 "$cxx" "${common_flags[@]}" $python_includes -c "$linuxcnc_root/$source" -o "$obj" + source_index=$((source_index + 1)) done echo "linuxcnc rs274 source object build passed (${#sources[@]} objects)" echo "built objects in $build_dir" - diff --git a/test-linuxcnc-source-syntax.sh b/test-linuxcnc-source-syntax.sh index 917e75d..e41acce 100755 --- a/test-linuxcnc-source-syntax.sh +++ b/test-linuxcnc-source-syntax.sh @@ -6,16 +6,58 @@ cd "$(dirname "$0")" linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} cxx=${CXX:-g++} python_includes=$(python3.13-config --includes 2>/dev/null || python3-config --includes) +manifest=${1:-linuxcnc-rs274-source-files.txt} + +if [[ ! -f "$manifest" ]]; then + echo "missing manifest: $manifest" >&2 + exit 1 +fi + +if [[ ! -d "$linuxcnc_root" ]]; then + echo "missing LinuxCNC root: $linuxcnc_root" >&2 + exit 1 +fi while IFS=: read -r group path note; do case "$group" in - ""|\#*) continue ;; + ""|\#*) + continue + ;; + core|binding|tooldata) + ;; + *) + echo "unknown manifest group: $group" >&2 + exit 1 + ;; esac if [[ ! -f "$linuxcnc_root/$path" ]]; then echo "missing manifest file: $path" >&2 exit 1 fi -done < linuxcnc-rs274-source-files.txt +done < "$manifest" + +missing_source_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_rs274_manifest_source.XXXXXX.txt") +missing_source_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_rs274_manifest_source.XXXXXX.log") +printf 'core:src/emc/rs274ngc/does_not_exist.cc:test missing source\n' >"$missing_source_manifest" +if ./test-linuxcnc-source-objects.sh "$missing_source_manifest" >"$missing_source_log" 2>&1; then + echo "linuxcnc source object probe accepted missing manifest file" >&2 + exit 1 +fi +if ! grep -F "missing manifest file: src/emc/rs274ngc/does_not_exist.cc" "$missing_source_log" >/dev/null; then + echo "linuxcnc source object probe did not report missing manifest file clearly" >&2 + sed -n '1,20p' "$missing_source_log" >&2 + exit 1 +fi +if ./test-linuxcnc-source-link.sh "$missing_source_manifest" >"$missing_source_log" 2>&1; then + echo "linuxcnc source link probe accepted missing manifest file" >&2 + exit 1 +fi +if ! grep -F "missing manifest file: src/emc/rs274ngc/does_not_exist.cc" "$missing_source_log" >/dev/null; then + echo "linuxcnc source link probe did not report missing manifest file clearly" >&2 + sed -n '1,20p' "$missing_source_log" >&2 + exit 1 +fi +rm -f "$missing_source_manifest" "$missing_source_log" common_flags=( -std=c++17 @@ -45,4 +87,3 @@ for source in "${probe_sources[@]}"; do done echo "linuxcnc rs274 source syntax probe passed" - diff --git a/test-linuxcnc-wasm-blockers.sh b/test-linuxcnc-wasm-blockers.sh new file mode 100755 index 0000000..54efd62 --- /dev/null +++ b/test-linuxcnc-wasm-blockers.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")" + +manifest=${1:-linuxcnc-rs274-wasm-source-files.txt} + +missing_root=/tmp/does-not-exist-linuxcnc +missing_root_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_wasm_blocker_missing_root.XXXXXX.log") +if LINUXCNC_ROOT="$missing_root" ./analyze-linuxcnc-wasm-blockers.sh "$manifest" >"$missing_root_log" 2>&1; then + echo "wasm blocker analyzer accepted missing LinuxCNC root" >&2 + exit 1 +fi +if ! grep -F "missing LinuxCNC root: $missing_root" "$missing_root_log" >/dev/null; then + echo "wasm blocker analyzer did not report missing LinuxCNC root clearly" >&2 + sed -n '1,20p' "$missing_root_log" >&2 + exit 1 +fi +rm -f "$missing_root_log" + +output=$(./analyze-linuxcnc-wasm-blockers.sh "$manifest") + +missing_source_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_wasm_blocker_manifest_source.XXXXXX.txt") +missing_source_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_wasm_blocker_manifest_source.XXXXXX.log") +printf 'blocked:src/emc/tooldata/does_not_exist.cc:test missing blocked source\n' >"$missing_source_manifest" +if ./analyze-linuxcnc-wasm-blockers.sh "$missing_source_manifest" >"$missing_source_log" 2>&1; then + echo "wasm blocker analyzer accepted missing manifest source" >&2 + exit 1 +fi +if ! grep -F "missing manifest source: src/emc/tooldata/does_not_exist.cc" "$missing_source_log" >/dev/null; then + echo "wasm blocker analyzer did not report missing manifest source clearly" >&2 + sed -n '1,20p' "$missing_source_log" >&2 + exit 1 +fi +rm -f "$missing_source_manifest" "$missing_source_log" + +grep -F "[python]" <<<"$output" >/dev/null +grep -F "(none)" <<<"$(sed -n '/^\[python\]/,/^$/p' <<<"$output")" >/dev/null +grep -F "[dlopen]" <<<"$output" >/dev/null +grep -F "(none)" <<<"$(sed -n '/^\[dlopen\]/,/^$/p' <<<"$output")" >/dev/null +grep -F "[tooldata]" <<<"$output" >/dev/null +grep -F "src/emc/tooldata/tooldata_mmap.cc" <<<"$output" >/dev/null +grep -F "[tooldata-users]" <<<"$output" >/dev/null +grep -F "(none)" <<<"$(sed -n '/^\[tooldata-users\]/,/^$/p' <<<"$output")" >/dev/null +grep -F "[native-backend]" <<<"$output" >/dev/null +grep -F "src/emc/tooldata/tooldata_mmap.cc" <<<"$(sed -n '/^\[native-backend\]/,/^$/p' <<<"$output")" >/dev/null +grep -F "[native-fs]" <<<"$output" >/dev/null +grep -F "src/emc/tooldata/tooldata_mmap.cc" <<<"$(sed -n '/^\[native-fs\]/,/^$/p' <<<"$output")" >/dev/null + +echo "linuxcnc wasm blocker scan passed" diff --git a/test-linuxcnc-wasm-cmake-safe-probe.sh b/test-linuxcnc-wasm-cmake-safe-probe.sh new file mode 100755 index 0000000..7b8c40b --- /dev/null +++ b/test-linuxcnc-wasm-cmake-safe-probe.sh @@ -0,0 +1,260 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")" + +linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} +build_dir=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_linuxcnc_wasm_cmake_safe_probe.XXXXXX") +manifest=${1:-linuxcnc-rs274-wasm-source-files.txt} + +if [[ ! -f "$manifest" ]]; then + echo "missing manifest: $manifest" >&2 + exit 1 +fi + +if [[ ! -d "$linuxcnc_root" ]]; then + echo "missing LinuxCNC root: $linuxcnc_root" >&2 + exit 1 +fi +trap 'rm -rf "$build_dir"' EXIT + +missing_root=/tmp/does-not-exist-linuxcnc +missing_root_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_build_wasm_missing_root.XXXXXX.log") +if LINUXCNC_ROOT="$missing_root" ./build-wasm.sh >"$missing_root_log" 2>&1; then + echo "build-wasm accepted missing LinuxCNC root: $missing_root" >&2 + exit 1 +fi +if ! grep -F "missing LinuxCNC root: $missing_root" "$missing_root_log" >/dev/null; then + echo "build-wasm did not report missing LinuxCNC root clearly" >&2 + sed -n '1,20p' "$missing_root_log" >&2 + exit 1 +fi +grep -F 'exec 9>"${TMPDIR:-/tmp}/cnc_sim_build_wasm.lock"' build-wasm.sh >/dev/null +grep -F "flock 9" build-wasm.sh >/dev/null +rm -f "$missing_root_log" + +missing_root_scripts=( + test-linuxcnc-wasm-interp-base-link.sh + test-linuxcnc-wasm-interp-find-link.sh + test-linuxcnc-wasm-python-plugin-link.sh + test-linuxcnc-wasm-rs274ngc-pre-object.sh + test-linuxcnc-wasm-tooldata-common-link.sh + test-linuxcnc-wasm-tooldata-link.sh + test-linuxcnc-wasm-tooldata-mmap-symbols.sh + test-linuxcnc-wasm-tooldata-runtime-symbols.sh +) +for script in "${missing_root_scripts[@]}"; do + if LINUXCNC_ROOT="$missing_root" "./$script" >"$missing_root_log" 2>&1; then + echo "$script accepted missing LinuxCNC root: $missing_root" >&2 + exit 1 + fi + if ! grep -F "missing LinuxCNC root: $missing_root" "$missing_root_log" >/dev/null; then + echo "$script did not report missing LinuxCNC root clearly" >&2 + sed -n '1,20p' "$missing_root_log" >&2 + exit 1 + fi +done +rm -f "$missing_root_log" + +missing_source_root=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_missing_linuxcnc_source_root.XXXXXX") +missing_source_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_linuxcnc_source.XXXXXX.log") +mkdir -p "$missing_source_root/src" "$missing_source_root/include" + +fixed_source_scripts=( + "test-linuxcnc-wasm-interp-base-link.sh:src/emc/rs274ngc/interp_base.cc" + "test-linuxcnc-wasm-interp-find-link.sh:src/emc/tooldata/tooldata_common.cc" + "test-linuxcnc-wasm-rs274ngc-pre-object.sh:src/emc/rs274ngc/rs274ngc_pre.cc" + "test-linuxcnc-wasm-tooldata-common-link.sh:src/emc/tooldata/tooldata_common.cc" + "test-linuxcnc-wasm-tooldata-link.sh:src/emc/tooldata/tooldata_common.cc" +) +for script_and_path in "${fixed_source_scripts[@]}"; do + script=${script_and_path%%:*} + source_path=${script_and_path#*:} + if LINUXCNC_ROOT="$missing_source_root" "./$script" >"$missing_source_log" 2>&1; then + echo "$script accepted missing LinuxCNC source: $source_path" >&2 + exit 1 + fi + if ! grep -F "missing LinuxCNC source: $source_path" "$missing_source_log" >/dev/null; then + echo "$script did not report missing LinuxCNC source clearly" >&2 + sed -n '1,20p' "$missing_source_log" >&2 + exit 1 + fi +done +rm -rf "$missing_source_root" +rm -f "$missing_source_log" + +unknown_group_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_unknown_wasm_manifest_group.XXXXXX.txt") +unknown_group_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_unknown_wasm_manifest_group.XXXXXX.log") +printf 'bogus:src/emc/rs274ngc/interp_arc.cc:test unknown group\n' >"$unknown_group_manifest" +for script in \ + analyze-linuxcnc-wasm-blockers.sh \ + test-linuxcnc-wasm-source-syntax.sh \ + test-linuxcnc-wasm-source-objects.sh \ + test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh \ + test-linuxcnc-wasm-rs274ngc-pre-link.sh; do + if "./$script" "$unknown_group_manifest" >"$unknown_group_log" 2>&1; then + echo "$script accepted unknown manifest group" >&2 + exit 1 + fi + if ! grep -F "unknown manifest group: bogus" "$unknown_group_log" >/dev/null; then + echo "$script did not report unknown manifest group clearly" >&2 + sed -n '1,20p' "$unknown_group_log" >&2 + exit 1 + fi +done +rm -f "$unknown_group_manifest" "$unknown_group_log" + +blocker_scan_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_wasm_blocker_scan.XXXXXX.log") +./test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh >"$blocker_scan_log" 2>&1 +blocker_report_file=$(awk '/^wrote / { print $2 }' "$blocker_scan_log") +if [[ -z "$blocker_report_file" || ! -f "$blocker_report_file" ]]; then + echo "wasm rs274ngc_pre blocker scan did not preserve its report file" >&2 + sed -n '1,20p' "$blocker_scan_log" >&2 + exit 1 +fi +rm -f "$blocker_report_file" "$blocker_scan_log" + +missing_source_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_wasm_manifest_source.XXXXXX.txt") +missing_source_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_wasm_manifest_source.XXXXXX.log") +printf 'core:src/emc/rs274ngc/does_not_exist.cc:test missing source\n' >"$missing_source_manifest" +if ./test-linuxcnc-wasm-source-syntax.sh "$missing_source_manifest" >"$missing_source_log" 2>&1; then + echo "wasm source syntax probe accepted missing manifest source" >&2 + exit 1 +fi +if ! grep -F "missing manifest source: src/emc/rs274ngc/does_not_exist.cc" "$missing_source_log" >/dev/null; then + echo "wasm source syntax probe did not report missing manifest source clearly" >&2 + sed -n '1,20p' "$missing_source_log" >&2 + exit 1 +fi +if ./test-linuxcnc-wasm-source-objects.sh "$missing_source_manifest" >"$missing_source_log" 2>&1; then + echo "wasm source object probe accepted missing manifest source" >&2 + exit 1 +fi +if ! grep -F "missing manifest source: src/emc/rs274ngc/does_not_exist.cc" "$missing_source_log" >/dev/null; then + echo "wasm source object probe did not report missing manifest source clearly" >&2 + sed -n '1,20p' "$missing_source_log" >&2 + exit 1 +fi +if ./test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh "$missing_source_manifest" >"$missing_source_log" 2>&1; then + echo "wasm rs274ngc_pre blocker scan accepted missing manifest source" >&2 + exit 1 +fi +if ! grep -F "missing manifest source: src/emc/rs274ngc/does_not_exist.cc" "$missing_source_log" >/dev/null; then + echo "wasm rs274ngc_pre blocker scan did not report missing manifest source clearly" >&2 + sed -n '1,20p' "$missing_source_log" >&2 + exit 1 +fi +if ./test-linuxcnc-wasm-rs274ngc-pre-link.sh "$missing_source_manifest" >"$missing_source_log" 2>&1; then + echo "wasm rs274ngc_pre link probe accepted missing manifest source" >&2 + exit 1 +fi +if ! grep -F "missing manifest source: src/emc/rs274ngc/does_not_exist.cc" "$missing_source_log" >/dev/null; then + echo "wasm rs274ngc_pre link probe did not report missing manifest source clearly" >&2 + sed -n '1,20p' "$missing_source_log" >&2 + exit 1 +fi +rm -f "$missing_source_manifest" "$missing_source_log" + +linuxcnc_root=$(cd "$linuxcnc_root" && pwd) +default_manifest="$PWD/linuxcnc-rs274-wasm-source-files.txt" + +manifest_core_count=0 +manifest_blocked_count=0 +while IFS=: read -r group path note; do + case "$group" in + core) + manifest_core_count=$((manifest_core_count + 1)) + ;; + blocked) + manifest_blocked_count=$((manifest_blocked_count + 1)) + ;; + ""|\#*) + continue + ;; + *) + echo "unknown manifest group: $group" >&2 + exit 1 + ;; + esac + if [[ ! -f "$linuxcnc_root/$path" ]]; then + echo "missing manifest source: $path" >&2 + exit 1 + fi +done < "$manifest" + +required_shims=( + wasm_shims/dlfcn.cc + wasm_shims/emc_status_shim.cc + wasm_shims/gettext_shim.cc + wasm_shims/linuxcnc_runtime_shim.cc + wasm_shims/python_c_api_shim.cc + wasm_shims/rtapi_compat.cc + wasm_shims/tooldata/tooldata_mmap_backend.cc + wasm_shims/tooldata/tooldata_runtime_stubs.cc + wasm_shims/pythonplugin/python_plugin.cc +) +required_project_sources=( + src/canon_event_sink.cpp + src/linuxcnc_canon_bridge.cpp + src/linuxcnc_tooldata_fixture.cpp + src/rtcp_kinematics.cpp +) + +if [[ "$manifest" == "$default_manifest" ]] && [[ "$manifest_core_count" -ne 25 || "$manifest_blocked_count" -ne 1 ]]; then + echo "unexpected wasm manifest partition: core=$manifest_core_count blocked=$manifest_blocked_count" >&2 + exit 1 +fi +if [[ "$manifest_core_count" -eq 0 || "$manifest_blocked_count" -eq 0 ]]; then + echo "unexpected wasm manifest partition: core=$manifest_core_count blocked=$manifest_blocked_count" >&2 + exit 1 +fi + +for shim in "${required_shims[@]}"; do + if ! grep -F "$shim" core/CMakeLists.txt >/dev/null; then + echo "missing CMake wasm-safe shim: $shim" >&2 + exit 1 + fi +done + +for source in "${required_project_sources[@]}"; do + if ! grep -F "$source" core/CMakeLists.txt >/dev/null; then + echo "missing CMake wasm-safe project source: $source" >&2 + exit 1 + fi +done + +grep -F "add_executable(linuxcnc_rs274_wasm_safe_probe EXCLUDE_FROM_ALL" core/CMakeLists.txt >/dev/null +grep -F "wasm_shims/rs274ngc_pre_probe_main.cc" core/CMakeLists.txt >/dev/null +grep -F "target_link_libraries(linuxcnc_rs274_wasm_safe_probe PRIVATE fmt)" core/CMakeLists.txt >/dev/null +grep -F "add_custom_target(linuxcnc_rs274_wasm_blocked_sources" core/CMakeLists.txt >/dev/null +grep -F "LinuxCNC source root does not exist" core/CMakeLists.txt >/dev/null +grep -F 'if(NOT IS_DIRECTORY "${CNC_SIM_LINUXCNC_ROOT_ABS}")' core/CMakeLists.txt >/dev/null +grep -F "LinuxCNC wasm source manifest is not a file" core/CMakeLists.txt >/dev/null +grep -F 'if(IS_DIRECTORY "${CNC_SIM_LINUXCNC_WASM_SOURCE_MANIFEST}")' core/CMakeLists.txt >/dev/null +grep -F "LinuxCNC wasm manifest source does not exist" core/CMakeLists.txt >/dev/null +grep -F 'if(NOT EXISTS "${manifest_source}")' core/CMakeLists.txt >/dev/null +grep -F "LinuxCNC wasm manifest source is not a file" core/CMakeLists.txt >/dev/null +grep -F 'if(IS_DIRECTORY "${manifest_source}")' core/CMakeLists.txt >/dev/null +awk ' + /set\(manifest_source/ { manifest_source = NR } + /Unknown LinuxCNC wasm source manifest group/ { unknown_group = NR } + END { exit !(unknown_group && manifest_source && unknown_group < manifest_source) } +' core/CMakeLists.txt +grep -F "LinuxCNC wasm source manifest has no blocked entries" core/CMakeLists.txt >/dev/null + +if ! command -v cmake >/dev/null 2>&1; then + echo "cmake not found; skipping linuxcnc wasm-safe CMake probe target" + exit 0 +fi + +cmake -S core -B "$build_dir" \ + -DCMAKE_BUILD_TYPE=Release \ + -DCNC_SIM_LINUXCNC_ROOT="$linuxcnc_root" \ + -DCNC_SIM_LINUXCNC_WASM_SOURCE_MANIFEST="$manifest" \ + -DCNC_SIM_ENABLE_LINUXCNC_WASM_SAFE_PROBE=ON + +cmake --build "$build_dir" --target linuxcnc_rs274_wasm_safe_probe_objects +cmake --build "$build_dir" --target linuxcnc_rs274_wasm_safe_probe +"$build_dir/linuxcnc_rs274_wasm_safe_probe" + +echo "linuxcnc wasm-safe CMake probe target passed" diff --git a/test-linuxcnc-wasm-interp-base-link.sh b/test-linuxcnc-wasm-interp-base-link.sh new file mode 100755 index 0000000..c310909 --- /dev/null +++ b/test-linuxcnc-wasm-interp-base-link.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")" + +linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} +cxx=${CXX:-g++} +build_dir=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_linuxcnc_wasm_interp_base_link.XXXXXX") + +if [[ ! -d "$linuxcnc_root" ]]; then + echo "missing LinuxCNC root: $linuxcnc_root" >&2 + exit 1 +fi +if [[ ! -f "$linuxcnc_root/src/emc/rs274ngc/interp_base.cc" ]]; then + echo "missing LinuxCNC source: src/emc/rs274ngc/interp_base.cc" >&2 + exit 1 +fi +trap 'rm -rf "$build_dir"' EXIT + +common_flags=( + -std=c++17 + -DULAPI + -I "$(pwd)/core/wasm_shims" + -I "$linuxcnc_root/src" + -I "$linuxcnc_root/src/emc" + -I "$linuxcnc_root/src/emc/nml_intf" + -I "$linuxcnc_root/src/emc/rs274ngc" + -I "$linuxcnc_root/src/emc/motion" + -I "$linuxcnc_root/include" +) + +"$cxx" "${common_flags[@]}" \ + core/wasm_shims/dlfcn.cc \ + "$linuxcnc_root/src/emc/rs274ngc/interp_base.cc" \ + core/wasm_shims/interp_base_probe_main.cc \ + -o "$build_dir/interp_base_probe" + +"$build_dir/interp_base_probe" + +echo "linuxcnc wasm interp_base link probe passed" diff --git a/test-linuxcnc-wasm-interp-find-link.sh b/test-linuxcnc-wasm-interp-find-link.sh new file mode 100755 index 0000000..9b3a653 --- /dev/null +++ b/test-linuxcnc-wasm-interp-find-link.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")" + +linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} +cxx=${CXX:-g++} +build_dir=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_linuxcnc_wasm_interp_find_link.XXXXXX") + +if [[ ! -d "$linuxcnc_root" ]]; then + echo "missing LinuxCNC root: $linuxcnc_root" >&2 + exit 1 +fi +for required_source in \ + src/emc/tooldata/tooldata_common.cc \ + src/emc/rs274ngc/interp_find.cc; do + if [[ ! -f "$linuxcnc_root/$required_source" ]]; then + echo "missing LinuxCNC source: $required_source" >&2 + exit 1 + fi +done +trap 'rm -rf "$build_dir"' EXIT + +common_flags=( + -std=c++17 + -DULAPI + -ffunction-sections + -fdata-sections + -I "$(pwd)/core/wasm_shims" + -I "$linuxcnc_root/src" + -I "$linuxcnc_root/src/emc" + -I "$linuxcnc_root/src/emc/nml_intf" + -I "$linuxcnc_root/src/emc/rs274ngc" + -I "$linuxcnc_root/src/emc/motion" + -I "$linuxcnc_root/include" +) + +"$cxx" "${common_flags[@]}" \ + -c "$linuxcnc_root/src/emc/tooldata/tooldata_common.cc" \ + -o "$build_dir/tooldata_common.o" +"$cxx" "${common_flags[@]}" \ + -c core/wasm_shims/tooldata/tooldata_mmap_backend.cc \ + -o "$build_dir/tooldata_mmap_backend.o" +"$cxx" "${common_flags[@]}" \ + -c core/wasm_shims/tooldata/tooldata_runtime_stubs.cc \ + -o "$build_dir/tooldata_runtime_stubs.o" +"$cxx" "${common_flags[@]}" \ + -c core/wasm_shims/rtapi_compat.cc \ + -o "$build_dir/rtapi_compat.o" +"$cxx" "${common_flags[@]}" \ + -c core/wasm_shims/interp_find_probe_stubs.cc \ + -o "$build_dir/interp_find_probe_stubs.o" +"$cxx" "${common_flags[@]}" \ + -c "$linuxcnc_root/src/emc/rs274ngc/interp_find.cc" \ + -o "$build_dir/interp_find.o" +"$cxx" "${common_flags[@]}" \ + -c core/wasm_shims/interp_find_probe_main.cc \ + -o "$build_dir/interp_find_probe_main.o" + +"$cxx" \ + "$build_dir/tooldata_common.o" \ + "$build_dir/tooldata_mmap_backend.o" \ + "$build_dir/tooldata_runtime_stubs.o" \ + "$build_dir/rtapi_compat.o" \ + "$build_dir/interp_find_probe_stubs.o" \ + "$build_dir/interp_find.o" \ + "$build_dir/interp_find_probe_main.o" \ + -Wl,--gc-sections \ + -o "$build_dir/interp_find_probe" + +"$build_dir/interp_find_probe" + +echo "linuxcnc wasm interp_find link probe passed" diff --git a/test-linuxcnc-wasm-python-plugin-link.sh b/test-linuxcnc-wasm-python-plugin-link.sh new file mode 100755 index 0000000..42546ad --- /dev/null +++ b/test-linuxcnc-wasm-python-plugin-link.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")" + +linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} +cxx=${CXX:-g++} +build_dir=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_linuxcnc_wasm_python_plugin_link.XXXXXX") + +if [[ ! -d "$linuxcnc_root" ]]; then + echo "missing LinuxCNC root: $linuxcnc_root" >&2 + exit 1 +fi +trap 'rm -rf "$build_dir"' EXIT + +common_flags=( + -std=c++17 + -DULAPI + -I "$(pwd)/core/wasm_shims" + -I "$linuxcnc_root/src" + -I "$linuxcnc_root/src/emc" + -I "$linuxcnc_root/src/emc/nml_intf" + -I "$linuxcnc_root/src/emc/rs274ngc" + -I "$linuxcnc_root/src/emc/motion" + -I "$linuxcnc_root/include" +) + +"$cxx" "${common_flags[@]}" \ + core/wasm_shims/pythonplugin/python_plugin.cc \ + core/wasm_shims/pythonplugin/python_plugin_probe_main.cc \ + -o "$build_dir/python_plugin_probe" + +"$build_dir/python_plugin_probe" + +echo "linuxcnc wasm python_plugin link probe passed" diff --git a/test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh b/test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh new file mode 100755 index 0000000..55059f3 --- /dev/null +++ b/test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh @@ -0,0 +1,167 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")" + +linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} +cxx=${CXX:-g++} +build_dir=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_linuxcnc_wasm_rs274ngc_pre_link_blockers.XXXXXX") +report_file=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_linuxcnc_wasm_rs274ngc_pre_undefined.XXXXXX.txt") +manifest=${1:-linuxcnc-rs274-wasm-source-files.txt} + +if [[ ! -f "$manifest" ]]; then + echo "missing manifest: $manifest" >&2 + exit 1 +fi + +if [[ ! -d "$linuxcnc_root" ]]; then + echo "missing LinuxCNC root: $linuxcnc_root" >&2 + exit 1 +fi +trap 'rm -rf "$build_dir"' EXIT + +common_flags=( + -std=c++20 + -DULAPI + -ffunction-sections + -fdata-sections + -include wctype.h + -I "$(pwd)/core/include" + -I "$(pwd)/core/wasm_shims" + -I "$linuxcnc_root/src" + -I "$linuxcnc_root/src/emc" + -I "$linuxcnc_root/src/emc/nml_intf" + -I "$linuxcnc_root/src/emc/rs274ngc" + -I "$linuxcnc_root/src/emc/motion" + -I "$linuxcnc_root/include" +) + +shim_sources=( + core/wasm_shims/dlfcn.cc + core/wasm_shims/emc_status_shim.cc + core/wasm_shims/gettext_shim.cc + core/wasm_shims/linuxcnc_runtime_shim.cc + core/wasm_shims/python_c_api_shim.cc + core/wasm_shims/rtapi_compat.cc + core/wasm_shims/tooldata/tooldata_mmap_backend.cc + core/wasm_shims/tooldata/tooldata_runtime_stubs.cc + core/wasm_shims/pythonplugin/python_plugin.cc +) + +project_sources=( + core/src/canon_event_sink.cpp + core/src/linuxcnc_canon_bridge.cpp + core/src/linuxcnc_tooldata_fixture.cpp + core/src/rtcp_kinematics.cpp +) + +linuxcnc_sources=() +while IFS=: read -r group path note; do + case "$group" in + core) + if [[ ! -f "$linuxcnc_root/$path" ]]; then + echo "missing manifest source: $path" >&2 + exit 1 + fi + linuxcnc_sources+=("$linuxcnc_root/$path") + ;; + ""|\#*|blocked) + ;; + *) + echo "unknown manifest group: $group" >&2 + exit 1 + ;; + esac +done < "$manifest" + +shim_index=0 +for source in "${shim_sources[@]}"; do + obj="$build_dir/shim_${shim_index}.o" + "$cxx" "${common_flags[@]}" -c "$source" -o "$obj" + shim_index=$((shim_index + 1)) +done + +project_index=0 +for source in "${project_sources[@]}"; do + obj="$build_dir/project_${project_index}.o" + "$cxx" "${common_flags[@]}" -c "$source" -o "$obj" + project_index=$((project_index + 1)) +done + +linuxcnc_index=0 +for source in "${linuxcnc_sources[@]}"; do + obj="$build_dir/linuxcnc_${linuxcnc_index}.o" + "$cxx" "${common_flags[@]}" -c "$source" -o "$obj" + linuxcnc_index=$((linuxcnc_index + 1)) +done + +undefined_file="$build_dir/undefined.raw" +defined_file="$build_dir/defined.raw" + +nm -u "$build_dir"/*.o \ + | awk '{print $2}' \ + | sed '/^$/d' \ + | LC_ALL=C sort -u \ + > "$undefined_file" + +nm --defined-only "$build_dir"/*.o \ + | awk '{print $3}' \ + | sed '/^$/d' \ + | LC_ALL=C sort -u \ + > "$defined_file" + +{ + comm -23 "$undefined_file" "$defined_file" \ + | grep -Ev '^(_GLOBAL_OFFSET_TABLE_|_Unwind_Resume)$' \ + | grep -Ev '^(__assert_fail|__cxa_|__divdc3|__dso_handle|__errno_location|__gxx_personality_v0|__isoc23_|__muldc3|__stack_chk_fail)' \ + | grep -Ev '^(abort|access|acos|asin|atan2|atof|atoi|basename|cabs|calloc|ceil|close|closedir|copysign|cos|cosl|exit|exp|fclose|fdatasync|fdopen|feof|fflush|fgetc|fgets|fileno|floor|fmod|fopen|fprintf|fputs|free|freelocale|fseek|fstat|ftell|fwrite|getcwd|getenv|getpid|gettimeofday|hypot|islower|isprint|isspace|isupper|isxdigit|link|localtime|log|lstat|malloc|memchr|memcmp|memcpy|memmove|memset|mkstemp|nearbyint|newlocale|open|opendir|perror|pow|printf|pthread_mutex_lock|pthread_mutex_unlock|putc|puts|read|readdir|realpath|realloc|remove|rename|setlocale|sin|sinl|snprintf|sprintf|sqrt|sscanf|stat|stderr|stdout|strcasecmp|strcat|strchr|strcmp|strcpy|strdup|strerror|strlen|strncasecmp|strncat|strncmp|strncpy|strrchr|strspn|strstr|strtod|strtok_r|strtol|strtoul|tan|tanl|tolower|toupper|towlower|unlink|uselocale|vfprintf|vsnprintf|wordexp|wordfree)$' \ + | grep -Ev '^(_ZN3fmt|_ZSt|_ZNSt|_ZNKSt|_ZNKRSt|_ZNS|_ZTI|_ZTS|_ZTV|_Zdl|_Znwm|_Znam|_Zda|_ZdaPv|_ZdaPvm|_ZdlPvm|_ZTv|_ZTh)' +} > "$report_file" || true + +if grep -F "_ZN6Interp13read_commentEPcPiP12block_structPd" "$report_file" >/dev/null; then + echo "unexpected unresolved read_comment after interp_read.cc" >&2 + exit 1 +fi +if grep -F "_ZN6Interp15find_tool_indexEP5setupiPi" "$report_file" >/dev/null; then + echo "unexpected unresolved find_tool_index after interp_find.cc" >&2 + exit 1 +fi +if grep -F "_ZN6Interp16find_named_paramEPKcPiPd" "$report_file" >/dev/null; then + echo "unexpected unresolved find_named_param after interp_namedparams.cc" >&2 + exit 1 +fi +if grep -Fx "emcStatus" "$report_file" >/dev/null; then + echo "unexpected unresolved emcStatus after shim" >&2 + exit 1 +fi +if grep -Fx "gettext" "$report_file" >/dev/null; then + echo "unexpected unresolved gettext after shim" >&2 + exit 1 +fi +if grep -Fx "_task" "$report_file" >/dev/null; then + echo "unexpected unresolved _task after runtime shim" >&2 + exit 1 +fi +if grep -Fx "builtin_modules" "$report_file" >/dev/null; then + echo "unexpected unresolved builtin_modules after runtime shim" >&2 + exit 1 +fi +for hal_symbol in \ + hal_get_param_value_by_name \ + hal_get_pin_value_by_name \ + hal_get_signal_value_by_name \ + hal_init \ + hal_ready; do + if grep -Fx "$hal_symbol" "$report_file" >/dev/null; then + echo "unexpected unresolved $hal_symbol after runtime shim" >&2 + exit 1 + fi +done +if [[ -s "$report_file" ]]; then + echo "unexpected unresolved project symbols after rs274ngc_pre shim coverage:" >&2 + sed -n '1,120p' "$report_file" >&2 + exit 1 +fi + +echo "linuxcnc wasm rs274ngc_pre link blocker scan passed" +echo "wrote $report_file" diff --git a/test-linuxcnc-wasm-rs274ngc-pre-link.sh b/test-linuxcnc-wasm-rs274ngc-pre-link.sh new file mode 100755 index 0000000..e143dcf --- /dev/null +++ b/test-linuxcnc-wasm-rs274ngc-pre-link.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")" + +linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} +cxx=${CXX:-g++} +build_dir=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_linuxcnc_wasm_rs274ngc_pre_link.XXXXXX") +manifest=${1:-linuxcnc-rs274-wasm-source-files.txt} + +if [[ ! -f "$manifest" ]]; then + echo "missing manifest: $manifest" >&2 + exit 1 +fi + +if [[ ! -d "$linuxcnc_root" ]]; then + echo "missing LinuxCNC root: $linuxcnc_root" >&2 + exit 1 +fi +trap 'rm -rf "$build_dir"' EXIT + +common_flags=( + -std=c++20 + -DULAPI + -ffunction-sections + -fdata-sections + -I "$(pwd)/core/src" + -include wctype.h + -I "$(pwd)/core/include" + -I "$(pwd)/core/wasm_shims" + -I "$linuxcnc_root/src" + -I "$linuxcnc_root/src/emc" + -I "$linuxcnc_root/src/emc/nml_intf" + -I "$linuxcnc_root/src/emc/rs274ngc" + -I "$linuxcnc_root/src/emc/motion" + -I "$linuxcnc_root/include" +) + +sources=( + core/wasm_shims/dlfcn.cc + core/wasm_shims/emc_status_shim.cc + core/wasm_shims/gettext_shim.cc + core/wasm_shims/linuxcnc_runtime_shim.cc + core/wasm_shims/python_c_api_shim.cc + core/wasm_shims/rtapi_compat.cc + core/wasm_shims/tooldata/tooldata_mmap_backend.cc + core/wasm_shims/tooldata/tooldata_runtime_stubs.cc + core/wasm_shims/pythonplugin/python_plugin.cc + core/src/canon_event_sink.cpp + core/src/linuxcnc_canon_bridge.cpp + core/src/linuxcnc_tooldata_fixture.cpp + core/src/rtcp_kinematics.cpp +) + +while IFS=: read -r group path note; do + case "$group" in + core) + if [[ ! -f "$linuxcnc_root/$path" ]]; then + echo "missing manifest source: $path" >&2 + exit 1 + fi + sources+=("$linuxcnc_root/$path") + ;; + ""|\#*|blocked) + ;; + *) + echo "unknown manifest group: $group" >&2 + exit 1 + ;; + esac +done < "$manifest" + +sources+=(core/wasm_shims/rs274ngc_pre_probe_main.cc) + +objects=() +object_index=0 +for source in "${sources[@]}"; do + obj="$build_dir/source_${object_index}.o" + "$cxx" "${common_flags[@]}" -c "$source" -o "$obj" + objects+=("$obj") + object_index=$((object_index + 1)) +done + +"$cxx" "${objects[@]}" \ + -Wl,--gc-sections \ + -lfmt \ + -o "$build_dir/rs274ngc_pre_probe" + +"$build_dir/rs274ngc_pre_probe" + +echo "linuxcnc wasm rs274ngc_pre link probe passed" diff --git a/test-linuxcnc-wasm-rs274ngc-pre-object.sh b/test-linuxcnc-wasm-rs274ngc-pre-object.sh new file mode 100755 index 0000000..fae2531 --- /dev/null +++ b/test-linuxcnc-wasm-rs274ngc-pre-object.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")" + +linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} +cxx=${CXX:-g++} +build_dir=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_linuxcnc_wasm_rs274ngc_pre_object.XXXXXX") + +if [[ ! -d "$linuxcnc_root" ]]; then + echo "missing LinuxCNC root: $linuxcnc_root" >&2 + exit 1 +fi +if [[ ! -f "$linuxcnc_root/src/emc/rs274ngc/rs274ngc_pre.cc" ]]; then + echo "missing LinuxCNC source: src/emc/rs274ngc/rs274ngc_pre.cc" >&2 + exit 1 +fi +trap 'rm -rf "$build_dir"' EXIT + +common_flags=( + -std=c++17 + -DULAPI + -I "$(pwd)/core/wasm_shims" + -I "$linuxcnc_root/src" + -I "$linuxcnc_root/src/emc" + -I "$linuxcnc_root/src/emc/nml_intf" + -I "$linuxcnc_root/src/emc/rs274ngc" + -I "$linuxcnc_root/src/emc/motion" + -I "$linuxcnc_root/include" +) + +"$cxx" "${common_flags[@]}" \ + -c "$linuxcnc_root/src/emc/rs274ngc/rs274ngc_pre.cc" \ + -o "$build_dir/rs274ngc_pre.o" + +echo "linuxcnc wasm rs274ngc_pre object probe passed" diff --git a/test-linuxcnc-wasm-source-objects.sh b/test-linuxcnc-wasm-source-objects.sh new file mode 100755 index 0000000..994e350 --- /dev/null +++ b/test-linuxcnc-wasm-source-objects.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")" + +linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} +manifest=${1:-linuxcnc-rs274-wasm-source-files.txt} +cxx=${CXX:-g++} +build_dir=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_linuxcnc_wasm_safe_probe.XXXXXX") +shim_sources=( + core/wasm_shims/dlfcn.cc + core/wasm_shims/emc_status_shim.cc + core/wasm_shims/gettext_shim.cc + core/wasm_shims/linuxcnc_runtime_shim.cc + core/wasm_shims/python_c_api_shim.cc + core/wasm_shims/rtapi_compat.cc + core/wasm_shims/tooldata/tooldata_mmap_backend.cc + core/wasm_shims/tooldata/tooldata_runtime_stubs.cc + core/wasm_shims/pythonplugin/python_plugin.cc +) + +if [[ ! -f "$manifest" ]]; then + echo "missing manifest: $manifest" >&2 + exit 1 +fi + +if [[ ! -d "$linuxcnc_root" ]]; then + echo "missing LinuxCNC root: $linuxcnc_root" >&2 + exit 1 +fi +trap 'rm -rf "$build_dir"' EXIT + +common_flags=( + -std=c++20 + -DULAPI + -include wctype.h + -I "$(pwd)/core/include" + -I "$(pwd)/core/src" + -I "$(pwd)/core/wasm_shims" + -I "$linuxcnc_root/src" + -I "$linuxcnc_root/src/emc" + -I "$linuxcnc_root/src/emc/nml_intf" + -I "$linuxcnc_root/src/emc/rs274ngc" + -I "$linuxcnc_root/src/emc/motion" + -I "$linuxcnc_root/include" +) + +sources=() +while IFS=: read -r group path note; do + case "$group" in + core) + if [[ ! -f "$linuxcnc_root/$path" ]]; then + echo "missing manifest source: $path" >&2 + exit 1 + fi + sources+=("$path") + ;; + ""|\#*|blocked) + ;; + *) + echo "unknown manifest group: $group" >&2 + exit 1 + ;; + esac +done < "$manifest" + +shim_index=0 +for source in "${shim_sources[@]}"; do + obj="$build_dir/shim_${shim_index}.o" + "$cxx" "${common_flags[@]}" -c "$source" -o "$obj" + shim_index=$((shim_index + 1)) +done + +source_index=0 +for source in "${sources[@]}"; do + obj="$build_dir/linuxcnc_${source_index}.o" + "$cxx" "${common_flags[@]}" -c "$linuxcnc_root/$source" -o "$obj" + source_index=$((source_index + 1)) +done + +echo "linuxcnc wasm-safe source object probe passed (${#sources[@]} linuxcnc objects + ${#shim_sources[@]} shims)" diff --git a/test-linuxcnc-wasm-source-syntax.sh b/test-linuxcnc-wasm-source-syntax.sh new file mode 100755 index 0000000..11f9f23 --- /dev/null +++ b/test-linuxcnc-wasm-source-syntax.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")" + +linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} +manifest=${1:-linuxcnc-rs274-wasm-source-files.txt} +cxx=${CXX:-g++} + +if [[ ! -f "$manifest" ]]; then + echo "missing manifest: $manifest" >&2 + exit 1 +fi + +if [[ ! -d "$linuxcnc_root" ]]; then + echo "missing LinuxCNC root: $linuxcnc_root" >&2 + exit 1 +fi + +common_flags=( + -std=c++20 + -DULAPI + -fsyntax-only + -include wctype.h + -I "$(pwd)/core/include" + -I "$(pwd)/core/src" + -I "$(pwd)/core/wasm_shims" + -I "$linuxcnc_root/src" + -I "$linuxcnc_root/src/emc" + -I "$linuxcnc_root/src/emc/nml_intf" + -I "$linuxcnc_root/src/emc/rs274ngc" + -I "$linuxcnc_root/src/emc/motion" + -I "$linuxcnc_root/include" +) + +sources=() +while IFS=: read -r group path note; do + case "$group" in + core) + if [[ ! -f "$linuxcnc_root/$path" ]]; then + echo "missing manifest source: $path" >&2 + exit 1 + fi + sources+=("$path") + ;; + ""|\#*|blocked) + ;; + *) + echo "unknown manifest group: $group" >&2 + exit 1 + ;; + esac +done < "$manifest" + +for source in "${sources[@]}"; do + "$cxx" "${common_flags[@]}" "$linuxcnc_root/$source" +done + +echo "linuxcnc wasm-safe source syntax probe passed (${#sources[@]} files)" diff --git a/test-linuxcnc-wasm-tooldata-common-link.sh b/test-linuxcnc-wasm-tooldata-common-link.sh new file mode 100755 index 0000000..4e9e27c --- /dev/null +++ b/test-linuxcnc-wasm-tooldata-common-link.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")" + +linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} +cxx=${CXX:-g++} +build_dir=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_linuxcnc_wasm_tooldata_common_link.XXXXXX") + +if [[ ! -d "$linuxcnc_root" ]]; then + echo "missing LinuxCNC root: $linuxcnc_root" >&2 + exit 1 +fi +if [[ ! -f "$linuxcnc_root/src/emc/tooldata/tooldata_common.cc" ]]; then + echo "missing LinuxCNC source: src/emc/tooldata/tooldata_common.cc" >&2 + exit 1 +fi +trap 'rm -rf "$build_dir"' EXIT + +common_flags=( + -std=c++17 + -DULAPI + -I "$linuxcnc_root/src/emc/tooldata" + -I "$linuxcnc_root/src" + -I "$linuxcnc_root/src/emc" + -I "$linuxcnc_root/src/emc/nml_intf" + -I "$linuxcnc_root/src/emc/rs274ngc" + -I "$linuxcnc_root/src/emc/motion" + -I "$linuxcnc_root/include" +) + +"$cxx" "${common_flags[@]}" \ + "$linuxcnc_root/src/emc/tooldata/tooldata_common.cc" \ + core/wasm_shims/tooldata/tooldata_mmap_backend.cc \ + core/wasm_shims/tooldata/tooldata_runtime_stubs.cc \ + core/wasm_shims/tooldata/tooldata_common_probe_main.cc \ + -o "$build_dir/tooldata_common_probe" + +"$build_dir/tooldata_common_probe" + +echo "linuxcnc wasm tooldata_common link probe passed" diff --git a/test-linuxcnc-wasm-tooldata-link.sh b/test-linuxcnc-wasm-tooldata-link.sh new file mode 100755 index 0000000..144c389 --- /dev/null +++ b/test-linuxcnc-wasm-tooldata-link.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")" + +linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} +cxx=${CXX:-g++} +build_dir=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_linuxcnc_wasm_tooldata_link.XXXXXX") + +if [[ ! -d "$linuxcnc_root" ]]; then + echo "missing LinuxCNC root: $linuxcnc_root" >&2 + exit 1 +fi +if [[ ! -f "$linuxcnc_root/src/emc/tooldata/tooldata_common.cc" ]]; then + echo "missing LinuxCNC source: src/emc/tooldata/tooldata_common.cc" >&2 + exit 1 +fi +trap 'rm -rf "$build_dir"' EXIT + +common_flags=( + -std=c++17 + -DULAPI + -I "$(pwd)/core/wasm_shims" + -I "$linuxcnc_root/src" + -I "$linuxcnc_root/src/emc" + -I "$linuxcnc_root/src/emc/nml_intf" + -I "$linuxcnc_root/src/emc/rs274ngc" + -I "$linuxcnc_root/src/emc/motion" + -I "$linuxcnc_root/include" +) + +"$cxx" "${common_flags[@]}" \ + "$linuxcnc_root/src/emc/tooldata/tooldata_common.cc" \ + core/wasm_shims/tooldata/tooldata_mmap_backend.cc \ + core/wasm_shims/tooldata/tooldata_runtime_stubs.cc \ + core/wasm_shims/tooldata/tooldata_probe_main.cc \ + -o "$build_dir/tooldata_probe" + +"$build_dir/tooldata_probe" + +echo "linuxcnc wasm tooldata shim link probe passed" diff --git a/test-linuxcnc-wasm-tooldata-mmap-symbols.sh b/test-linuxcnc-wasm-tooldata-mmap-symbols.sh new file mode 100755 index 0000000..1e12f3c --- /dev/null +++ b/test-linuxcnc-wasm-tooldata-mmap-symbols.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")" + +linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} +cxx=${CXX:-g++} +build_dir=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_linuxcnc_wasm_tooldata_mmap_symbols.XXXXXX") + +if [[ ! -d "$linuxcnc_root" ]]; then + echo "missing LinuxCNC root: $linuxcnc_root" >&2 + exit 1 +fi +trap 'rm -rf "$build_dir"' EXIT + +common_flags=( + -std=c++17 + -DULAPI + -I "$(pwd)/core/wasm_shims" + -I "$linuxcnc_root/src" + -I "$linuxcnc_root/src/emc" + -I "$linuxcnc_root/src/emc/nml_intf" + -I "$linuxcnc_root/src/emc/rs274ngc" + -I "$linuxcnc_root/src/emc/motion" + -I "$linuxcnc_root/include" +) + +"$cxx" "${common_flags[@]}" \ + -c core/wasm_shims/tooldata/tooldata_mmap_backend.cc \ + -o "$build_dir/tooldata_mmap_backend.o" + +nm --defined-only "$build_dir/tooldata_mmap_backend.o" \ + | awk '$2 ~ /^[A-Z]$/ {print $3}' \ + | LC_ALL=C sort -u \ + > "$build_dir/backend.exports" + +expected_exports=( + tool_mmap_close + tool_mmap_creator + tool_mmap_is_random_toolchanger + tool_mmap_user + tooldata_find_index_for_tool + tooldata_get + tooldata_last_index_get + tooldata_last_index_set + tooldata_put + tooldata_reset +) + +printf '%s\n' "${expected_exports[@]}" | LC_ALL=C sort -u > "$build_dir/expected.exports" + +if ! comm -23 "$build_dir/expected.exports" "$build_dir/backend.exports" > "$build_dir/missing.exports"; then + exit 1 +fi + +if [[ -s "$build_dir/missing.exports" ]]; then + echo "missing wasm tooldata mmap backend exports:" >&2 + cat "$build_dir/missing.exports" >&2 + exit 1 +fi + +comm -13 "$build_dir/expected.exports" "$build_dir/backend.exports" > "$build_dir/unexpected.exports" +if [[ -s "$build_dir/unexpected.exports" ]]; then + echo "unexpected wasm tooldata mmap backend exports:" >&2 + cat "$build_dir/unexpected.exports" >&2 + exit 1 +fi + +echo "linuxcnc wasm tooldata_mmap symbol probe passed" diff --git a/test-linuxcnc-wasm-tooldata-runtime-symbols.sh b/test-linuxcnc-wasm-tooldata-runtime-symbols.sh new file mode 100755 index 0000000..e85a76e --- /dev/null +++ b/test-linuxcnc-wasm-tooldata-runtime-symbols.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")" + +linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} +cxx=${CXX:-g++} +build_dir=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_linuxcnc_wasm_tooldata_runtime_symbols.XXXXXX") + +if [[ ! -d "$linuxcnc_root" ]]; then + echo "missing LinuxCNC root: $linuxcnc_root" >&2 + exit 1 +fi +trap 'rm -rf "$build_dir"' EXIT + +common_flags=( + -std=c++17 + -DULAPI + -I "$(pwd)/core/wasm_shims" + -I "$linuxcnc_root/src" + -I "$linuxcnc_root/src/emc" + -I "$linuxcnc_root/src/emc/nml_intf" + -I "$linuxcnc_root/src/emc/rs274ngc" + -I "$linuxcnc_root/src/emc/motion" + -I "$linuxcnc_root/include" +) + +"$cxx" "${common_flags[@]}" \ + -c core/wasm_shims/tooldata/tooldata_runtime_stubs.cc \ + -o "$build_dir/tooldata_runtime_stubs.o" + +nm --defined-only "$build_dir/tooldata_runtime_stubs.o" \ + | awk '$2 ~ /^[A-Z]$/ {print $3}' \ + | LC_ALL=C sort -u \ + > "$build_dir/runtime.exports" + +expected_exports=( + tool_nml_register + tooldata_db_getall + tooldata_db_init + tooldata_db_notify +) + +printf '%s\n' "${expected_exports[@]}" | LC_ALL=C sort -u > "$build_dir/expected.exports" + +comm -23 "$build_dir/expected.exports" "$build_dir/runtime.exports" > "$build_dir/missing.exports" +if [[ -s "$build_dir/missing.exports" ]]; then + echo "missing wasm tooldata runtime stub exports:" >&2 + cat "$build_dir/missing.exports" >&2 + exit 1 +fi + +comm -13 "$build_dir/expected.exports" "$build_dir/runtime.exports" > "$build_dir/unexpected.exports" +if [[ -s "$build_dir/unexpected.exports" ]]; then + echo "unexpected wasm tooldata runtime stub exports:" >&2 + cat "$build_dir/unexpected.exports" >&2 + exit 1 +fi + +echo "linuxcnc wasm tooldata runtime symbol probe passed" diff --git a/test-native.sh b/test-native.sh index 817daa6..a5588d1 100755 --- a/test-native.sh +++ b/test-native.sh @@ -4,8 +4,8 @@ set -euo pipefail cd "$(dirname "$0")" cxx=${CXX:-g++} -build_dir=${TMPDIR:-/tmp}/cnc_sim_native -mkdir -p "$build_dir" +build_dir=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_native.XXXXXX") +trap 'rm -rf "$build_dir"' EXIT "$cxx" -std=c++17 -I core/include -I core/src \ core/src/canon_event_sink.cpp \ @@ -49,13 +49,18 @@ mkdir -p "$build_dir" "$build_dir/rtcp_kinematics_smoke" "$build_dir/simulator_gcode_controls_smoke" "$build_dir/cnc_sim_api_smoke" -"$build_dir/cnc_sim_dump" tests/gcode/basic_mill.ngc >/tmp/cnc_sim_basic_mill.json -"$build_dir/cnc_sim_dump" tests/gcode/incremental_and_r_arc.ngc >/tmp/cnc_sim_incremental_and_r_arc.json -"$build_dir/cnc_sim_dump" tests/gcode/smoke_subprogram_m98.ngc >/tmp/cnc_sim_smoke_subprogram_m98.json -"$build_dir/cnc_sim_dump" tests/gcode/smoke_oword_subprogram.ngc >/tmp/cnc_sim_smoke_oword_subprogram.json +basic_mill_json="$build_dir/cnc_sim_basic_mill.json" +incremental_arc_json="$build_dir/cnc_sim_incremental_and_r_arc.json" +subprogram_m98_json="$build_dir/cnc_sim_smoke_subprogram_m98.json" +oword_subprogram_json="$build_dir/cnc_sim_smoke_oword_subprogram.json" + +"$build_dir/cnc_sim_dump" tests/gcode/basic_mill.ngc >"$basic_mill_json" +"$build_dir/cnc_sim_dump" tests/gcode/incremental_and_r_arc.ngc >"$incremental_arc_json" +"$build_dir/cnc_sim_dump" tests/gcode/smoke_subprogram_m98.ngc >"$subprogram_m98_json" +"$build_dir/cnc_sim_dump" tests/gcode/smoke_oword_subprogram.ngc >"$oword_subprogram_json" echo "native tests passed" -echo "dumped /tmp/cnc_sim_basic_mill.json" -echo "dumped /tmp/cnc_sim_incremental_and_r_arc.json" -echo "dumped /tmp/cnc_sim_smoke_subprogram_m98.json" -echo "dumped /tmp/cnc_sim_smoke_oword_subprogram.json" +echo "dumped $basic_mill_json" +echo "dumped $incremental_arc_json" +echo "dumped $subprogram_m98_json" +echo "dumped $oword_subprogram_json" diff --git a/tests/gcode/linuxcnc_block_delete.ngc b/tests/gcode/linuxcnc_block_delete.ngc new file mode 100644 index 0000000..b524d98 --- /dev/null +++ b/tests/gcode/linuxcnc_block_delete.ngc @@ -0,0 +1,4 @@ +G21 G90 G17 F100 + /N10 G1 X5 +G1 X2 +M30 diff --git a/tests/gcode/linuxcnc_canned_cycle_a_error.ngc b/tests/gcode/linuxcnc_canned_cycle_a_error.ngc new file mode 100644 index 0000000..b7fc874 --- /dev/null +++ b/tests/gcode/linuxcnc_canned_cycle_a_error.ngc @@ -0,0 +1,2 @@ +G21 G90 G17 +G81 X1 A10 Z-1 R1 diff --git a/tests/gcode/linuxcnc_canned_cycle_b_error.ngc b/tests/gcode/linuxcnc_canned_cycle_b_error.ngc new file mode 100644 index 0000000..a6f8486 --- /dev/null +++ b/tests/gcode/linuxcnc_canned_cycle_b_error.ngc @@ -0,0 +1,2 @@ +G21 G90 G17 +G81 X1 B10 Z-1 R1 diff --git a/tests/gcode/linuxcnc_canned_cycle_c_error.ngc b/tests/gcode/linuxcnc_canned_cycle_c_error.ngc new file mode 100644 index 0000000..aeaee15 --- /dev/null +++ b/tests/gcode/linuxcnc_canned_cycle_c_error.ngc @@ -0,0 +1,2 @@ +G21 G90 G17 +G81 X1 C10 Z-1 R1 diff --git a/tests/gcode/linuxcnc_canned_cycle_g18_missing_y_error.ngc b/tests/gcode/linuxcnc_canned_cycle_g18_missing_y_error.ngc new file mode 100644 index 0000000..5a128c7 --- /dev/null +++ b/tests/gcode/linuxcnc_canned_cycle_g18_missing_y_error.ngc @@ -0,0 +1,3 @@ +G21 G90 G18 +F100 +G81 X1 Z-1 R1 diff --git a/tests/gcode/linuxcnc_canned_cycle_g18_r_less_y_error.ngc b/tests/gcode/linuxcnc_canned_cycle_g18_r_less_y_error.ngc new file mode 100644 index 0000000..052d717 --- /dev/null +++ b/tests/gcode/linuxcnc_canned_cycle_g18_r_less_y_error.ngc @@ -0,0 +1,3 @@ +G21 G90 G18 +F100 +G81 X1 Y2 Z-1 R1 diff --git a/tests/gcode/linuxcnc_canned_cycle_g19_missing_x_error.ngc b/tests/gcode/linuxcnc_canned_cycle_g19_missing_x_error.ngc new file mode 100644 index 0000000..60198b2 --- /dev/null +++ b/tests/gcode/linuxcnc_canned_cycle_g19_missing_x_error.ngc @@ -0,0 +1,3 @@ +G21 G90 G19 +F100 +G81 Y1 Z-1 R1 diff --git a/tests/gcode/linuxcnc_canned_cycle_g19_r_less_x_error.ngc b/tests/gcode/linuxcnc_canned_cycle_g19_r_less_x_error.ngc new file mode 100644 index 0000000..0275315 --- /dev/null +++ b/tests/gcode/linuxcnc_canned_cycle_g19_r_less_x_error.ngc @@ -0,0 +1,3 @@ +G21 G90 G19 +F100 +G81 X2 Y1 Z-1 R1 diff --git a/tests/gcode/linuxcnc_canned_cycle_g73_missing_q_error.ngc b/tests/gcode/linuxcnc_canned_cycle_g73_missing_q_error.ngc new file mode 100644 index 0000000..3781124 --- /dev/null +++ b/tests/gcode/linuxcnc_canned_cycle_g73_missing_q_error.ngc @@ -0,0 +1,3 @@ +G21 G90 G17 +F100 +G73 X1 Z-1 R1 diff --git a/tests/gcode/linuxcnc_canned_cycle_g73_q0_error.ngc b/tests/gcode/linuxcnc_canned_cycle_g73_q0_error.ngc new file mode 100644 index 0000000..e1923af --- /dev/null +++ b/tests/gcode/linuxcnc_canned_cycle_g73_q0_error.ngc @@ -0,0 +1,3 @@ +G21 G90 G17 +F100 +G73 X1 Z-1 R1 Q0 diff --git a/tests/gcode/linuxcnc_canned_cycle_g74_clockwise_error.ngc b/tests/gcode/linuxcnc_canned_cycle_g74_clockwise_error.ngc new file mode 100644 index 0000000..fe199d4 --- /dev/null +++ b/tests/gcode/linuxcnc_canned_cycle_g74_clockwise_error.ngc @@ -0,0 +1,4 @@ +G21 G90 G17 +F100 +S100 M3 +G74 X1 Z-1 R1 diff --git a/tests/gcode/linuxcnc_canned_cycle_g74_stopped_spindle_error.ngc b/tests/gcode/linuxcnc_canned_cycle_g74_stopped_spindle_error.ngc new file mode 100644 index 0000000..c544296 --- /dev/null +++ b/tests/gcode/linuxcnc_canned_cycle_g74_stopped_spindle_error.ngc @@ -0,0 +1,3 @@ +G21 G90 G17 +F100 +G74 X1 Z-1 R1 diff --git a/tests/gcode/linuxcnc_canned_cycle_g82_missing_p_error.ngc b/tests/gcode/linuxcnc_canned_cycle_g82_missing_p_error.ngc new file mode 100644 index 0000000..4a1ef60 --- /dev/null +++ b/tests/gcode/linuxcnc_canned_cycle_g82_missing_p_error.ngc @@ -0,0 +1,3 @@ +G21 G90 G17 +F100 +G82 X1 Z-1 R1 diff --git a/tests/gcode/linuxcnc_canned_cycle_g83_missing_q_error.ngc b/tests/gcode/linuxcnc_canned_cycle_g83_missing_q_error.ngc new file mode 100644 index 0000000..63b5e4d --- /dev/null +++ b/tests/gcode/linuxcnc_canned_cycle_g83_missing_q_error.ngc @@ -0,0 +1,3 @@ +G21 G90 G17 +F100 +G83 X1 Z-1 R1 diff --git a/tests/gcode/linuxcnc_canned_cycle_g83_q0_error.ngc b/tests/gcode/linuxcnc_canned_cycle_g83_q0_error.ngc new file mode 100644 index 0000000..7f58816 --- /dev/null +++ b/tests/gcode/linuxcnc_canned_cycle_g83_q0_error.ngc @@ -0,0 +1,3 @@ +G21 G90 G17 +F100 +G83 X1 Z-1 R1 Q0 diff --git a/tests/gcode/linuxcnc_canned_cycle_g84_counterclockwise_error.ngc b/tests/gcode/linuxcnc_canned_cycle_g84_counterclockwise_error.ngc new file mode 100644 index 0000000..c7360ca --- /dev/null +++ b/tests/gcode/linuxcnc_canned_cycle_g84_counterclockwise_error.ngc @@ -0,0 +1,4 @@ +G21 G90 G17 +F100 +S100 M4 +G84 X1 Z-1 R1 diff --git a/tests/gcode/linuxcnc_canned_cycle_g84_stopped_spindle_error.ngc b/tests/gcode/linuxcnc_canned_cycle_g84_stopped_spindle_error.ngc new file mode 100644 index 0000000..0ebe7eb --- /dev/null +++ b/tests/gcode/linuxcnc_canned_cycle_g84_stopped_spindle_error.ngc @@ -0,0 +1,3 @@ +G21 G90 G17 +F100 +G84 X1 Z-1 R1 diff --git a/tests/gcode/linuxcnc_canned_cycle_g86_missing_p_error.ngc b/tests/gcode/linuxcnc_canned_cycle_g86_missing_p_error.ngc new file mode 100644 index 0000000..0b136b2 --- /dev/null +++ b/tests/gcode/linuxcnc_canned_cycle_g86_missing_p_error.ngc @@ -0,0 +1,3 @@ +G21 G90 G17 +F100 +G86 X1 Z-1 R1 diff --git a/tests/gcode/linuxcnc_canned_cycle_g86_stopped_spindle_error.ngc b/tests/gcode/linuxcnc_canned_cycle_g86_stopped_spindle_error.ngc new file mode 100644 index 0000000..9fb982a --- /dev/null +++ b/tests/gcode/linuxcnc_canned_cycle_g86_stopped_spindle_error.ngc @@ -0,0 +1,3 @@ +G21 G90 G17 +F100 +G86 X1 Z-1 R1 P0.1 diff --git a/tests/gcode/linuxcnc_canned_cycle_g87_missing_i_error.ngc b/tests/gcode/linuxcnc_canned_cycle_g87_missing_i_error.ngc new file mode 100644 index 0000000..17b80de --- /dev/null +++ b/tests/gcode/linuxcnc_canned_cycle_g87_missing_i_error.ngc @@ -0,0 +1,4 @@ +G21 G90 G17 +F100 +S100 M3 +G87 X1 Z-1 R1 J0.1 K0 diff --git a/tests/gcode/linuxcnc_canned_cycle_g87_missing_j_error.ngc b/tests/gcode/linuxcnc_canned_cycle_g87_missing_j_error.ngc new file mode 100644 index 0000000..250694c --- /dev/null +++ b/tests/gcode/linuxcnc_canned_cycle_g87_missing_j_error.ngc @@ -0,0 +1,4 @@ +G21 G90 G17 +F100 +S100 M3 +G87 X1 Z-1 R1 I0.1 K0 diff --git a/tests/gcode/linuxcnc_canned_cycle_g87_missing_k_error.ngc b/tests/gcode/linuxcnc_canned_cycle_g87_missing_k_error.ngc new file mode 100644 index 0000000..3e00705 --- /dev/null +++ b/tests/gcode/linuxcnc_canned_cycle_g87_missing_k_error.ngc @@ -0,0 +1,4 @@ +G21 G90 G17 +F100 +S100 M3 +G87 X1 Z-1 R1 I0.1 J0.1 diff --git a/tests/gcode/linuxcnc_canned_cycle_g87_stopped_spindle_error.ngc b/tests/gcode/linuxcnc_canned_cycle_g87_stopped_spindle_error.ngc new file mode 100644 index 0000000..8508f29 --- /dev/null +++ b/tests/gcode/linuxcnc_canned_cycle_g87_stopped_spindle_error.ngc @@ -0,0 +1,3 @@ +G21 G90 G17 +F100 +G87 X1 Z-1 R1 I0.1 J0.1 K0 diff --git a/tests/gcode/linuxcnc_canned_cycle_g88_missing_p_error.ngc b/tests/gcode/linuxcnc_canned_cycle_g88_missing_p_error.ngc new file mode 100644 index 0000000..ed8f0c0 --- /dev/null +++ b/tests/gcode/linuxcnc_canned_cycle_g88_missing_p_error.ngc @@ -0,0 +1,4 @@ +G21 G90 G17 +F100 +S100 M3 +G88 X1 Z-1 R1 diff --git a/tests/gcode/linuxcnc_canned_cycle_g88_stopped_spindle_error.ngc b/tests/gcode/linuxcnc_canned_cycle_g88_stopped_spindle_error.ngc new file mode 100644 index 0000000..17e57b6 --- /dev/null +++ b/tests/gcode/linuxcnc_canned_cycle_g88_stopped_spindle_error.ngc @@ -0,0 +1,3 @@ +G21 G90 G17 +F100 +G88 X1 Z-1 R1 P0.1 diff --git a/tests/gcode/linuxcnc_canned_cycle_g89_missing_p_error.ngc b/tests/gcode/linuxcnc_canned_cycle_g89_missing_p_error.ngc new file mode 100644 index 0000000..8f039e1 --- /dev/null +++ b/tests/gcode/linuxcnc_canned_cycle_g89_missing_p_error.ngc @@ -0,0 +1,3 @@ +G21 G90 G17 +F100 +G89 X1 Z-1 R1 diff --git a/tests/gcode/linuxcnc_canned_cycle_l0_error.ngc b/tests/gcode/linuxcnc_canned_cycle_l0_error.ngc new file mode 100644 index 0000000..34c5c82 --- /dev/null +++ b/tests/gcode/linuxcnc_canned_cycle_l0_error.ngc @@ -0,0 +1,3 @@ +G21 G90 G17 +F100 +G81 X1 Z-1 R1 L0 diff --git a/tests/gcode/linuxcnc_canned_cycle_missing_r_error.ngc b/tests/gcode/linuxcnc_canned_cycle_missing_r_error.ngc new file mode 100644 index 0000000..f9b5c55 --- /dev/null +++ b/tests/gcode/linuxcnc_canned_cycle_missing_r_error.ngc @@ -0,0 +1,3 @@ +G21 G90 G17 +F100 +G81 X1 Z-1 diff --git a/tests/gcode/linuxcnc_canned_cycle_missing_z_error.ngc b/tests/gcode/linuxcnc_canned_cycle_missing_z_error.ngc new file mode 100644 index 0000000..d68811b --- /dev/null +++ b/tests/gcode/linuxcnc_canned_cycle_missing_z_error.ngc @@ -0,0 +1,3 @@ +G21 G90 G17 +F100 +G81 X1 R1 diff --git a/tests/gcode/linuxcnc_canned_cycle_r_less_z_error.ngc b/tests/gcode/linuxcnc_canned_cycle_r_less_z_error.ngc new file mode 100644 index 0000000..1c5250d --- /dev/null +++ b/tests/gcode/linuxcnc_canned_cycle_r_less_z_error.ngc @@ -0,0 +1,3 @@ +G21 G90 G17 +F100 +G81 X1 Z2 R1 diff --git a/tests/gcode/linuxcnc_canned_cycle_u_in_xy_error.ngc b/tests/gcode/linuxcnc_canned_cycle_u_in_xy_error.ngc new file mode 100644 index 0000000..791838f --- /dev/null +++ b/tests/gcode/linuxcnc_canned_cycle_u_in_xy_error.ngc @@ -0,0 +1,3 @@ +G21 G90 G17 +F100 +G81 X1 U2 Z-1 R1