From 40a63c76f7c13f9c3525d99eddf0688fe41b29b3 Mon Sep 17 00:00:00 2001 From: cnc Date: Wed, 27 May 2026 11:14:06 +0800 Subject: [PATCH] =?UTF-8?q?=E9=AB=98=E6=95=88=E7=8E=87=E6=8E=A8=E8=BF=9B?= =?UTF-8?q?=20LinuxCNC=20=E6=BA=90=E7=A0=81=E5=AF=B9=E9=BD=90=E6=B5=81?= =?UTF-8?q?=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 说明:集中 LinuxCNC 源码清单与 wasm/native probe 的路径解析,补强 manifest、shim、source-link 检查,并保持 LinuxCNC 源码后端对齐验证流程可复用。 验证:./test-native.sh;./test-linuxcnc-source-link.sh;./test-all-native.sh;wasm source/probe 相关脚本。 --- analyze-linuxcnc-wasm-blockers.sh | 100 +- build-wasm.sh | 41 +- core/CMakeLists.txt | 5 + core/src/smoke_gcode_parser.cpp | 584 ++++++-- .../cnc_sim_api_linuxcnc_rs274_smoke.cpp | 31 + core/tests/cnc_sim_api_smoke.cpp | 381 +++++ core/tests/linuxcnc_gees_table_smoke.cpp | 1235 +++++++++++++++++ core/tests/rtcp_kinematics_smoke.cpp | 8 + list-cnc-sim-core-sources.sh | 44 + list-linuxcnc-bridge-smoke-project-sources.sh | 30 + list-linuxcnc-rs274-api-project-sources.sh | 53 + list-linuxcnc-rs274-dump-project-sources.sh | 33 + list-linuxcnc-source-files.sh | 25 + list-linuxcnc-source-manifest-sources.sh | 72 + list-linuxcnc-source-support-objects.sh | 45 + list-linuxcnc-wasm-manifest-sources.sh | 70 + list-linuxcnc-wasm-safe-project-sources.sh | 45 + list-linuxcnc-wasm-safe-shims.sh | 45 + test-all-native.sh | 191 ++- test-linuxcnc-api-native.sh | 14 +- test-linuxcnc-bridge-native.sh | 7 +- test-linuxcnc-rs274-native.sh | 11 +- test-linuxcnc-source-link.sh | 96 +- test-linuxcnc-source-objects.sh | 22 +- test-linuxcnc-source-syntax.sh | 36 +- test-linuxcnc-wasm-blockers.sh | 66 + test-linuxcnc-wasm-cmake-safe-probe.sh | 401 +++++- test-linuxcnc-wasm-interp-base-link.sh | 11 +- test-linuxcnc-wasm-interp-find-link.sh | 27 +- test-linuxcnc-wasm-python-plugin-link.sh | 4 +- ...inuxcnc-wasm-rs274ngc-pre-link-blockers.sh | 43 +- test-linuxcnc-wasm-rs274ngc-pre-link.sh | 45 +- test-linuxcnc-wasm-rs274ngc-pre-object.sh | 7 +- test-linuxcnc-wasm-source-objects.sh | 38 +- test-linuxcnc-wasm-source-syntax.sh | 24 +- test-linuxcnc-wasm-tooldata-common-link.sh | 14 +- test-linuxcnc-wasm-tooldata-link.sh | 14 +- test-linuxcnc-wasm-tooldata-mmap-symbols.sh | 35 +- ...-linuxcnc-wasm-tooldata-runtime-symbols.sh | 44 +- test-native.sh | 31 +- ...inuxcnc_canned_cycle_g98_retract_error.ngc | 3 + .../gcode/linuxcnc_cutter_comp_g71_error.ngc | 3 + ...linuxcnc_cutter_comp_tool_change_error.ngc | 4 + .../linuxcnc_cutter_comp_wait_input_error.ngc | 3 + 44 files changed, 3566 insertions(+), 475 deletions(-) create mode 100644 core/tests/linuxcnc_gees_table_smoke.cpp create mode 100755 list-cnc-sim-core-sources.sh create mode 100755 list-linuxcnc-bridge-smoke-project-sources.sh create mode 100755 list-linuxcnc-rs274-api-project-sources.sh create mode 100755 list-linuxcnc-rs274-dump-project-sources.sh create mode 100755 list-linuxcnc-source-files.sh create mode 100755 list-linuxcnc-source-manifest-sources.sh create mode 100755 list-linuxcnc-source-support-objects.sh create mode 100755 list-linuxcnc-wasm-manifest-sources.sh create mode 100755 list-linuxcnc-wasm-safe-project-sources.sh create mode 100755 list-linuxcnc-wasm-safe-shims.sh create mode 100644 tests/gcode/linuxcnc_canned_cycle_g98_retract_error.ngc create mode 100644 tests/gcode/linuxcnc_cutter_comp_g71_error.ngc create mode 100644 tests/gcode/linuxcnc_cutter_comp_tool_change_error.ngc create mode 100644 tests/gcode/linuxcnc_cutter_comp_wait_input_error.ngc diff --git a/analyze-linuxcnc-wasm-blockers.sh b/analyze-linuxcnc-wasm-blockers.sh index 4af3877..39e8175 100755 --- a/analyze-linuxcnc-wasm-blockers.sh +++ b/analyze-linuxcnc-wasm-blockers.sh @@ -17,34 +17,61 @@ if [[ ! -d "$linuxcnc_root" ]]; then fi python_blockers=() +core_python_sources=() dlopen_blockers=() +core_dlopen_sources=() tooldata_blockers=() +core_tooldata_sources=() shimmed_tooldata_users=() +core_tooldata_users=() native_backend_blockers=() native_fs_blockers=() +core_native_fs_sources=() +core_count=0 +blocked_count=0 +blocked_replacements=() +core_source_list=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_wasm_blocker_core_sources.XXXXXX.txt") +blocked_source_list=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_wasm_blocker_blocked_sources.XXXXXX.txt") +trap 'rm -f "$core_source_list" "$blocked_source_list"' EXIT -while IFS=: read -r group path note; do - [[ -z "$group" || "$group" == \#* ]] && continue - case "$group" in - core) - continue - ;; - blocked) +blocked_replacement_for() { + case "$1" in + src/emc/tooldata/tooldata_mmap.cc) + ./list-linuxcnc-wasm-safe-shims.sh tooldata_mmap_backend.cc ;; *) - echo "unknown manifest group: $group" >&2 - exit 1 + return 1 ;; esac +} +./list-linuxcnc-wasm-manifest-sources.sh "$manifest" core > "$core_source_list" +./list-linuxcnc-wasm-manifest-sources.sh "$manifest" blocked > "$blocked_source_list" +core_count=$(wc -l < "$core_source_list") +blocked_count=$(wc -l < "$blocked_source_list") + +scan_source() { + local group=$1 + local path=$2 full_path="$linuxcnc_root/$path" - if [[ ! -f "$full_path" ]]; then - echo "missing manifest source: $path" >&2 - exit 1 + if [[ "$group" == "blocked" ]]; then + if ! replacement=$(blocked_replacement_for "$path"); then + echo "missing blocked replacement mapping: $path" >&2 + exit 1 + fi + if [[ ! -f "$replacement" ]]; then + echo "missing blocked replacement source: $replacement" >&2 + exit 1 + fi + blocked_replacements+=("$path -> $replacement") fi if [[ "$path" == src/emc/tooldata/* ]]; then - tooldata_blockers+=("$path") + if [[ "$group" == "blocked" ]]; then + tooldata_blockers+=("$path") + else + core_tooldata_sources+=("$path") + fi fi if [[ "$path" == "src/emc/tooldata/tooldata_mmap.cc" ]]; then @@ -52,21 +79,47 @@ while IFS=: read -r group path note; do fi if grep -Eq 'Python\.h|boost/python|pythonplugin|PyObject|PyInit_' "$full_path"; then - python_blockers+=("$path") + if [[ "$group" == "blocked" ]]; then + python_blockers+=("$path") + else + core_python_sources+=("$path") + fi fi if grep -Eq 'dlopen|dlsym|RTLD_' "$full_path"; then - dlopen_blockers+=("$path") + if [[ "$group" == "blocked" ]]; then + dlopen_blockers+=("$path") + else + core_dlopen_sources+=("$path") + fi fi if grep -Eq 'mkstemp|mkstemps|dirent\.h|opendir|readdir|unistd\.h' "$full_path"; then - native_fs_blockers+=("$path") + if [[ "$group" == "blocked" ]]; then + native_fs_blockers+=("$path") + else + core_native_fs_sources+=("$path") + fi fi if [[ "$path" != src/emc/tooldata/* ]] && grep -Eq 'tooldata/tooldata.hh|tooldata_' "$full_path"; then - shimmed_tooldata_users+=("$path") + if [[ "$group" == "blocked" ]]; then + shimmed_tooldata_users+=("$path") + else + core_tooldata_users+=("$path") + fi fi -done < "$manifest" +} + +while IFS= read -r path; do + [[ -z "$path" ]] && continue + scan_source core "$path" +done < "$core_source_list" + +while IFS= read -r path; do + [[ -z "$path" ]] && continue + scan_source blocked "$path" +done < "$blocked_source_list" print_group() { local title=$1 @@ -80,9 +133,20 @@ print_group() { echo } +echo "[manifest]" +echo "core=$core_count" +echo "blocked=$blocked_count" +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[@]}" +print_group "blocked-replacements" "${blocked_replacements[@]}" +print_group "core-python-shimmed" "${core_python_sources[@]}" +print_group "core-dlopen-shimmed" "${core_dlopen_sources[@]}" +print_group "core-tooldata-shimmed" "${core_tooldata_sources[@]}" +print_group "core-tooldata-users-shimmed" "${core_tooldata_users[@]}" +print_group "core-native-fs-shimmed" "${core_native_fs_sources[@]}" diff --git a/build-wasm.sh b/build-wasm.sh index 7acb856..a6e59e7 100755 --- a/build-wasm.sh +++ b/build-wasm.sh @@ -4,27 +4,55 @@ 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 +manifest=$(cd "$(dirname "$manifest")" && pwd)/$(basename "$manifest") linuxcnc_root=$(cd "$linuxcnc_root" && pwd) +default_manifest="$PWD/linuxcnc-rs274-wasm-source-files.txt" # This script writes fixed build and public artifact paths, so serialize it. exec 9>"${TMPDIR:-/tmp}/cnc_sim_build_wasm.lock" flock 9 +manifest_core_count=0 +manifest_blocked_count=0 +manifest_core_sources=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_wasm_manifest_core.XXXXXX.txt") +manifest_blocked_sources=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_wasm_manifest_blocked.XXXXXX.txt") +trap 'rm -f "$manifest_core_sources" "$manifest_blocked_sources"' EXIT +LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-manifest-sources.sh "$manifest" core > "$manifest_core_sources" +LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-manifest-sources.sh "$manifest" blocked > "$manifest_blocked_sources" +manifest_core_count=$(wc -l < "$manifest_core_sources") +manifest_blocked_count=$(wc -l < "$manifest_blocked_sources") + +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 +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 + echo "LinuxCNC wasm blocker scan" -./test-linuxcnc-wasm-blockers.sh +./test-linuxcnc-wasm-blockers.sh "$manifest" echo "LinuxCNC wasm-safe source syntax probe" -./test-linuxcnc-wasm-source-syntax.sh +./test-linuxcnc-wasm-source-syntax.sh "$manifest" echo "LinuxCNC wasm-safe source object probe" -./test-linuxcnc-wasm-source-objects.sh +./test-linuxcnc-wasm-source-objects.sh "$manifest" echo "LinuxCNC wasm-safe CMake probe target" -./test-linuxcnc-wasm-cmake-safe-probe.sh +./test-linuxcnc-wasm-cmake-safe-probe.sh "$manifest" echo "LinuxCNC wasm tooldata shim link probe" ./test-linuxcnc-wasm-tooldata-link.sh @@ -51,10 +79,10 @@ 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 +./test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh "$manifest" echo "LinuxCNC wasm rs274ngc_pre link probe" -./test-linuxcnc-wasm-rs274ngc-pre-link.sh +./test-linuxcnc-wasm-rs274ngc-pre-link.sh "$manifest" if ! command -v emcmake >/dev/null 2>&1; then echo "Emscripten is required. Install/activate emsdk so emcmake and emcc are in PATH." >&2 @@ -64,6 +92,7 @@ fi emcmake cmake -S core -B build/wasm \ -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/wasm diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 7fb238b..982443a 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -243,6 +243,11 @@ if(NOT EMSCRIPTEN) target_link_libraries(rtcp_kinematics_smoke PRIVATE cnc_sim_core) target_include_directories(rtcp_kinematics_smoke PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) + add_executable(linuxcnc_gees_table_smoke + tests/linuxcnc_gees_table_smoke.cpp + ) + target_include_directories(linuxcnc_gees_table_smoke PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) + add_executable(simulator_gcode_controls_smoke tests/simulator_gcode_controls_smoke.cpp ) diff --git a/core/src/smoke_gcode_parser.cpp b/core/src/smoke_gcode_parser.cpp index 33d3a44..50b3e67 100644 --- a/core/src/smoke_gcode_parser.cpp +++ b/core/src/smoke_gcode_parser.cpp @@ -1,5 +1,7 @@ #include "smoke_gcode_parser.h" +#include "simulator_gcode_controls.h" + #include #include #include @@ -16,6 +18,12 @@ namespace { +constexpr int kMaxSubprogramDepth = 10; +constexpr int kFirstSubprogramParameter = 1; +constexpr int kMaxSubprogramParameters = 30; +constexpr size_t kSubprogramParameterSlots = + static_cast(kFirstSubprogramParameter + kMaxSubprogramParameters); + struct Word { char letter; double value; @@ -56,7 +64,7 @@ struct CallFrame { bool modal_lathe_diameter_mode; int modal_plane; double modal_unit_scale; - std::array params; + std::array params; std::unordered_map named_params; }; @@ -96,13 +104,51 @@ enum class OwordKind { EndSub }; -constexpr size_t kMaxSubprogramDepth = 16; constexpr int kMaxOwordLoopIterations = 100000; constexpr int kMaxNumberedParameter = 5602; +constexpr double kLinuxCncGCodeScale = 10.0; +constexpr int kLinuxCncGCodeTableSize = 1000; +constexpr int kLinuxCncMaxGCode = 999; +constexpr double kGCodeRoundUpFraction = 0.999; +constexpr double kGCodeOutOfRangeFraction = 0.001; +constexpr int kLinuxCncMCodeTableSize = 200; +constexpr int kLinuxCncMaxMCode = 199; +constexpr int kMaxGModalGroups = 17; +constexpr int kMaxMModalGroups = 11; +constexpr int kMaxMCodesOnLine = 4; +constexpr int kLinuxCncReaderTableSize = 256; +constexpr double kPWordIntegerTolerance = 0.001; +constexpr int kArcMinPRepeatCount = 1; +constexpr int kM19MinOrientMode = 0; +constexpr int kM19MaxOrientMode = 2; +constexpr double kM19MaxOrientAngle = 360.0; +constexpr double kM19MinWaitSeconds = 0.0; +constexpr double kG10PIntegerRoundoff = 0.0001; +constexpr double kG10PIntegerTolerance = 0.0002; +constexpr int kG10MinCoordinateSystemIndex = 0; +constexpr int kG10MaxCoordinateSystemIndex = 9; +constexpr int kG10MinToolNumber = 1; +constexpr std::array kG10AllowedLNumbers = {0, 1, 2, 10, 11, 20}; +constexpr std::array kG10CoordinateSystemLNumbers = {2, 20}; +constexpr std::array kG10ToolTableLNumbers = {1, 10, 11}; +constexpr int kMaxToolOrientation = 9; constexpr int kModalMotionG33 = 33; constexpr int kModalMotionG331 = 331; constexpr int kModalMotionG76 = 76; constexpr int kSmokeNumSpindles = 1; +constexpr int kM61MinToolNumber = 0; +constexpr int kMinAnalogOutputIndex = 0; +constexpr double kIntegerReadRoundUpFraction = 0.9999; +constexpr double kIntegerReadNonIntegerFraction = 0.0001; +constexpr double kNearIntegerRoundOffset = 0.5; +constexpr double kNearIntegerTolerance = 0.0001; +constexpr double kToleranceEqual = 1e-6; +constexpr double kMmPerInch = 25.4; +constexpr double kRadiusToleranceInch = 0.00005; +constexpr double kRadiusToleranceMm = kRadiusToleranceInch * kMmPerInch; +constexpr double kCenterArcRadiusToleranceInch = 2.0 * 0.001 * M_SQRT2; +constexpr double kCenterArcRadiusToleranceMm = 2.0 * 0.01 * M_SQRT2; +constexpr double kSpiralRelativeTolerance = 0.001; constexpr int kCutterCompOffEvent = 400; constexpr int kCutterCompLeftEvent = 410; constexpr int kCutterCompRightEvent = 420; @@ -123,15 +169,24 @@ enum class LinuxCncReaderSymbol { 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); +std::string linuxcnc_unknown_unary_word_error(char first); +size_t linuxcnc_unary_operation_prefix_length(const char *text); +bool looks_like_bracketed_alpha_value(const char *text); +std::string linuxcnc_unknown_operation_error(char first); 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{}; +template +bool contains_int(const std::array &values, int value) { + return std::find(values.begin(), values.end(), value) != values.end(); +} + +const std::array &linuxcnc_reader_symbols() { + static const std::array table = [] { + std::array out{}; out.fill(LinuxCncReaderSymbol::None); out[static_cast('#')] = LinuxCncReaderSymbol::ParameterSetting; out[static_cast('$')] = LinuxCncReaderSymbol::Dollar; @@ -150,9 +205,9 @@ const std::array &linuxcnc_reader_symbols() { return table; } -const std::array &linuxcnc_g_modal_group_table() { - static const std::array table = [] { - std::array out{}; +const std::array &linuxcnc_g_modal_group_table() { + static const std::array table = [] { + std::array out{}; out.fill(-1); auto set = [&out](int code, int group) { out[static_cast(code)] = group; @@ -200,8 +255,6 @@ const std::array &linuxcnc_g_modal_group_table() { set(430, 8); set(431, 8); set(432, 8); - set(434, 8); - set(435, 8); set(490, 8); set(520, 0); set(530, 0); @@ -257,9 +310,9 @@ const std::array &linuxcnc_g_modal_group_table() { return table; } -const std::array &linuxcnc_m_modal_group_table() { - static const std::array table = [] { - std::array out{}; +const std::array &linuxcnc_m_modal_group_table() { + static const std::array table = [] { + std::array out{}; out.fill(-1); auto set = [&out](int code, int group) { out[static_cast(code)] = group; @@ -296,9 +349,8 @@ const std::array &linuxcnc_m_modal_group_table() { set(71, 7); set(72, 7); set(73, 7); - set(98, 10); set(99, 4); - for (int code = 100; code <= 199; ++code) { + for (int code = 100; code <= kLinuxCncMaxMCode; ++code) { set(code, 10); } return out; @@ -585,9 +637,9 @@ enum class LinuxCncIntegerReadResult { 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) { + if (fraction > kIntegerReadRoundUpFraction) { parsed_integer = static_cast(std::ceil(float_value)); - } else if (fraction > 0.0001) { + } else if (fraction > kIntegerReadNonIntegerFraction) { return LinuxCncIntegerReadResult::NonInteger; } @@ -950,6 +1002,96 @@ bool validate_linuxcnc_top_level_unary_items(const std::string &line, std::strin return true; } +bool validate_linuxcnc_word_alpha_values(const std::string &line, std::string *error) { + int depth = 0; + bool in_named_parameter = false; + for (size_t i = 0; i < line.size(); ++i) { + const char ch = line[i]; + if (in_named_parameter) { + if (ch == '>') { + in_named_parameter = false; + } + continue; + } + if (ch == '<') { + in_named_parameter = true; + continue; + } + if (ch == '[') { + ++depth; + continue; + } + if (ch == ']') { + if (depth > 0) { + --depth; + } + continue; + } + if (depth > 0 || !std::isalpha(static_cast(ch))) { + continue; + } + if (i > 0 && std::isalpha(static_cast(line[i - 1]))) { + 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) || before == '<') { + continue; + } + + size_t value_start = i + 1; + while (value_start < line.size() && + std::isspace(static_cast(line[value_start]))) { + ++value_start; + } + if ((value_start < line.size()) && + (line[value_start] == '+' || line[value_start] == '-')) { + const size_t sign = value_start; + ++value_start; + while (value_start < line.size() && + std::isspace(static_cast(line[value_start]))) { + ++value_start; + } + if (value_start < line.size() && + (std::isdigit(static_cast(line[value_start])) || line[value_start] == '.')) { + value_start = sign; + } + } + if (value_start >= line.size() || + !std::isalpha(static_cast(line[value_start]))) { + continue; + } + size_t value_end = 0; + if (unary_function_span(line, value_start, &value_end)) { + i = value_end - 1; + continue; + } + const size_t unary_prefix = linuxcnc_unary_operation_prefix_length(line.c_str() + value_start); + if (unary_prefix != 0) { + size_t bracket = value_start + unary_prefix; + while (bracket < line.size() && + std::isspace(static_cast(line[bracket]))) { + ++bracket; + } + if (bracket >= line.size() || line[bracket] != '[') { + if (error) { + *error = "Left bracket missing after unary operation name"; + } + return false; + } + continue; + } + if (error) { + *error = linuxcnc_unknown_unary_word_error(line[value_start]); + } + 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); @@ -1177,6 +1319,12 @@ bool parse_word_list_impl(const std::string &line, ParsedWordList *parsed, std:: } return false; } + if (strict && looks_like_bracketed_alpha_value(text + i)) { + if (error) { + *error = linuxcnc_unknown_unary_word_error(text[i]); + } + return false; + } continue; } const size_t next = i + consumed; @@ -1404,28 +1552,28 @@ bool has_m_code(const std::vector &word_list, int code) { } int linuxcnc_g_code_number(double value) { - const double scaled = value * 10.0; + const double scaled = value * kLinuxCncGCodeScale; int code = static_cast(std::floor(scaled)); const double fraction = scaled - static_cast(code); - if (fraction > 0.999) { + if (fraction > kGCodeRoundUpFraction) { code = static_cast(std::ceil(scaled)); } return code; } bool validate_linuxcnc_g_code_value(double value, std::string *error) { - const double scaled = value * 10.0; + const double scaled = value * kLinuxCncGCodeScale; int code = static_cast(std::floor(scaled)); const double fraction = scaled - static_cast(code); - if (fraction > 0.999) { + if (fraction > kGCodeRoundUpFraction) { code = static_cast(std::ceil(scaled)); - } else if (fraction > 0.001) { + } else if (fraction > kGCodeOutOfRangeFraction) { if (error) { *error = "G-code out of range"; } return false; } - if (code > 999) { + if (code > kLinuxCncMaxGCode) { if (error) { *error = "G-code out of range"; } @@ -1481,29 +1629,29 @@ bool smoke_tool_number_exists(int tool_number) { int supported_linuxcnc_g_modal_group(double value) { const int code = linuxcnc_g_code_number(value); - if (code < 0 || code >= 1000) { + if (code < 0 || code >= kLinuxCncGCodeTableSize) { return -1; } return linuxcnc_g_modal_group_table()[static_cast(code)]; } int supported_linuxcnc_m_modal_group(int value) { - if (value < 0 || value >= 200) { + if (value < 0 || value >= kLinuxCncMCodeTableSize) { return -1; } return linuxcnc_m_modal_group_table()[static_cast(value)]; } bool smoke_m_code_has_linuxcnc_or_simulator_use(int value) { - if (value == 428 || value == 429 || value == 430) { + if (value == 98 || value == 428 || value == 429 || value == 430) { return true; } return supported_linuxcnc_m_modal_group(value) >= 0; } bool check_linuxcnc_modal_group_conflicts(const std::vector &word_list, std::string *error) { - std::array g_modes{}; - std::array m_modes{}; + std::array g_modes{}; + std::array m_modes{}; g_modes.fill(-1); for (const Word &word : word_list) { if (word.letter == 'G') { @@ -1647,6 +1795,50 @@ bool consume_linuxcnc_oword_bracket_argument(const std::string &line, size_t *po return true; } +bool linuxcnc_oword_next_argument_is_bracketed(const std::string &line) { + 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 false; + } + ++pos; + if (pos < line.size() && line[pos] == '<') { + const size_t close = line.find('>', pos + 1); + if (close == std::string::npos) { + return false; + } + 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; + } + while (pos < line.size() && std::isspace(static_cast(line[pos]))) { + ++pos; + } + return pos < line.size() && line[pos] == '['; +} + bool validate_linuxcnc_oword_tail(const std::string &line, OwordKind kind, std::string *error) { @@ -1725,7 +1917,10 @@ bool parameter_value(const std::vector &call_stack, const std::unordered_map ¶meters, int index, double *value) { - if (index > 0 && index <= 30 && !call_stack.empty() && call_stack.back().oword) { + if (index >= kFirstSubprogramParameter && + index < kFirstSubprogramParameter + kMaxSubprogramParameters && + !call_stack.empty() && + call_stack.back().oword) { *value = call_stack.back().params[static_cast(index)]; return true; } @@ -1837,6 +2032,85 @@ bool truthy(double value) { return value != 0.0; } +bool is_linuxcnc_unary_function_name(const std::string &name) { + return name == "ABS" || + name == "ACOS" || + name == "ASIN" || + name == "ATAN" || + name == "COS" || + name == "EXP" || + name == "EXISTS" || + name == "FIX" || + name == "FUP" || + name == "LN" || + name == "ROUND" || + name == "SIN" || + name == "SQRT" || + name == "TAN"; +} + +std::string linuxcnc_unknown_unary_word_error(char first) { + const char lower = static_cast(std::tolower(static_cast(first))); + switch (lower) { + case 'a': + case 'c': + case 'e': + case 'f': + case 'l': + case 'r': + case 's': + case 't': + return "Unknown word starting with " + std::string(1, lower); + default: + return "Unknown word where unary operation could be"; + } +} + +std::string linuxcnc_unknown_operation_error(char first) { + const char lower = static_cast(std::tolower(static_cast(first))); + switch (lower) { + case 'a': + case 'e': + case 'g': + case 'l': + case 'm': + case 'n': + case 'o': + case 'x': + return "Unknown operation name starting with " + std::string(1, lower); + default: + return "Unknown operation"; + } +} + +size_t linuxcnc_unary_operation_prefix_length(const char *text) { + const std::string upper = uppercase(text); + static const std::array kUnaryFunctions = { + "ABS", "ACOS", "ASIN", "ATAN", "COS", "EXP", "EXISTS", + "FIX", "FUP", "LN", "ROUND", "SIN", "SQRT", "TAN"}; + for (const char *name : kUnaryFunctions) { + const size_t length = std::char_traits::length(name); + if (upper.rfind(name, 0) == 0) { + return length; + } + } + return 0; +} + +bool looks_like_bracketed_alpha_value(const char *text) { + size_t pos = 0; + if (!std::isalpha(static_cast(text[pos]))) { + return false; + } + while (std::isalpha(static_cast(text[pos]))) { + ++pos; + } + while (text[pos] == ' ' || text[pos] == '\t' || text[pos] == '\r') { + ++pos; + } + return text[pos] == '['; +} + bool is_smoke_readonly_named_parameter(const std::string &name) { return name == "_vmajor" || name == "_VMAJOR" || @@ -1961,8 +2235,7 @@ bool is_smoke_readonly_named_parameter(const std::string &name) { } bool is_smoke_readonly_numbered_parameter(int index) { - return (index >= 5061 && index <= 5070) || - (index >= 5400 && index <= 5413) || + return (index >= 5400 && index <= 5413) || (index >= 5420 && index <= 5428); } @@ -1972,7 +2245,24 @@ bool is_smoke_persistent_numbered_parameter(int index) { } bool is_smoke_system_persistent_numbered_parameter(int index) { - return index >= 5161 && index <= 5399; + if (index >= 5161 && index <= 5169) { + return true; + } + if (index >= 5181 && index <= 5189) { + return true; + } + if (index >= 5210 && index <= 5219) { + return true; + } + if (index == 5220) { + return true; + } + for (int base = 5221; base <= 5381; base += 20) { + if (index >= base && index <= base + 9) { + return true; + } + } + return false; } class ExpressionParser { @@ -1987,6 +2277,13 @@ public: bool parse(double *value) { *value = parse_logical(); skip_space(); + if (ok_ && pos_ != text_.size()) { + if (std::isalpha(static_cast(text_[pos_]))) { + fail(linuxcnc_unknown_operation_error(text_[pos_]).c_str()); + } else { + fail("Unknown operation"); + } + } return ok_ && pos_ == text_.size() && std::isfinite(*value); } @@ -2024,7 +2321,6 @@ private: } double parse_comparison() { - constexpr double kToleranceEqual = 1e-6; double value = parse_additive(); while (ok_) { skip_space(); @@ -2075,8 +2371,7 @@ private: if (consume_keyword("MOD")) { const double rhs = parse_power(); if (rhs == 0.0) { - ok_ = false; - return 0.0; + return fail("Calculation resulted in 'not a number'"); } value = std::fmod(value, rhs); if (value < 0.0) { @@ -2087,8 +2382,7 @@ private: } else if (consume('/')) { const double rhs = parse_power(); if (rhs == 0.0) { - ok_ = false; - return 0.0; + return fail("Attempt to divide by zero"); } value /= rhs; } else { @@ -2107,8 +2401,7 @@ private: } const double rhs = parse_unary(); if (value < 0.0 && std::floor(rhs) != rhs) { - ok_ = false; - return 0.0; + return fail("Attempt to raise negative to non integer power"); } value = std::pow(value, rhs); if (!std::isfinite(value)) { @@ -2135,9 +2428,18 @@ private: if (consume('[')) { const double value = parse_logical(); if (!consume(']')) { - ok_ = false; + if (pos_ < text_.size() && std::isalpha(static_cast(text_[pos_]))) { + return fail(linuxcnc_unknown_operation_error(text_[pos_]).c_str()); + } + return fail("Unknown operation"); } if (!std::isfinite(value)) { + if (std::isinf(value)) { + return fail("Calculation resulted in 'infinity'"); + } + if (std::isnan(value)) { + return fail("Calculation resulted in 'not a number'"); + } ok_ = false; return 0.0; } @@ -2149,9 +2451,15 @@ private: if (pos_ < text_.size() && std::isalpha(static_cast(text_[pos_]))) { const std::string name = uppercase(parse_identifier()); if (name == "EXISTS") { + if (pos_ >= text_.size() || text_[pos_] != '[') { + return fail("Left bracket missing after unary operation name"); + } return parse_exists_function(); } if (!consume('[')) { + if (is_linuxcnc_unary_function_name(name)) { + return fail("Left bracket missing after unary operation name"); + } ok_ = false; return 0.0; } @@ -2165,20 +2473,20 @@ private: result = std::fabs(argument); } else if (name == "ACOS") { if (argument < -1.0 || argument > 1.0) { - ok_ = false; - return 0.0; + return fail("Argument to acos out of range"); } result = radians_to_degrees(std::acos(argument)); } else if (name == "ASIN") { if (argument < -1.0 || argument > 1.0) { - ok_ = false; - return 0.0; + return fail("Argument to asin out of range"); } result = radians_to_degrees(std::asin(argument)); } else if (name == "ATAN") { - if (!consume('/') || !consume('[')) { - ok_ = false; - return 0.0; + if (!consume('/')) { + return fail("Slash missing after first atan argument"); + } + if (!consume('[')) { + return fail("Left bracket missing after slash with atan"); } const double argument2 = parse_logical(); if (!consume(']')) { @@ -2188,16 +2496,14 @@ private: result = radians_to_degrees(std::atan2(argument, argument2)); } else if (name == "SQRT") { if (argument < 0.0) { - ok_ = false; - return 0.0; + return fail("Negative argument to sqrt"); } result = std::sqrt(argument); } else if (name == "EXP") { result = std::exp(argument); } else if (name == "LN") { if (argument <= 0.0) { - ok_ = false; - return 0.0; + return fail("Zero or negative argument to ln"); } result = std::log(argument); } else if (name == "FIX") { @@ -2217,6 +2523,12 @@ private: return 0.0; } if (!std::isfinite(result)) { + if (std::isinf(result)) { + return fail("Calculation resulted in 'infinity'"); + } + if (std::isnan(result)) { + return fail("Calculation resulted in 'not a number'"); + } ok_ = false; return 0.0; } @@ -2226,6 +2538,9 @@ private: double value = 0.0; size_t consumed = 0; if (!parse_linuxcnc_real_value(text_.c_str() + pos_, &value, &consumed)) { + if (pos_ >= text_.size()) { + return fail("No characters found in reading real value"); + } ok_ = false; return 0.0; } @@ -2920,7 +3235,40 @@ bool linuxcnc_real_value_span(const std::string &line, size_t start, size_t *end return parameter_reference_index_span(line, start + 1, end); } if (std::isalpha(static_cast(c))) { - return unary_function_span(line, start, end); + if (unary_function_span(line, start, end)) { + return true; + } + size_t identifier_end = start; + while (identifier_end < line.size() && + std::isalpha(static_cast(line[identifier_end]))) { + ++identifier_end; + } + if (is_linuxcnc_unary_function_name(uppercase(line.substr(start, identifier_end - start)))) { + size_t first_open = identifier_end; + while (first_open < line.size() && std::isspace(static_cast(line[first_open]))) { + ++first_open; + } + if (first_open < line.size() && line[first_open] == '[') { + const size_t first_close = find_matching_square_bracket(line, first_open); + if (first_close != std::string::npos) { + if (uppercase(line.substr(start, identifier_end - start)) == "ATAN") { + size_t slash = first_close + 1; + while (slash < line.size() && std::isspace(static_cast(line[slash]))) { + ++slash; + } + if (slash < line.size() && line[slash] == '/') { + *end = slash + 1; + return true; + } + } + *end = first_close + 1; + return true; + } + } + *end = identifier_end; + return true; + } + return false; } double value = 0.0; size_t consumed = 0; @@ -3263,6 +3611,12 @@ std::vector parse_oword_call_arguments(const std::string &line, } const std::string text = trim(line.substr(i + 1, close - i - 1)); if (!text.empty()) { + if (args.size() >= kMaxSubprogramParameters) { + if (error) { + *error = "Too many subroutine parameters"; + } + return {}; + } double value = 0.0; if (!evaluate_expression(text, call_stack, parameters, named_parameters, &value)) { if (error) { @@ -4084,8 +4438,8 @@ bool integer_word_value(const std::unordered_map &words, } bool linuxcnc_convert_near_int(double value, int *result) { - *result = static_cast(value + 0.5); - return std::fabs(static_cast(*result) - value) < 0.0001; + *result = static_cast(value + kNearIntegerRoundOffset); + return std::fabs(static_cast(*result) - value) < kNearIntegerTolerance; } int positive_integer_word(const std::unordered_map &words, char letter, int fallback) { @@ -4552,12 +4906,7 @@ bool validate_arc_center_radius_consistency(const CncSimEvent &event, const double radius1 = std::hypot(center_first - start_first, center_second - start_second); const double radius2 = std::hypot(center_first - end_first, center_second - end_second); - constexpr double kMmPerInch = 25.4; - constexpr double kRadiusToleranceInch = 0.00005; - constexpr double kCenterArcRadiusToleranceInch = 2.0 * 0.001 * 1.4142135623730951; - constexpr double kCenterArcRadiusToleranceMm = 2.0 * 0.01 * 1.4142135623730951; - constexpr double kSpiralRelativeTolerance = 0.001; - const double radius_tolerance = kRadiusToleranceInch * kMmPerInch; + const double radius_tolerance = kRadiusToleranceMm; if (radius1 < radius_tolerance || radius2 < radius_tolerance) { if (error) { std::ostringstream out; @@ -4578,7 +4927,7 @@ bool validate_arc_center_radius_consistency(const CncSimEvent &event, const double abs_err = std::fabs(radius1 - radius2); const double larger_radius = std::max(radius1, radius2); const double rel_err = larger_radius > 0.0 ? abs_err / larger_radius : 0.0; - const double spiral_abs_tolerance = unit_scale == 25.4 + const double spiral_abs_tolerance = unit_scale == kMmPerInch ? kCenterArcRadiusToleranceInch * kMmPerInch : kCenterArcRadiusToleranceMm; if ((abs_err > spiral_abs_tolerance * 100.0) || @@ -4629,7 +4978,7 @@ bool validate_r_arc_reaches_end_point(const CncSimEvent &event, int plane, doubl const double mid_second = (start_second + end_second) * 0.5; const double half_length = std::hypot(mid_first - end_first, mid_second - end_second); const double abs_radius = std::fabs(radius); - const double tolerance = 0.00127; + const double tolerance = kRadiusToleranceMm; if (start_first == end_first && start_second == end_second) { if (error) { *error = "Current point same as end point of arc"; @@ -5062,9 +5411,9 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string 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["_metric"] = sink_.unit_scale() == 1.0 ? 1.0 : 0.0; - named_parameters["_imperial"] = sink_.unit_scale() == 25.4 ? 1.0 : 0.0; + named_parameters["_imperial"] = sink_.unit_scale() == kMmPerInch ? 1.0 : 0.0; named_parameters["_METRIC"] = sink_.unit_scale() == 1.0 ? 1.0 : 0.0; - named_parameters["_IMPERIAL"] = sink_.unit_scale() == 25.4 ? 1.0 : 0.0; + named_parameters["_IMPERIAL"] = sink_.unit_scale() == kMmPerInch ? 1.0 : 0.0; named_parameters["_absolute"] = absolute_ ? 1.0 : 0.0; named_parameters["_incremental"] = absolute_ ? 0.0 : 1.0; named_parameters["_inverse_time"] = feed_mode_ == 1 ? 1.0 : 0.0; @@ -5319,7 +5668,10 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string } else { named_parameters[assignment.name] = assignment.value; } - } else if (assignment.index > 0 && assignment.index <= 30 && !call_stack.empty() && call_stack.back().oword) { + } else if (assignment.index >= kFirstSubprogramParameter && + assignment.index < kFirstSubprogramParameter + kMaxSubprogramParameters && + !call_stack.empty() && + call_stack.back().oword) { call_stack.back().params[static_cast(assignment.index)] = assignment.value; } else { parameters[assignment.index] = assignment.value; @@ -5353,6 +5705,7 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string 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_word_alpha_values(line, error) || !validate_linuxcnc_nonleading_oword_items(line, error) || !validate_linuxcnc_nonleading_m98_items(line, error) || !validate_linuxcnc_top_level_brackets(line, error)) { @@ -5371,6 +5724,24 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string if (!validate_linuxcnc_top_level_reader_symbols(line, error)) { return -1; } + std::vector simulator_control_actions; + if (!has_keyword_oword && + parse_simulator_gcode_control_line(line, &simulator_control_actions)) { + bool simulator_only_line = true; + for (const auto &action : simulator_control_actions) { + emit_simulator_gcode_control_action(sink_, action, line_number); + if (action.kind == SimulatorGcodeControlKind::RtcpState && + !action.rtcp_enabled) { + simulator_only_line = false; + } + if (stop_if_callback_aborted(sink_, error)) { + return -1; + } + } + if (simulator_only_line) { + continue; + } + } ParsedWordList parsed_word_list; if (!parse_word_list_impl(line, &parsed_word_list, error, true)) { return -1; @@ -5585,13 +5956,13 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string } if (words.count('P') && (has_g_code(word_list, 2.0) || has_g_code(word_list, 3.0))) { const int p_value = rounded_word(words, 'P', 0); - if (std::fabs(static_cast(p_value) - words.at('P')) > 0.001) { + if (std::fabs(static_cast(p_value) - words.at('P')) > kPWordIntegerTolerance) { if (error) { *error = "P value not an integer with G2 or G3"; } return -1; } - if (p_value < 1) { + if (p_value < kArcMinPRepeatCount) { if (error) { *error = "P value should be 1 or greater with G2 or G3"; } @@ -5600,13 +5971,13 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string } if (words.count('P') && has_m_code(word_list, 19)) { const int p_value = rounded_word(words, 'P', 0); - if (std::fabs(static_cast(p_value) - words.at('P')) > 0.001) { + if (std::fabs(static_cast(p_value) - words.at('P')) > kPWordIntegerTolerance) { if (error) { *error = "P value not an integer with M19 G2 or G3"; } return -1; } - if (p_value < 0 || p_value > 2) { + if (p_value < kM19MinOrientMode || p_value > kM19MaxOrientMode) { if (error) { *error = "P value must be 0,1,or 2 with M19"; } @@ -5626,13 +5997,13 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string return -1; } if (words.count('R') && has_m_code(word_list, 19) && - (words.at('R') < 0.0 || words.at('R') > 360.0)) { + (words.at('R') < 0.0 || words.at('R') > kM19MaxOrientAngle)) { if (error) { *error = "R value must be within 0..360 with M19"; } return -1; } - if (words.count('Q') && has_m_code(word_list, 19) && words.at('Q') <= 0.0) { + if (words.count('Q') && has_m_code(word_list, 19) && words.at('Q') <= kM19MinWaitSeconds) { if (error) { *error = "Q word with M19 requires a value > 0"; } @@ -5698,7 +6069,7 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string } if (has_g71_g72_roughing_cycle && cutter_comp_on_) { if (error) { - *error = "G71/G72 cannot be used with cutter compensation enabled"; + *error = "G71.0 cannot be used with cutter compensation enabled"; } return -1; } @@ -5714,7 +6085,20 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string const bool oword_endsub = current_oword_kind == OwordKind::EndSub; if (current_oword_kind == OwordKind::If || current_oword_kind == OwordKind::ElseIf) { + if (current_oword_kind == OwordKind::If && + !linuxcnc_oword_next_argument_is_bracketed(keyword_line)) { + if (error) { + *error = "Left bracket missing after 'if'"; + } + return -1; + } if (current_oword_kind == OwordKind::ElseIf) { + if (!linuxcnc_oword_next_argument_is_bracketed(keyword_line)) { + if (error) { + *error = "Left bracket missing after 'elseif'"; + } + return -1; + } if (conditional_stack.empty()) { if (error) { *error = "O-word elseif without active if"; @@ -5813,6 +6197,12 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string continue; } if (current_oword_kind == OwordKind::While) { + if (!linuxcnc_oword_next_argument_is_bracketed(keyword_line)) { + if (error) { + *error = "Left bracket missing after 'while'"; + } + return -1; + } const size_t do_index = find_matching_do_start(lines, current_pc, o_identifier); if (do_index != lines.size()) { bool condition = false; @@ -5869,6 +6259,12 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string continue; } if (current_oword_kind == OwordKind::Repeat) { + if (!linuxcnc_oword_next_argument_is_bracketed(keyword_line)) { + if (error) { + *error = "Left bracket missing after 'repeat'"; + } + return -1; + } const size_t endrepeat = find_matching_loop_end(lines, pc, o_identifier, OwordKind::Repeat); if (endrepeat == lines.size()) { if (error) { @@ -5978,7 +6374,7 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string bool coordinate_control = false; if (has_g_code(word_list, 10.0)) { const int l = rounded_word(words, 'L', 0); - if (l != 0 && l != 1 && l != 2 && l != 10 && l != 11 && l != 20) { + if (!contains_int(kG10AllowedLNumbers, l)) { if (error) { *error = "Line with G10 does not have L0, L1, L10, L11, L2, or L20"; } @@ -5989,7 +6385,7 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string 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 (((p_value + kG10PIntegerRoundoff) - p_int) > kG10PIntegerTolerance) { if (error) { *error = "P value not an integer with G10"; } @@ -5997,19 +6393,21 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string } } const int requested_p = rounded_word(words, 'P', -1); - if ((l == 2 || l == 20) && (requested_p < 0 || requested_p > 9)) { + if (contains_int(kG10CoordinateSystemLNumbers, l) && + (requested_p < kG10MinCoordinateSystemIndex || + requested_p > kG10MaxCoordinateSystemIndex)) { if (error) { *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 (contains_int(kG10ToolTableLNumbers, l) && requested_p < kG10MinToolNumber) { if (error) { *error = "P value out of range with G10 L" + std::to_string(l); } return -1; } - if (l == 1 || l == 10 || l == 11) { + if (contains_int(kG10ToolTableLNumbers, l)) { if (!smoke_tool_number_exists(requested_p)) { if (error) { *error = "Requested tool " + std::to_string(requested_p) + " not found in the tool table"; @@ -6167,7 +6565,7 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string } return -1; } - if (orientation > 9) { + if (orientation > kMaxToolOrientation) { if (error) { *error = "Invalid tool orientation"; } @@ -6187,8 +6585,8 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string coordinate_control = true; } const int p = requested_p == 0 ? active_g5x_index : requested_p; - if ((l == 2 || l == 20) && p > 0) { - if ((l == 2 || l == 20) && (words.count('I') || words.count('J'))) { + if (contains_int(kG10CoordinateSystemLNumbers, l) && p > 0) { + if (words.count('I') || words.count('J')) { if (error) { *error = "I J words not allowed with G10 L2"; } @@ -6234,7 +6632,7 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string } } coordinate_control = true; - } else if (l == 2 || l == 20) { + } else if (contains_int(kG10CoordinateSystemLNumbers, l)) { if (error) { *error = "G10 L2/L20 requires a P coordinate system index from 0 to 9"; } @@ -6456,7 +6854,7 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string return -1; } } else if (g == 20) { - sink_.use_length_units(25.4, line_number); + sink_.use_length_units(kMmPerInch, line_number); if (stop_if_callback_aborted(sink_, error)) { return -1; } @@ -7047,7 +7445,7 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string } return -1; } - if (m_code > 199 && m_code != 428 && m_code != 429 && m_code != 430) { + if (m_code > kLinuxCncMaxMCode && m_code != 428 && m_code != 429 && m_code != 430) { if (error) { *error = "M-code greater than 199: M" + std::to_string(m_code); } @@ -7062,7 +7460,7 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string m_codes.push_back(m_code); } } - if (m_codes.size() > 4) { + if (m_codes.size() > static_cast(kMaxMCodesOnLine)) { if (error) { *error = "Too many m codes on line"; } @@ -7148,9 +7546,9 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string } return -1; } - if (call_stack.size() >= kMaxSubprogramDepth) { + if (static_cast(call_stack.size()) + 1 >= kMaxSubprogramDepth) { if (error) { - *error = "O-word subprogram nesting limit exceeded"; + *error = "Too many subroutine levels"; } return -1; } @@ -7167,8 +7565,10 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string } return -1; } - for (size_t i = 0; i < args.size() && i < 30; ++i) { - frame.params[i + 1] = args[i]; + for (size_t i = 0; + i < args.size() && i < static_cast(kMaxSubprogramParameters); + ++i) { + frame.params[i + kFirstSubprogramParameter] = args[i]; } call_stack.push_back(frame); pc = subprogram->second.start; @@ -7396,7 +7796,7 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string } } else if (m == 61) { const int tool_number = rounded_word(words, 'Q', -1); - if (!words.count('Q') || tool_number < 0) { + if (!words.count('Q') || tool_number < kM61MinToolNumber) { if (error) { *error = "Need non-negative Q-word to specify tool number with M61"; } @@ -7530,7 +7930,7 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string } return -1; } - if (rounded_word(words, 'E', 0) < 0) { + if (rounded_word(words, 'E', 0) < kMinAnalogOutputIndex) { if (error) { *error = m == 67 ? "Invalid analog index with M67" : "Invalid analog index with M68"; } @@ -7589,9 +7989,9 @@ int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string } return -1; } - if (call_stack.size() >= kMaxSubprogramDepth) { + if (static_cast(call_stack.size()) + 1 >= kMaxSubprogramDepth) { if (error) { - *error = "M98 subprogram nesting limit exceeded"; + *error = "Too many subroutine levels"; } return -1; } diff --git a/core/tests/cnc_sim_api_linuxcnc_rs274_smoke.cpp b/core/tests/cnc_sim_api_linuxcnc_rs274_smoke.cpp index 56ed5e3..6db7401 100644 --- a/core/tests/cnc_sim_api_linuxcnc_rs274_smoke.cpp +++ b/core/tests/cnc_sim_api_linuxcnc_rs274_smoke.cpp @@ -443,6 +443,37 @@ int main() { ok &= expect(saw_g432_h_tool_length, "expected LinuxCNC G43.2 H tool-table additive event"); ok &= expect(saw_g49_dynamic_tool_length_clear, "expected LinuxCNC G49 dynamic tool length clear event"); + const char g49_tool_offset_parameter_program[] = + "G21 G90 G17\n" + "G43.2 H1\n" + "O10 if [#<_tool_offset> EQ 1]\n" + "G1 X1 F100\n" + "O10 endif\n" + "G49\n" + "O10 if [#<_tool_offset> EQ 0]\n" + "G1 X2 F100\n" + "O10 endif\n" + "M30\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + g49_tool_offset_parameter_program, + sizeof(g49_tool_offset_parameter_program) - 1) == 0, + cnc_sim_last_error(sim)); + bool saw_g49_tool_offset_parameter_before = false; + bool saw_g49_tool_offset_parameter_after = false; + for (const auto &event : events) { + saw_g49_tool_offset_parameter_before = saw_g49_tool_offset_parameter_before || + (event.type == CNC_SIM_EVENT_LINEAR_FEED && + event.line == 4 && + near(event.end.x, 1.0)); + saw_g49_tool_offset_parameter_after = saw_g49_tool_offset_parameter_after || + (event.type == CNC_SIM_EVENT_LINEAR_FEED && + event.line == 8 && + near(event.end.x, 2.0)); + } + ok &= expect(saw_g49_tool_offset_parameter_before && saw_g49_tool_offset_parameter_after, + "expected LinuxCNC G49 to clear the applied tool offset parameter before the next motion"); + const char spindle_program[] = "G21 G90 G17\n" "S1200 M3\n" diff --git a/core/tests/cnc_sim_api_smoke.cpp b/core/tests/cnc_sim_api_smoke.cpp index 19bc350..9b861bf 100644 --- a/core/tests/cnc_sim_api_smoke.cpp +++ b/core/tests/cnc_sim_api_smoke.cpp @@ -1384,6 +1384,33 @@ int main() { ok &= expect(saw_immediate_g49_named_restore, "expected G49 to restore readonly position named parameters before the next motion"); + const char g49_tool_offset_parameter_program[] = + "G21 G90 G17\n" + "G43.2 H1\n" + "O10 if [#<_tool_offset> EQ 1]\n" + "G1 X1 F100\n" + "O10 endif\n" + "G49\n" + "O10 if [#<_tool_offset> EQ 0]\n" + "G1 X2 F100\n" + "O10 endif\n" + "M30\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + g49_tool_offset_parameter_program, + sizeof(g49_tool_offset_parameter_program) - 1) == 0, + cnc_sim_last_error(sim)); + bool saw_g49_tool_offset_parameter = false; + for (const auto &event : events) { + saw_g49_tool_offset_parameter = saw_g49_tool_offset_parameter || + (event.type == CNC_SIM_EVENT_LINEAR_FEED && + near(event.end.x, 1.0)) || + (event.type == CNC_SIM_EVENT_LINEAR_FEED && + near(event.end.x, 2.0)); + } + ok &= expect(saw_g49_tool_offset_parameter, + "expected G49 to clear the applied tool offset parameter before the next motion"); + const char g43_tool_length_program[] = "G21 G90 G17\n" "G43 H1\n" @@ -1718,6 +1745,8 @@ int main() { missing_g43_h_tool_program, sizeof(missing_g43_h_tool_program) - 1) != 0, "expected LinuxCNC G43 H to reject tool numbers missing from the tool table"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Requested tool 3 not found in the tool table", + "expected LinuxCNC missing-G43-H-tool error text"); const char invalid_h_word_program[] = "G21 G90 G17\n" @@ -1826,6 +1855,8 @@ int main() { invalid_cutter_g53_program, sizeof(invalid_cutter_g53_program) - 1) != 0, "expected G53 to reject cutter compensation"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Cannot use g53 with cutter radius comp", + "expected LinuxCNC G53 cutter-comp error text"); const char invalid_cutter_g28_program[] = "G21 G90 G17\n" @@ -1836,6 +1867,8 @@ int main() { invalid_cutter_g28_program, sizeof(invalid_cutter_g28_program) - 1) != 0, "expected G28/G30 to reject cutter compensation"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Cannot use g28 or g30 with cutter radius comp", + "expected LinuxCNC G28 cutter-comp error text"); const char invalid_cutter_g281_program[] = "G21 G90 G17\n" @@ -1846,6 +1879,8 @@ int main() { invalid_cutter_g281_program, sizeof(invalid_cutter_g281_program) - 1) != 0, "expected G28.1/G30.1 to reject cutter compensation"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Cannot set reference point with cutter compensation in effect", + "expected LinuxCNC G28.1 cutter-comp error text"); const char invalid_cutter_g92_program[] = "G21 G90 G17\n" @@ -1856,6 +1891,8 @@ int main() { invalid_cutter_g92_program, sizeof(invalid_cutter_g92_program) - 1) != 0, "expected G92 to reject cutter compensation"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Cannot change axis offsets with cutter radius comp", + "expected LinuxCNC G92 cutter-comp error text"); const char invalid_cutter_g64_program[] = "G21 G90 G17\n" @@ -1866,6 +1903,8 @@ int main() { invalid_cutter_g64_program, sizeof(invalid_cutter_g64_program) - 1) != 0, "expected G64 to reject cutter compensation"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Cannot change control mode with cutter radius compensation on", + "expected LinuxCNC G64 cutter-comp error text"); const char invalid_cutter_override_program[] = "G21 G90 G17\n" @@ -1876,6 +1915,8 @@ int main() { invalid_cutter_override_program, sizeof(invalid_cutter_override_program) - 1) != 0, "expected override controls to reject cutter compensation"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Cannot disable overrides with cutter radius compensation on", + "expected LinuxCNC M50 cutter-comp error text"); const char invalid_cutter_digital_output_program[] = "G21 G90 G17\n" @@ -1886,6 +1927,8 @@ int main() { invalid_cutter_digital_output_program, sizeof(invalid_cutter_digital_output_program) - 1) != 0, "expected digital outputs to reject cutter compensation"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Cannot set motion output with cutter radius compensation on", + "expected LinuxCNC M62 cutter-comp error text"); const char invalid_cutter_motion_digital_output_program[] = "G21 G90 G17\n" @@ -1909,6 +1952,8 @@ int main() { invalid_cutter_analog_output_program, sizeof(invalid_cutter_analog_output_program) - 1) != 0, "expected analog outputs to reject cutter compensation"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Cannot set motion analog output with cutter radius compensation on", + "expected LinuxCNC M67 cutter-comp error text"); const char invalid_cutter_g76_program[] = "G21 G90 G17\n" @@ -1919,6 +1964,8 @@ int main() { invalid_cutter_g76_program, sizeof(invalid_cutter_g76_program) - 1) != 0, "expected G76 to reject cutter compensation"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Cannot use G76 threading cycle with cutter radius compensation on", + "expected LinuxCNC G76 cutter-comp error text"); const char invalid_cutter_g98_program[] = "G21 G90 G17\n" @@ -1929,6 +1976,9 @@ int main() { invalid_cutter_g98_program, sizeof(invalid_cutter_g98_program) - 1) != 0, "expected G98/G99 to reject cutter compensation"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == + "Cannot change retract mode with cutter radius compensation on", + "expected LinuxCNC G98 cutter-comp error text"); const char invalid_cutter_tool_change_program[] = "G21 G90 G17\n" @@ -1940,6 +1990,8 @@ int main() { invalid_cutter_tool_change_program, sizeof(invalid_cutter_tool_change_program) - 1) != 0, "expected M6 to reject cutter compensation"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Cannot change tools with cutter radius compensation on", + "expected LinuxCNC M6 cutter-comp error text"); const char missing_t_for_m6_program[] = "G21 G90 G17\n" @@ -1961,6 +2013,8 @@ int main() { invalid_cutter_wait_input_program, sizeof(invalid_cutter_wait_input_program) - 1) != 0, "expected M66 to reject cutter compensation"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Cannot wait for digital input with cutter radius compensation on", + "expected LinuxCNC M66 cutter-comp error text"); const char invalid_g431_motion_program[] = "G21 G90 G17\n" @@ -1970,6 +2024,8 @@ int main() { invalid_g431_motion_program, sizeof(invalid_g431_motion_program) - 1) != 0, "expected G43.1 to reject motion on the same line"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Cannot command motion on the same line as G43.1", + "expected LinuxCNC G43.1 same-line-motion error text"); const char mixed_g432_h_axis_program[] = "G21 G90 G17\n" @@ -1979,6 +2035,8 @@ int main() { mixed_g432_h_axis_program, sizeof(mixed_g432_h_axis_program) - 1) != 0, "expected G43.2 to reject mixed H and axis words"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "G43.2: Can not have both H and axis words", + "expected LinuxCNC G43.2 mixed-H-axis error text"); const char missing_g432_h_tool_program[] = "G21 G90 G17\n" @@ -1988,6 +2046,8 @@ int main() { missing_g432_h_tool_program, sizeof(missing_g432_h_tool_program) - 1) != 0, "expected LinuxCNC G43.2 H to reject tool numbers missing from the tool table"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Requested tool 3 not found in the tool table", + "expected LinuxCNC missing-G43.2-H-tool error text"); const char invalid_g432_program[] = "G21 G90 G17\n" @@ -1997,6 +2057,8 @@ int main() { invalid_g432_program, sizeof(invalid_g432_program) - 1) != 0, "expected G43.2 to require H or axis words"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "G43.2: No axes specified and H word missing", + "expected LinuxCNC G43.2 missing-H-axis error text"); const char spindle_program[] = "G21\n" @@ -3769,6 +3831,9 @@ int main() { events.clear(); ok &= expect(cnc_sim_parse_program(sim, cutter_comp_cycle_program, sizeof(cutter_comp_cycle_program) - 1) != 0, "expected canned cycle with cutter compensation on to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == + "Cannot use canned cycles with cutter compensation on", + "expected LinuxCNC canned-cycle cutter-comp error text"); const char missing_g73_q_program[] = "G21 G90 G17\n" @@ -4202,6 +4267,8 @@ int main() { events.clear(); ok &= expect(cnc_sim_parse_program(sim, recursive_subprogram, sizeof(recursive_subprogram) - 1) != 0, "expected recursive M98 nesting limit to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Too many subroutine levels", + "expected LinuxCNC recursive-M98 nesting-limit error text"); const char fanuc_subprogram_endsub_program[] = "G21\n" @@ -4650,6 +4717,24 @@ int main() { sizeof(out_of_range_numbered_parameter_reference) - 1) != 0, "expected out-of-range numbered parameter reference to fail"); + const char zero_numbered_parameter_assignment[] = + "#0 = 1\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + zero_numbered_parameter_assignment, + sizeof(zero_numbered_parameter_assignment) - 1) != 0, + "expected #0 numbered parameter assignment to fail"); + + const char zero_numbered_parameter_reference[] = + "G21 G90\n" + "F100\n" + "G1 X#0\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + zero_numbered_parameter_reference, + sizeof(zero_numbered_parameter_reference) - 1) != 0, + "expected #0 numbered parameter reference to fail"); + const char named_oword_subprogram[] = "G21 G90\n" "G0 X0 Y0 Z0\n" @@ -4792,6 +4877,72 @@ int main() { ok &= expect(saw_named_parameter_local, "expected local named parameter assignment in O-word call"); ok &= expect(saw_named_parameter_global_after_call, "expected global named parameter after O-word return"); + const char too_many_oword_call_parameters_program[] = + "O sub\n" + "O endsub\n" + "O call " + "[1] [2] [3] [4] [5] [6] [7] [8] [9] [10] " + "[11] [12] [13] [14] [15] [16] [17] [18] [19] [20] " + "[21] [22] [23] [24] [25] [26] [27] [28] [29] [30] [31]\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + too_many_oword_call_parameters_program, + sizeof(too_many_oword_call_parameters_program) - 1) != 0, + "expected LinuxCNC to reject more than 30 O-word call parameters"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Too many subroutine parameters", + "expected LinuxCNC too-many-O-word-call-parameters error text"); + + const char empty_oword_call_parameter_program[] = + "O sub\n" + "O endsub\n" + "O call []\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + empty_oword_call_parameter_program, + sizeof(empty_oword_call_parameter_program) - 1) != 0, + "expected LinuxCNC to reject an empty O-word call parameter expression"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "No characters found in reading real value", + "expected LinuxCNC empty-O-word-call-parameter error text"); + + const char too_many_oword_call_levels_program[] = + "O sub\n" + "O call\n" + "O endsub\n" + "O sub\n" + "O call\n" + "O endsub\n" + "O sub\n" + "O call\n" + "O endsub\n" + "O sub\n" + "O call\n" + "O endsub\n" + "O sub\n" + "O call\n" + "O endsub\n" + "O sub\n" + "O call\n" + "O endsub\n" + "O sub\n" + "O call\n" + "O endsub\n" + "O sub\n" + "O call\n" + "O endsub\n" + "O sub\n" + "O call\n" + "O endsub\n" + "O sub\n" + "O endsub\n" + "O call\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + too_many_oword_call_levels_program, + sizeof(too_many_oword_call_levels_program) - 1) != 0, + "expected LinuxCNC to reject the tenth nested O-word call level"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Too many subroutine levels", + "expected LinuxCNC too-many-subroutine-levels error text"); + const char parameter_expression_assignment_program[] = "G20\n" "# = 2\n" @@ -4939,6 +5090,9 @@ int main() { cutter_comp_arc_exit_program, sizeof(cutter_comp_arc_exit_program) - 1) != 0, "expected LinuxCNC to reject arc move immediately after exiting cutter compensation"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == + "The move just after exiting cutter compensation mode must be straight, not an arc", + "expected LinuxCNC cutter-comp arc-exit error text"); const char abort_hot_comment_program[] = "#42 = 0\n" @@ -4970,6 +5124,9 @@ int main() { cutter_comp_gouging_program, sizeof(cutter_comp_gouging_program) - 1) != 0, "expected LinuxCNC to reject straight-feed concave cutter-comp gouging"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == + "Straight feed in concave corner cannot be reached by the tool without gouging", + "expected LinuxCNC cutter-comp gouging error text"); const char readonly_numbered_parameter_program[] = "G21 G90 G17\n" @@ -6218,6 +6375,8 @@ int main() { malformed_number_program, sizeof(malformed_number_program) - 1) != 0, "expected LinuxCNC bad number format to reject malformed real values"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "bad number format (conversion failed) parsing '1.2.3'", + "expected LinuxCNC malformed-real-value error text"); const char unused_p_word_program[] = "G21 G90\n" @@ -6417,6 +6576,9 @@ int main() { g71_rejects_cutter_comp_program, sizeof(g71_rejects_cutter_comp_program) - 1) != 0, "expected LinuxCNC to reject G71/G72 with cutter compensation enabled"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == + "G71.0 cannot be used with cutter compensation enabled", + "expected LinuxCNC G71 cutter-comp error text"); const char negative_l_word_program[] = "G21 G90\n" @@ -7594,6 +7756,17 @@ int main() { sizeof(invalid_g62_code_program) - 1) != 0, "expected LinuxCNC to reject G62 as an unknown G code"); + const char mixed_g434_motion_program[] = + "G21 G90\n" + "G43.4 H7 X10\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + mixed_g434_motion_program, + sizeof(mixed_g434_motion_program) - 1) != 0, + "expected LinuxCNC to reject mixed G43.4 motion as an unknown G code"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Unknown g code used", + "expected mixed G43.4 motion to use LinuxCNC unknown-G error text"); + const char invalid_character_program[] = "G21 G90\n" "?\n"; @@ -8685,6 +8858,64 @@ int main() { missing_function_brackets_program, sizeof(missing_function_brackets_program) - 1) != 0, "expected LinuxCNC function syntax to require bracketed arguments"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Left bracket missing after unary operation name", + "expected LinuxCNC missing-function-left-bracket error text"); + + const char missing_word_function_brackets_program[] = + "G21 G90\n" + "F100\n" + "G1 XSIN30\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + missing_word_function_brackets_program, + sizeof(missing_word_function_brackets_program) - 1) != 0, + "expected LinuxCNC word function syntax to require bracketed arguments"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Left bracket missing after unary operation name", + "expected LinuxCNC word missing-function-left-bracket error text"); + + const char unknown_function_word_program[] = + "G21 G90\n" + "F100\n" + "G1 XFOO[1]\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + unknown_function_word_program, + sizeof(unknown_function_word_program) - 1) != 0, + "expected LinuxCNC to reject unknown unary word values"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Unknown word starting with f", + "expected LinuxCNC unknown-unary-word error text"); + + const char unknown_function_word_without_brackets_program[] = + "G21 G90\n" + "F100\n" + "G1 XFOO\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + unknown_function_word_without_brackets_program, + sizeof(unknown_function_word_without_brackets_program) - 1) != 0, + "expected LinuxCNC to reject unknown unbracketed unary word values"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Unknown word starting with f", + "expected LinuxCNC unknown-unbracketed-unary-word error text"); + + const char atan_missing_slash_program[] = + "#1 = ATAN[1]\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + atan_missing_slash_program, + sizeof(atan_missing_slash_program) - 1) != 0, + "expected LinuxCNC ATAN syntax to require a slash before the second argument"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Slash missing after first atan argument", + "expected LinuxCNC missing-ATAN-slash error text"); + + const char atan_missing_second_bracket_program[] = + "#1 = ATAN[1]/1\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + atan_missing_second_bracket_program, + sizeof(atan_missing_second_bracket_program) - 1) != 0, + "expected LinuxCNC ATAN syntax to require a bracketed second argument"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Left bracket missing after slash with atan", + "expected LinuxCNC missing-ATAN-second-left-bracket error text"); const char negative_sqrt_program[] = "#1 = SQRT[-1]\n" @@ -8694,6 +8925,102 @@ int main() { negative_sqrt_program, sizeof(negative_sqrt_program) - 1) != 0, "expected LinuxCNC to reject negative SQRT arguments"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Negative argument to sqrt", + "expected LinuxCNC negative-SQRT error text"); + + const char zero_ln_program[] = + "#1 = LN[0]\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + zero_ln_program, + sizeof(zero_ln_program) - 1) != 0, + "expected LinuxCNC to reject zero LN arguments"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Zero or negative argument to ln", + "expected LinuxCNC zero-LN error text"); + + const char out_of_range_acos_program[] = + "#1 = ACOS[2]\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + out_of_range_acos_program, + sizeof(out_of_range_acos_program) - 1) != 0, + "expected LinuxCNC to reject ACOS arguments outside [-1, 1]"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Argument to acos out of range", + "expected LinuxCNC out-of-range-ACOS error text"); + + const char out_of_range_asin_program[] = + "#1 = ASIN[2]\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + out_of_range_asin_program, + sizeof(out_of_range_asin_program) - 1) != 0, + "expected LinuxCNC to reject ASIN arguments outside [-1, 1]"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Argument to asin out of range", + "expected LinuxCNC out-of-range-ASIN error text"); + + const char negative_fractional_power_program[] = + "#1 = [-2 ** 0.5]\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + negative_fractional_power_program, + sizeof(negative_fractional_power_program) - 1) != 0, + "expected LinuxCNC to reject a negative base raised to a non-integer power"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Attempt to raise negative to non integer power", + "expected LinuxCNC negative-fractional-power error text"); + + const char divide_by_zero_program[] = + "#1 = [1 / 0]\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + divide_by_zero_program, + sizeof(divide_by_zero_program) - 1) != 0, + "expected LinuxCNC to reject division by zero"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Attempt to divide by zero", + "expected LinuxCNC divide-by-zero error text"); + + const char modulo_by_zero_program[] = + "#1 = [1 MOD 0]\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + modulo_by_zero_program, + sizeof(modulo_by_zero_program) - 1) != 0, + "expected LinuxCNC to reject modulo by zero"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Calculation resulted in 'not a number'", + "expected LinuxCNC modulo-by-zero error text"); + + const char unknown_n_operation_program[] = + "#1 = [1 NAND 1]\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + unknown_n_operation_program, + sizeof(unknown_n_operation_program) - 1) != 0, + "expected LinuxCNC to reject unknown n-starting binary operation names"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Unknown operation name starting with n", + "expected LinuxCNC unknown-n-operation error text"); + + const char unknown_word_bracket_operation_program[] = + "G21 G90\n" + "F100\n" + "G1 X[1 AXX 1]\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + unknown_word_bracket_operation_program, + sizeof(unknown_word_bracket_operation_program) - 1) != 0, + "expected LinuxCNC to reject unknown word bracket binary operation names"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Unknown operation name starting with a", + "expected LinuxCNC unknown-word-bracket-operation error text"); + + const char unknown_symbol_word_bracket_operation_program[] = + "G21 G90\n" + "F100\n" + "G1 X[1 ? 2]\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + unknown_symbol_word_bracket_operation_program, + sizeof(unknown_symbol_word_bracket_operation_program) - 1) != 0, + "expected LinuxCNC to reject unknown symbolic word bracket operations"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Unknown operation", + "expected LinuxCNC unknown-symbol-word-bracket-operation error text"); const char infinite_expression_program[] = "#1 = EXP[1000]\n" @@ -8703,6 +9030,8 @@ int main() { infinite_expression_program, sizeof(infinite_expression_program) - 1) != 0, "expected LinuxCNC to reject infinite expression results"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Calculation resulted in 'infinity'", + "expected LinuxCNC infinite-expression error text"); const char hidden_infinite_expression_program[] = "O10 if [EXP[1000] EQ EXP[1000]]\n" @@ -8723,6 +9052,8 @@ int main() { hidden_infinite_bracket_expression_program, sizeof(hidden_infinite_bracket_expression_program) - 1) != 0, "expected LinuxCNC to reject infinite bracket expression values before comparison"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Calculation resulted in 'infinity'", + "expected LinuxCNC infinite-bracket-expression error text"); const char spaced_function_bracket_program[] = "G21 G90\n" @@ -9714,6 +10045,56 @@ int main() { 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_missing_bracket_program[] = + "O100 if\n" + "G1 X1\n" + "O100 endif\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + oword_if_missing_bracket_program, + sizeof(oword_if_missing_bracket_program) - 1) != 0, + "expected LinuxCNC O-word if without a bracketed condition to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Left bracket missing after 'if'", + "expected LinuxCNC missing-O-word-if-left-bracket error text"); + + const char oword_while_missing_bracket_program[] = + "O100 while\n" + "G1 X1\n" + "O100 endwhile\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + oword_while_missing_bracket_program, + sizeof(oword_while_missing_bracket_program) - 1) != 0, + "expected LinuxCNC O-word while without a bracketed condition to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Left bracket missing after 'while'", + "expected LinuxCNC missing-O-word-while-left-bracket error text"); + + const char oword_repeat_missing_bracket_program[] = + "O100 repeat\n" + "G1 X1\n" + "O100 endrepeat\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + oword_repeat_missing_bracket_program, + sizeof(oword_repeat_missing_bracket_program) - 1) != 0, + "expected LinuxCNC O-word repeat without a bracketed count to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Left bracket missing after 'repeat'", + "expected LinuxCNC missing-O-word-repeat-left-bracket error text"); + + const char oword_elseif_missing_bracket_program[] = + "O100 if [0]\n" + "G1 X1\n" + "O100 elseif\n" + "G1 X2\n" + "O100 endif\n"; + events.clear(); + ok &= expect(cnc_sim_parse_program(sim, + oword_elseif_missing_bracket_program, + sizeof(oword_elseif_missing_bracket_program) - 1) != 0, + "expected LinuxCNC O-word elseif without a bracketed condition to fail"); + ok &= expect(std::string(cnc_sim_last_error(sim)) == "Left bracket missing after 'elseif'", + "expected LinuxCNC missing-O-word-elseif-left-bracket error text"); + const char oword_if_extra_gcode_program[] = "O100 if [1] G1 X1\n" "O100 endif\n" diff --git a/core/tests/linuxcnc_gees_table_smoke.cpp b/core/tests/linuxcnc_gees_table_smoke.cpp new file mode 100644 index 0000000..b93ff65 --- /dev/null +++ b/core/tests/linuxcnc_gees_table_smoke.cpp @@ -0,0 +1,1235 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +bool expect(bool value, const char *message) { + if (!value) { + std::cerr << "FAIL: " << message << '\n'; + return false; + } + return true; +} + +std::string read_file(const std::string &path) { + std::ifstream input(path); + std::ostringstream buffer; + buffer << input.rdbuf(); + return buffer.str(); +} + +std::string strip_comments(const std::string &text) { + std::string stripped; + stripped.reserve(text.size()); + + bool in_line_comment = false; + bool in_block_comment = false; + for (std::size_t i = 0; i < text.size(); ++i) { + if (in_line_comment) { + if (text[i] == '\n') { + in_line_comment = false; + stripped.push_back('\n'); + } + continue; + } + if (in_block_comment) { + if (text[i] == '*' && i + 1 < text.size() && text[i + 1] == '/') { + in_block_comment = false; + ++i; + } + continue; + } + if (text[i] == '/' && i + 1 < text.size() && text[i + 1] == '/') { + in_line_comment = true; + ++i; + continue; + } + if (text[i] == '/' && i + 1 < text.size() && text[i + 1] == '*') { + in_block_comment = true; + ++i; + continue; + } + stripped.push_back(text[i]); + } + + return stripped; +} + +std::vector parse_linuxcnc_int_table(const std::string &source, const std::string &marker) { + const std::size_t marker_pos = source.find(marker); + if (marker_pos == std::string::npos) { + return {}; + } + const std::size_t begin = source.find('{', marker_pos); + const std::size_t end = source.find("};", begin); + if (begin == std::string::npos || end == std::string::npos || end <= begin) { + return {}; + } + + const std::string body = strip_comments(source.substr(begin + 1, end - begin - 1)); + std::vector values; + const std::regex integer_pattern("-?\\d+"); + for (std::sregex_iterator it(body.begin(), body.end(), integer_pattern), last; it != last; ++it) { + const std::size_t pos = static_cast(it->position()); + const std::size_t after = pos + static_cast(it->length()); + const bool embedded_prefix = + pos > 0 && (std::isalnum(static_cast(body[pos - 1])) || body[pos - 1] == '_'); + const bool embedded_suffix = + after < body.size() && (std::isalnum(static_cast(body[after])) || body[after] == '_'); + if (!embedded_prefix && !embedded_suffix) { + values.push_back(std::stoi(it->str())); + } + } + return values; +} + +std::string linuxcnc_root_from_env() { + const char *root = std::getenv("LINUXCNC_ROOT"); + return root && root[0] != '\0' ? root : "../linuxcnc"; +} + +int parse_named_integer_constant(const std::string &source, const std::string &name) { + const std::regex pattern(name + R"(\s*=\s*(\d+))"); + std::smatch match; + if (!std::regex_search(source, match, pattern)) { + return -1; + } + return std::stoi(match[1].str()); +} + +int parse_constexpr_integer(const std::string &source, const std::string &name) { + const std::regex pattern(R"(constexpr\s+int\s+)" + name + R"(\s*=\s*(\d+))"); + std::smatch match; + if (!std::regex_search(source, match, pattern)) { + return -1; + } + return std::stoi(match[1].str()); +} + +double parse_constexpr_double(const std::string &source, const std::string &name) { + const std::regex pattern(R"(constexpr\s+double\s+)" + name + R"(\s*=\s*([0-9]+(?:\.[0-9]+)?(?:e-?\d+)?))"); + std::smatch match; + if (!std::regex_search(source, match, pattern)) { + return -1.0; + } + return std::stod(match[1].str()); +} + +int parse_define_integer(const std::string &source, const std::string &name) { + const std::regex pattern(R"(#define\s+)" + name + R"(\s+(\d+))"); + std::smatch match; + if (!std::regex_search(source, match, pattern)) { + return -1; + } + return std::stoi(match[1].str()); +} + +double parse_define_double(const std::string &source, const std::string &name) { + const std::regex pattern(R"(#define\s+)" + name + R"(\s+([0-9]+(?:\.[0-9]+)?(?:e-?\d+)?))"); + std::smatch match; + if (!std::regex_search(source, match, pattern)) { + return -1.0; + } + return std::stod(match[1].str()); +} + +double parse_define_double_expression(const std::string &source, const std::string &name) { + const std::regex simple_pattern(R"(#define\s+)" + name + R"(\s+([0-9]+(?:\.[0-9]+)?(?:e-?\d+)?))"); + std::smatch match; + if (std::regex_search(source, match, simple_pattern)) { + return std::stod(match[1].str()); + } + + const std::regex sqrt2_pattern( + R"(#define\s+)" + name + R"(\s+\(\s*([0-9]+(?:\.[0-9]+)?)\s*\*\s*([0-9]+(?:\.[0-9]+)?)\s*\*\s*M_SQRT2\s*\))"); + if (std::regex_search(source, match, sqrt2_pattern)) { + return std::stod(match[1].str()) * std::stod(match[2].str()) * M_SQRT2; + } + return -1.0; +} + +double parse_define_double_product_expression(const std::string &source, + const std::string &name, + const std::string &left_name, + double left_value, + const std::string &right_name, + double right_value) { + const std::regex pattern( + R"(#define\s+)" + name + R"(\s+\(\s*)" + left_name + R"(\s*\*\s*)" + + right_name + R"(\s*\))"); + std::smatch match; + if (!std::regex_search(source, match, pattern)) { + return -1.0; + } + return left_value * right_value; +} + +double parse_constexpr_double_product_expression(const std::string &source, + const std::string &name, + const std::string &left_name, + double left_value, + const std::string &right_name, + double right_value) { + const std::regex pattern( + R"(constexpr\s+double\s+)" + name + R"(\s*=\s*)" + left_name + + R"(\s*\*\s*)" + right_name + R"(\s*;)"); + std::smatch match; + if (!std::regex_search(source, match, pattern)) { + return -1.0; + } + return left_value * right_value; +} + +double parse_constexpr_double_expression(const std::string &source, const std::string &name) { + const std::regex simple_pattern(R"(constexpr\s+double\s+)" + name + R"(\s*=\s*([0-9]+(?:\.[0-9]+)?(?:e-?\d+)?))"); + std::smatch match; + if (std::regex_search(source, match, simple_pattern)) { + const std::size_t after = static_cast(match.position(1) + match.length(1)); + std::size_t next = after; + while (next < source.size() && std::isspace(static_cast(source[next]))) { + ++next; + } + if (next >= source.size() || source[next] == ';') { + return std::stod(match[1].str()); + } + } + + const std::regex sqrt2_pattern( + R"(constexpr\s+double\s+)" + name + R"(\s*=\s*([0-9]+(?:\.[0-9]+)?)\s*\*\s*([0-9]+(?:\.[0-9]+)?)\s*\*\s*M_SQRT2)"); + if (std::regex_search(source, match, sqrt2_pattern)) { + return std::stod(match[1].str()) * std::stod(match[2].str()) * M_SQRT2; + } + return -1.0; +} + +double parse_m19_max_orient_angle(const std::string &source) { + const std::regex pattern(R"(r_number\s*>\s*([0-9]+(?:\.[0-9]+)?)[\s\S]*?R value must be within 0\.\.360 with M19)"); + std::smatch match; + if (!std::regex_search(source, match, pattern)) { + return -1.0; + } + return std::stod(match[1].str()); +} + +double parse_p_word_integer_tolerance(const std::string &source) { + const std::size_t message_pos = source.find("P value not an integer with M19 G2 or G3"); + if (message_pos == std::string::npos) { + return -1.0; + } + const std::size_t begin = source.rfind("fabs", message_pos); + if (begin == std::string::npos) { + return -1.0; + } + const std::string check = source.substr(begin, message_pos - begin); + const std::regex pattern(R"(>\s*([0-9]+(?:\.[0-9]+)?))"); + std::smatch match; + if (!std::regex_search(check, match, pattern)) { + return -1.0; + } + return std::stod(match[1].str()); +} + +int parse_arc_min_p_repeat_count(const std::string &source) { + const std::size_t message_pos = source.find("P value should be 1 or greater with G2 or G3"); + if (message_pos == std::string::npos) { + return -1; + } + const std::size_t begin = source.rfind("round_to_int(block->p_number)", message_pos); + if (begin == std::string::npos) { + return -1; + } + const std::string check = source.substr(begin, message_pos - begin); + const std::regex pattern(R"(<\s*(\d+))"); + std::smatch match; + if (!std::regex_search(check, match, pattern)) { + return -1; + } + return std::stoi(match[1].str()); +} + +std::pair parse_m19_orient_mode_range(const std::string &source) { + const std::regex pattern( + R"(block->m_modes\[7\]\s*==\s*19[\s\S]*?p_value\s*>\s*(\d+)[\s\S]*?p_value\s*<\s*(\d+)[\s\S]*?P value must be 0,1,or 2 with M19)"); + std::smatch match; + if (!std::regex_search(source, match, pattern)) { + return {-1, -1}; + } + return {std::stoi(match[2].str()), std::stoi(match[1].str())}; +} + +double parse_m19_min_wait_seconds(const std::string &source) { + const std::size_t message_pos = source.find("Q word with M19 requires a value > 0"); + if (message_pos == std::string::npos) { + return -1.0; + } + const std::size_t begin = source.rfind("block->q_number", message_pos); + if (begin == std::string::npos) { + return -1.0; + } + const std::string check = source.substr(begin, message_pos - begin); + const std::regex pattern(R"(<=\s*([0-9]+(?:\.[0-9]+)?))"); + std::smatch match; + if (!std::regex_search(check, match, pattern)) { + return -1.0; + } + return std::stod(match[1].str()); +} + +std::pair parse_is_near_int_constants(const std::string &source) { + const std::size_t marker_pos = source.find("static int is_near_int"); + if (marker_pos == std::string::npos) { + return {-1.0, -1.0}; + } + const std::size_t end = source.find('}', marker_pos); + if (end == std::string::npos) { + return {-1.0, -1.0}; + } + const std::string body = source.substr(marker_pos, end - marker_pos); + const std::regex offset_pattern(R"(value\s*\+\s*(\.?[0-9]+(?:\.[0-9]+)?))"); + const std::regex tolerance_pattern(R"(<\s*(\.?[0-9]+(?:\.[0-9]+)?))"); + std::smatch offset_match; + std::smatch tolerance_match; + if (!std::regex_search(body, offset_match, offset_pattern) || + !std::regex_search(body, tolerance_match, tolerance_pattern)) { + return {-1.0, -1.0}; + } + const auto parse = [](std::string value) { + if (!value.empty() && value.front() != '.') { + return std::stod(value); + } + return std::stod("0" + value); + }; + return {parse(offset_match[1].str()), parse(tolerance_match[1].str())}; +} + +int parse_m61_min_tool_number(const std::string &source) { + const std::size_t message_pos = + source.find("Need non-negative Q-word to specify tool number with M61"); + if (message_pos == std::string::npos) { + return -1; + } + const std::size_t begin = source.rfind("toolno", message_pos); + if (begin == std::string::npos) { + return -1; + } + const std::string check = source.substr(begin, message_pos - begin); + const std::regex pattern(R"(toolno\s*<\s*(\d+))"); + std::smatch match; + if (!std::regex_search(check, match, pattern)) { + return -1; + } + return std::stoi(match[1].str()); +} + +int parse_max_m_code_from_read_m(const std::string &source) { + const std::size_t read_m_pos = source.find("Interp::read_m"); + if (read_m_pos == std::string::npos) { + return -1; + } + const std::size_t message_pos = source.find("NCE_M_CODE_GREATER_THAN_199", read_m_pos); + if (message_pos == std::string::npos) { + return -1; + } + const std::size_t begin = source.rfind("CHKS", message_pos); + if (begin == std::string::npos || begin < read_m_pos) { + return -1; + } + const std::string check = source.substr(begin, message_pos - begin); + const std::regex pattern(R"(value\s*>\s*(\d+))"); + std::smatch match; + if (!std::regex_search(check, match, pattern)) { + return -1; + } + return std::stoi(match[1].str()); +} + +std::vector parse_g_code_read_real_constants(const std::string &source) { + const std::size_t read_g_pos = source.find("Interp::read_g"); + if (read_g_pos == std::string::npos) { + return {}; + } + const std::size_t end = source.find("CHKS((value > 999)", read_g_pos); + if (end == std::string::npos) { + return {}; + } + const std::string body = source.substr(read_g_pos, end - read_g_pos); + std::vector values; + const std::regex scale_pattern(R"(([0-9]+(?:\.[0-9]+)?)\s*\*\s*value_read)"); + const std::regex fraction_pattern(R"(value_read\s*-\s*value\)\s*>\s*([0-9]+(?:\.[0-9]+)?))"); + std::smatch match; + if (std::regex_search(body, match, scale_pattern)) { + values.push_back(std::stod(match[1].str())); + } + for (std::sregex_iterator it(body.begin(), body.end(), fraction_pattern), last; it != last; ++it) { + values.push_back(std::stod((*it)[1].str())); + } + return values; +} + +std::vector parse_integer_read_fraction_constants(const std::string &source) { + const std::size_t read_integer_pos = source.find("Interp::read_integer_value"); + if (read_integer_pos == std::string::npos) { + return {}; + } + const std::size_t end = source.find("return INTERP_OK;", read_integer_pos); + if (end == std::string::npos) { + return {}; + } + const std::string body = source.substr(read_integer_pos, end - read_integer_pos); + std::vector values; + const std::regex pattern(R"(float_value\s*-\s*\*integer_ptr\)\s*>\s*([0-9]+(?:\.[0-9]+)?))"); + for (std::sregex_iterator it(body.begin(), body.end(), pattern), last; it != last; ++it) { + values.push_back(std::stod((*it)[1].str())); + } + return values; +} + +int parse_max_g_code_from_read_g(const std::string &source) { + const std::size_t read_g_pos = source.find("Interp::read_g"); + if (read_g_pos == std::string::npos) { + return -1; + } + const std::size_t begin = source.find("CHKS((value >", read_g_pos); + if (begin == std::string::npos) { + return -1; + } + const std::size_t end = source.find("NCE_G_CODE_OUT_OF_RANGE", begin); + if (end == std::string::npos) { + return -1; + } + const std::string check = source.substr(begin, end - begin); + const std::regex pattern(R"(value\s*>\s*(\d+))"); + std::smatch match; + if (!std::regex_search(check, match, pattern)) { + return -1; + } + return std::stoi(match[1].str()); +} + +int parse_min_analog_output_index(const std::string &source) { + const std::size_t message_pos = source.find("Invalid analog index with M67"); + if (message_pos == std::string::npos) { + return -1; + } + const std::size_t begin = source.rfind("round_to_int(block->e_number)", message_pos); + if (begin == std::string::npos) { + return -1; + } + const std::string check = source.substr(begin, message_pos - begin); + const std::regex pattern(R"(<\s*(\d+))"); + std::smatch match; + if (!std::regex_search(check, match, pattern)) { + return -1; + } + return std::stoi(match[1].str()); +} + +std::pair parse_g10_p_integer_check(const std::string &source) { + const std::size_t message_pos = source.find("P value not an integer with G10"); + if (message_pos == std::string::npos) { + return {-1.0, -1.0}; + } + const std::size_t begin = source.rfind("block->p_number", message_pos); + if (begin == std::string::npos) { + return {-1.0, -1.0}; + } + const std::string check = source.substr(begin, message_pos - begin); + const std::regex pattern( + R"(block->p_number\s*\+\s*([0-9]+(?:\.[0-9]+)?)[\s\S]*?>\s*([0-9]+(?:\.[0-9]+)?))"); + std::smatch match; + if (!std::regex_search(check, match, pattern)) { + return {-1.0, -1.0}; + } + return {std::stod(match[1].str()), std::stod(match[2].str())}; +} + +std::vector parse_g10_allowed_l_numbers(const std::string &source) { + const std::size_t message_pos = source.find("Line with G10 does not have L0, L1, L10, L11, L2, or L20"); + if (message_pos == std::string::npos) { + return {}; + } + const std::size_t begin = source.rfind("CHKS", message_pos); + if (begin == std::string::npos) { + return {}; + } + const std::string check = source.substr(begin, message_pos - begin); + std::vector values; + const std::regex pattern(R"(block->l_number\s*!=\s*(\d+))"); + for (std::sregex_iterator it(check.begin(), check.end(), pattern), last; it != last; ++it) { + values.push_back(std::stoi((*it)[1].str())); + } + std::sort(values.begin(), values.end()); + values.erase(std::unique(values.begin(), values.end()), values.end()); + return values; +} + +std::vector parse_smoke_constexpr_int_array(const std::string &source, const std::string &name) { + const std::size_t marker_pos = source.find(name); + if (marker_pos == std::string::npos) { + return {}; + } + const std::size_t begin = source.find('{', marker_pos); + const std::size_t end = source.find('}', begin); + if (begin == std::string::npos || end == std::string::npos || end <= begin) { + return {}; + } + std::vector values; + const std::string body = source.substr(begin + 1, end - begin - 1); + const std::regex integer_pattern(R"(\d+)"); + for (std::sregex_iterator it(body.begin(), body.end(), integer_pattern), last; it != last; ++it) { + values.push_back(std::stoi(it->str())); + } + std::sort(values.begin(), values.end()); + values.erase(std::unique(values.begin(), values.end()), values.end()); + return values; +} + +std::vector parse_g10_l_numbers_for_message(const std::string &source, const std::string &message) { + const std::size_t message_pos = source.find(message); + if (message_pos == std::string::npos) { + return {}; + } + const std::size_t begin = source.rfind("CHKS", message_pos); + if (begin == std::string::npos) { + return {}; + } + const std::string check = source.substr(begin, message_pos - begin); + std::vector values; + const std::regex pattern(R"(block->l_number\s*==\s*(\d+))"); + for (std::sregex_iterator it(check.begin(), check.end(), pattern), last; it != last; ++it) { + values.push_back(std::stoi((*it)[1].str())); + } + std::sort(values.begin(), values.end()); + values.erase(std::unique(values.begin(), values.end()), values.end()); + return values; +} + +int parse_g10_max_tool_orientation(const std::string &source) { + const std::regex pattern(R"(q\s*>\s*(\d+)[\s\S]*?Invalid tool orientation)"); + std::smatch match; + if (!std::regex_search(source, match, pattern)) { + return -1; + } + return std::stoi(match[1].str()); +} + +std::pair parse_g10_coordinate_system_index_range(const std::string &source) { + const std::regex pattern( + R"(p_int\s*<\s*(\d+)[\s\S]*?p_int\s*>\s*(\d+)[\s\S]*?P value out of range \(0-9\) with G10 L%d)"); + std::smatch match; + if (!std::regex_search(source, match, pattern)) { + return {-1, -1}; + } + return {std::stoi(match[1].str()), std::stoi(match[2].str())}; +} + +int parse_g10_min_tool_number(const std::string &source) { + const std::regex pattern( + R"(block->l_number == 1[\s\S]*?block->l_number == 10[\s\S]*?block->l_number == 11[\s\S]*?p_int\s*<\s*(\d+)[\s\S]*?P value out of range with G10 L%d)"); + std::smatch match; + if (!std::regex_search(source, match, pattern)) { + return -1; + } + return std::stoi(match[1].str()); +} + +int parse_enum_ordinal(const std::string &source, const std::string &enum_name, const std::string &enumerator) { + const std::size_t marker_pos = source.find("enum " + enum_name); + if (marker_pos == std::string::npos) { + return -1; + } + const std::size_t begin = source.find('{', marker_pos); + const std::size_t end = source.find("};", begin); + if (begin == std::string::npos || end == std::string::npos || end <= begin) { + return -1; + } + + const std::string body = strip_comments(source.substr(begin + 1, end - begin - 1)); + const std::regex item_pattern(R"(([A-Za-z_][A-Za-z0-9_]*)(?:\s*=\s*(\d+))?)"); + int value = -1; + for (std::sregex_iterator it(body.begin(), body.end(), item_pattern), last; it != last; ++it) { + ++value; + if ((*it)[2].matched) { + value = std::stoi((*it)[2].str()); + } + if ((*it)[1].str() == enumerator) { + return value; + } + } + return -1; +} + +int parse_int_array_size(const std::string &source, const std::string &name) { + const std::regex pattern(R"(\bint\s+)" + name + R"(\s*\[(\d+)\])"); + std::smatch match; + if (!std::regex_search(source, match, pattern)) { + return -1; + } + return std::stoi(match[1].str()); +} + +int parse_named_array_size(const std::string &source, const std::string &name) { + const std::regex pattern(R"(\b)" + name + R"(\s*\[(\d+)\])"); + std::smatch match; + if (!std::regex_search(source, match, pattern)) { + return -1; + } + return std::stoi(match[1].str()); +} + +std::vector parse_linuxcnc_reader_indices(const std::string &source) { + const std::size_t marker_pos = source.find("Interp::default_readers"); + if (marker_pos == std::string::npos) { + return {}; + } + const std::size_t begin = source.find('{', marker_pos); + const std::size_t end = source.find("};", begin); + if (begin == std::string::npos || end == std::string::npos || end <= begin) { + return {}; + } + + std::vector values; + int index = 0; + const std::string body = strip_comments(source.substr(begin + 1, end - begin - 1)); + const std::regex item_pattern(R"(0|&Interp::read_[A-Za-z_]+)"); + for (std::sregex_iterator it(body.begin(), body.end(), item_pattern), last; it != last; ++it) { + if (it->str() != "0") { + values.push_back(index); + } + ++index; + } + return values; +} + +std::vector parse_smoke_reader_symbol_indices(const std::string &source) { + std::vector values; + + const std::regex char_pattern( + R"(out\[static_cast\('(.{1})'\)\]\s*=\s*LinuxCncReaderSymbol::[A-Za-z]+)"); + for (std::sregex_iterator it(source.begin(), source.end(), char_pattern), last; it != last; ++it) { + values.push_back(static_cast((*it)[1].str()[0])); + } + + const std::regex range_pattern( + R"(for\s*\(unsigned char ch = '(.{1})'; ch <= '(.{1})'; \+\+ch\))"); + for (std::sregex_iterator it(source.begin(), source.end(), range_pattern), last; it != last; ++it) { + const unsigned char first = static_cast((*it)[1].str()[0]); + const unsigned char last_char = static_cast((*it)[2].str()[0]); + for (unsigned char ch = first; ch <= last_char; ++ch) { + values.push_back(ch); + } + } + + std::sort(values.begin(), values.end()); + values.erase(std::unique(values.begin(), values.end()), values.end()); + values.erase(std::remove_if(values.begin(), + values.end(), + [](int value) { + return value >= 'A' && value <= 'Z'; + }), + values.end()); + return values; +} + +std::vector parse_smoke_modal_table(const std::string &source, + const std::string &function_name, + std::size_t table_size) { + const std::size_t marker_pos = source.find(function_name); + if (marker_pos == std::string::npos) { + return {}; + } + const std::size_t begin = source.find("out.fill(-1);", marker_pos); + const std::size_t end = source.find("return out;", begin); + if (begin == std::string::npos || end == std::string::npos || end <= begin) { + return {}; + } + + std::vector values(table_size, -1); + const std::string body = source.substr(begin, end - begin); + const std::regex set_pattern("set\\((\\d+),\\s*(-?\\d+)\\)"); + for (std::sregex_iterator it(body.begin(), body.end(), set_pattern), last; it != last; ++it) { + const int code = std::stoi((*it)[1].str()); + const int group = std::stoi((*it)[2].str()); + if (code >= 0 && static_cast(code) < values.size()) { + values[static_cast(code)] = group; + } + } + + const std::regex range_pattern( + R"(for\s*\(int code = (\d+); code <= (\d+); \+\+code\)\s*\{\s*set\(code,\s*(-?\d+)\))"); + for (std::sregex_iterator it(body.begin(), body.end(), range_pattern), last; it != last; ++it) { + const int first = std::stoi((*it)[1].str()); + const int last_code = std::stoi((*it)[2].str()); + const int group = std::stoi((*it)[3].str()); + for (int code = first; code <= last_code && static_cast(code) < values.size(); ++code) { + values[static_cast(code)] = group; + } + } + + const std::regex named_range_pattern( + R"(for\s*\(int code = (\d+); code <= ([A-Za-z_][A-Za-z0-9_]*); \+\+code\)\s*\{\s*set\(code,\s*(-?\d+)\))"); + for (std::sregex_iterator it(body.begin(), body.end(), named_range_pattern), last; it != last; ++it) { + const int first = std::stoi((*it)[1].str()); + const int last_code = parse_constexpr_integer(source, (*it)[2].str()); + const int group = std::stoi((*it)[3].str()); + for (int code = first; code <= last_code && static_cast(code) < values.size(); ++code) { + values[static_cast(code)] = group; + } + } + + return values; +} + +std::vector parse_smoke_readonly_numbered_parameters(const std::string &source) { + const std::size_t marker_pos = source.find("bool is_smoke_readonly_numbered_parameter"); + if (marker_pos == std::string::npos) { + return {}; + } + const std::size_t begin = source.find('{', marker_pos); + const std::size_t end = source.find('}', begin); + if (begin == std::string::npos || end == std::string::npos || end <= begin) { + return {}; + } + + std::vector values; + const std::string body = source.substr(begin, end - begin); + const std::regex range_pattern(R"(index\s*>=\s*(\d+)\s*&&\s*index\s*<=\s*(\d+))"); + for (std::sregex_iterator it(body.begin(), body.end(), range_pattern), last; it != last; ++it) { + const int first = std::stoi((*it)[1].str()); + const int last_code = std::stoi((*it)[2].str()); + for (int value = first; value <= last_code; ++value) { + values.push_back(value); + } + } + + const std::regex equality_pattern(R"(index\s*==\s*(\d+))"); + for (std::sregex_iterator it(body.begin(), body.end(), equality_pattern), last; it != last; ++it) { + values.push_back(std::stoi((*it)[1].str())); + } + + std::sort(values.begin(), values.end()); + values.erase(std::unique(values.begin(), values.end()), values.end()); + return values; +} + +std::vector parse_smoke_system_persistent_numbered_parameters(const std::string &source) { + const std::size_t marker_pos = source.find("bool is_smoke_system_persistent_numbered_parameter"); + if (marker_pos == std::string::npos) { + return {}; + } + const std::size_t begin = source.find('{', marker_pos); + if (begin == std::string::npos) { + return {}; + } + std::size_t end = begin; + int depth = 0; + for (; end < source.size(); ++end) { + if (source[end] == '{') { + ++depth; + } else if (source[end] == '}') { + --depth; + if (depth == 0) { + break; + } + } + } + if (end >= source.size() || end <= begin) { + return {}; + } + + std::vector values; + const std::string body = source.substr(begin, end - begin); + const std::regex range_pattern(R"(index\s*>=\s*(\d+)\s*&&\s*index\s*<=\s*(\d+))"); + for (std::sregex_iterator it(body.begin(), body.end(), range_pattern), last; it != last; ++it) { + const int first = std::stoi((*it)[1].str()); + const int last_value = std::stoi((*it)[2].str()); + for (int value = first; value <= last_value; ++value) { + values.push_back(value); + } + } + + const std::regex equality_pattern(R"(index\s*==\s*(\d+))"); + for (std::sregex_iterator it(body.begin(), body.end(), equality_pattern), last; it != last; ++it) { + values.push_back(std::stoi((*it)[1].str())); + } + + const std::regex loop_range_pattern( + R"(for\s*\(int base = (\d+); base <= (\d+); base \+= (\d+)\)[\s\S]*?index\s*>=\s*base\s*&&\s*index\s*<=\s*base\s*\+\s*(\d+))"); + for (std::sregex_iterator it(body.begin(), body.end(), loop_range_pattern), last; it != last; ++it) { + const int first_base = std::stoi((*it)[1].str()); + const int last_base = std::stoi((*it)[2].str()); + const int step = std::stoi((*it)[3].str()); + const int width = std::stoi((*it)[4].str()); + for (int base = first_base; base <= last_base; base += step) { + for (int value = base; value <= base + width; ++value) { + values.push_back(value); + } + } + } + + std::sort(values.begin(), values.end()); + values.erase(std::unique(values.begin(), values.end()), values.end()); + return values; +} + +bool expect_tables_equal(const std::vector &actual, + const std::vector &expected, + const char *label) { + if (actual.size() != expected.size()) { + std::cerr << "FAIL: " << label << " table size mismatch\n"; + return false; + } + for (std::size_t i = 0; i < actual.size(); ++i) { + if (actual[i] != expected[i]) { + std::cerr << "FAIL: " << label << " table mismatch at code " << i + << ": smoke=" << actual[i] + << " linuxcnc=" << expected[i] << '\n'; + return false; + } + } + return true; +} + +} // namespace + +int main() { + const std::string interp_array_path = + linuxcnc_root_from_env() + "/src/emc/rs274ngc/interp_array.cc"; + const std::string interp_check_path = + linuxcnc_root_from_env() + "/src/emc/rs274ngc/interp_check.cc"; + const std::string interp_convert_path = + linuxcnc_root_from_env() + "/src/emc/rs274ngc/interp_convert.cc"; + const std::string interp_read_path = + linuxcnc_root_from_env() + "/src/emc/rs274ngc/interp_read.cc"; + const std::string interp_internal_path = + linuxcnc_root_from_env() + "/src/emc/rs274ngc/interp_internal.hh"; + const std::string parameter_def_path = + linuxcnc_root_from_env() + "/src/emc/rs274ngc/interp_parameter_def.hh"; + const std::string linuxcnc_h_path = + linuxcnc_root_from_env() + "/include/linuxcnc.h"; + const std::string source = read_file(interp_array_path); + const std::string interp_check_source = read_file(interp_check_path); + const std::string interp_convert_source = read_file(interp_convert_path); + const std::string interp_read_source = read_file(interp_read_path); + const std::string interp_internal_source = read_file(interp_internal_path); + const std::string parameter_def_source = read_file(parameter_def_path); + const std::string linuxcnc_h_source = read_file(linuxcnc_h_path); + const std::string smoke_source = read_file("core/src/smoke_gcode_parser.cpp"); + + bool ok = true; + ok &= expect(!source.empty(), "expected to read LinuxCNC interp_array.cc"); + ok &= expect(!interp_check_source.empty(), "expected to read LinuxCNC interp_check.cc"); + ok &= expect(!interp_convert_source.empty(), "expected to read LinuxCNC interp_convert.cc"); + ok &= expect(!interp_read_source.empty(), "expected to read LinuxCNC interp_read.cc"); + ok &= expect(!interp_internal_source.empty(), "expected to read LinuxCNC interp_internal.hh"); + ok &= expect(!parameter_def_source.empty(), "expected to read LinuxCNC interp_parameter_def.hh"); + ok &= expect(!linuxcnc_h_source.empty(), "expected to read LinuxCNC linuxcnc.h"); + ok &= expect(!smoke_source.empty(), "expected to read smoke_gcode_parser.cpp"); + + const int linuxcnc_max_parameters = + parse_named_integer_constant(parameter_def_source, "RS274NGC_MAX_PARAMETERS"); + const int smoke_max_parameters = parse_constexpr_integer(smoke_source, "kMaxNumberedParameter"); + ok &= expect(linuxcnc_max_parameters == 5602, "expected LinuxCNC RS274NGC_MAX_PARAMETERS value"); + ok &= expect(smoke_max_parameters == linuxcnc_max_parameters, + "expected smoke numbered parameter max to match LinuxCNC"); + + const int linuxcnc_subroutine_levels = + parse_define_integer(interp_internal_source, "INTERP_SUB_ROUTINE_LEVELS"); + const int smoke_subprogram_depth = parse_constexpr_integer(smoke_source, "kMaxSubprogramDepth"); + ok &= expect(linuxcnc_subroutine_levels == 10, "expected LinuxCNC INTERP_SUB_ROUTINE_LEVELS value"); + ok &= expect(smoke_subprogram_depth == linuxcnc_subroutine_levels, + "expected smoke subprogram depth to match LinuxCNC"); + + const int linuxcnc_subroutine_parameters = + parse_define_integer(interp_internal_source, "INTERP_SUB_PARAMS"); + const int smoke_subprogram_parameters = + parse_constexpr_integer(smoke_source, "kMaxSubprogramParameters"); + ok &= expect(linuxcnc_subroutine_parameters == 30, "expected LinuxCNC INTERP_SUB_PARAMS value"); + ok &= expect(smoke_subprogram_parameters == linuxcnc_subroutine_parameters, + "expected smoke subprogram parameter count to match LinuxCNC"); + + const int linuxcnc_first_subroutine_parameter = + parse_define_integer(interp_internal_source, "INTERP_FIRST_SUBROUTINE_PARAM"); + const int smoke_first_subprogram_parameter = + parse_constexpr_integer(smoke_source, "kFirstSubprogramParameter"); + ok &= expect(linuxcnc_first_subroutine_parameter == 1, + "expected LinuxCNC INTERP_FIRST_SUBROUTINE_PARAM value"); + ok &= expect(smoke_first_subprogram_parameter == linuxcnc_first_subroutine_parameter, + "expected smoke first subprogram parameter to match LinuxCNC"); + + const int linuxcnc_max_g_modal_groups = + parse_enum_ordinal(interp_internal_source, "ModalGroups", "GM_MAX_MODAL_GROUPS"); + const int smoke_max_g_modal_groups = parse_constexpr_integer(smoke_source, "kMaxGModalGroups"); + ok &= expect(linuxcnc_max_g_modal_groups == 17, "expected LinuxCNC GM_MAX_MODAL_GROUPS value"); + ok &= expect(smoke_max_g_modal_groups == linuxcnc_max_g_modal_groups, + "expected smoke G modal group count to match LinuxCNC"); + + const int linuxcnc_max_m_modal_groups = parse_int_array_size(interp_internal_source, "m_modes"); + const int smoke_max_m_modal_groups = parse_constexpr_integer(smoke_source, "kMaxMModalGroups"); + ok &= expect(linuxcnc_max_m_modal_groups == 11, "expected LinuxCNC m_modes array size"); + ok &= expect(smoke_max_m_modal_groups == linuxcnc_max_m_modal_groups, + "expected smoke M modal group count to match LinuxCNC"); + + const double linuxcnc_tolerance_equal = parse_define_double(interp_internal_source, "TOLERANCE_EQUAL"); + const double smoke_tolerance_equal = parse_constexpr_double(smoke_source, "kToleranceEqual"); + ok &= expect(linuxcnc_tolerance_equal == 1e-6, "expected LinuxCNC TOLERANCE_EQUAL value"); + ok &= expect(smoke_tolerance_equal == linuxcnc_tolerance_equal, + "expected smoke expression comparison tolerance to match LinuxCNC"); + + const double linuxcnc_mm_per_inch = parse_define_double(linuxcnc_h_source, "MM_PER_INCH"); + const double smoke_mm_per_inch = parse_constexpr_double(smoke_source, "kMmPerInch"); + ok &= expect(linuxcnc_mm_per_inch == 25.4, "expected LinuxCNC MM_PER_INCH value"); + ok &= expect(smoke_mm_per_inch == linuxcnc_mm_per_inch, + "expected smoke inch-to-mm scale to match LinuxCNC"); + + const double linuxcnc_radius_tolerance_inch = + parse_define_double_expression(interp_internal_source, "RADIUS_TOLERANCE_INCH"); + const double smoke_radius_tolerance_inch = + parse_constexpr_double(smoke_source, "kRadiusToleranceInch"); + ok &= expect(linuxcnc_radius_tolerance_inch == 0.00005, + "expected LinuxCNC RADIUS_TOLERANCE_INCH value"); + ok &= expect(smoke_radius_tolerance_inch == linuxcnc_radius_tolerance_inch, + "expected smoke radius tolerance inch to match LinuxCNC"); + + const double linuxcnc_radius_tolerance_mm = + parse_define_double_product_expression(interp_internal_source, + "RADIUS_TOLERANCE_MM", + "RADIUS_TOLERANCE_INCH", + linuxcnc_radius_tolerance_inch, + "MM_PER_INCH", + linuxcnc_mm_per_inch); + const double smoke_radius_tolerance_mm = + parse_constexpr_double_product_expression(smoke_source, + "kRadiusToleranceMm", + "kRadiusToleranceInch", + smoke_radius_tolerance_inch, + "kMmPerInch", + smoke_mm_per_inch); + ok &= expect(linuxcnc_radius_tolerance_mm == linuxcnc_radius_tolerance_inch * linuxcnc_mm_per_inch, + "expected LinuxCNC RADIUS_TOLERANCE_MM expression"); + ok &= expect(smoke_radius_tolerance_mm == linuxcnc_radius_tolerance_mm, + "expected smoke radius tolerance mm to match LinuxCNC"); + + const double linuxcnc_center_arc_radius_tolerance_inch = + parse_define_double_expression(interp_internal_source, "CENTER_ARC_RADIUS_TOLERANCE_INCH"); + const double smoke_center_arc_radius_tolerance_inch = + parse_constexpr_double_expression(smoke_source, "kCenterArcRadiusToleranceInch"); + ok &= expect(linuxcnc_center_arc_radius_tolerance_inch > 0.0, + "expected LinuxCNC CENTER_ARC_RADIUS_TOLERANCE_INCH value"); + ok &= expect(std::fabs(smoke_center_arc_radius_tolerance_inch - + linuxcnc_center_arc_radius_tolerance_inch) < 1e-15, + "expected smoke center arc radius tolerance inch to match LinuxCNC"); + + const double linuxcnc_center_arc_radius_tolerance_mm = + parse_define_double_expression(interp_internal_source, "CENTER_ARC_RADIUS_TOLERANCE_MM"); + const double smoke_center_arc_radius_tolerance_mm = + parse_constexpr_double_expression(smoke_source, "kCenterArcRadiusToleranceMm"); + ok &= expect(linuxcnc_center_arc_radius_tolerance_mm > 0.0, + "expected LinuxCNC CENTER_ARC_RADIUS_TOLERANCE_MM value"); + ok &= expect(std::fabs(smoke_center_arc_radius_tolerance_mm - + linuxcnc_center_arc_radius_tolerance_mm) < 1e-15, + "expected smoke center arc radius tolerance mm to match LinuxCNC"); + + const double linuxcnc_spiral_relative_tolerance = + parse_define_double_expression(interp_internal_source, "SPIRAL_RELATIVE_TOLERANCE"); + const double smoke_spiral_relative_tolerance = + parse_constexpr_double(smoke_source, "kSpiralRelativeTolerance"); + ok &= expect(linuxcnc_spiral_relative_tolerance == 0.001, + "expected LinuxCNC SPIRAL_RELATIVE_TOLERANCE value"); + ok &= expect(smoke_spiral_relative_tolerance == linuxcnc_spiral_relative_tolerance, + "expected smoke spiral relative tolerance to match LinuxCNC"); + + const int linuxcnc_max_m_codes_on_line = parse_define_integer(interp_internal_source, "MAX_EMS"); + const int smoke_max_m_codes_on_line = parse_constexpr_integer(smoke_source, "kMaxMCodesOnLine"); + ok &= expect(linuxcnc_max_m_codes_on_line == 4, "expected LinuxCNC MAX_EMS value"); + ok &= expect(smoke_max_m_codes_on_line == linuxcnc_max_m_codes_on_line, + "expected smoke M-code count limit to match LinuxCNC"); + + const int linuxcnc_reader_table_size = parse_named_array_size(source, "default_readers"); + const int smoke_reader_table_size = parse_constexpr_integer(smoke_source, "kLinuxCncReaderTableSize"); + ok &= expect(linuxcnc_reader_table_size == 256, "expected LinuxCNC default_readers table size"); + ok &= expect(smoke_reader_table_size == linuxcnc_reader_table_size, + "expected smoke reader table size to match LinuxCNC"); + const std::vector linuxcnc_reader_indices = parse_linuxcnc_reader_indices(source); + const std::vector smoke_reader_indices = parse_smoke_reader_symbol_indices(smoke_source); + ok &= expect(linuxcnc_reader_indices.size() == 30, "expected LinuxCNC default reader character count"); + std::vector linuxcnc_top_level_reader_indices = linuxcnc_reader_indices; + linuxcnc_top_level_reader_indices.push_back('n'); + linuxcnc_top_level_reader_indices.push_back('o'); + std::sort(linuxcnc_top_level_reader_indices.begin(), linuxcnc_top_level_reader_indices.end()); + ok &= expect_tables_equal(smoke_reader_indices, linuxcnc_top_level_reader_indices, "reader character"); + + const double linuxcnc_m19_max_orient_angle = parse_m19_max_orient_angle(interp_check_source); + const double smoke_m19_max_orient_angle = parse_constexpr_double(smoke_source, "kM19MaxOrientAngle"); + ok &= expect(linuxcnc_m19_max_orient_angle == 360.0, "expected LinuxCNC M19 max orient angle"); + ok &= expect(smoke_m19_max_orient_angle == linuxcnc_m19_max_orient_angle, + "expected smoke M19 max orient angle to match LinuxCNC"); + + const double linuxcnc_p_word_integer_tolerance = parse_p_word_integer_tolerance(interp_check_source); + const double smoke_p_word_integer_tolerance = + parse_constexpr_double(smoke_source, "kPWordIntegerTolerance"); + ok &= expect(linuxcnc_p_word_integer_tolerance == 0.001, + "expected LinuxCNC P-word integer tolerance"); + ok &= expect(smoke_p_word_integer_tolerance == linuxcnc_p_word_integer_tolerance, + "expected smoke P-word integer tolerance to match LinuxCNC"); + + const int linuxcnc_arc_min_p_repeat_count = parse_arc_min_p_repeat_count(interp_check_source); + const int smoke_arc_min_p_repeat_count = + parse_constexpr_integer(smoke_source, "kArcMinPRepeatCount"); + ok &= expect(linuxcnc_arc_min_p_repeat_count == 1, + "expected LinuxCNC G2/G3 minimum P repeat count"); + ok &= expect(smoke_arc_min_p_repeat_count == linuxcnc_arc_min_p_repeat_count, + "expected smoke G2/G3 minimum P repeat count to match LinuxCNC"); + + const std::pair linuxcnc_m19_orient_mode_range = + parse_m19_orient_mode_range(interp_check_source); + const int smoke_m19_min_orient_mode = parse_constexpr_integer(smoke_source, "kM19MinOrientMode"); + const int smoke_m19_max_orient_mode = parse_constexpr_integer(smoke_source, "kM19MaxOrientMode"); + ok &= expect(linuxcnc_m19_orient_mode_range.first == 0, "expected LinuxCNC M19 minimum P mode"); + ok &= expect(linuxcnc_m19_orient_mode_range.second == 2, "expected LinuxCNC M19 maximum P mode"); + ok &= expect(smoke_m19_min_orient_mode == linuxcnc_m19_orient_mode_range.first, + "expected smoke M19 minimum P mode to match LinuxCNC"); + ok &= expect(smoke_m19_max_orient_mode == linuxcnc_m19_orient_mode_range.second, + "expected smoke M19 maximum P mode to match LinuxCNC"); + + const double linuxcnc_m19_min_wait_seconds = parse_m19_min_wait_seconds(interp_convert_source); + const double smoke_m19_min_wait_seconds = parse_constexpr_double(smoke_source, "kM19MinWaitSeconds"); + ok &= expect(linuxcnc_m19_min_wait_seconds == 0.0, "expected LinuxCNC M19 minimum Q wait seconds"); + ok &= expect(smoke_m19_min_wait_seconds == linuxcnc_m19_min_wait_seconds, + "expected smoke M19 minimum Q wait seconds to match LinuxCNC"); + + const std::pair linuxcnc_is_near_int_constants = + parse_is_near_int_constants(interp_convert_source); + const double smoke_near_integer_round_offset = + parse_constexpr_double(smoke_source, "kNearIntegerRoundOffset"); + const double smoke_near_integer_tolerance = + parse_constexpr_double(smoke_source, "kNearIntegerTolerance"); + ok &= expect(linuxcnc_is_near_int_constants.first == 0.5, + "expected LinuxCNC is_near_int round offset"); + ok &= expect(linuxcnc_is_near_int_constants.second == 0.0001, + "expected LinuxCNC is_near_int tolerance"); + ok &= expect(smoke_near_integer_round_offset == linuxcnc_is_near_int_constants.first, + "expected smoke is_near_int round offset to match LinuxCNC"); + ok &= expect(smoke_near_integer_tolerance == linuxcnc_is_near_int_constants.second, + "expected smoke is_near_int tolerance to match LinuxCNC"); + + const int linuxcnc_m61_min_tool_number = parse_m61_min_tool_number(interp_convert_source); + const int smoke_m61_min_tool_number = parse_constexpr_integer(smoke_source, "kM61MinToolNumber"); + ok &= expect(linuxcnc_m61_min_tool_number == 0, "expected LinuxCNC M61 minimum Q tool number"); + ok &= expect(smoke_m61_min_tool_number == linuxcnc_m61_min_tool_number, + "expected smoke M61 minimum Q tool number to match LinuxCNC"); + + const int linuxcnc_min_analog_output_index = parse_min_analog_output_index(interp_convert_source); + const int smoke_min_analog_output_index = parse_constexpr_integer(smoke_source, "kMinAnalogOutputIndex"); + ok &= expect(linuxcnc_min_analog_output_index == 0, + "expected LinuxCNC M67/M68 minimum analog output index"); + ok &= expect(smoke_min_analog_output_index == linuxcnc_min_analog_output_index, + "expected smoke M67/M68 minimum analog output index to match LinuxCNC"); + + const std::vector linuxcnc_g_code_read_real_constants = + parse_g_code_read_real_constants(interp_read_source); + const double smoke_g_code_scale = parse_constexpr_double(smoke_source, "kLinuxCncGCodeScale"); + const double smoke_g_code_round_up_fraction = + parse_constexpr_double(smoke_source, "kGCodeRoundUpFraction"); + const double smoke_g_code_out_of_range_fraction = + parse_constexpr_double(smoke_source, "kGCodeOutOfRangeFraction"); + ok &= expect(linuxcnc_g_code_read_real_constants.size() == 3, + "expected LinuxCNC read_g scale and fraction constants"); + if (linuxcnc_g_code_read_real_constants.size() == 3) { + ok &= expect(linuxcnc_g_code_read_real_constants[0] == 10.0, + "expected LinuxCNC read_g scale"); + ok &= expect(linuxcnc_g_code_read_real_constants[1] == 0.999, + "expected LinuxCNC read_g round-up fraction"); + ok &= expect(linuxcnc_g_code_read_real_constants[2] == 0.001, + "expected LinuxCNC read_g out-of-range fraction"); + ok &= expect(smoke_g_code_scale == linuxcnc_g_code_read_real_constants[0], + "expected smoke read_g scale to match LinuxCNC"); + ok &= expect(smoke_g_code_round_up_fraction == linuxcnc_g_code_read_real_constants[1], + "expected smoke read_g round-up fraction to match LinuxCNC"); + ok &= expect(smoke_g_code_out_of_range_fraction == linuxcnc_g_code_read_real_constants[2], + "expected smoke read_g out-of-range fraction to match LinuxCNC"); + } + + const int linuxcnc_max_g_code_from_read_g = parse_max_g_code_from_read_g(interp_read_source); + const int smoke_max_g_code_from_constant = parse_constexpr_integer(smoke_source, "kLinuxCncMaxGCode"); + ok &= expect(linuxcnc_max_g_code_from_read_g == 999, "expected LinuxCNC read_g maximum G-code"); + ok &= expect(smoke_max_g_code_from_constant == linuxcnc_max_g_code_from_read_g, + "expected smoke maximum G-code to match LinuxCNC read_g"); + + const int linuxcnc_max_m_code_from_read_m = parse_max_m_code_from_read_m(interp_read_source); + const int smoke_max_m_code_from_constant = parse_constexpr_integer(smoke_source, "kLinuxCncMaxMCode"); + ok &= expect(linuxcnc_max_m_code_from_read_m == 199, "expected LinuxCNC read_m maximum M-code"); + ok &= expect(smoke_max_m_code_from_constant == linuxcnc_max_m_code_from_read_m, + "expected smoke maximum M-code to match LinuxCNC read_m"); + + const std::vector linuxcnc_integer_read_fraction_constants = + parse_integer_read_fraction_constants(interp_read_source); + const double smoke_integer_read_round_up_fraction = + parse_constexpr_double(smoke_source, "kIntegerReadRoundUpFraction"); + const double smoke_integer_read_non_integer_fraction = + parse_constexpr_double(smoke_source, "kIntegerReadNonIntegerFraction"); + ok &= expect(linuxcnc_integer_read_fraction_constants.size() == 2, + "expected LinuxCNC read_integer_value fraction constants"); + if (linuxcnc_integer_read_fraction_constants.size() == 2) { + ok &= expect(linuxcnc_integer_read_fraction_constants[0] == 0.9999, + "expected LinuxCNC read_integer_value round-up fraction"); + ok &= expect(linuxcnc_integer_read_fraction_constants[1] == 0.0001, + "expected LinuxCNC read_integer_value non-integer fraction"); + ok &= expect(smoke_integer_read_round_up_fraction == + linuxcnc_integer_read_fraction_constants[0], + "expected smoke read_integer_value round-up fraction to match LinuxCNC"); + ok &= expect(smoke_integer_read_non_integer_fraction == + linuxcnc_integer_read_fraction_constants[1], + "expected smoke read_integer_value non-integer fraction to match LinuxCNC"); + } + + const std::pair linuxcnc_g10_p_integer_check = + parse_g10_p_integer_check(interp_check_source); + const double smoke_g10_p_integer_roundoff = + parse_constexpr_double(smoke_source, "kG10PIntegerRoundoff"); + const double smoke_g10_p_integer_tolerance = + parse_constexpr_double(smoke_source, "kG10PIntegerTolerance"); + ok &= expect(linuxcnc_g10_p_integer_check.first == 0.0001, + "expected LinuxCNC G10 P integer roundoff"); + ok &= expect(linuxcnc_g10_p_integer_check.second == 0.0002, + "expected LinuxCNC G10 P integer tolerance"); + ok &= expect(smoke_g10_p_integer_roundoff == linuxcnc_g10_p_integer_check.first, + "expected smoke G10 P integer roundoff to match LinuxCNC"); + ok &= expect(smoke_g10_p_integer_tolerance == linuxcnc_g10_p_integer_check.second, + "expected smoke G10 P integer tolerance to match LinuxCNC"); + + const std::vector linuxcnc_g10_allowed_l_numbers = + parse_g10_allowed_l_numbers(interp_check_source); + const std::vector smoke_g10_allowed_l_numbers = + parse_smoke_constexpr_int_array(smoke_source, "kG10AllowedLNumbers"); + ok &= expect(!linuxcnc_g10_allowed_l_numbers.empty(), "expected LinuxCNC G10 allowed L-number set"); + ok &= expect_tables_equal(smoke_g10_allowed_l_numbers, + linuxcnc_g10_allowed_l_numbers, + "G10 allowed L-number"); + + const std::vector linuxcnc_g10_coordinate_system_l_numbers = + parse_g10_l_numbers_for_message(interp_check_source, + "P value out of range (0-9) with G10 L%d"); + const std::vector smoke_g10_coordinate_system_l_numbers = + parse_smoke_constexpr_int_array(smoke_source, "kG10CoordinateSystemLNumbers"); + ok &= expect(!linuxcnc_g10_coordinate_system_l_numbers.empty(), + "expected LinuxCNC G10 coordinate-system L-number set"); + ok &= expect_tables_equal(smoke_g10_coordinate_system_l_numbers, + linuxcnc_g10_coordinate_system_l_numbers, + "G10 coordinate-system L-number"); + + const std::vector linuxcnc_g10_tool_table_l_numbers = + parse_g10_l_numbers_for_message(interp_check_source, + "P value out of range with G10 L%d"); + const std::vector smoke_g10_tool_table_l_numbers = + parse_smoke_constexpr_int_array(smoke_source, "kG10ToolTableLNumbers"); + ok &= expect(!linuxcnc_g10_tool_table_l_numbers.empty(), + "expected LinuxCNC G10 tool-table L-number set"); + ok &= expect_tables_equal(smoke_g10_tool_table_l_numbers, + linuxcnc_g10_tool_table_l_numbers, + "G10 tool-table L-number"); + + const int linuxcnc_g10_max_tool_orientation = parse_g10_max_tool_orientation(interp_convert_source); + const int smoke_g10_max_tool_orientation = parse_constexpr_integer(smoke_source, "kMaxToolOrientation"); + ok &= expect(linuxcnc_g10_max_tool_orientation == 9, "expected LinuxCNC G10 max tool orientation"); + ok &= expect(smoke_g10_max_tool_orientation == linuxcnc_g10_max_tool_orientation, + "expected smoke G10 max tool orientation to match LinuxCNC"); + + const std::pair linuxcnc_g10_coordinate_system_index_range = + parse_g10_coordinate_system_index_range(interp_check_source); + const int smoke_g10_min_coordinate_system_index = + parse_constexpr_integer(smoke_source, "kG10MinCoordinateSystemIndex"); + const int smoke_g10_max_coordinate_system_index = + parse_constexpr_integer(smoke_source, "kG10MaxCoordinateSystemIndex"); + ok &= expect(linuxcnc_g10_coordinate_system_index_range.first == 0, + "expected LinuxCNC G10 coordinate-system minimum P index"); + ok &= expect(linuxcnc_g10_coordinate_system_index_range.second == 9, + "expected LinuxCNC G10 coordinate-system maximum P index"); + ok &= expect(smoke_g10_min_coordinate_system_index == + linuxcnc_g10_coordinate_system_index_range.first, + "expected smoke G10 coordinate-system minimum P index to match LinuxCNC"); + ok &= expect(smoke_g10_max_coordinate_system_index == + linuxcnc_g10_coordinate_system_index_range.second, + "expected smoke G10 coordinate-system maximum P index to match LinuxCNC"); + + const int linuxcnc_g10_min_tool_number = parse_g10_min_tool_number(interp_check_source); + const int smoke_g10_min_tool_number = parse_constexpr_integer(smoke_source, "kG10MinToolNumber"); + ok &= expect(linuxcnc_g10_min_tool_number == 1, "expected LinuxCNC G10 minimum P tool number"); + ok &= expect(smoke_g10_min_tool_number == linuxcnc_g10_min_tool_number, + "expected smoke G10 minimum P tool number to match LinuxCNC"); + + const int smoke_m_code_table_size = + parse_constexpr_integer(smoke_source, "kLinuxCncMCodeTableSize"); + const int smoke_max_m_code = parse_constexpr_integer(smoke_source, "kLinuxCncMaxMCode"); + const int smoke_g_code_table_size = + parse_constexpr_integer(smoke_source, "kLinuxCncGCodeTableSize"); + const int smoke_max_g_code = parse_constexpr_integer(smoke_source, "kLinuxCncMaxGCode"); + + const std::vector gees = parse_linuxcnc_int_table(source, "const int Interp::gees[] ="); + const std::vector smoke_g = + parse_smoke_modal_table(smoke_source, + "linuxcnc_g_modal_group_table", + static_cast(smoke_g_code_table_size)); + ok &= expect(gees.size() == static_cast(smoke_g_code_table_size), + "expected complete LinuxCNC gees[] table"); + ok &= expect(smoke_g.size() == static_cast(smoke_g_code_table_size), + "expected complete smoke G modal table"); + ok &= expect(smoke_max_g_code == static_cast(gees.size()) - 1, + "expected smoke max G code to match LinuxCNC gees[] table"); + if (gees.size() > 490) { + ok &= expect(gees[430] == 8, "expected LinuxCNC G43 to remain tool-length modal group 8"); + ok &= expect(gees[434] == -1, "expected LinuxCNC G43.4 to stay out of gees[]"); + ok &= expect(gees[435] == -1, "expected LinuxCNC G43.5 to stay out of gees[]"); + ok &= expect(gees[490] == 8, "expected LinuxCNC G49 to remain tool-length modal group 8"); + } + if (gees.size() == smoke_g.size()) { + ok &= expect_tables_equal(smoke_g, gees, "G modal group"); + } + + const std::vector ems = parse_linuxcnc_int_table(source, "const int Interp::ems[] ="); + const std::vector smoke_m = + parse_smoke_modal_table(smoke_source, + "linuxcnc_m_modal_group_table", + static_cast(smoke_m_code_table_size)); + ok &= expect(ems.size() == static_cast(smoke_m_code_table_size), + "expected complete LinuxCNC ems[] table"); + ok &= expect(smoke_m.size() == static_cast(smoke_m_code_table_size), + "expected complete smoke M modal table"); + ok &= expect(smoke_max_m_code == static_cast(ems.size()) - 1, + "expected smoke max M code to match LinuxCNC ems[] table"); + if (ems.size() == smoke_m.size()) { + ok &= expect_tables_equal(smoke_m, ems, "M modal group"); + } + + const std::vector readonly = parse_linuxcnc_int_table(source, "const int Interp::readonly_parameters[] ="); + const std::vector smoke_readonly = parse_smoke_readonly_numbered_parameters(smoke_source); + ok &= expect(readonly.size() == 23, "expected complete LinuxCNC readonly_parameters[] table"); + ok &= expect(!smoke_readonly.empty(), "expected smoke readonly numbered parameter table"); + if (!readonly.empty() && !smoke_readonly.empty()) { + ok &= expect_tables_equal(smoke_readonly, readonly, "readonly numbered parameter"); + } + + std::vector required = parse_linuxcnc_int_table(source, "const int Interp::required_parameters[] ="); + if (!required.empty() && required.back() == 5602) { + required.pop_back(); + } + const std::vector smoke_system_persistent = + parse_smoke_system_persistent_numbered_parameters(smoke_source); + ok &= expect(required.size() == 119, "expected complete LinuxCNC required_parameters[] table"); + ok &= expect(!smoke_system_persistent.empty(), "expected smoke system persistent numbered parameter table"); + if (!required.empty() && !smoke_system_persistent.empty()) { + ok &= expect_tables_equal(smoke_system_persistent, + required, + "system persistent numbered parameter"); + } + + return ok ? 0 : 1; +} diff --git a/core/tests/rtcp_kinematics_smoke.cpp b/core/tests/rtcp_kinematics_smoke.cpp index f191e16..75298fa 100644 --- a/core/tests/rtcp_kinematics_smoke.cpp +++ b/core/tests/rtcp_kinematics_smoke.cpp @@ -63,5 +63,13 @@ int main() { const CncSimPose roundtrip = rtcp_tool_tip_from_pivot(pivot, 123.4); ok &= expect_pose_near(roundtrip, tip, "RTCP pivot/tool-tip roundtrip"); + tip = {}; + tip.a = 90.0; + tip.b = 90.0; + const RtcpVector vector = rtcp_tool_vector_from_pose(tip, 10.0); + ok &= expect_near(vector.x, 0.0, "A90 B90 tool vector x"); + ok &= expect_near(vector.y, 10.0, "A90 B90 tool vector y"); + ok &= expect_near(vector.z, 0.0, "A90 B90 tool vector z"); + return ok ? 0 : 1; } diff --git a/list-cnc-sim-core-sources.sh b/list-cnc-sim-core-sources.sh new file mode 100755 index 0000000..bc22776 --- /dev/null +++ b/list-cnc-sim-core-sources.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")" + +filter=${1:-} + +awk ' + /set\(cnc_sim_core_sources/ { + in_list = 1 + next + } + in_list && /^[[:space:]]*\)/ { + exit + } + in_list { + line = $0 + sub(/^[[:space:]]*/, "", line) + sub(/[[:space:]]*$/, "", line) + if (line == "") { + next + } + print "core/" line + } +' core/CMakeLists.txt | while IFS= read -r source; do + if [[ ! -f "$source" ]]; then + echo "missing cnc sim core source: $source" >&2 + exit 1 + fi + if [[ -n "$filter" && "$(basename "$source")" != "$filter" ]]; then + continue + fi + printf '%s\n' "$source" +done | { + found=0 + while IFS= read -r source; do + found=1 + printf '%s\n' "$source" + done + if [[ -n "$filter" && "$found" -eq 0 ]]; then + echo "missing cnc sim core source in CMake list: $filter" >&2 + exit 1 + fi +} diff --git a/list-linuxcnc-bridge-smoke-project-sources.sh b/list-linuxcnc-bridge-smoke-project-sources.sh new file mode 100755 index 0000000..19e0592 --- /dev/null +++ b/list-linuxcnc-bridge-smoke-project-sources.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")" + +filter=${1:-} + +sources=( + core/src/canon_event_sink.cpp + core/src/linuxcnc_canon_bridge.cpp + core/src/rtcp_kinematics.cpp +) + +found=0 +for source in "${sources[@]}"; do + if [[ ! -f "$source" ]]; then + echo "missing LinuxCNC bridge smoke project source: $source" >&2 + exit 1 + fi + if [[ -n "$filter" && "$(basename "$source")" != "$filter" ]]; then + continue + fi + found=1 + printf '%s\n' "$source" +done + +if [[ -n "$filter" && "$found" -eq 0 ]]; then + echo "missing LinuxCNC bridge smoke project source in list: $filter" >&2 + exit 1 +fi diff --git a/list-linuxcnc-rs274-api-project-sources.sh b/list-linuxcnc-rs274-api-project-sources.sh new file mode 100755 index 0000000..149d66f --- /dev/null +++ b/list-linuxcnc-rs274-api-project-sources.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")" + +filter=${1:-} + +awk ' + /set\(cnc_sim_core_sources/ { + in_core = 1 + next + } + in_core && /^[[:space:]]*\)/ { + in_core = 0 + next + } + /list\(APPEND cnc_sim_core_sources/ { + in_backend_sources = 1 + next + } + in_backend_sources && /^[[:space:]]*\)/ { + in_backend_sources = 0 + next + } + in_core || in_backend_sources { + line = $0 + sub(/^[[:space:]]*/, "", line) + sub(/[[:space:]]*$/, "", line) + if (line == "") { + next + } + print "core/" line + } +' core/CMakeLists.txt | while IFS= read -r source; do + if [[ ! -f "$source" ]]; then + echo "missing LinuxCNC rs274 API project source: $source" >&2 + exit 1 + fi + if [[ -n "$filter" && "$(basename "$source")" != "$filter" ]]; then + continue + fi + printf '%s\n' "$source" +done | { + found=0 + while IFS= read -r source; do + found=1 + printf '%s\n' "$source" + done + if [[ -n "$filter" && "$found" -eq 0 ]]; then + echo "missing LinuxCNC rs274 API project source in CMake list: $filter" >&2 + exit 1 + fi +} diff --git a/list-linuxcnc-rs274-dump-project-sources.sh b/list-linuxcnc-rs274-dump-project-sources.sh new file mode 100755 index 0000000..4cede5b --- /dev/null +++ b/list-linuxcnc-rs274-dump-project-sources.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")" + +filter=${1:-} + +sources=( + core/src/canon_event_sink.cpp + core/src/linuxcnc_canon_bridge.cpp + core/src/linuxcnc_tooldata_fixture.cpp + core/src/rtcp_kinematics.cpp + core/src/simulator_gcode_controls.cpp + core/tools/linuxcnc_rs274_dump.cpp +) + +found=0 +for source in "${sources[@]}"; do + if [[ ! -f "$source" ]]; then + echo "missing LinuxCNC rs274 dump project source: $source" >&2 + exit 1 + fi + if [[ -n "$filter" && "$(basename "$source")" != "$filter" ]]; then + continue + fi + found=1 + printf '%s\n' "$source" +done + +if [[ -n "$filter" && "$found" -eq 0 ]]; then + echo "missing LinuxCNC rs274 dump project source in list: $filter" >&2 + exit 1 +fi diff --git a/list-linuxcnc-source-files.sh b/list-linuxcnc-source-files.sh new file mode 100755 index 0000000..2d3c063 --- /dev/null +++ b/list-linuxcnc-source-files.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +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 + +if [[ "$#" -eq 0 ]]; then + echo "usage: $0 ..." >&2 + exit 1 +fi + +for source in "$@"; do + full_source="$linuxcnc_root/$source" + if [[ ! -f "$full_source" ]]; then + echo "missing LinuxCNC source: $source" >&2 + exit 1 + fi + printf '%s\n' "$full_source" +done diff --git a/list-linuxcnc-source-manifest-sources.sh b/list-linuxcnc-source-manifest-sources.sh new file mode 100755 index 0000000..da0cc30 --- /dev/null +++ b/list-linuxcnc-source-manifest-sources.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")" + +manifest=${1:-linuxcnc-rs274-source-files.txt} +filter_group=${2:-core} +output_mode=${3:-relative} +linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} + +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 + +case "$filter_group" in + core|binding|tooldata|all) + ;; + *) + echo "unknown manifest source filter: $filter_group" >&2 + exit 1 + ;; +esac + +case "$output_mode" in + relative|full) + ;; + *) + echo "unknown manifest source output mode: $output_mode" >&2 + exit 1 + ;; +esac + +seen_sources=() +while IFS=: read -r group path note; do + case "$group" in + ""|\#*) + 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 + if [[ "$group" == "core" ]]; then + if printf '%s\n' "${seen_sources[@]}" | grep -Fx -- "$path" >/dev/null; then + echo "duplicate manifest source: $path" >&2 + exit 1 + fi + seen_sources+=("$path") + fi + + if [[ "$filter_group" == "all" || "$filter_group" == "$group" ]]; then + if [[ "$output_mode" == "full" ]]; then + printf '%s\n' "$linuxcnc_root/$path" + else + printf '%s\n' "$path" + fi + fi +done < "$manifest" diff --git a/list-linuxcnc-source-support-objects.sh b/list-linuxcnc-source-support-objects.sh new file mode 100755 index 0000000..d660e20 --- /dev/null +++ b/list-linuxcnc-source-support-objects.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")" + +linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} +filter=${1:-} + +if [[ ! -d "$linuxcnc_root" ]]; then + echo "missing LinuxCNC root: $linuxcnc_root" >&2 + exit 1 +fi + +objects=( + src/objects/emc/rs274ngc/interpmodule.o + src/objects/emc/rs274ngc/canonmodule.o + src/objects/emc/rs274ngc/pyarrays.o + src/objects/emc/rs274ngc/pyblock.o + src/objects/emc/rs274ngc/pyemctypes.o + src/objects/emc/rs274ngc/pyinterp1.o + src/objects/emc/rs274ngc/pyparamclass.o + src/objects/emc/nml_intf/emcops.o + src/objects/emc/sai/dummyemcstat.o + src/objects/libnml/nml/stat_msg.o +) + +found=0 +for object in "${objects[@]}"; do + full_object="$linuxcnc_root/$object" + if [[ ! -f "$full_object" ]]; then + echo "missing LinuxCNC support object: $full_object" >&2 + echo "build LinuxCNC first or set LINUXCNC_ROOT to a built tree" >&2 + exit 1 + fi + if [[ -n "$filter" && "$(basename "$object")" != "$filter" ]]; then + continue + fi + found=1 + printf '%s\n' "$full_object" +done + +if [[ -n "$filter" && "$found" -eq 0 ]]; then + echo "missing LinuxCNC support object in list: $filter" >&2 + exit 1 +fi diff --git a/list-linuxcnc-wasm-manifest-sources.sh b/list-linuxcnc-wasm-manifest-sources.sh new file mode 100755 index 0000000..c89404c --- /dev/null +++ b/list-linuxcnc-wasm-manifest-sources.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")" + +manifest=${1:-linuxcnc-rs274-wasm-source-files.txt} +filter_group=${2:-core} +output_mode=${3:-relative} +linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} + +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 + +case "$filter_group" in + core|blocked|all) + ;; + *) + echo "unknown manifest source filter: $filter_group" >&2 + exit 1 + ;; +esac + +case "$output_mode" in + relative|full) + ;; + *) + echo "unknown manifest source output mode: $output_mode" >&2 + exit 1 + ;; +esac + +seen_sources=() +while IFS=: read -r group path note; do + case "$group" in + ""|\#*) + continue + ;; + core|blocked) + ;; + *) + echo "unknown manifest group: $group" >&2 + exit 1 + ;; + esac + + if [[ ! -f "$linuxcnc_root/$path" ]]; then + echo "missing manifest source: $path" >&2 + exit 1 + fi + if printf '%s\n' "${seen_sources[@]}" | grep -Fx -- "$path" >/dev/null; then + echo "duplicate manifest source: $path" >&2 + exit 1 + fi + seen_sources+=("$path") + + if [[ "$filter_group" == "all" || "$filter_group" == "$group" ]]; then + if [[ "$output_mode" == "full" ]]; then + printf '%s\n' "$linuxcnc_root/$path" + else + printf '%s\n' "$path" + fi + fi +done < "$manifest" diff --git a/list-linuxcnc-wasm-safe-project-sources.sh b/list-linuxcnc-wasm-safe-project-sources.sh new file mode 100755 index 0000000..f12fefa --- /dev/null +++ b/list-linuxcnc-wasm-safe-project-sources.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")" + +filter=${1:-} + +awk ' + /set\(linuxcnc_rs274_wasm_safe_probe_project_sources/ { + in_list = 1 + next + } + in_list && /^[[:space:]]*\)/ { + exit + } + in_list { + line = $0 + sub(/^[[:space:]]*/, "", line) + sub(/[[:space:]]*$/, "", line) + if (line == "") { + next + } + sub(/^\$\{CMAKE_CURRENT_SOURCE_DIR\}\//, "core/", line) + print line + } +' core/CMakeLists.txt | while IFS= read -r source; do + if [[ ! -f "$source" ]]; then + echo "missing wasm-safe project source: $source" >&2 + exit 1 + fi + if [[ -n "$filter" && "$(basename "$source")" != "$filter" ]]; then + continue + fi + printf '%s\n' "$source" +done | { + found=0 + while IFS= read -r source; do + found=1 + printf '%s\n' "$source" + done + if [[ -n "$filter" && "$found" -eq 0 ]]; then + echo "missing wasm-safe project source in CMake list: $filter" >&2 + exit 1 + fi +} diff --git a/list-linuxcnc-wasm-safe-shims.sh b/list-linuxcnc-wasm-safe-shims.sh new file mode 100755 index 0000000..5feaf06 --- /dev/null +++ b/list-linuxcnc-wasm-safe-shims.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")" + +filter=${1:-} + +awk ' + /set\(linuxcnc_wasm_safe_probe_shims/ { + in_list = 1 + next + } + in_list && /^[[:space:]]*\)/ { + exit + } + in_list { + line = $0 + sub(/^[[:space:]]*/, "", line) + sub(/[[:space:]]*$/, "", line) + if (line == "") { + next + } + sub(/^\$\{CMAKE_CURRENT_SOURCE_DIR\}\//, "core/", line) + print line + } +' core/CMakeLists.txt | while IFS= read -r source; do + if [[ ! -f "$source" ]]; then + echo "missing wasm-safe shim source: $source" >&2 + exit 1 + fi + if [[ -n "$filter" && "$(basename "$source")" != "$filter" ]]; then + continue + fi + printf '%s\n' "$source" +done | { + found=0 + while IFS= read -r source; do + found=1 + printf '%s\n' "$source" + done + if [[ -n "$filter" && "$found" -eq 0 ]]; then + echo "missing wasm-safe shim in CMake list: $filter" >&2 + exit 1 + fi +} diff --git a/test-all-native.sh b/test-all-native.sh index 0d2dbf3..5004fca 100755 --- a/test-all-native.sh +++ b/test-all-native.sh @@ -3,13 +3,41 @@ set -euo pipefail cd "$(dirname "$0")" +manifest=${1:-linuxcnc-rs274-source-files.txt} +linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc} + 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 "missing LinuxCNC support object:" list-linuxcnc-source-support-objects.sh >/dev/null +grep -F "build LinuxCNC first or set LINUXCNC_ROOT to a built tree" list-linuxcnc-source-support-objects.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 +grep -F "CNC_SIM_ENABLE_LINUXCNC_RS274_BACKEND is native-only; it links LinuxCNC librs274" core/CMakeLists.txt >/dev/null +grep -F "Set CNC_SIM_LINUXCNC_ROOT to the LinuxCNC source root" core/CMakeLists.txt >/dev/null +grep -F "find_package(Python3 REQUIRED COMPONENTS Development)" core/CMakeLists.txt >/dev/null +grep -F "target_compile_definitions(cnc_sim_objects" core/CMakeLists.txt >/dev/null +grep -F "CNC_SIM_ENABLE_LINUXCNC_RS274_BACKEND" core/CMakeLists.txt >/dev/null +grep -F 'target_link_directories(cnc_sim_core' core/CMakeLists.txt >/dev/null +grep -F "add_executable(cnc_sim_api_linuxcnc_rs274_smoke" core/CMakeLists.txt >/dev/null +grep -F "target_link_libraries(cnc_sim_core" core/CMakeLists.txt >/dev/null +grep -F "rs274" core/CMakeLists.txt >/dev/null +grep -F "tooldata" core/CMakeLists.txt >/dev/null +grep -F "Python3::Python" core/CMakeLists.txt >/dev/null +grep -F '"-Wl,-rpath,${CNC_SIM_LINUXCNC_ROOT}/lib"' core/CMakeLists.txt >/dev/null +grep -F "add_library(cnc_sim_linuxcnc_canon_bridge STATIC" core/CMakeLists.txt >/dev/null +grep -F "target_link_libraries(cnc_sim_linuxcnc_canon_bridge PRIVATE cnc_sim_core tooldata)" core/CMakeLists.txt >/dev/null +grep -F './list-linuxcnc-rs274-dump-project-sources.sh > "$project_source_list"' test-linuxcnc-source-link.sh >/dev/null +grep -F './list-linuxcnc-rs274-dump-project-sources.sh > "$project_source_list"' test-linuxcnc-rs274-native.sh >/dev/null +grep -F './list-linuxcnc-rs274-api-project-sources.sh > "$project_source_list"' test-linuxcnc-api-native.sh >/dev/null +grep -F './list-cnc-sim-core-sources.sh > "$core_source_list"' test-native.sh >/dev/null +grep -F './list-linuxcnc-bridge-smoke-project-sources.sh > "$project_source_list"' test-linuxcnc-bridge-native.sh >/dev/null +grep -F 'LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-support-objects.sh > "$support_object_list"' test-linuxcnc-source-link.sh >/dev/null for script in \ + test-linuxcnc-source-syntax.sh \ + test-linuxcnc-source-objects.sh \ + test-linuxcnc-source-link.sh \ test-linuxcnc-bridge-native.sh \ test-linuxcnc-rs274-native.sh \ test-linuxcnc-api-native.sh; do @@ -25,10 +53,31 @@ for script in \ done rm -f "$missing_root_log" +missing_manifest=${TMPDIR:-/tmp}/cnc_sim_missing_native_manifest.txt +missing_manifest_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_native_manifest.XXXXXX.log") +rm -f "$missing_manifest" +for script in \ + list-linuxcnc-source-manifest-sources.sh \ + test-linuxcnc-source-syntax.sh \ + test-linuxcnc-source-objects.sh \ + test-linuxcnc-source-link.sh; do + if "./$script" "$missing_manifest" >"$missing_manifest_log" 2>&1; then + echo "$script accepted missing manifest: $missing_manifest" >&2 + exit 1 + fi + if ! grep -F "missing manifest: $missing_manifest" "$missing_manifest_log" >/dev/null; then + echo "$script did not report missing manifest clearly" >&2 + sed -n '1,20p' "$missing_manifest_log" >&2 + exit 1 + fi +done +rm -f "$missing_manifest_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 \ + list-linuxcnc-source-manifest-sources.sh \ test-linuxcnc-source-syntax.sh \ test-linuxcnc-source-objects.sh \ test-linuxcnc-source-link.sh; do @@ -44,6 +93,142 @@ for script in \ done rm -f "$unknown_group_manifest" "$unknown_group_log" +duplicate_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_native_duplicate_manifest.XXXXXX.txt") +duplicate_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_native_duplicate_manifest.XXXXXX.log") +printf 'core:src/emc/rs274ngc/interp_arc.cc:test duplicate source\ncore:src/emc/rs274ngc/interp_arc.cc:test duplicate source\n' >"$duplicate_manifest" +for script in \ + list-linuxcnc-source-manifest-sources.sh \ + test-linuxcnc-source-syntax.sh \ + test-linuxcnc-source-objects.sh \ + test-linuxcnc-source-link.sh; do + if "./$script" "$duplicate_manifest" >"$duplicate_log" 2>&1; then + echo "$script accepted duplicate manifest source" >&2 + exit 1 + fi + if ! grep -F "duplicate manifest source: src/emc/rs274ngc/interp_arc.cc" "$duplicate_log" >/dev/null; then + echo "$script did not report duplicate manifest source clearly" >&2 + sed -n '1,20p' "$duplicate_log" >&2 + exit 1 + fi +done +rm -f "$duplicate_manifest" "$duplicate_log" + +./list-linuxcnc-source-manifest-sources.sh "$manifest" core >/dev/null +grep -Fx "src/emc/rs274ngc/interp_arc.cc" \ + < <(./list-linuxcnc-source-manifest-sources.sh "$manifest" core) >/dev/null +grep -Fx "src/emc/rs274ngc/interpmodule.cc" \ + < <(./list-linuxcnc-source-manifest-sources.sh "$manifest" binding) >/dev/null +grep -Fx "src/emc/tooldata/tooldata_common.cc" \ + < <(./list-linuxcnc-source-manifest-sources.sh "$manifest" tooldata) >/dev/null +unknown_filter_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_native_unknown_manifest_filter.XXXXXX.log") +if ./list-linuxcnc-source-manifest-sources.sh "$manifest" bogus >"$unknown_filter_log" 2>&1; then + echo "native manifest source lister accepted unknown filter" >&2 + exit 1 +fi +if ! grep -F "unknown manifest source filter: bogus" "$unknown_filter_log" >/dev/null; then + echo "native manifest source lister did not report unknown filter clearly" >&2 + sed -n '1,20p' "$unknown_filter_log" >&2 + exit 1 +fi +rm -f "$unknown_filter_log" +unknown_output_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_native_unknown_manifest_output.XXXXXX.log") +if ./list-linuxcnc-source-manifest-sources.sh "$manifest" core bogus >"$unknown_output_log" 2>&1; then + echo "native manifest source lister accepted unknown output mode" >&2 + exit 1 +fi +if ! grep -F "unknown manifest source output mode: bogus" "$unknown_output_log" >/dev/null; then + echo "native manifest source lister did not report unknown output mode clearly" >&2 + sed -n '1,20p' "$unknown_output_log" >&2 + exit 1 +fi +rm -f "$unknown_output_log" +grep -Fx "$linuxcnc_root/src/emc/rs274ngc/interp_arc.cc" \ + < <(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-manifest-sources.sh "$manifest" core full) >/dev/null + +./list-linuxcnc-rs274-dump-project-sources.sh >/dev/null +grep -Fx "core/tools/linuxcnc_rs274_dump.cpp" \ + < <(./list-linuxcnc-rs274-dump-project-sources.sh linuxcnc_rs274_dump.cpp) >/dev/null +missing_dump_source_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_rs274_dump_project_source.XXXXXX.log") +if ./list-linuxcnc-rs274-dump-project-sources.sh does_not_exist.cc >"$missing_dump_source_log" 2>&1; then + echo "rs274 dump project source lister accepted missing source filter" >&2 + exit 1 +fi +if ! grep -F "missing LinuxCNC rs274 dump project source in list: does_not_exist.cc" "$missing_dump_source_log" >/dev/null; then + echo "rs274 dump project source lister did not report missing source filter clearly" >&2 + sed -n '1,20p' "$missing_dump_source_log" >&2 + exit 1 +fi +rm -f "$missing_dump_source_log" + +./list-linuxcnc-rs274-api-project-sources.sh >/dev/null +grep -Fx "core/src/linuxcnc_rs274_backend.cpp" \ + < <(./list-linuxcnc-rs274-api-project-sources.sh linuxcnc_rs274_backend.cpp) >/dev/null +missing_api_source_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_rs274_api_project_source.XXXXXX.log") +if ./list-linuxcnc-rs274-api-project-sources.sh does_not_exist.cc >"$missing_api_source_log" 2>&1; then + echo "rs274 API project source lister accepted missing source filter" >&2 + exit 1 +fi +if ! grep -F "missing LinuxCNC rs274 API project source in CMake list: does_not_exist.cc" "$missing_api_source_log" >/dev/null; then + echo "rs274 API project source lister did not report missing source filter clearly" >&2 + sed -n '1,20p' "$missing_api_source_log" >&2 + exit 1 +fi +rm -f "$missing_api_source_log" + +./list-cnc-sim-core-sources.sh >/dev/null +grep -Fx "core/src/smoke_gcode_parser.cpp" \ + < <(./list-cnc-sim-core-sources.sh smoke_gcode_parser.cpp) >/dev/null +missing_core_source_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_core_source.XXXXXX.log") +if ./list-cnc-sim-core-sources.sh does_not_exist.cc >"$missing_core_source_log" 2>&1; then + echo "cnc sim core source lister accepted missing source filter" >&2 + exit 1 +fi +if ! grep -F "missing cnc sim core source in CMake list: does_not_exist.cc" "$missing_core_source_log" >/dev/null; then + echo "cnc sim core source lister did not report missing source filter clearly" >&2 + sed -n '1,20p' "$missing_core_source_log" >&2 + exit 1 +fi +rm -f "$missing_core_source_log" + +./list-linuxcnc-bridge-smoke-project-sources.sh >/dev/null +grep -Fx "core/src/linuxcnc_canon_bridge.cpp" \ + < <(./list-linuxcnc-bridge-smoke-project-sources.sh linuxcnc_canon_bridge.cpp) >/dev/null +missing_bridge_source_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_bridge_smoke_project_source.XXXXXX.log") +if ./list-linuxcnc-bridge-smoke-project-sources.sh does_not_exist.cc >"$missing_bridge_source_log" 2>&1; then + echo "LinuxCNC bridge smoke project source lister accepted missing source filter" >&2 + exit 1 +fi +if ! grep -F "missing LinuxCNC bridge smoke project source in list: does_not_exist.cc" "$missing_bridge_source_log" >/dev/null; then + echo "LinuxCNC bridge smoke project source lister did not report missing source filter clearly" >&2 + sed -n '1,20p' "$missing_bridge_source_log" >&2 + exit 1 +fi +rm -f "$missing_bridge_source_log" + +LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-support-objects.sh >/dev/null +grep -F "/src/objects/emc/rs274ngc/interpmodule.o" \ + < <(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-support-objects.sh interpmodule.o) >/dev/null +missing_support_object_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_linuxcnc_support_object.XXXXXX.log") +if LINUXCNC_ROOT="$missing_root" ./list-linuxcnc-source-support-objects.sh >"$missing_support_object_log" 2>&1; then + echo "LinuxCNC support object lister accepted missing LinuxCNC root: $missing_root" >&2 + exit 1 +fi +if ! grep -F "missing LinuxCNC root: $missing_root" "$missing_support_object_log" >/dev/null; then + echo "LinuxCNC support object lister did not report missing LinuxCNC root clearly" >&2 + sed -n '1,20p' "$missing_support_object_log" >&2 + exit 1 +fi +if LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-support-objects.sh does_not_exist.o >"$missing_support_object_log" 2>&1; then + echo "LinuxCNC support object lister accepted missing object filter" >&2 + exit 1 +fi +if ! grep -F "missing LinuxCNC support object in list: does_not_exist.o" "$missing_support_object_log" >/dev/null; then + echo "LinuxCNC support object lister did not report missing object filter clearly" >&2 + sed -n '1,20p' "$missing_support_object_log" >&2 + exit 1 +fi +rm -f "$missing_support_object_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" @@ -59,9 +244,9 @@ fi rm -f "$missing_source_manifest" "$missing_source_log" ./test-native.sh -./test-linuxcnc-source-syntax.sh -./test-linuxcnc-source-objects.sh -./test-linuxcnc-source-link.sh +./test-linuxcnc-source-syntax.sh "$manifest" +./test-linuxcnc-source-objects.sh "$manifest" +./test-linuxcnc-source-link.sh "$manifest" ./test-linuxcnc-bridge-native.sh ./test-linuxcnc-rs274-native.sh ./test-linuxcnc-api-native.sh diff --git a/test-linuxcnc-api-native.sh b/test-linuxcnc-api-native.sh index 08f82fb..752e68c 100755 --- a/test-linuxcnc-api-native.sh +++ b/test-linuxcnc-api-native.sh @@ -16,6 +16,10 @@ trap 'rm -rf "$build_dir"' EXIT var_file="$build_dir/rs274ngc-api.var" cp "$linuxcnc_root/tests/halui/jogging/sim.var" "$var_file" +project_source_list="$build_dir/linuxcnc_rs274_api_project_sources.txt" +./list-linuxcnc-rs274-api-project-sources.sh > "$project_source_list" +mapfile -t project_sources < "$project_source_list" + "$cxx" -std=c++17 \ -DCNC_SIM_ENABLE_LINUXCNC_RS274_BACKEND \ $(python3.13-config --includes 2>/dev/null || python3-config --includes) \ @@ -27,15 +31,7 @@ cp "$linuxcnc_root/tests/halui/jogging/sim.var" "$var_file" -I "$linuxcnc_root/src/emc/rs274ngc" \ -I "$linuxcnc_root/src/emc/motion" \ -I "$linuxcnc_root/include" \ - core/src/canon_event_sink.cpp \ - core/src/cnc_sim_api.cpp \ - core/src/gcode_backend.cpp \ - core/src/linuxcnc_canon_bridge.cpp \ - core/src/linuxcnc_rs274_backend.cpp \ - core/src/linuxcnc_tooldata_fixture.cpp \ - core/src/rtcp_kinematics.cpp \ - core/src/simulator_gcode_controls.cpp \ - core/src/smoke_gcode_parser.cpp \ + "${project_sources[@]}" \ core/tests/cnc_sim_api_linuxcnc_rs274_smoke.cpp \ -L "$linuxcnc_root/lib" \ -Wl,-rpath,"$linuxcnc_root/lib" \ diff --git a/test-linuxcnc-bridge-native.sh b/test-linuxcnc-bridge-native.sh index 7cbc5fc..ba51e08 100755 --- a/test-linuxcnc-bridge-native.sh +++ b/test-linuxcnc-bridge-native.sh @@ -13,6 +13,9 @@ if [[ ! -d "$linuxcnc_root" ]]; then fi trap 'rm -rf "$build_dir"' EXIT +project_source_list="$build_dir/linuxcnc_bridge_smoke_project_sources.txt" +./list-linuxcnc-bridge-smoke-project-sources.sh > "$project_source_list" +mapfile -t project_sources < "$project_source_list" "$cxx" -std=c++17 \ -I core/include \ @@ -23,9 +26,7 @@ trap 'rm -rf "$build_dir"' EXIT -I "$linuxcnc_root/src/emc/motion" \ -I "$linuxcnc_root/src" \ -I "$linuxcnc_root/include" \ - core/src/canon_event_sink.cpp \ - core/src/linuxcnc_canon_bridge.cpp \ - core/src/rtcp_kinematics.cpp \ + "${project_sources[@]}" \ core/tests/linuxcnc_canon_bridge_smoke.cpp \ -L "$linuxcnc_root/lib" \ -Wl,-rpath,"$linuxcnc_root/lib" \ diff --git a/test-linuxcnc-rs274-native.sh b/test-linuxcnc-rs274-native.sh index 64a38ca..6ddf096 100755 --- a/test-linuxcnc-rs274-native.sh +++ b/test-linuxcnc-rs274-native.sh @@ -25,6 +25,10 @@ mkdir -p "$output_dir" cp "$linuxcnc_root/tests/halui/jogging/sim.var" "$base_var_file" cp "$base_var_file" "$var_file" +project_source_list="$build_dir/linuxcnc_rs274_dump_project_sources.txt" +./list-linuxcnc-rs274-dump-project-sources.sh > "$project_source_list" +mapfile -t project_sources < "$project_source_list" + "$cxx" -std=c++17 \ $(python3.13-config --includes 2>/dev/null || python3-config --includes) \ -I core/include \ @@ -35,12 +39,7 @@ cp "$base_var_file" "$var_file" -I "$linuxcnc_root/src/emc/rs274ngc" \ -I "$linuxcnc_root/src/emc/motion" \ -I "$linuxcnc_root/include" \ - core/src/canon_event_sink.cpp \ - core/src/linuxcnc_canon_bridge.cpp \ - core/src/linuxcnc_tooldata_fixture.cpp \ - core/src/rtcp_kinematics.cpp \ - core/src/simulator_gcode_controls.cpp \ - core/tools/linuxcnc_rs274_dump.cpp \ + "${project_sources[@]}" \ -L "$linuxcnc_root/lib" \ -Wl,-rpath,"$linuxcnc_root/lib" \ -lrs274 \ diff --git a/test-linuxcnc-source-link.sh b/test-linuxcnc-source-link.sh index 4ca79c8..5b2be97 100755 --- a/test-linuxcnc-source-link.sh +++ b/test-linuxcnc-source-link.sh @@ -40,67 +40,37 @@ common_flags=( ) linuxcnc_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 - linuxcnc_sources+=("$path") - ;; - ""|\#*|binding|tooldata) - ;; - *) - echo "unknown manifest group: $group" >&2 - exit 1 - ;; - esac -done < "$manifest" +source_list="$build_dir/linuxcnc_core_sources.txt" +LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-manifest-sources.sh "$manifest" core full > "$source_list" +mapfile -t linuxcnc_sources < "$source_list" 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" + "$cxx" "${common_flags[@]}" -c "$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 \ - core/src/linuxcnc_tooldata_fixture.cpp \ - core/src/rtcp_kinematics.cpp \ - core/src/simulator_gcode_controls.cpp \ - core/tools/linuxcnc_rs274_dump.cpp; do +project_source_list="$build_dir/linuxcnc_rs274_dump_project_sources.txt" +./list-linuxcnc-rs274-dump-project-sources.sh > "$project_source_list" +mapfile -t project_sources < "$project_source_list" +for source in "${project_sources[@]}"; do obj="$build_dir/project_${project_index}.o" "$cxx" "${common_flags[@]}" -c "$source" -o "$obj" objects+=("$obj") project_index=$((project_index + 1)) done -support_objects=( - "$linuxcnc_root/src/objects/emc/rs274ngc/interpmodule.o" - "$linuxcnc_root/src/objects/emc/rs274ngc/canonmodule.o" - "$linuxcnc_root/src/objects/emc/rs274ngc/pyarrays.o" - "$linuxcnc_root/src/objects/emc/rs274ngc/pyblock.o" - "$linuxcnc_root/src/objects/emc/rs274ngc/pyemctypes.o" - "$linuxcnc_root/src/objects/emc/rs274ngc/pyinterp1.o" - "$linuxcnc_root/src/objects/emc/rs274ngc/pyparamclass.o" - "$linuxcnc_root/src/objects/emc/nml_intf/emcops.o" - "$linuxcnc_root/src/objects/emc/sai/dummyemcstat.o" - "$linuxcnc_root/src/objects/libnml/nml/stat_msg.o" -) +"$cxx" -std=c++17 -I core/include -I core/src \ + core/tests/linuxcnc_gees_table_smoke.cpp \ + -o "$build_dir/linuxcnc_gees_table_smoke" -for obj in "${support_objects[@]}"; do - if [[ ! -f "$obj" ]]; then - echo "missing LinuxCNC support object: $obj" >&2 - echo "build LinuxCNC first or set LINUXCNC_ROOT to a built tree" >&2 - exit 1 - fi -done +support_object_list="$build_dir/linuxcnc_support_objects.txt" +LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-support-objects.sh > "$support_object_list" +mapfile -t support_objects < "$support_object_list" "$cxx" "${objects[@]}" "${support_objects[@]}" \ -L "$linuxcnc_root/lib" \ @@ -126,6 +96,8 @@ mkdir -p "$output_dir" cp "$linuxcnc_root/tests/halui/jogging/sim.var" "$base_var_file" cp "$base_var_file" "$var_file" +LINUXCNC_ROOT="$linuxcnc_root" "$build_dir/linuxcnc_gees_table_smoke" + CNC_SIM_RS274_VAR="$var_file" \ "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/basic_mill.ngc \ >"$output_dir/cnc_sim_linuxcnc_source_basic_mill.json" @@ -543,6 +515,38 @@ if CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ 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_cutter_comp_tool_change_error.ngc \ + >"$output_dir/cnc_sim_linuxcnc_source_cutter_comp_tool_change_error.json" 2>"$output_dir/cnc_sim_linuxcnc_source_cutter_comp_tool_change_error.err"; then + echo "expected linuxcnc_cutter_comp_tool_change_error.ngc to fail" >&2 + exit 1 +fi +grep -q "Cannot change tools with cutter radius compensation on" \ + "$output_dir/cnc_sim_linuxcnc_source_cutter_comp_tool_change_error.err" +if CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ + "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_cutter_comp_wait_input_error.ngc \ + >"$output_dir/cnc_sim_linuxcnc_source_cutter_comp_wait_input_error.json" 2>"$output_dir/cnc_sim_linuxcnc_source_cutter_comp_wait_input_error.err"; then + echo "expected linuxcnc_cutter_comp_wait_input_error.ngc to fail" >&2 + exit 1 +fi +grep -q "Cannot wait for digital input with cutter radius compensation on" \ + "$output_dir/cnc_sim_linuxcnc_source_cutter_comp_wait_input_error.err" +if CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ + "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_cutter_comp_g71_error.ngc \ + >"$output_dir/cnc_sim_linuxcnc_source_cutter_comp_g71_error.json" 2>"$output_dir/cnc_sim_linuxcnc_source_cutter_comp_g71_error.err"; then + echo "expected linuxcnc_cutter_comp_g71_error.ngc to fail" >&2 + exit 1 +fi +grep -q "G71.0 cannot be used with cutter compensation enabled" \ + "$output_dir/cnc_sim_linuxcnc_source_cutter_comp_g71_error.err" +if CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ + "$build_dir/linuxcnc_rs274_source_dump" tests/gcode/linuxcnc_canned_cycle_g98_retract_error.ngc \ + >"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g98_retract_error.json" 2>"$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g98_retract_error.err"; then + echo "expected linuxcnc_canned_cycle_g98_retract_error.ngc to fail" >&2 + exit 1 +fi +grep -q "Cannot change retract mode with cutter radius compensation on" \ + "$output_dir/cnc_sim_linuxcnc_source_canned_cycle_g98_retract_error.err" 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 \ >"$output_dir/cnc_sim_linuxcnc_source_nested_subroutine_definition_error.json" 2>"$output_dir/cnc_sim_linuxcnc_source_nested_subroutine_definition_error.err"; then @@ -555,6 +559,8 @@ if CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ echo "expected linuxcnc_ccomp_arcexit_error.ngc to fail" >&2 exit 1 fi +grep -q "The move just after exiting cutter compensation mode must be straight, not an arc" \ + "$output_dir/cnc_sim_linuxcnc_source_ccomp_arcexit_error.err" 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 \ >"$output_dir/cnc_sim_linuxcnc_source_abort_hot_comment.json" 2>"$output_dir/cnc_sim_linuxcnc_source_abort_hot_comment.err"; then @@ -567,6 +573,8 @@ if CNC_SIM_RS274_FILE_MODE=1 CNC_SIM_RS274_VAR="$var_file" \ echo "expected linuxcnc_ccomp_gouging_error.ngc to fail" >&2 exit 1 fi +grep -q "Straight feed in concave corner cannot be reached by the tool without gouging" \ + "$output_dir/cnc_sim_linuxcnc_source_ccomp_gouging_error.err" 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 \ diff --git a/test-linuxcnc-source-objects.sh b/test-linuxcnc-source-objects.sh index 4027adb..4416240 100755 --- a/test-linuxcnc-source-objects.sh +++ b/test-linuxcnc-source-objects.sh @@ -34,29 +34,15 @@ common_flags=( ) 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) - ;; - *) - echo "unknown manifest group: $group" >&2 - exit 1 - ;; - esac -done < "$manifest" +source_list="$build_dir/linuxcnc_core_sources.txt" +LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-manifest-sources.sh "$manifest" core full > "$source_list" +mapfile -t sources < "$source_list" source_index=0 for source in "${sources[@]}"; do obj="$build_dir/linuxcnc_${source_index}.o" # shellcheck disable=SC2086 - "$cxx" "${common_flags[@]}" $python_includes -c "$linuxcnc_root/$source" -o "$obj" + "$cxx" "${common_flags[@]}" $python_includes -c "$source" -o "$obj" source_index=$((source_index + 1)) done diff --git a/test-linuxcnc-source-syntax.sh b/test-linuxcnc-source-syntax.sh index e41acce..8ffbcc7 100755 --- a/test-linuxcnc-source-syntax.sh +++ b/test-linuxcnc-source-syntax.sh @@ -18,23 +18,9 @@ if [[ ! -d "$linuxcnc_root" ]]; then exit 1 fi -while IFS=: read -r group path note; do - case "$group" in - ""|\#*) - 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 < "$manifest" +source_list=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_linuxcnc_source_syntax_sources.XXXXXX.txt") +trap 'rm -f "$source_list"' EXIT +LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-manifest-sources.sh "$manifest" all full > "$source_list" 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") @@ -72,18 +58,10 @@ common_flags=( -I "$linuxcnc_root/include" ) -probe_sources=( - src/emc/rs274ngc/interp_base.cc - src/emc/rs274ngc/modal_state.cc - src/emc/rs274ngc/interp_arc.cc - src/emc/rs274ngc/interp_find.cc - src/emc/rs274ngc/interp_read.cc - src/emc/rs274ngc/nurbs_additional_functions.cc -) - -for source in "${probe_sources[@]}"; do +while IFS= read -r source; do + [[ -z "$source" ]] && continue # shellcheck disable=SC2086 - "$cxx" "${common_flags[@]}" $python_includes "$linuxcnc_root/$source" -done + "$cxx" "${common_flags[@]}" $python_includes "$source" +done < "$source_list" echo "linuxcnc rs274 source syntax probe passed" diff --git a/test-linuxcnc-wasm-blockers.sh b/test-linuxcnc-wasm-blockers.sh index 54efd62..f66441d 100755 --- a/test-linuxcnc-wasm-blockers.sh +++ b/test-linuxcnc-wasm-blockers.sh @@ -5,6 +5,20 @@ cd "$(dirname "$0")" manifest=${1:-linuxcnc-rs274-wasm-source-files.txt} +missing_manifest=${TMPDIR:-/tmp}/cnc_sim_missing_wasm_blocker_manifest.txt +missing_manifest_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_wasm_blocker_manifest.XXXXXX.log") +rm -f "$missing_manifest" +if ./analyze-linuxcnc-wasm-blockers.sh "$missing_manifest" >"$missing_manifest_log" 2>&1; then + echo "wasm blocker analyzer accepted missing manifest: $missing_manifest" >&2 + exit 1 +fi +if ! grep -F "missing manifest: $missing_manifest" "$missing_manifest_log" >/dev/null; then + echo "wasm blocker analyzer did not report missing manifest clearly" >&2 + sed -n '1,20p' "$missing_manifest_log" >&2 + exit 1 +fi +rm -f "$missing_manifest_log" + 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 @@ -34,6 +48,44 @@ if ! grep -F "missing manifest source: src/emc/tooldata/does_not_exist.cc" "$mis fi rm -f "$missing_source_manifest" "$missing_source_log" +duplicate_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_duplicate_wasm_blocker_manifest.XXXXXX.txt") +duplicate_manifest_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_duplicate_wasm_blocker_manifest.XXXXXX.log") +cat >"$duplicate_manifest" <<'EOF' +core:src/emc/rs274ngc/interp_arc.cc:duplicate core entry +core:src/emc/rs274ngc/interp_arc.cc:duplicate core entry +blocked:src/emc/tooldata/tooldata_mmap.cc:blocked entry +EOF +if ./analyze-linuxcnc-wasm-blockers.sh "$duplicate_manifest" >"$duplicate_manifest_log" 2>&1; then + echo "wasm blocker analyzer accepted duplicate manifest source" >&2 + exit 1 +fi +if ! grep -F "duplicate manifest source: src/emc/rs274ngc/interp_arc.cc" "$duplicate_manifest_log" >/dev/null; then + echo "wasm blocker analyzer did not report duplicate manifest source clearly" >&2 + sed -n '1,20p' "$duplicate_manifest_log" >&2 + exit 1 +fi +rm -f "$duplicate_manifest" "$duplicate_manifest_log" + +missing_replacement_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_wasm_blocked_replacement.XXXXXX.txt") +missing_replacement_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_wasm_blocked_replacement.XXXXXX.log") +cat >"$missing_replacement_manifest" <<'EOF' +core:src/emc/rs274ngc/interp_arc.cc:core entry +blocked:src/emc/tooldata/tooldata_db.cc:blocked entry without wasm replacement +EOF +if ./analyze-linuxcnc-wasm-blockers.sh "$missing_replacement_manifest" >"$missing_replacement_log" 2>&1; then + echo "wasm blocker analyzer accepted blocked source without replacement mapping" >&2 + exit 1 +fi +if ! grep -F "missing blocked replacement mapping: src/emc/tooldata/tooldata_db.cc" "$missing_replacement_log" >/dev/null; then + echo "wasm blocker analyzer did not report missing replacement mapping clearly" >&2 + sed -n '1,20p' "$missing_replacement_log" >&2 + exit 1 +fi +rm -f "$missing_replacement_manifest" "$missing_replacement_log" + +grep -F "[manifest]" <<<"$output" >/dev/null +grep -F "core=25" <<<"$(sed -n '/^\[manifest\]/,/^$/p' <<<"$output")" >/dev/null +grep -F "blocked=1" <<<"$(sed -n '/^\[manifest\]/,/^$/p' <<<"$output")" >/dev/null grep -F "[python]" <<<"$output" >/dev/null grep -F "(none)" <<<"$(sed -n '/^\[python\]/,/^$/p' <<<"$output")" >/dev/null grep -F "[dlopen]" <<<"$output" >/dev/null @@ -46,5 +98,19 @@ 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 +grep -F "[blocked-replacements]" <<<"$output" >/dev/null +grep -F "src/emc/tooldata/tooldata_mmap.cc -> core/wasm_shims/tooldata/tooldata_mmap_backend.cc" \ + <<<"$(sed -n '/^\[blocked-replacements\]/,/^$/p' <<<"$output")" >/dev/null +grep -F "[core-python-shimmed]" <<<"$output" >/dev/null +grep -F "src/emc/rs274ngc/interp_python.cc" <<<"$(sed -n '/^\[core-python-shimmed\]/,/^$/p' <<<"$output")" >/dev/null +grep -F "src/emc/rs274ngc/rs274ngc_pre.cc" <<<"$(sed -n '/^\[core-python-shimmed\]/,/^$/p' <<<"$output")" >/dev/null +grep -F "[core-dlopen-shimmed]" <<<"$output" >/dev/null +grep -F "src/emc/rs274ngc/interp_base.cc" <<<"$(sed -n '/^\[core-dlopen-shimmed\]/,/^$/p' <<<"$output")" >/dev/null +grep -F "[core-tooldata-shimmed]" <<<"$output" >/dev/null +grep -F "src/emc/tooldata/tooldata_common.cc" <<<"$(sed -n '/^\[core-tooldata-shimmed\]/,/^$/p' <<<"$output")" >/dev/null +grep -F "[core-tooldata-users-shimmed]" <<<"$output" >/dev/null +grep -F "src/emc/rs274ngc/interp_find.cc" <<<"$(sed -n '/^\[core-tooldata-users-shimmed\]/,/^$/p' <<<"$output")" >/dev/null +grep -F "[core-native-fs-shimmed]" <<<"$output" >/dev/null +grep -F "src/emc/rs274ngc/interp_o_word.cc" <<<"$(sed -n '/^\[core-native-fs-shimmed\]/,/^$/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 index 7b8c40b..48ceb7e 100755 --- a/test-linuxcnc-wasm-cmake-safe-probe.sh +++ b/test-linuxcnc-wasm-cmake-safe-probe.sh @@ -18,6 +18,20 @@ if [[ ! -d "$linuxcnc_root" ]]; then fi trap 'rm -rf "$build_dir"' EXIT +missing_manifest=${TMPDIR:-/tmp}/cnc_sim_missing_build_wasm_manifest.txt +missing_manifest_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_build_wasm_manifest.XXXXXX.log") +rm -f "$missing_manifest" +if ./build-wasm.sh "$missing_manifest" >"$missing_manifest_log" 2>&1; then + echo "build-wasm accepted missing manifest: $missing_manifest" >&2 + exit 1 +fi +if ! grep -F "missing manifest: $missing_manifest" "$missing_manifest_log" >/dev/null; then + echo "build-wasm did not report missing manifest clearly" >&2 + sed -n '1,20p' "$missing_manifest_log" >&2 + exit 1 +fi +rm -f "$missing_manifest_log" + 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 @@ -31,13 +45,124 @@ if ! grep -F "missing LinuxCNC root: $missing_root" "$missing_root_log" >/dev/nu 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 +grep -F -- './test-linuxcnc-wasm-blockers.sh "$manifest"' build-wasm.sh >/dev/null +grep -F -- './test-linuxcnc-wasm-source-syntax.sh "$manifest"' build-wasm.sh >/dev/null +grep -F -- './test-linuxcnc-wasm-source-objects.sh "$manifest"' build-wasm.sh >/dev/null +grep -F -- './test-linuxcnc-wasm-cmake-safe-probe.sh "$manifest"' build-wasm.sh >/dev/null +grep -F -- './test-linuxcnc-wasm-tooldata-link.sh' build-wasm.sh >/dev/null +grep -F -- './test-linuxcnc-wasm-tooldata-common-link.sh' build-wasm.sh >/dev/null +grep -F -- './test-linuxcnc-wasm-tooldata-mmap-symbols.sh' build-wasm.sh >/dev/null +grep -F -- './test-linuxcnc-wasm-tooldata-runtime-symbols.sh' build-wasm.sh >/dev/null +grep -F -- './test-linuxcnc-wasm-interp-base-link.sh' build-wasm.sh >/dev/null +grep -F -- './test-linuxcnc-wasm-interp-find-link.sh' build-wasm.sh >/dev/null +grep -F -- './test-linuxcnc-wasm-python-plugin-link.sh' build-wasm.sh >/dev/null +grep -F -- './test-linuxcnc-wasm-rs274ngc-pre-object.sh' build-wasm.sh >/dev/null +grep -F -- './test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh "$manifest"' build-wasm.sh >/dev/null +grep -F -- './test-linuxcnc-wasm-rs274ngc-pre-link.sh "$manifest"' build-wasm.sh >/dev/null +grep -F -- '-DCNC_SIM_LINUXCNC_WASM_SOURCE_MANIFEST="$manifest"' build-wasm.sh >/dev/null +grep -F -- 'if ! command -v emcmake >/dev/null 2>&1; then' build-wasm.sh >/dev/null +grep -F "Emscripten is required. Install/activate emsdk so emcmake and emcc are in PATH." build-wasm.sh >/dev/null +grep -F -- 'emcmake cmake -S core -B build/wasm \' build-wasm.sh >/dev/null +grep -F -- 'default_manifest="$PWD/linuxcnc-rs274-wasm-source-files.txt"' build-wasm.sh >/dev/null +grep -F -- 'LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-manifest-sources.sh "$manifest" core > "$manifest_core_sources"' build-wasm.sh >/dev/null +grep -F -- 'LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-manifest-sources.sh "$manifest" blocked > "$manifest_blocked_sources"' build-wasm.sh >/dev/null +grep -F -- 'if [[ "$manifest" == "$default_manifest" ]] && [[ "$manifest_core_count" -ne 25 || "$manifest_blocked_count" -ne 1 ]]; then' build-wasm.sh >/dev/null +./list-linuxcnc-wasm-safe-shims.sh >/dev/null +./list-linuxcnc-wasm-safe-project-sources.sh >/dev/null +./list-linuxcnc-wasm-manifest-sources.sh "$manifest" core >/dev/null +grep -Fx "src/emc/rs274ngc/interp_arc.cc" \ + < <(./list-linuxcnc-wasm-manifest-sources.sh "$manifest" core) >/dev/null +grep -Fx "src/emc/tooldata/tooldata_mmap.cc" \ + < <(./list-linuxcnc-wasm-manifest-sources.sh "$manifest" blocked) >/dev/null +unknown_manifest_filter_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_unknown_wasm_manifest_filter.XXXXXX.log") +if ./list-linuxcnc-wasm-manifest-sources.sh "$manifest" bogus >"$unknown_manifest_filter_log" 2>&1; then + echo "wasm manifest source lister accepted unknown filter" >&2 + exit 1 +fi +if ! grep -F "unknown manifest source filter: bogus" "$unknown_manifest_filter_log" >/dev/null; then + echo "wasm manifest source lister did not report unknown filter clearly" >&2 + sed -n '1,20p' "$unknown_manifest_filter_log" >&2 + exit 1 +fi +rm -f "$unknown_manifest_filter_log" +unknown_manifest_output_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_unknown_wasm_manifest_output.XXXXXX.log") +if ./list-linuxcnc-wasm-manifest-sources.sh "$manifest" core bogus >"$unknown_manifest_output_log" 2>&1; then + echo "wasm manifest source lister accepted unknown output mode" >&2 + exit 1 +fi +if ! grep -F "unknown manifest source output mode: bogus" "$unknown_manifest_output_log" >/dev/null; then + echo "wasm manifest source lister did not report unknown output mode clearly" >&2 + sed -n '1,20p' "$unknown_manifest_output_log" >&2 + exit 1 +fi +rm -f "$unknown_manifest_output_log" +grep -Fx "$linuxcnc_root/src/emc/rs274ngc/interp_arc.cc" \ + < <(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-manifest-sources.sh "$manifest" core full) >/dev/null +grep -Fx "core/wasm_shims/dlfcn.cc" < <(./list-linuxcnc-wasm-safe-shims.sh dlfcn.cc) >/dev/null +grep -Fx "core/wasm_shims/pythonplugin/python_plugin.cc" \ + < <(./list-linuxcnc-wasm-safe-shims.sh python_plugin.cc) >/dev/null +grep -Fx "core/wasm_shims/rtapi_compat.cc" \ + < <(./list-linuxcnc-wasm-safe-shims.sh rtapi_compat.cc) >/dev/null +grep -Fx "core/wasm_shims/tooldata/tooldata_mmap_backend.cc" \ + < <(./list-linuxcnc-wasm-safe-shims.sh tooldata_mmap_backend.cc) >/dev/null +grep -Fx "core/wasm_shims/tooldata/tooldata_runtime_stubs.cc" \ + < <(./list-linuxcnc-wasm-safe-shims.sh tooldata_runtime_stubs.cc) >/dev/null +grep -Fx "core/src/linuxcnc_canon_bridge.cpp" \ + < <(./list-linuxcnc-wasm-safe-project-sources.sh linuxcnc_canon_bridge.cpp) >/dev/null +missing_shim_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_wasm_safe_shim.XXXXXX.log") +if ./list-linuxcnc-wasm-safe-shims.sh does_not_exist.cc >"$missing_shim_log" 2>&1; then + echo "wasm-safe shim lister accepted missing shim filter" >&2 + exit 1 +fi +if ! grep -F "missing wasm-safe shim in CMake list: does_not_exist.cc" "$missing_shim_log" >/dev/null; then + echo "wasm-safe shim lister did not report missing shim filter clearly" >&2 + sed -n '1,20p' "$missing_shim_log" >&2 + exit 1 +fi +rm -f "$missing_shim_log" +missing_project_source_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_wasm_safe_project_source.XXXXXX.log") +if ./list-linuxcnc-wasm-safe-project-sources.sh does_not_exist.cc >"$missing_project_source_log" 2>&1; then + echo "wasm-safe project source lister accepted missing source filter" >&2 + exit 1 +fi +if ! grep -F "missing wasm-safe project source in CMake list: does_not_exist.cc" "$missing_project_source_log" >/dev/null; then + echo "wasm-safe project source lister did not report missing source filter clearly" >&2 + sed -n '1,20p' "$missing_project_source_log" >&2 + exit 1 +fi +rm -f "$missing_project_source_log" rm -f "$missing_root_log" +missing_wasm_script_manifest=${TMPDIR:-/tmp}/cnc_sim_missing_wasm_script_manifest.txt +missing_wasm_script_manifest_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_wasm_script_manifest.XXXXXX.log") +rm -f "$missing_wasm_script_manifest" +for script in \ + list-linuxcnc-wasm-manifest-sources.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" "$missing_wasm_script_manifest" >"$missing_wasm_script_manifest_log" 2>&1; then + echo "$script accepted missing manifest: $missing_wasm_script_manifest" >&2 + exit 1 + fi + if ! grep -F "missing manifest: $missing_wasm_script_manifest" "$missing_wasm_script_manifest_log" >/dev/null; then + echo "$script did not report missing manifest clearly" >&2 + sed -n '1,20p' "$missing_wasm_script_manifest_log" >&2 + exit 1 + fi +done +rm -f "$missing_wasm_script_manifest_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-rs274ngc-pre-link-blockers.sh + test-linuxcnc-wasm-rs274ngc-pre-link.sh + test-linuxcnc-wasm-source-objects.sh + test-linuxcnc-wasm-source-syntax.sh test-linuxcnc-wasm-tooldata-common-link.sh test-linuxcnc-wasm-tooldata-link.sh test-linuxcnc-wasm-tooldata-mmap-symbols.sh @@ -66,6 +191,8 @@ fixed_source_scripts=( "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" + "test-linuxcnc-wasm-tooldata-mmap-symbols.sh:src/emc/tooldata/tooldata_mmap.cc" + "test-linuxcnc-wasm-tooldata-runtime-symbols.sh:src/emc/tooldata/tooldata_db.cc" ) for script_and_path in "${fixed_source_scripts[@]}"; do script=${script_and_path%%:*} @@ -83,9 +210,50 @@ done rm -rf "$missing_source_root" rm -f "$missing_source_log" +missing_interp_find_root=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_missing_interp_find_source_root.XXXXXX") +missing_interp_find_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_interp_find_source.XXXXXX.log") +mkdir -p "$missing_interp_find_root/src/emc/tooldata" "$missing_interp_find_root/src/emc/rs274ngc" "$missing_interp_find_root/include" +touch "$missing_interp_find_root/src/emc/tooldata/tooldata_common.cc" +if LINUXCNC_ROOT="$missing_interp_find_root" ./test-linuxcnc-wasm-interp-find-link.sh >"$missing_interp_find_log" 2>&1; then + echo "test-linuxcnc-wasm-interp-find-link.sh accepted missing LinuxCNC source: src/emc/rs274ngc/interp_find.cc" >&2 + exit 1 +fi +if ! grep -F "missing LinuxCNC source: src/emc/rs274ngc/interp_find.cc" "$missing_interp_find_log" >/dev/null; then + echo "test-linuxcnc-wasm-interp-find-link.sh did not report missing interp_find source clearly" >&2 + sed -n '1,20p' "$missing_interp_find_log" >&2 + exit 1 +fi +rm -rf "$missing_interp_find_root" +rm -f "$missing_interp_find_log" + +missing_tooldata_runtime_root=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_missing_tooldata_runtime_source_root.XXXXXX") +missing_tooldata_runtime_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_missing_tooldata_runtime_source.XXXXXX.log") +mkdir -p "$missing_tooldata_runtime_root/src/emc/tooldata" "$missing_tooldata_runtime_root/include" +touch "$missing_tooldata_runtime_root/src/emc/tooldata/tooldata_db.cc" +if LINUXCNC_ROOT="$missing_tooldata_runtime_root" ./test-linuxcnc-wasm-tooldata-runtime-symbols.sh >"$missing_tooldata_runtime_log" 2>&1; then + echo "test-linuxcnc-wasm-tooldata-runtime-symbols.sh accepted missing LinuxCNC source: src/emc/tooldata/tooldata_nml.cc" >&2 + exit 1 +fi +if ! grep -F "missing LinuxCNC source: src/emc/tooldata/tooldata_nml.cc" "$missing_tooldata_runtime_log" >/dev/null; then + echo "test-linuxcnc-wasm-tooldata-runtime-symbols.sh did not report missing tooldata_nml source clearly" >&2 + sed -n '1,20p' "$missing_tooldata_runtime_log" >&2 + exit 1 +fi +rm -rf "$missing_tooldata_runtime_root" +rm -f "$missing_tooldata_runtime_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" +if ./build-wasm.sh "$unknown_group_manifest" >"$unknown_group_log" 2>&1; then + echo "build-wasm accepted unknown manifest group" >&2 + exit 1 +fi +if ! grep -F "unknown manifest group: bogus" "$unknown_group_log" >/dev/null; then + echo "build-wasm did not report unknown manifest group clearly" >&2 + sed -n '1,20p' "$unknown_group_log" >&2 + exit 1 +fi for script in \ analyze-linuxcnc-wasm-blockers.sh \ test-linuxcnc-wasm-source-syntax.sh \ @@ -117,6 +285,15 @@ 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 ./build-wasm.sh "$missing_source_manifest" >"$missing_source_log" 2>&1; then + echo "build-wasm 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 "build-wasm did not report missing manifest source clearly" >&2 + sed -n '1,20p' "$missing_source_log" >&2 + exit 1 +fi 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 @@ -155,50 +332,174 @@ if ! grep -F "missing manifest source: src/emc/rs274ngc/does_not_exist.cc" "$mis fi rm -f "$missing_source_manifest" "$missing_source_log" +empty_partition_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_empty_wasm_manifest_partition.XXXXXX.log") +empty_core_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_empty_wasm_manifest_core.XXXXXX.txt") +empty_blocked_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_empty_wasm_manifest_blocked.XXXXXX.txt") +printf 'blocked:src/emc/tooldata/tooldata_mmap.cc:test empty core partition\n' >"$empty_core_manifest" +if ./build-wasm.sh "$empty_core_manifest" >"$empty_partition_log" 2>&1; then + echo "build-wasm accepted empty core manifest partition" >&2 + exit 1 +fi +if ! grep -F "unexpected wasm manifest partition: core=0 blocked=1" "$empty_partition_log" >/dev/null; then + echo "build-wasm did not report empty core manifest partition clearly" >&2 + sed -n '1,20p' "$empty_partition_log" >&2 + exit 1 +fi +printf 'core:src/emc/rs274ngc/interp_arc.cc:test empty blocked partition\n' >"$empty_blocked_manifest" +if ./build-wasm.sh "$empty_blocked_manifest" >"$empty_partition_log" 2>&1; then + echo "build-wasm accepted empty blocked manifest partition" >&2 + exit 1 +fi +if ! grep -F "unexpected wasm manifest partition: core=1 blocked=0" "$empty_partition_log" >/dev/null; then + echo "build-wasm did not report empty blocked manifest partition clearly" >&2 + sed -n '1,20p' "$empty_partition_log" >&2 + exit 1 +fi +rm -f "$empty_core_manifest" "$empty_blocked_manifest" "$empty_partition_log" + +bad_default_manifest_dir=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_bad_default_wasm_manifest.XXXXXX") +bad_default_manifest_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_bad_default_wasm_manifest.XXXXXX.log") +linuxcnc_root_abs=$(cd "$linuxcnc_root" && pwd) +cp build-wasm.sh "$bad_default_manifest_dir/build-wasm.sh" +cp list-linuxcnc-wasm-manifest-sources.sh "$bad_default_manifest_dir/list-linuxcnc-wasm-manifest-sources.sh" +printf 'core:src/emc/rs274ngc/interp_arc.cc:test bad default core count\nblocked:src/emc/tooldata/tooldata_mmap.cc:test bad default blocked count\n' \ + >"$bad_default_manifest_dir/linuxcnc-rs274-wasm-source-files.txt" +if LINUXCNC_ROOT="$linuxcnc_root_abs" "$bad_default_manifest_dir/build-wasm.sh" >"$bad_default_manifest_log" 2>&1; then + echo "build-wasm accepted bad default wasm manifest partition" >&2 + exit 1 +fi +if ! grep -F "unexpected wasm manifest partition: core=1 blocked=1" "$bad_default_manifest_log" >/dev/null; then + echo "build-wasm did not enforce default wasm manifest partition for no-arg entry" >&2 + sed -n '1,20p' "$bad_default_manifest_log" >&2 + exit 1 +fi +rm -rf "$bad_default_manifest_dir" +rm -f "$bad_default_manifest_log" + +duplicate_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_duplicate_wasm_manifest.XXXXXX.txt") +duplicate_manifest_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_duplicate_wasm_manifest.XXXXXX.log") +cat >"$duplicate_manifest" <<'EOF' +core:src/emc/rs274ngc/interp_arc.cc:duplicate core entry +core:src/emc/rs274ngc/interp_arc.cc:duplicate core entry +blocked:src/emc/tooldata/tooldata_mmap.cc:blocked entry +EOF +if ./build-wasm.sh "$duplicate_manifest" >"$duplicate_manifest_log" 2>&1; then + echo "build-wasm accepted duplicate manifest source" >&2 + exit 1 +fi +if ! grep -F "duplicate manifest source: src/emc/rs274ngc/interp_arc.cc" "$duplicate_manifest_log" >/dev/null; then + echo "build-wasm did not report duplicate manifest source clearly" >&2 + sed -n '1,20p' "$duplicate_manifest_log" >&2 + exit 1 +fi +rm -f "$duplicate_manifest" "$duplicate_manifest_log" + +comment_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_comment_wasm_manifest.XXXXXX.txt") +comment_manifest_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_comment_wasm_manifest.XXXXXX.log") +cat >"$comment_manifest" <<'EOF' +# LinuxCNC rs274 source subset for browser-safe source-link probing. +# Format: group:path:note +core:src/emc/rs274ngc/interp_arc.cc:commented core entry +blocked:src/emc/tooldata/tooldata_mmap.cc:commented blocked entry +EOF +if ./build-wasm.sh "$comment_manifest" >"$comment_manifest_log" 2>&1; then + : +fi +if grep -F "duplicate manifest source" "$comment_manifest_log" >/dev/null; then + echo "build-wasm rejected commented manifest lines as duplicates" >&2 + sed -n '1,20p' "$comment_manifest_log" >&2 + exit 1 +fi +rm -f "$comment_manifest" "$comment_manifest_log" + +duplicate_source_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_duplicate_wasm_source_manifest.XXXXXX.txt") +duplicate_source_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_duplicate_wasm_source_manifest.XXXXXX.log") +cat >"$duplicate_source_manifest" <<'EOF' +core:src/emc/rs274ngc/interp_arc.cc:duplicate core entry +core:src/emc/rs274ngc/interp_arc.cc:duplicate core entry +blocked:src/emc/tooldata/tooldata_mmap.cc:blocked entry +EOF +if ./test-linuxcnc-wasm-source-objects.sh "$duplicate_source_manifest" >"$duplicate_source_log" 2>&1; then + echo "source object probe accepted duplicate manifest source" >&2 + exit 1 +fi +if ! grep -F "duplicate manifest source: src/emc/rs274ngc/interp_arc.cc" "$duplicate_source_log" >/dev/null; then + echo "source object probe did not report duplicate manifest source clearly" >&2 + sed -n '1,20p' "$duplicate_source_log" >&2 + exit 1 +fi +rm -f "$duplicate_source_manifest" "$duplicate_source_log" + +duplicate_syntax_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_duplicate_wasm_syntax_manifest.XXXXXX.txt") +duplicate_syntax_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_duplicate_wasm_syntax_manifest.XXXXXX.log") +cat >"$duplicate_syntax_manifest" <<'EOF' +core:src/emc/rs274ngc/interp_arc.cc:duplicate core entry +core:src/emc/rs274ngc/interp_arc.cc:duplicate core entry +blocked:src/emc/tooldata/tooldata_mmap.cc:blocked entry +EOF +if ./test-linuxcnc-wasm-source-syntax.sh "$duplicate_syntax_manifest" >"$duplicate_syntax_log" 2>&1; then + echo "syntax probe accepted duplicate manifest source" >&2 + exit 1 +fi +if ! grep -F "duplicate manifest source: src/emc/rs274ngc/interp_arc.cc" "$duplicate_syntax_log" >/dev/null; then + echo "syntax probe did not report duplicate manifest source clearly" >&2 + sed -n '1,20p' "$duplicate_syntax_log" >&2 + exit 1 +fi +rm -f "$duplicate_syntax_manifest" "$duplicate_syntax_log" + +duplicate_pre_blocker_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_duplicate_wasm_pre_blocker_manifest.XXXXXX.txt") +duplicate_pre_blocker_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_duplicate_wasm_pre_blocker_manifest.XXXXXX.log") +cat >"$duplicate_pre_blocker_manifest" <<'EOF' +core:src/emc/rs274ngc/interp_arc.cc:duplicate core entry +core:src/emc/rs274ngc/interp_arc.cc:duplicate core entry +blocked:src/emc/tooldata/tooldata_mmap.cc:blocked entry +EOF +if ./test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh "$duplicate_pre_blocker_manifest" >"$duplicate_pre_blocker_log" 2>&1; then + echo "rs274ngc_pre blocker scan accepted duplicate manifest source" >&2 + exit 1 +fi +if ! grep -F "duplicate manifest source: src/emc/rs274ngc/interp_arc.cc" "$duplicate_pre_blocker_log" >/dev/null; then + echo "rs274ngc_pre blocker scan did not report duplicate manifest source clearly" >&2 + sed -n '1,20p' "$duplicate_pre_blocker_log" >&2 + exit 1 +fi +rm -f "$duplicate_pre_blocker_manifest" "$duplicate_pre_blocker_log" + +duplicate_pre_link_manifest=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_duplicate_wasm_pre_link_manifest.XXXXXX.txt") +duplicate_pre_link_log=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_duplicate_wasm_pre_link_manifest.XXXXXX.log") +cat >"$duplicate_pre_link_manifest" <<'EOF' +core:src/emc/rs274ngc/interp_arc.cc:duplicate core entry +core:src/emc/rs274ngc/interp_arc.cc:duplicate core entry +blocked:src/emc/tooldata/tooldata_mmap.cc:blocked entry +EOF +if ./test-linuxcnc-wasm-rs274ngc-pre-link.sh "$duplicate_pre_link_manifest" >"$duplicate_pre_link_log" 2>&1; then + echo "rs274ngc_pre link probe accepted duplicate manifest source" >&2 + exit 1 +fi +if ! grep -F "duplicate manifest source: src/emc/rs274ngc/interp_arc.cc" "$duplicate_pre_link_log" >/dev/null; then + echo "rs274ngc_pre link probe did not report duplicate manifest source clearly" >&2 + sed -n '1,20p' "$duplicate_pre_link_log" >&2 + exit 1 +fi +rm -f "$duplicate_pre_link_manifest" "$duplicate_pre_link_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" +manifest_core_sources="$build_dir/wasm_core_sources.txt" +manifest_blocked_sources="$build_dir/wasm_blocked_sources.txt" +./list-linuxcnc-wasm-manifest-sources.sh "$manifest" core > "$manifest_core_sources" +./list-linuxcnc-wasm-manifest-sources.sh "$manifest" blocked > "$manifest_blocked_sources" +manifest_core_count=$(wc -l < "$manifest_core_sources") +manifest_blocked_count=$(wc -l < "$manifest_blocked_sources") -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 -) +required_shim_sources="$build_dir/wasm_safe_shim_sources.txt" +./list-linuxcnc-wasm-safe-shims.sh > "$required_shim_sources" +required_project_sources="$build_dir/wasm_safe_project_sources.txt" +./list-linuxcnc-wasm-safe-project-sources.sh > "$required_project_sources" 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 @@ -209,19 +510,23 @@ if [[ "$manifest_core_count" -eq 0 || "$manifest_blocked_count" -eq 0 ]]; then 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 +while IFS= read -r shim; do + [[ -z "$shim" ]] && continue + cmake_shim=${shim#core/} + if ! grep -F "$cmake_shim" core/CMakeLists.txt >/dev/null; then + echo "missing CMake wasm-safe shim: $cmake_shim" >&2 exit 1 fi -done +done < "$required_shim_sources" -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 +while IFS= read -r source; do + [[ -z "$source" ]] && continue + cmake_source=${source#core/} + if ! grep -F "$cmake_source" core/CMakeLists.txt >/dev/null; then + echo "missing CMake wasm-safe project source: $cmake_source" >&2 exit 1 fi -done +done < "$required_project_sources" 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 @@ -229,8 +534,11 @@ grep -F "target_link_libraries(linuxcnc_rs274_wasm_safe_probe PRIVATE fmt)" core 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 "Missing LinuxCNC wasm source manifest" core/CMakeLists.txt >/dev/null +grep -F 'if(NOT EXISTS "${CNC_SIM_LINUXCNC_WASM_SOURCE_MANIFEST}")' 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 "Bad LinuxCNC wasm source manifest line" 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 @@ -240,6 +548,7 @@ awk ' /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 core entries" core/CMakeLists.txt >/dev/null grep -F "LinuxCNC wasm source manifest has no blocked entries" core/CMakeLists.txt >/dev/null if ! command -v cmake >/dev/null 2>&1; then diff --git a/test-linuxcnc-wasm-interp-base-link.sh b/test-linuxcnc-wasm-interp-base-link.sh index c310909..d41acbf 100755 --- a/test-linuxcnc-wasm-interp-base-link.sh +++ b/test-linuxcnc-wasm-interp-base-link.sh @@ -11,12 +11,11 @@ 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 +dlfcn_shim=$(./list-linuxcnc-wasm-safe-shims.sh dlfcn.cc) +interp_base_source=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-files.sh src/emc/rs274ngc/interp_base.cc) + common_flags=( -std=c++17 -DULAPI @@ -30,8 +29,8 @@ common_flags=( ) "$cxx" "${common_flags[@]}" \ - core/wasm_shims/dlfcn.cc \ - "$linuxcnc_root/src/emc/rs274ngc/interp_base.cc" \ + "$dlfcn_shim" \ + "$interp_base_source" \ core/wasm_shims/interp_base_probe_main.cc \ -o "$build_dir/interp_base_probe" diff --git a/test-linuxcnc-wasm-interp-find-link.sh b/test-linuxcnc-wasm-interp-find-link.sh index 9b3a653..b76f2b0 100755 --- a/test-linuxcnc-wasm-interp-find-link.sh +++ b/test-linuxcnc-wasm-interp-find-link.sh @@ -11,16 +11,17 @@ 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 +tooldata_mmap_backend_shim=$(./list-linuxcnc-wasm-safe-shims.sh tooldata_mmap_backend.cc) +tooldata_runtime_stubs_shim=$(./list-linuxcnc-wasm-safe-shims.sh tooldata_runtime_stubs.cc) +rtapi_compat_shim=$(./list-linuxcnc-wasm-safe-shims.sh rtapi_compat.cc) +readarray -t linuxcnc_sources < <(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-files.sh \ + src/emc/tooldata/tooldata_common.cc \ + src/emc/rs274ngc/interp_find.cc) +tooldata_common_source=${linuxcnc_sources[0]} +interp_find_source=${linuxcnc_sources[1]} + common_flags=( -std=c++17 -DULAPI @@ -36,22 +37,22 @@ common_flags=( ) "$cxx" "${common_flags[@]}" \ - -c "$linuxcnc_root/src/emc/tooldata/tooldata_common.cc" \ + -c "$tooldata_common_source" \ -o "$build_dir/tooldata_common.o" "$cxx" "${common_flags[@]}" \ - -c core/wasm_shims/tooldata/tooldata_mmap_backend.cc \ + -c "$tooldata_mmap_backend_shim" \ -o "$build_dir/tooldata_mmap_backend.o" "$cxx" "${common_flags[@]}" \ - -c core/wasm_shims/tooldata/tooldata_runtime_stubs.cc \ + -c "$tooldata_runtime_stubs_shim" \ -o "$build_dir/tooldata_runtime_stubs.o" "$cxx" "${common_flags[@]}" \ - -c core/wasm_shims/rtapi_compat.cc \ + -c "$rtapi_compat_shim" \ -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" \ + -c "$interp_find_source" \ -o "$build_dir/interp_find.o" "$cxx" "${common_flags[@]}" \ -c core/wasm_shims/interp_find_probe_main.cc \ diff --git a/test-linuxcnc-wasm-python-plugin-link.sh b/test-linuxcnc-wasm-python-plugin-link.sh index 42546ad..ad0fa43 100755 --- a/test-linuxcnc-wasm-python-plugin-link.sh +++ b/test-linuxcnc-wasm-python-plugin-link.sh @@ -13,6 +13,8 @@ if [[ ! -d "$linuxcnc_root" ]]; then fi trap 'rm -rf "$build_dir"' EXIT +python_plugin_shim=$(./list-linuxcnc-wasm-safe-shims.sh python_plugin.cc) + common_flags=( -std=c++17 -DULAPI @@ -26,7 +28,7 @@ common_flags=( ) "$cxx" "${common_flags[@]}" \ - core/wasm_shims/pythonplugin/python_plugin.cc \ + "$python_plugin_shim" \ core/wasm_shims/pythonplugin/python_plugin_probe_main.cc \ -o "$build_dir/python_plugin_probe" diff --git a/test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh b/test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh index 55059f3..c66d7a1 100755 --- a/test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh +++ b/test-linuxcnc-wasm-rs274ngc-pre-link-blockers.sh @@ -36,43 +36,18 @@ common_flags=( -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 -) +shim_list="$build_dir/wasm_safe_shims.txt" +./list-linuxcnc-wasm-safe-shims.sh > "$shim_list" +mapfile -t shim_sources < "$shim_list" -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 -) +project_source_list="$build_dir/wasm_safe_project_sources.txt" +./list-linuxcnc-wasm-safe-project-sources.sh > "$project_source_list" +mapfile -t project_sources < "$project_source_list" 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" +source_list="$build_dir/wasm_core_sources.txt" +LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-manifest-sources.sh "$manifest" core full > "$source_list" +mapfile -t linuxcnc_sources < "$source_list" shim_index=0 for source in "${shim_sources[@]}"; do diff --git a/test-linuxcnc-wasm-rs274ngc-pre-link.sh b/test-linuxcnc-wasm-rs274ngc-pre-link.sh index e143dcf..e4122bb 100755 --- a/test-linuxcnc-wasm-rs274ngc-pre-link.sh +++ b/test-linuxcnc-wasm-rs274ngc-pre-link.sh @@ -36,39 +36,20 @@ common_flags=( -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 -) +shim_list="$build_dir/wasm_safe_shims.txt" +./list-linuxcnc-wasm-safe-shims.sh > "$shim_list" +mapfile -t sources < "$shim_list" +project_source_list="$build_dir/wasm_safe_project_sources.txt" +./list-linuxcnc-wasm-safe-project-sources.sh > "$project_source_list" +mapfile -t project_sources < "$project_source_list" +sources+=("${project_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+=("$linuxcnc_root/$path") - ;; - ""|\#*|blocked) - ;; - *) - echo "unknown manifest group: $group" >&2 - exit 1 - ;; - esac -done < "$manifest" +source_list="$build_dir/wasm_core_sources.txt" +LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-manifest-sources.sh "$manifest" core full > "$source_list" +mapfile -t linuxcnc_sources < "$source_list" +for source in "${linuxcnc_sources[@]}"; do + sources+=("$source") +done sources+=(core/wasm_shims/rs274ngc_pre_probe_main.cc) diff --git a/test-linuxcnc-wasm-rs274ngc-pre-object.sh b/test-linuxcnc-wasm-rs274ngc-pre-object.sh index fae2531..0f38382 100755 --- a/test-linuxcnc-wasm-rs274ngc-pre-object.sh +++ b/test-linuxcnc-wasm-rs274ngc-pre-object.sh @@ -11,11 +11,8 @@ 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 +rs274ngc_pre_source=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-files.sh src/emc/rs274ngc/rs274ngc_pre.cc) common_flags=( -std=c++17 @@ -30,7 +27,7 @@ common_flags=( ) "$cxx" "${common_flags[@]}" \ - -c "$linuxcnc_root/src/emc/rs274ngc/rs274ngc_pre.cc" \ + -c "$rs274ngc_pre_source" \ -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 index 994e350..029057b 100755 --- a/test-linuxcnc-wasm-source-objects.sh +++ b/test-linuxcnc-wasm-source-objects.sh @@ -7,17 +7,6 @@ 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 @@ -30,6 +19,10 @@ if [[ ! -d "$linuxcnc_root" ]]; then fi trap 'rm -rf "$build_dir"' EXIT +shim_list="$build_dir/wasm_safe_shims.txt" +./list-linuxcnc-wasm-safe-shims.sh > "$shim_list" +mapfile -t shim_sources < "$shim_list" + common_flags=( -std=c++20 -DULAPI @@ -45,24 +38,9 @@ common_flags=( -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" +source_list="$build_dir/wasm_core_sources.txt" +LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-manifest-sources.sh "$manifest" core full > "$source_list" +mapfile -t sources < "$source_list" shim_index=0 for source in "${shim_sources[@]}"; do @@ -74,7 +52,7 @@ 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" + "$cxx" "${common_flags[@]}" -c "$source" -o "$obj" source_index=$((source_index + 1)) done diff --git a/test-linuxcnc-wasm-source-syntax.sh b/test-linuxcnc-wasm-source-syntax.sh index 11f9f23..f701574 100755 --- a/test-linuxcnc-wasm-source-syntax.sh +++ b/test-linuxcnc-wasm-source-syntax.sh @@ -33,27 +33,13 @@ common_flags=( -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" +source_list=$(mktemp "${TMPDIR:-/tmp}/cnc_sim_linuxcnc_wasm_source_syntax_sources.XXXXXX.txt") +trap 'rm -f "$source_list"' EXIT +LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-wasm-manifest-sources.sh "$manifest" core full > "$source_list" +mapfile -t sources < "$source_list" for source in "${sources[@]}"; do - "$cxx" "${common_flags[@]}" "$linuxcnc_root/$source" + "$cxx" "${common_flags[@]}" "$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 index 4e9e27c..4ae81f2 100755 --- a/test-linuxcnc-wasm-tooldata-common-link.sh +++ b/test-linuxcnc-wasm-tooldata-common-link.sh @@ -11,12 +11,12 @@ 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 +tooldata_mmap_backend_shim=$(./list-linuxcnc-wasm-safe-shims.sh tooldata_mmap_backend.cc) +tooldata_runtime_stubs_shim=$(./list-linuxcnc-wasm-safe-shims.sh tooldata_runtime_stubs.cc) +tooldata_common_source=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-files.sh src/emc/tooldata/tooldata_common.cc) + common_flags=( -std=c++17 -DULAPI @@ -30,9 +30,9 @@ common_flags=( ) "$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 \ + "$tooldata_common_source" \ + "$tooldata_mmap_backend_shim" \ + "$tooldata_runtime_stubs_shim" \ core/wasm_shims/tooldata/tooldata_common_probe_main.cc \ -o "$build_dir/tooldata_common_probe" diff --git a/test-linuxcnc-wasm-tooldata-link.sh b/test-linuxcnc-wasm-tooldata-link.sh index 144c389..7f7af57 100755 --- a/test-linuxcnc-wasm-tooldata-link.sh +++ b/test-linuxcnc-wasm-tooldata-link.sh @@ -11,12 +11,12 @@ 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 +tooldata_mmap_backend_shim=$(./list-linuxcnc-wasm-safe-shims.sh tooldata_mmap_backend.cc) +tooldata_runtime_stubs_shim=$(./list-linuxcnc-wasm-safe-shims.sh tooldata_runtime_stubs.cc) +tooldata_common_source=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-files.sh src/emc/tooldata/tooldata_common.cc) + common_flags=( -std=c++17 -DULAPI @@ -30,9 +30,9 @@ common_flags=( ) "$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 \ + "$tooldata_common_source" \ + "$tooldata_mmap_backend_shim" \ + "$tooldata_runtime_stubs_shim" \ core/wasm_shims/tooldata/tooldata_probe_main.cc \ -o "$build_dir/tooldata_probe" diff --git a/test-linuxcnc-wasm-tooldata-mmap-symbols.sh b/test-linuxcnc-wasm-tooldata-mmap-symbols.sh index 1e12f3c..fdf9269 100755 --- a/test-linuxcnc-wasm-tooldata-mmap-symbols.sh +++ b/test-linuxcnc-wasm-tooldata-mmap-symbols.sh @@ -13,6 +13,9 @@ if [[ ! -d "$linuxcnc_root" ]]; then fi trap 'rm -rf "$build_dir"' EXIT +tooldata_mmap_backend_shim=$(./list-linuxcnc-wasm-safe-shims.sh tooldata_mmap_backend.cc) +tooldata_mmap_source=$(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-files.sh src/emc/tooldata/tooldata_mmap.cc) + common_flags=( -std=c++17 -DULAPI @@ -26,42 +29,36 @@ common_flags=( ) "$cxx" "${common_flags[@]}" \ - -c core/wasm_shims/tooldata/tooldata_mmap_backend.cc \ + -c "$tooldata_mmap_backend_shim" \ -o "$build_dir/tooldata_mmap_backend.o" +"$cxx" "${common_flags[@]}" \ + -c "$tooldata_mmap_source" \ + -o "$build_dir/linuxcnc_tooldata_mmap.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 -) +nm --defined-only "$build_dir/linuxcnc_tooldata_mmap.o" \ + | awk '$2 ~ /^[A-Z]$/ {print $3}' \ + | LC_ALL=C sort -u \ + > "$build_dir/linuxcnc.exports" -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 +if ! comm -23 "$build_dir/linuxcnc.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 + echo "missing wasm tooldata mmap backend exports from LinuxCNC tooldata_mmap.cc:" >&2 cat "$build_dir/missing.exports" >&2 exit 1 fi -comm -13 "$build_dir/expected.exports" "$build_dir/backend.exports" > "$build_dir/unexpected.exports" +comm -13 "$build_dir/linuxcnc.exports" "$build_dir/backend.exports" > "$build_dir/unexpected.exports" if [[ -s "$build_dir/unexpected.exports" ]]; then - echo "unexpected wasm tooldata mmap backend exports:" >&2 + echo "unexpected wasm tooldata mmap backend exports beyond LinuxCNC tooldata_mmap.cc:" >&2 cat "$build_dir/unexpected.exports" >&2 exit 1 fi diff --git a/test-linuxcnc-wasm-tooldata-runtime-symbols.sh b/test-linuxcnc-wasm-tooldata-runtime-symbols.sh index e85a76e..7f37d72 100755 --- a/test-linuxcnc-wasm-tooldata-runtime-symbols.sh +++ b/test-linuxcnc-wasm-tooldata-runtime-symbols.sh @@ -13,6 +13,14 @@ if [[ ! -d "$linuxcnc_root" ]]; then fi trap 'rm -rf "$build_dir"' EXIT +tooldata_mmap_backend_shim=$(./list-linuxcnc-wasm-safe-shims.sh tooldata_mmap_backend.cc) +tooldata_runtime_stubs_shim=$(./list-linuxcnc-wasm-safe-shims.sh tooldata_runtime_stubs.cc) +readarray -t linuxcnc_sources < <(LINUXCNC_ROOT="$linuxcnc_root" ./list-linuxcnc-source-files.sh \ + src/emc/tooldata/tooldata_db.cc \ + src/emc/tooldata/tooldata_nml.cc) +tooldata_db_source=${linuxcnc_sources[0]} +tooldata_nml_source=${linuxcnc_sources[1]} + common_flags=( -std=c++17 -DULAPI @@ -26,33 +34,49 @@ common_flags=( ) "$cxx" "${common_flags[@]}" \ - -c core/wasm_shims/tooldata/tooldata_runtime_stubs.cc \ + -c "$tooldata_runtime_stubs_shim" \ -o "$build_dir/tooldata_runtime_stubs.o" +"$cxx" "${common_flags[@]}" \ + -c "$tooldata_mmap_backend_shim" \ + -o "$build_dir/tooldata_mmap_backend.o" + +"$cxx" "${common_flags[@]}" \ + -c "$tooldata_db_source" \ + -o "$build_dir/linuxcnc_tooldata_db.o" + +"$cxx" "${common_flags[@]}" \ + -c "$tooldata_nml_source" \ + -o "$build_dir/linuxcnc_tooldata_nml.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 -) +nm --defined-only "$build_dir/tooldata_mmap_backend.o" \ + | awk '$2 ~ /^[A-Z]$/ {print $3}' \ + | LC_ALL=C sort -u \ + > "$build_dir/backend.exports" -printf '%s\n' "${expected_exports[@]}" | LC_ALL=C sort -u > "$build_dir/expected.exports" +nm --defined-only "$build_dir"/linuxcnc_tooldata_*.o \ + | awk '$2 ~ /^[A-Z]$/ {print $3}' \ + | LC_ALL=C sort -u \ + > "$build_dir/linuxcnc-runtime.exports" + +comm -23 "$build_dir/linuxcnc-runtime.exports" "$build_dir/backend.exports" \ + > "$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 + echo "missing wasm tooldata runtime stub exports from LinuxCNC runtime sources:" >&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 + echo "unexpected wasm tooldata runtime stub exports beyond LinuxCNC runtime sources:" >&2 cat "$build_dir/unexpected.exports" >&2 exit 1 fi diff --git a/test-native.sh b/test-native.sh index a5588d1..6f83de7 100755 --- a/test-native.sh +++ b/test-native.sh @@ -6,47 +6,42 @@ cd "$(dirname "$0")" cxx=${CXX:-g++} build_dir=$(mktemp -d "${TMPDIR:-/tmp}/cnc_sim_native.XXXXXX") trap 'rm -rf "$build_dir"' EXIT +core_source_list="$build_dir/cnc_sim_core_sources.txt" +./list-cnc-sim-core-sources.sh > "$core_source_list" +mapfile -t core_sources < "$core_source_list" "$cxx" -std=c++17 -I core/include -I core/src \ - core/src/canon_event_sink.cpp \ - core/src/rtcp_kinematics.cpp \ + "${core_sources[@]}" \ core/tests/canon_event_sink_smoke.cpp \ -o "$build_dir/canon_event_sink_smoke" "$cxx" -std=c++17 -I core/include -I core/src \ - core/src/rtcp_kinematics.cpp \ + "${core_sources[@]}" \ core/tests/rtcp_kinematics_smoke.cpp \ -o "$build_dir/rtcp_kinematics_smoke" "$cxx" -std=c++17 -I core/include -I core/src \ - core/src/canon_event_sink.cpp \ - core/src/rtcp_kinematics.cpp \ - core/src/simulator_gcode_controls.cpp \ + core/tests/linuxcnc_gees_table_smoke.cpp \ + -o "$build_dir/linuxcnc_gees_table_smoke" + +"$cxx" -std=c++17 -I core/include -I core/src \ + "${core_sources[@]}" \ core/tests/simulator_gcode_controls_smoke.cpp \ -o "$build_dir/simulator_gcode_controls_smoke" "$cxx" -std=c++17 -I core/include -I core/src \ - core/src/canon_event_sink.cpp \ - core/src/cnc_sim_api.cpp \ - core/src/gcode_backend.cpp \ - core/src/rtcp_kinematics.cpp \ - core/src/simulator_gcode_controls.cpp \ - core/src/smoke_gcode_parser.cpp \ + "${core_sources[@]}" \ core/tests/cnc_sim_api_smoke.cpp \ -o "$build_dir/cnc_sim_api_smoke" "$cxx" -std=c++17 -I core/include -I core/src \ - core/src/canon_event_sink.cpp \ - core/src/cnc_sim_api.cpp \ - core/src/gcode_backend.cpp \ - core/src/rtcp_kinematics.cpp \ - core/src/simulator_gcode_controls.cpp \ - core/src/smoke_gcode_parser.cpp \ + "${core_sources[@]}" \ core/tools/cnc_sim_dump.cpp \ -o "$build_dir/cnc_sim_dump" "$build_dir/canon_event_sink_smoke" "$build_dir/rtcp_kinematics_smoke" +"$build_dir/linuxcnc_gees_table_smoke" "$build_dir/simulator_gcode_controls_smoke" "$build_dir/cnc_sim_api_smoke" basic_mill_json="$build_dir/cnc_sim_basic_mill.json" diff --git a/tests/gcode/linuxcnc_canned_cycle_g98_retract_error.ngc b/tests/gcode/linuxcnc_canned_cycle_g98_retract_error.ngc new file mode 100644 index 0000000..9ce6ce3 --- /dev/null +++ b/tests/gcode/linuxcnc_canned_cycle_g98_retract_error.ngc @@ -0,0 +1,3 @@ +G21 G90 G17 +G41 D1 +G98 diff --git a/tests/gcode/linuxcnc_cutter_comp_g71_error.ngc b/tests/gcode/linuxcnc_cutter_comp_g71_error.ngc new file mode 100644 index 0000000..7f83aeb --- /dev/null +++ b/tests/gcode/linuxcnc_cutter_comp_g71_error.ngc @@ -0,0 +1,3 @@ +G21 G90 G18 +G41.1 D1 +G71 Q1 diff --git a/tests/gcode/linuxcnc_cutter_comp_tool_change_error.ngc b/tests/gcode/linuxcnc_cutter_comp_tool_change_error.ngc new file mode 100644 index 0000000..60169d3 --- /dev/null +++ b/tests/gcode/linuxcnc_cutter_comp_tool_change_error.ngc @@ -0,0 +1,4 @@ +G21 G90 G17 +T1 +G41 D1 +M6 diff --git a/tests/gcode/linuxcnc_cutter_comp_wait_input_error.ngc b/tests/gcode/linuxcnc_cutter_comp_wait_input_error.ngc new file mode 100644 index 0000000..9d3aab4 --- /dev/null +++ b/tests/gcode/linuxcnc_cutter_comp_wait_input_error.ngc @@ -0,0 +1,3 @@ +G21 G90 G17 +G41 D1 +M66 P0 L0