Files
wasm-simulator/generate-linuxcnc-switchkins-remap-table.sh

1253 lines
43 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"
# LinuxCNC source basis: generated switchkins/remap tables are extracted from
# LinuxCNC configs/sim INI/HAL files and remap_subs/{428,429,430}remap.ngc
# sources listed in linuxcnc-kinematics-source-files.txt.
linuxcnc_root=${LINUXCNC_ROOT:-../linuxcnc}
mode=table
all_output_dir=
usage() {
echo "usage: $0 [--config-cases|--json-cases|--all-output-dir DIR] [manifest]" >&2
}
case "${1:-}" in
--config-cases)
mode=config-cases
shift
;;
--json-cases)
mode=json-cases
shift
;;
--all-output-dir)
mode=all
if [[ -z "${2:-}" || "${2:-}" == --* ]]; then
usage
echo "missing output directory for --all-output-dir" >&2
exit 1
fi
all_output_dir=$2
shift 2
;;
--help|-h)
usage
exit 0
;;
--*)
usage
echo "unknown switchkins remap table generator option: $1" >&2
exit 1
;;
esac
manifest=${1:-linuxcnc-kinematics-source-files.txt}
if [[ -n "${2:-}" ]]; then
usage
echo "too many switchkins remap table generator arguments" >&2
exit 1
fi
if [[ ! -d "$linuxcnc_root" ]]; then
echo "missing LinuxCNC root: $linuxcnc_root" >&2
exit 1
fi
if [[ ! -f "$manifest" ]]; then
echo "missing kinematics manifest: $manifest" >&2
exit 1
fi
if [[ "$mode" == all && -z "$all_output_dir" ]]; then
echo "missing output directory for --all-output-dir" >&2
exit 1
fi
linuxcnc_root=$(cd "$linuxcnc_root" && pwd)
read_ini_switchkins_config() {
local file=$1
awk '
function trim(value) {
sub(/^[[:space:]]*/, "", value)
sub(/[[:space:]]*$/, "", value)
return value
}
{
line = $0
sub(/[[:space:]]*[#;].*$/, "", line)
line = trim(line)
if (line ~ /^\[[^][]+\]$/) {
section = tolower(substr(line, 2, length(line) - 2))
next
}
if (line !~ /=/) {
next
}
key = line
sub(/[[:space:]]*=.*/, "", key)
key = tolower(trim(key))
value = line
sub(/^[^=]*=[[:space:]]*/, "", value)
value = trim(value)
if (section == "emc" && key == "machine") {
print "machine\t" value
} else if (section == "kins" && key == "kinematics") {
print "kinematics\t" value
} else if (section == "hal" && key == "halfile") {
print "halfile\t" value
} else if (section == "hal" && key == "postgui_halfile") {
print "postgui_halfile\t" value
} else if (section == "hal" && key == "halcmd") {
lower = tolower(value)
if (lower ~ /(^|[[:space:]])loadusr([[:space:]]|$)/) {
command = value
sub(/^.*(^|[[:space:]])loadusr[[:space:]]+/, "", command)
while (command ~ /^-[^[:space:]]+[[:space:]]+/) {
sub(/^-[^[:space:]]+[[:space:]]+/, "", command)
}
sub(/[[:space:]].*$/, "", command)
if (command != "") {
print "halcmd_loadusr\t" command
}
}
} else if (section == "emcio" && key == "tool_table") {
print "tool_table\t" value
} else if (section == "rs274ngc" && key == "subroutine_path") {
print "subroutine_path\t" value
} else if (section == "rs274ngc" && key == "on_abort_command") {
lower = tolower(value)
if (match(lower, /o[[:space:]]*<[^>]+>[[:space:]]*call/)) {
ngc = substr(value, RSTART, RLENGTH)
sub(/^[Oo][[:space:]]*</, "", ngc)
sub(/>[[:space:]]*[Cc][Aa][Ll][Ll].*$/, "", ngc)
print "on_abort_ngc\t" ngc
}
} else if (section == "rs274ngc" && key == "remap") {
lower = tolower(value)
for (mcode = 428; mcode <= 430; mcode++) {
if (lower ~ "m" mcode "([^0-9]|$)" &&
match(lower, /ngc[[:space:]]*=[[:space:]]*[^[:space:]]+/)) {
ngc = substr(value, RSTART, RLENGTH)
sub(/^[Nn][Gg][Cc][[:space:]]*=[[:space:]]*/, "", ngc)
print "remap_" mcode "\t" ngc
}
}
if (match(lower, /ngc[[:space:]]*=[[:space:]]*[^[:space:]]+/)) {
ngc = substr(value, RSTART, RLENGTH)
sub(/^[Nn][Gg][Cc][[:space:]]*=[[:space:]]*/, "", ngc)
print "remap_ngc\t" ngc
}
if (match(lower, /python[[:space:]]*=[[:space:]]*[^[:space:]]+/)) {
python = substr(value, RSTART, RLENGTH)
sub(/^[Pp][Yy][Tt][Hh][Oo][Nn][[:space:]]*=[[:space:]]*/, "", python)
print "python_remap\t" python
}
} else if (section == "python" && key == "path_append") {
print "python_path_append\t" value
} else if (section == "python" && key == "toplevel") {
print "python_toplevel\t" value
}
}
' "$file"
}
read_loadusr_commands() {
local file=$1
awk '
function trim(value) {
sub(/^[[:space:]]*/, "", value)
sub(/[[:space:]]*$/, "", value)
return value
}
{
line = $0
sub(/[[:space:]]*#.*/, "", line)
line = trim(line)
lower = tolower(line)
if (lower !~ /(^|[[:space:]])loadusr([[:space:]]|$)/) {
next
}
sub(/^.*(^|[[:space:]])loadusr[[:space:]]+/, "", line)
while (line ~ /^-[^[:space:]]+[[:space:]]+/) {
sub(/^-[^[:space:]]+[[:space:]]+/, "", line)
}
sub(/[[:space:]].*$/, "", line)
if (line != "") {
print line
}
}
' "$file"
}
resolved_config_path=
resolve_config_path() {
local config_dir=$1
local path=$2
if [[ "$path" = /* ]]; then
resolved_config_path=$path
else
resolved_config_path=$config_dir/$path
fi
}
resolved_linuxcnc_path=
declare -A relative_linuxcnc_path_cache=()
relative_linuxcnc_path() {
local path=$1
if [[ -n "${relative_linuxcnc_path_cache[$path]+x}" ]]; then
resolved_linuxcnc_path=${relative_linuxcnc_path_cache[$path]}
return 0
fi
local absolute_path
absolute_path=$(cd "${path%/*}" && pwd)/${path##*/}
if [[ "$absolute_path" != "$linuxcnc_root/"* ]]; then
echo "resolved LinuxCNC source path escapes LinuxCNC root: $absolute_path" >&2
exit 1
fi
resolved_linuxcnc_path=${absolute_path#"$linuxcnc_root/"}
relative_linuxcnc_path_cache[$path]=$resolved_linuxcnc_path
}
declare -A config_dir_cache=()
declare -A manifest_config_sources=()
declare -A used_config_sources=()
declare -A manifest_remap_sources=()
declare -A used_remap_sources=()
declare -A manifest_tooldata_sources=()
declare -A used_tooldata_sources=()
declare -A manifest_asset_sources=()
declare -A used_asset_sources=()
manifest_config_count=0
manifest_remap_count=0
manifest_tooldata_count=0
manifest_asset_count=0
validate_manifest_source_path() {
local group=$1
local path=$2
if [[ -z "$path" ]]; then
echo "empty LinuxCNC $group source path in $manifest" >&2
exit 1
fi
if [[ "$path" = /* || "$path" == *..* ]]; then
echo "LinuxCNC $group source path must stay under LinuxCNC root: $path" >&2
exit 1
fi
if [[ ! -f "$linuxcnc_root/$path" ]]; then
echo "missing LinuxCNC $group source listed in $manifest: $path" >&2
exit 1
fi
}
while IFS=: read -r manifest_group manifest_path _manifest_note; do
case "$manifest_group" in
config)
validate_manifest_source_path "$manifest_group" "$manifest_path"
if [[ "$manifest_path" != *.ini && "$manifest_path" != *.hal ]]; then
echo "LinuxCNC config source must be an INI or HAL file in $manifest: $manifest_path" >&2
exit 1
fi
if [[ -n "${manifest_config_sources[$manifest_path]:-}" ]]; then
echo "duplicate LinuxCNC config source in $manifest: $manifest_path" >&2
exit 1
fi
manifest_config_sources[$manifest_path]=1
manifest_config_count=$((manifest_config_count + 1))
;;
remap)
validate_manifest_source_path "$manifest_group" "$manifest_path"
if [[ "$manifest_path" != *.ngc ]]; then
echo "LinuxCNC remap source must be an ngc file in $manifest: $manifest_path" >&2
exit 1
fi
case "${manifest_path##*/}" in
428remap.ngc|429remap.ngc|430remap.ngc)
;;
*)
echo "LinuxCNC switchkins remap source must be 428/429/430remap.ngc in $manifest: $manifest_path" >&2
exit 1
;;
esac
if [[ -n "${manifest_remap_sources[$manifest_path]:-}" ]]; then
echo "duplicate LinuxCNC remap source in $manifest: $manifest_path" >&2
exit 1
fi
manifest_remap_sources[$manifest_path]=1
manifest_remap_count=$((manifest_remap_count + 1))
;;
tooldata)
validate_manifest_source_path "$manifest_group" "$manifest_path"
if [[ "$manifest_path" != *.tbl ]]; then
echo "LinuxCNC switchkins tooldata source must be a tbl file in $manifest: $manifest_path" >&2
exit 1
fi
if [[ -n "${manifest_tooldata_sources[$manifest_path]:-}" ]]; then
echo "duplicate LinuxCNC switchkins tooldata source in $manifest: $manifest_path" >&2
exit 1
fi
manifest_tooldata_sources[$manifest_path]=1
manifest_tooldata_count=$((manifest_tooldata_count + 1))
;;
asset)
validate_manifest_source_path "$manifest_group" "$manifest_path"
if [[ -n "${manifest_asset_sources[$manifest_path]:-}" ]]; then
echo "duplicate LinuxCNC asset source in $manifest: $manifest_path" >&2
exit 1
fi
manifest_asset_sources[$manifest_path]=1
manifest_asset_count=$((manifest_asset_count + 1))
;;
esac
done <"$manifest"
resolved_config_dir=
config_dir_for() {
local config=$1
if [[ -z "${config_dir_cache[$config]:-}" ]]; then
config_dir_cache[$config]=$(cd "${config%/*}" && pwd)
fi
resolved_config_dir=${config_dir_cache[$config]}
}
resolved_remap_source=
remap_source_for_ngc() {
local config=$1
local ngc_name=$2
local subroutine_paths=${3:-}
resolved_remap_source=
if [[ "$ngc_name" != *.ngc ]]; then
ngc_name="$ngc_name.ngc"
fi
local config_dir
config_dir_for "$config"
config_dir=$resolved_config_dir
if [[ -z "$subroutine_paths" ]]; then
subroutine_paths="."
fi
local subroutine_path
while IFS= read -r subroutine_path; do
[[ -n "$subroutine_path" ]] || continue
local path_part
IFS=: read -ra path_parts <<<"$subroutine_path"
for path_part in "${path_parts[@]}"; do
[[ -n "$path_part" ]] || continue
local candidate_dir
resolve_config_path "$config_dir" "$path_part"
candidate_dir=$resolved_config_path
if [[ -f "$candidate_dir/$ngc_name" ]]; then
relative_linuxcnc_path "$candidate_dir/$ngc_name"
resolved_remap_source=$resolved_linuxcnc_path
return 0
fi
done
done <<<"$subroutine_paths"
return 1
}
resolved_config_source=
config_source_for_halfile() {
local config=$1
local halfile=$2
resolved_config_source=
[[ -n "$halfile" ]] || return 1
[[ "$halfile" == LIB:* ]] && return 1
local hal_path=${halfile%%[[:space:]]*}
[[ -n "$hal_path" ]] || return 1
local config_dir
config_dir_for "$config"
config_dir=$resolved_config_dir
local resolved
resolve_config_path "$config_dir" "$hal_path"
resolved=$resolved_config_path
if [[ ! -f "$resolved" ]]; then
echo "missing LinuxCNC HAL source referenced by ${config#"$linuxcnc_root/"}: $halfile" >&2
exit 1
fi
relative_linuxcnc_path "$resolved"
resolved_config_source=$resolved_linuxcnc_path
}
resolved_tooldata_source=
tooldata_source_for_tool_table() {
local config=$1
local tool_table=$2
resolved_tooldata_source=
[[ -n "$tool_table" ]] || return 1
local tool_path=${tool_table%%[[:space:]]*}
[[ -n "$tool_path" ]] || return 1
local config_dir
config_dir_for "$config"
config_dir=$resolved_config_dir
local resolved
resolve_config_path "$config_dir" "$tool_path"
resolved=$resolved_config_path
[[ -f "$resolved" ]] || return 1
relative_linuxcnc_path "$resolved"
resolved_tooldata_source=$resolved_linuxcnc_path
}
resolved_asset_source=
asset_source_for_path() {
local config=$1
local path=$2
resolved_asset_source=
[[ -n "$path" ]] || return 1
local config_dir
config_dir_for "$config"
config_dir=$resolved_config_dir
local resolved
resolve_config_path "$config_dir" "$path"
resolved=$resolved_config_path
[[ -f "$resolved" ]] || return 1
relative_linuxcnc_path "$resolved"
resolved_asset_source=$resolved_linuxcnc_path
}
resolved_loadusr_asset_source=
loadusr_asset_source_for_command() {
local config=$1
local command=$2
resolved_loadusr_asset_source=
[[ -n "$command" ]] || return 1
if [[ "$command" == */* || "$command" == *.py ]]; then
if asset_source_for_path "$config" "$command"; then
resolved_loadusr_asset_source=$resolved_asset_source
return 0
fi
if [[ "$command" != *.py ]] && asset_source_for_path "$config" "$command.py"; then
resolved_loadusr_asset_source=$resolved_asset_source
return 0
fi
return 1
fi
local source="src/hal/user_comps/vismach/$command.py"
if [[ -f "$linuxcnc_root/$source" ]]; then
resolved_loadusr_asset_source=$source
return 0
fi
if asset_source_for_path "$config" "$command.py"; then
resolved_loadusr_asset_source=$resolved_asset_source
return 0
fi
return 1
}
record_loadusr_asset() {
local config_path=$1
local config=$2
local command=$3
local label=$4
if loadusr_asset_source_for_command "$config" "$command"; then
loadusr_asset_source=$resolved_loadusr_asset_source
record_asset_source "$config_path" "$loadusr_asset_source" "$label"
record_python_import_assets "$config_path" "$loadusr_asset_source"
fi
}
resolved_python_module_source=
python_module_source_for_name() {
local config=$1
local module=$2
local python_paths=${3:-}
resolved_python_module_source=
[[ -n "$module" ]] || return 1
[[ "$module" == *.* ]] && return 1
local module_path=${module//.//}
if [[ -z "$python_paths" ]]; then
python_paths="."
fi
local config_dir
config_dir_for "$config"
config_dir=$resolved_config_dir
local python_path
while IFS= read -r python_path; do
[[ -n "$python_path" ]] || continue
local path_part
IFS=: read -ra path_parts <<<"$python_path"
for path_part in "${path_parts[@]}"; do
[[ -n "$path_part" ]] || continue
local candidate_dir
resolve_config_path "$config_dir" "$path_part"
candidate_dir=$resolved_config_path
if [[ -f "$candidate_dir/$module_path.py" ]]; then
relative_linuxcnc_path "$candidate_dir/$module_path.py"
resolved_python_module_source=$resolved_linuxcnc_path
return 0
fi
done
done <<<"$python_paths"
return 1
}
record_asset_source() {
local config_path=$1
local source=$2
local label=$3
if [[ -z "${manifest_asset_sources[$source]:-}" ]]; then
echo "LinuxCNC $label asset source is not listed as asset in $manifest: $source ($config_path)" >&2
exit 1
fi
used_asset_sources[$source]=1
}
declare -A walked_python_import_assets=()
record_python_import_assets() {
local config_path=$1
local source=$2
local walk_key="$config_path|$source"
if [[ -n "${walked_python_import_assets[$walk_key]:-}" ]]; then
return 0
fi
walked_python_import_assets[$walk_key]=1
local source_dir=${source%/*}
local imported
while IFS= read -r imported; do
[[ -n "$imported" ]] || continue
local import_source="$source_dir/$imported.py"
if [[ -f "$linuxcnc_root/$import_source" ]]; then
record_asset_source "$config_path" "$import_source" "Python import"
record_python_import_assets "$config_path" "$import_source"
fi
done < <(awk '
/^[[:space:]]*import[[:space:]]+[A-Za-z_][A-Za-z0-9_]*([[:space:]]|$)/ {
value = $0
sub(/^[[:space:]]*import[[:space:]]+/, "", value)
sub(/[[:space:],].*$/, "", value)
print value
}
/^[[:space:]]*from[[:space:]]+[A-Za-z_][A-Za-z0-9_]*[[:space:]]+import[[:space:]]+/ {
value = $0
sub(/^[[:space:]]*from[[:space:]]+/, "", value)
sub(/[[:space:]].*$/, "", value)
print value
}
' "$linuxcnc_root/$source")
}
resolved_kinstype=
declare -A kinstype_cache=()
kinstype_for_remap_source() {
local source
source=$1
resolved_kinstype=
if [[ -z "$source" ]]; then
resolved_kinstype=-1
return 0
fi
if [[ -n "${kinstype_cache[$source]+x}" ]]; then
resolved_kinstype=${kinstype_cache[$source]}
return 0
fi
resolved_kinstype=$(awk '
/^[[:space:]]*#<kinstype>[[:space:]]*=/ {
value = $0
sub(/^[[:space:]]*#<kinstype>[[:space:]]*=[[:space:]]*/, "", value)
sub(/[^0-9].*$/, "", value)
print value
exit
}
' "$linuxcnc_root/$source")
kinstype_cache[$source]=$resolved_kinstype
}
require_remap_source() {
local config_path=$1
local mcode=$2
local ngc_name=$3
local source=$4
if [[ -z "$source" ]]; then
echo "missing LinuxCNC M$mcode remap source for $config_path: ngc=$ngc_name" >&2
exit 1
fi
}
require_kinstype() {
local config_path=$1
local mcode=$2
local source=$3
local kinstype=$4
if [[ -z "$kinstype" || "$kinstype" == -1 ]]; then
echo "missing #<kinstype> assignment for LinuxCNC M$mcode remap source: $source ($config_path)" >&2
exit 1
fi
}
record_remap_source() {
local config_path=$1
local mcode=$2
local source=$3
if [[ -z "${manifest_remap_sources[$source]:-}" ]]; then
echo "LinuxCNC M$mcode remap source is not listed in $manifest: $source ($config_path)" >&2
exit 1
fi
used_remap_sources[$source]=1
}
record_config_source() {
local config_path=$1
local source=$2
local label=$3
if [[ -z "${manifest_config_sources[$source]:-}" ]]; then
echo "LinuxCNC $label source is not listed in $manifest: $source ($config_path)" >&2
exit 1
fi
used_config_sources[$source]=1
}
record_tooldata_source() {
local config_path=$1
local source=$2
if [[ -z "${manifest_tooldata_sources[$source]:-}" ]]; then
echo "LinuxCNC TOOL_TABLE source is not listed as tooldata in $manifest: $source ($config_path)" >&2
exit 1
fi
used_tooldata_sources[$source]=1
}
declare -A seen_aliases=()
declare -A seen_cases=()
declare -A seen_case_values=()
entries=()
case_entries=()
json_case_entries=()
processed_config_count=0
resolved_remap_count=0
unique_config_source_count=0
unique_resolved_remap_count=0
unique_tooldata_source_count=0
add_alias() {
local alias=$1
local m428=$2
local m429=$3
local m430=$4
alias=${alias,,}
alias=${alias//[[:space:]]/}
alias=${alias//\\/\\\\}
alias=${alias//\"/\\\"}
[[ -n "$alias" ]] || return 0
local types="$m428,$m429,$m430"
if [[ -n "${seen_aliases[$alias]:-}" ]]; then
if [[ "${seen_aliases[$alias]}" != "$types" ]]; then
echo "ambiguous switchkins alias '$alias': ${seen_aliases[$alias]} and $types" >&2
exit 1
fi
return 0
fi
seen_aliases[$alias]=$types
entries+=(" {\"$alias\", $m428, $m429, $m430},")
}
add_case() {
local field=$1
local value=$2
local m428=$3
local m429=$4
local m430=$5
[[ -n "$value" ]] || return 0
local types="$m428,$m429,$m430"
local value_key="$field|$value"
if [[ -n "${seen_case_values[$value_key]:-}" && "${seen_case_values[$value_key]}" != "$types" ]]; then
echo "ambiguous switchkins config case '$field=$value': ${seen_case_values[$value_key]} and $types" >&2
exit 1
fi
seen_case_values[$value_key]=$types
local key="$field|$value|$types"
[[ -z "${seen_cases[$key]:-}" ]] || return 0
seen_cases[$key]=1
local cpp_field=$field
local cpp_value=$value
cpp_field=${cpp_field//\\/\\\\}
cpp_field=${cpp_field//\"/\\\"}
cpp_value=${cpp_value//\\/\\\\}
cpp_value=${cpp_value//\"/\\\"}
case_entries+=(" {\"$cpp_field\", \"$cpp_value\", $m428, $m429, $m430},")
json_case_entries+=("{\"field\":\"$cpp_field\",\"value\":\"$cpp_value\",\"m428\":$m428,\"m429\":$m429,\"m430\":$m430}")
}
trimmed_machine_label=
trim_machine_label() {
local machine=$1
machine=${machine%%(*}
machine="${machine#"${machine%%[![:space:]]*}"}"
machine="${machine%"${machine##*[![:space:]]}"}"
trimmed_machine_label=$machine
}
add_kinematics_variants() {
local callback=$1
local field=$2
local kinematics=$3
local m428=$4
local m429=$5
local m430=$6
[[ -n "$kinematics" ]] || return 0
"$callback" "$field" "$kinematics" "$m428" "$m429" "$m430"
local first_word=${kinematics%%[[:space:]]*}
"$callback" "$field" "$first_word" "$m428" "$m429" "$m430"
"$callback" "$field" "${first_word%_kins}" "$m428" "$m429" "$m430"
"$callback" "$field" "${first_word%-kins}" "$m428" "$m429" "$m430"
local first_word_hyphen=${first_word//_/-}
local first_word_without_underscore_kins=${first_word%_kins}
local first_word_without_dash_kins=${first_word%-kins}
"$callback" "$field" "$first_word_hyphen" "$m428" "$m429" "$m430"
"$callback" "$field" "${first_word_without_underscore_kins//_/-}" "$m428" "$m429" "$m430"
"$callback" "$field" "${first_word_without_dash_kins//_/-}" "$m428" "$m429" "$m430"
}
add_alias_variant() {
local _field=$1
shift
add_alias "$@"
}
add_case_variant() {
add_case "$@"
}
add_halfile_case_fields() {
local field=$1
local halfile=$2
local m428=$3
local m429=$4
local m430=$5
local hal_base=${halfile##*/}
local hal_stem=${hal_base%.hal}
add_case "$field" "$halfile" "$m428" "$m429" "$m430"
add_case "$field" "$hal_base" "$m428" "$m429" "$m430"
add_case "$field" "$hal_stem" "$m428" "$m429" "$m430"
}
add_machine_aliases() {
local machine=$1
local m428=$2
local m429=$3
local m430=$4
[[ -n "$machine" ]] || return 0
add_alias "$machine" "$m428" "$m429" "$m430"
trim_machine_label "$machine"
add_alias "$trimmed_machine_label" "$m428" "$m429" "$m430"
}
add_kinematics_aliases() {
local kinematics=$1
local m428=$2
local m429=$3
local m430=$4
[[ -n "$kinematics" ]] || return 0
add_kinematics_variants add_alias_variant "" "$kinematics" "$m428" "$m429" "$m430"
}
add_switchkins_cases() {
local config_dir=$1
local config_stem=$2
local machine=$3
local kinematics=$4
local m428=$5
local m429=$6
local m430=$7
add_case "switchkins" "${config_dir##*/}" "$m428" "$m429" "$m430"
add_case "switchkins" "$config_stem" "$m428" "$m429" "$m430"
if [[ -n "$machine" ]]; then
add_case "switchkins" "$machine" "$m428" "$m429" "$m430"
trim_machine_label "$machine"
add_case "switchkins" "$trimmed_machine_label" "$m428" "$m429" "$m430"
fi
if [[ -n "$kinematics" ]]; then
add_kinematics_variants add_case_variant "switchkins" "$kinematics" "$m428" "$m429" "$m430"
fi
}
add_halfile_aliases() {
local halfile=$1
local m428=$2
local m429=$3
local m430=$4
[[ -n "$halfile" ]] || return 0
[[ "$halfile" == LIB:* ]] && return 0
add_alias "$halfile" "$m428" "$m429" "$m430"
local hal_base=${halfile##*/}
add_alias "$hal_base" "$m428" "$m429" "$m430"
add_alias "${hal_base%.hal}" "$m428" "$m429" "$m430"
}
add_halfile_cases() {
local halfile=$1
local m428=$2
local m429=$3
local m430=$4
[[ -n "$halfile" ]] || return 0
[[ "$halfile" == LIB:* ]] && return 0
add_halfile_case_fields "halFile" "$halfile" "$m428" "$m429" "$m430"
add_halfile_case_fields "halfile" "$halfile" "$m428" "$m429" "$m430"
add_halfile_case_fields "hal_file" "$halfile" "$m428" "$m429" "$m430"
add_halfile_case_fields "HALFILE" "$halfile" "$m428" "$m429" "$m430"
}
add_halfile_source_cases() {
local source=$1
local m428=$2
local m429=$3
local m430=$4
[[ -n "$source" ]] || return 0
add_case "halFile" "$source" "$m428" "$m429" "$m430"
add_case "halfile" "$source" "$m428" "$m429" "$m430"
add_case "hal_file" "$source" "$m428" "$m429" "$m430"
add_case "HALFILE" "$source" "$m428" "$m429" "$m430"
}
add_postgui_halfile_cases() {
local halfile=$1
local m428=$2
local m429=$3
local m430=$4
[[ -n "$halfile" ]] || return 0
[[ "$halfile" == LIB:* ]] && return 0
add_halfile_case_fields "postguiHalFile" "$halfile" "$m428" "$m429" "$m430"
add_halfile_case_fields "postguihalfile" "$halfile" "$m428" "$m429" "$m430"
add_halfile_case_fields "postgui_halfile" "$halfile" "$m428" "$m429" "$m430"
add_halfile_case_fields "postgui_hal_file" "$halfile" "$m428" "$m429" "$m430"
add_halfile_case_fields "POSTGUI_HALFILE" "$halfile" "$m428" "$m429" "$m430"
}
add_postgui_halfile_source_cases() {
local source=$1
local m428=$2
local m429=$3
local m430=$4
[[ -n "$source" ]] || return 0
add_case "postguiHalFile" "$source" "$m428" "$m429" "$m430"
add_case "postguihalfile" "$source" "$m428" "$m429" "$m430"
add_case "postgui_halfile" "$source" "$m428" "$m429" "$m430"
add_case "postgui_hal_file" "$source" "$m428" "$m429" "$m430"
add_case "POSTGUI_HALFILE" "$source" "$m428" "$m429" "$m430"
}
add_subroutine_path_cases() {
local subroutine_path=$1
local m428=$2
local m429=$3
local m430=$4
[[ -n "$subroutine_path" ]] || return 0
add_case "subroutinePath" "$subroutine_path" "$m428" "$m429" "$m430"
add_case "subroutinepath" "$subroutine_path" "$m428" "$m429" "$m430"
add_case "subroutine_path" "$subroutine_path" "$m428" "$m429" "$m430"
add_case "SUBROUTINE_PATH" "$subroutine_path" "$m428" "$m429" "$m430"
}
while IFS=: read -r group path _note; do
[[ "$group" == "config" && "$path" == *.ini ]] || continue
config="$linuxcnc_root/$path"
[[ -f "$config" ]] || {
echo "missing LinuxCNC config source: $path" >&2
exit 1
}
machine=
kinematics=
halfiles=
postgui_halfiles=
tool_tables=
subroutine_paths=
remap_ngcs=
on_abort_ngcs=
python_paths=
python_toplevels=
python_remaps=
halcmd_loadusrs=
remap_ngc_428=
remap_ngc_429=
remap_ngc_430=
while IFS=$'\t' read -r ini_key ini_value; do
case "$ini_key" in
machine)
[[ -n "$machine" ]] || machine=$ini_value
;;
kinematics)
[[ -n "$kinematics" ]] || kinematics=$ini_value
;;
halfile)
halfiles+="${halfiles:+$'\n'}$ini_value"
;;
postgui_halfile)
postgui_halfiles+="${postgui_halfiles:+$'\n'}$ini_value"
;;
tool_table)
tool_tables+="${tool_tables:+$'\n'}$ini_value"
;;
subroutine_path)
subroutine_paths+="${subroutine_paths:+$'\n'}$ini_value"
;;
remap_ngc)
remap_ngcs+="${remap_ngcs:+$'\n'}$ini_value"
;;
on_abort_ngc)
on_abort_ngcs+="${on_abort_ngcs:+$'\n'}$ini_value"
;;
python_path_append)
python_paths+="${python_paths:+$'\n'}$ini_value"
;;
python_toplevel)
python_toplevels+="${python_toplevels:+$'\n'}$ini_value"
;;
python_remap)
python_remaps+="${python_remaps:+$'\n'}$ini_value"
;;
halcmd_loadusr)
halcmd_loadusrs+="${halcmd_loadusrs:+$'\n'}$ini_value"
;;
remap_428)
[[ -n "$remap_ngc_428" ]] || remap_ngc_428=$ini_value
;;
remap_429)
[[ -n "$remap_ngc_429" ]] || remap_ngc_429=$ini_value
;;
remap_430)
[[ -n "$remap_ngc_430" ]] || remap_ngc_430=$ini_value
;;
esac
done < <(read_ini_switchkins_config "$config")
if [[ -z "$remap_ngc_428" && -z "$remap_ngc_429" && -z "$remap_ngc_430" ]]; then
continue
fi
if [[ -z "$subroutine_paths" ]]; then
subroutine_paths="."
fi
remap_source_428=
remap_source_429=
remap_source_430=
if [[ -n "$remap_ngc_428" ]]; then
if remap_source_for_ngc "$config" "$remap_ngc_428" "$subroutine_paths"; then
remap_source_428=$resolved_remap_source
fi
require_remap_source "$path" 428 "$remap_ngc_428" "$remap_source_428"
record_remap_source "$path" 428 "$remap_source_428"
resolved_remap_count=$((resolved_remap_count + 1))
fi
if [[ -n "$remap_ngc_429" ]]; then
if remap_source_for_ngc "$config" "$remap_ngc_429" "$subroutine_paths"; then
remap_source_429=$resolved_remap_source
fi
require_remap_source "$path" 429 "$remap_ngc_429" "$remap_source_429"
record_remap_source "$path" 429 "$remap_source_429"
resolved_remap_count=$((resolved_remap_count + 1))
fi
if [[ -n "$remap_ngc_430" ]]; then
if remap_source_for_ngc "$config" "$remap_ngc_430" "$subroutine_paths"; then
remap_source_430=$resolved_remap_source
fi
require_remap_source "$path" 430 "$remap_ngc_430" "$remap_source_430"
record_remap_source "$path" 430 "$remap_source_430"
resolved_remap_count=$((resolved_remap_count + 1))
fi
kinstype_for_remap_source "$remap_source_428"
m428=$resolved_kinstype
kinstype_for_remap_source "$remap_source_429"
m429=$resolved_kinstype
kinstype_for_remap_source "$remap_source_430"
m430=$resolved_kinstype
[[ -z "$remap_ngc_428" ]] || require_kinstype "$path" 428 "$remap_source_428" "$m428"
[[ -z "$remap_ngc_429" ]] || require_kinstype "$path" 429 "$remap_source_429" "$m429"
[[ -z "$remap_ngc_430" ]] || require_kinstype "$path" 430 "$remap_source_430" "$m430"
[[ -n "$m428" ]] || m428=-1
[[ -n "$m429" ]] || m429=-1
[[ -n "$m430" ]] || m430=-1
processed_config_count=$((processed_config_count + 1))
record_config_source "$path" "$path" "INI config"
config_dir=${path%/*}
config_base=${path##*/}
config_stem=${config_base%.ini}
add_case "config" "$path" "$m428" "$m429" "$m430"
add_case "configPath" "$path" "$m428" "$m429" "$m430"
add_case "configpath" "$path" "$m428" "$m429" "$m430"
add_case "config_path" "$path" "$m428" "$m429" "$m430"
add_case "ini" "$path" "$m428" "$m429" "$m430"
add_case "iniFile" "$path" "$m428" "$m429" "$m430"
add_case "inifile" "$path" "$m428" "$m429" "$m430"
add_case "iniFileName" "$path" "$m428" "$m429" "$m430"
add_case "inifilename" "$path" "$m428" "$m429" "$m430"
add_case "ini_file" "$path" "$m428" "$m429" "$m430"
add_case "ini_file_name" "$path" "$m428" "$m429" "$m430"
add_case "INI_FILE_NAME" "$path" "$m428" "$m429" "$m430"
add_case "machine" "$machine" "$m428" "$m429" "$m430"
add_case "kinematics" "$kinematics" "$m428" "$m429" "$m430"
add_switchkins_cases "$config_dir" "$config_stem" "$machine" "$kinematics" "$m428" "$m429" "$m430"
add_alias "$path" "$m428" "$m429" "$m430"
add_alias "$config_dir" "$m428" "$m429" "$m430"
add_alias "${config_dir##*/}" "$m428" "$m429" "$m430"
add_alias "$config_stem" "$m428" "$m429" "$m430"
add_machine_aliases "$machine" "$m428" "$m429" "$m430"
add_kinematics_aliases "$kinematics" "$m428" "$m429" "$m430"
while IFS= read -r halfile; do
if config_source_for_halfile "$config" "$halfile"; then
hal_source=$resolved_config_source
record_config_source "$path" "$hal_source" "HALFILE"
add_alias "$hal_source" "$m428" "$m429" "$m430"
add_halfile_source_cases "$hal_source" "$m428" "$m429" "$m430"
while IFS= read -r loadusr_command; do
[[ -n "$loadusr_command" ]] || continue
record_loadusr_asset "$path" "$linuxcnc_root/$hal_source" "$loadusr_command" "HALFILE loadusr"
done < <(read_loadusr_commands "$linuxcnc_root/$hal_source")
fi
add_halfile_cases "$halfile" "$m428" "$m429" "$m430"
add_halfile_aliases "$halfile" "$m428" "$m429" "$m430"
done <<<"$halfiles"
while IFS= read -r halfile; do
if config_source_for_halfile "$config" "$halfile"; then
hal_source=$resolved_config_source
record_config_source "$path" "$hal_source" "POSTGUI_HALFILE"
add_alias "$hal_source" "$m428" "$m429" "$m430"
add_postgui_halfile_source_cases "$hal_source" "$m428" "$m429" "$m430"
while IFS= read -r loadusr_command; do
[[ -n "$loadusr_command" ]] || continue
record_loadusr_asset "$path" "$linuxcnc_root/$hal_source" "$loadusr_command" "POSTGUI_HALFILE loadusr"
done < <(read_loadusr_commands "$linuxcnc_root/$hal_source")
fi
add_postgui_halfile_cases "$halfile" "$m428" "$m429" "$m430"
add_halfile_aliases "$halfile" "$m428" "$m429" "$m430"
done <<<"$postgui_halfiles"
while IFS= read -r tool_table; do
if tooldata_source_for_tool_table "$config" "$tool_table"; then
record_tooldata_source "$path" "$resolved_tooldata_source"
fi
done <<<"$tool_tables"
while IFS= read -r ngc_name; do
[[ -n "$ngc_name" ]] || continue
if remap_source_for_ngc "$config" "$ngc_name" "$subroutine_paths"; then
if [[ -z "${manifest_remap_sources[$resolved_remap_source]:-}" ]]; then
record_asset_source "$path" "$resolved_remap_source" "RS274NGC REMAP ngc"
fi
fi
done <<<"$remap_ngcs"
while IFS= read -r ngc_name; do
[[ -n "$ngc_name" ]] || continue
if remap_source_for_ngc "$config" "$ngc_name" "$subroutine_paths"; then
record_asset_source "$path" "$resolved_remap_source" "ON_ABORT_COMMAND"
fi
done <<<"$on_abort_ngcs"
while IFS= read -r toplevel; do
[[ -n "$toplevel" ]] || continue
if asset_source_for_path "$config" "$toplevel"; then
python_toplevel_source=$resolved_asset_source
record_asset_source "$path" "$python_toplevel_source" "PYTHON TOPLEVEL"
record_python_import_assets "$path" "$python_toplevel_source"
fi
done <<<"$python_toplevels"
while IFS= read -r python_remap; do
[[ -n "$python_remap" ]] || continue
if python_module_source_for_name "$config" "$python_remap" "$python_paths"; then
python_remap_source=$resolved_python_module_source
record_asset_source "$path" "$python_remap_source" "Python REMAP module"
record_python_import_assets "$path" "$python_remap_source"
fi
done <<<"$python_remaps"
while IFS= read -r loadusr_command; do
[[ -n "$loadusr_command" ]] || continue
record_loadusr_asset "$path" "$config" "$loadusr_command" "HALCMD loadusr"
done <<<"$halcmd_loadusrs"
for remap_source in "$remap_source_428" "$remap_source_429" "$remap_source_430"; do
if [[ -n "$remap_source" ]]; then
remap_dir=${remap_source%/*}
add_alias "$remap_source" "$m428" "$m429" "$m430"
add_alias "$remap_dir" "$m428" "$m429" "$m430"
add_alias "${remap_dir%/*}" "$m428" "$m429" "$m430"
add_alias "${remap_dir%/remap_subs}" "$m428" "$m429" "$m430"
remap_parent=${remap_dir%/remap_subs}
add_alias "${remap_parent##*/}" "$m428" "$m429" "$m430"
add_case "remap" "$remap_source" "$m428" "$m429" "$m430"
add_case "remap" "$remap_dir" "$m428" "$m429" "$m430"
add_case "remap" "$remap_parent" "$m428" "$m429" "$m430"
add_case "remap" "${remap_parent##*/}" "$m428" "$m429" "$m430"
add_subroutine_path_cases "$remap_dir" "$m428" "$m429" "$m430"
fi
done
done <"$manifest"
unique_config_source_count=${#used_config_sources[@]}
unique_resolved_remap_count=${#used_remap_sources[@]}
unique_tooldata_source_count=${#used_tooldata_sources[@]}
unique_asset_source_count=${#used_asset_sources[@]}
validate_generated_content() {
if ((manifest_config_count == 0)); then
echo "no LinuxCNC config sources listed in manifest: $manifest" >&2
exit 1
fi
if ((manifest_remap_count == 0)); then
echo "no LinuxCNC remap sources listed in manifest: $manifest" >&2
exit 1
fi
if ((processed_config_count == 0)); then
echo "no LinuxCNC switchkins remap configs found in manifest: $manifest" >&2
exit 1
fi
if ((resolved_remap_count == 0)); then
echo "no LinuxCNC M428/M429/M430 remap sources resolved from manifest: $manifest" >&2
exit 1
fi
if ((unique_resolved_remap_count == 0)); then
echo "no unique LinuxCNC M428/M429/M430 remap sources resolved from manifest: $manifest" >&2
exit 1
fi
if ((unique_config_source_count == 0)); then
echo "no unique LinuxCNC config sources used from manifest: $manifest" >&2
exit 1
fi
if ((manifest_tooldata_count > 0 && unique_tooldata_source_count == 0)); then
echo "no unique LinuxCNC TOOL_TABLE sources used from manifest: $manifest" >&2
exit 1
fi
if ((unique_config_source_count != manifest_config_count)); then
echo "LinuxCNC config manifest coverage mismatch: used $unique_config_source_count of $manifest_config_count config sources" >&2
local config_source
for config_source in "${!manifest_config_sources[@]}"; do
if [[ -z "${used_config_sources[$config_source]:-}" ]]; then
echo "unused LinuxCNC config manifest source: $config_source" >&2
fi
done
exit 1
fi
if ((unique_tooldata_source_count != manifest_tooldata_count)); then
echo "LinuxCNC tooldata manifest coverage mismatch: used $unique_tooldata_source_count of $manifest_tooldata_count tooldata sources" >&2
local tooldata_source
for tooldata_source in "${!manifest_tooldata_sources[@]}"; do
if [[ -z "${used_tooldata_sources[$tooldata_source]:-}" ]]; then
echo "unused LinuxCNC tooldata manifest source: $tooldata_source" >&2
fi
done
exit 1
fi
if ((unique_asset_source_count > manifest_asset_count)); then
echo "LinuxCNC asset manifest coverage mismatch: used $unique_asset_source_count of $manifest_asset_count asset sources" >&2
exit 1
fi
if ((unique_resolved_remap_count != manifest_remap_count)); then
echo "LinuxCNC remap manifest coverage mismatch: resolved $unique_resolved_remap_count of $manifest_remap_count remap sources" >&2
local remap_source
for remap_source in "${!manifest_remap_sources[@]}"; do
if [[ -z "${used_remap_sources[$remap_source]:-}" ]]; then
echo "unused LinuxCNC remap manifest source: $remap_source" >&2
fi
done
exit 1
fi
if ((${#entries[@]} == 0)); then
echo "empty generated switchkins remap alias table" >&2
exit 1
fi
if ((${#case_entries[@]} == 0)); then
echo "empty generated switchkins remap config cases" >&2
exit 1
fi
if ((${#case_entries[@]} != ${#json_case_entries[@]})); then
echo "generated C++/JSON switchkins config case count mismatch: ${#case_entries[@]} != ${#json_case_entries[@]}" >&2
exit 1
fi
}
validate_generated_content
write_table() {
cat <<'EOF'
// Generated by ./generate-linuxcnc-switchkins-remap-table.sh.
// Source: LinuxCNC INI MACHINE/KINEMATICS/HALFILE/POSTGUI_HALFILE/SUBROUTINE_PATH/REMAP entries and
// adjacent remap_subs/{428,429,430}remap.ngc #<kinstype> assignments.
EOF
printf '%s\n' "${entries[@]}"
}
write_config_cases() {
cat <<'EOF'
// Generated by ./generate-linuxcnc-switchkins-remap-table.sh --config-cases.
// Source: LinuxCNC INI config path, MACHINE, KINEMATICS, non-LIB HALFILE/POSTGUI_HALFILE,
// SUBROUTINE_PATH-resolved remap_subs entries, and adjacent M428/M429/M430 #<kinstype> assignments.
EOF
printf '%s\n' "${case_entries[@]}"
}
write_json_cases() {
printf '[\n'
for i in "${!json_case_entries[@]}"; do
suffix=,
if [[ "$i" == "$((${#json_case_entries[@]} - 1))" ]]; then
suffix=
fi
printf ' %s%s\n' "${json_case_entries[$i]}" "$suffix"
done
printf ']\n'
}
write_generated_file() {
local path=$1
local label=$2
local writer=$3
if [[ -e "$path" && ! -f "$path" ]]; then
echo "cannot write generated $label: $path is not a file" >&2
exit 1
fi
"$writer" >"$path"
}
if [[ "$mode" == table ]]; then
write_table
elif [[ "$mode" == config-cases ]]; then
write_config_cases
elif [[ "$mode" == json-cases ]]; then
write_json_cases
elif [[ "$mode" == all ]]; then
if [[ -e "$all_output_dir" && ! -d "$all_output_dir" ]]; then
echo "switchkins remap output path is not a directory: $all_output_dir" >&2
exit 1
fi
mkdir -p "$all_output_dir"
write_generated_file "$all_output_dir/linuxcnc_switchkins_remap_table.inc" "table" write_table
write_generated_file "$all_output_dir/linuxcnc_switchkins_remap_config_cases.inc" "config cases" write_config_cases
write_generated_file "$all_output_dir/linuxcnc_switchkins_remap_config_cases.json" "JSON config cases" write_json_cases
else
echo "unknown switchkins remap table generator mode: $mode" >&2
exit 1
fi