236 lines
6.6 KiB
Bash
Executable File
236 lines
6.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
#
|
||
# StarForth — Steady-State Virtual Machine Runtime
|
||
#
|
||
# Copyright (c) 2023–2025 Robert A. James
|
||
# All rights reserved.
|
||
#
|
||
# This file is part of the StarForth project.
|
||
#
|
||
# Licensed under the StarForth License, Version 1.0 (the "License");
|
||
# you may not use this file except in compliance with the License.
|
||
#
|
||
# You may obtain a copy of the License at:
|
||
# https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
|
||
#
|
||
# This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||
# express or implied, including but not limited to the warranties of
|
||
# merchantability, fitness for a particular purpose, and noninfringement.
|
||
#
|
||
# See the License for the specific language governing permissions and
|
||
# limitations under the License.
|
||
#
|
||
# StarForth — Steady-State Virtual Machine Runtime
|
||
# Copyright (c) 2023–2025 Robert A. James
|
||
# All rights reserved.
|
||
#
|
||
# This file is part of the StarForth project.
|
||
#
|
||
# Licensed under the StarForth License, Version 1.0 (the "License");
|
||
# you may not use this file except in compliance with the License.
|
||
#
|
||
# You may obtain a copy of the License at:
|
||
# https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
|
||
#
|
||
# This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||
# express or implied, including but not limited to the warranties of
|
||
# merchantability, fitness for a particular purpose, and noninfringement.
|
||
#
|
||
# See the License for the specific language governing permissions and
|
||
# limitations under the License.
|
||
#
|
||
#
|
||
|
||
# scripts/update-init.sh — interactive wrapper for tools/extract-init
|
||
# Scans a raw image in ./disks and writes a canonical ./conf/init-4.4th
|
||
|
||
set -euo pipefail
|
||
|
||
# --- Layout ---
|
||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||
TOOLS_DIR="${REPO_ROOT}/tools"
|
||
DISKS_DIR="${REPO_ROOT}/disks"
|
||
CONF_DIR="${REPO_ROOT}/conf"
|
||
|
||
SRC_C_PRIMARY="${TOOLS_DIR}/extract_init.c"
|
||
BIN_PRIMARY="${TOOLS_DIR}/extract-init"
|
||
|
||
OUT_DEFAULT="${CONF_DIR}/init.4th"
|
||
FBS_DEFAULT="1024"
|
||
START_DEFAULT="0"
|
||
END_DEFAULT="-1"
|
||
|
||
BOLD="$(printf '\033[1m')"
|
||
DIM="$(printf '\033[2m')"
|
||
RESET="$(printf '\033[0m')"
|
||
|
||
die(){ echo "ERROR: $*" >&2; exit 1; }
|
||
|
||
have_cmd(){ command -v "$1" >/dev/null 2>&1; }
|
||
|
||
pick_cc(){
|
||
if have_cmd gcc; then echo gcc
|
||
elif have_cmd cc; then echo cc
|
||
else die "No C compiler found (need gcc or cc)."
|
||
fi
|
||
}
|
||
|
||
need_rebuild(){
|
||
# return 0 if rebuild needed
|
||
local src="$1" bin="$2"
|
||
[[ ! -f "$bin" ]] && return 0
|
||
[[ ! -f "$src" ]] && return 1
|
||
local src_mtime bin_mtime
|
||
src_mtime=$(stat -c '%Y' "$src" 2>/dev/null || stat -f '%m' "$src")
|
||
bin_mtime=$(stat -c '%Y' "$bin" 2>/dev/null || stat -f '%m' "$bin")
|
||
[[ "$src_mtime" -gt "$bin_mtime" ]] && return 0 || return 1
|
||
}
|
||
|
||
build_extractor(){
|
||
[[ -f "$SRC_C_PRIMARY" ]] || die "Missing source: $SRC_C_PRIMARY"
|
||
mkdir -p "$TOOLS_DIR"
|
||
local CC FLAGS OUT
|
||
CC=$(pick_cc)
|
||
FLAGS="-std=c99 -O2 -Wall -Wextra -Werror"
|
||
OUT="$BIN_PRIMARY"
|
||
echo "${DIM}Building extractor: $CC $FLAGS -o $OUT $SRC_C_PRIMARY${RESET}"
|
||
"$CC" $FLAGS -o "$OUT" "$SRC_C_PRIMARY"
|
||
echo "${DIM}Built $OUT${RESET}"
|
||
}
|
||
|
||
ensure_layout(){
|
||
[[ -d "$DISKS_DIR" ]] || die "Missing disks directory: $DISKS_DIR"
|
||
[[ -d "$CONF_DIR" ]] || mkdir -p "$CONF_DIR"
|
||
if need_rebuild "$SRC_C_PRIMARY" "$BIN_PRIMARY"; then
|
||
build_extractor
|
||
fi
|
||
[[ -x "$BIN_PRIMARY" ]] || die "Extractor not executable: $BIN_PRIMARY"
|
||
}
|
||
|
||
ask_yes_no(){
|
||
local prompt="$1" default="${2:-y}" ans def_disp="Y/n"
|
||
[[ "$default" == "n" ]] && def_disp="y/N"
|
||
read -r -p "$prompt [$def_disp] " ans || true
|
||
ans="${ans:-$default}"
|
||
case "$ans" in
|
||
y|Y|yes|YES) return 0 ;;
|
||
n|N|no|NO) return 1 ;;
|
||
*) echo "Please answer y or n." >&2; ask_yes_no "$prompt" "$default" ;;
|
||
esac
|
||
}
|
||
|
||
list_disks(){
|
||
# List regular files; prefer common extensions but include all
|
||
find "$DISKS_DIR" -maxdepth 1 -type f -printf "%f\n" | sort
|
||
}
|
||
|
||
pick_disk(){
|
||
echo "${BOLD}Select a disk image from ${DISKS_DIR}:${RESET}"
|
||
mapfile -t DISKS < <(list_disks)
|
||
[[ "${#DISKS[@]}" -gt 0 ]] || die "No disk images found in $DISKS_DIR"
|
||
local i=1
|
||
for d in "${DISKS[@]}"; do printf " %2d) %s\n" "$i" "$d"; i=$((i+1)); done
|
||
local choice
|
||
while :; do
|
||
read -r -p "Enter choice [1-${#DISKS[@]}]: " choice || true
|
||
[[ "$choice" =~ ^[0-9]+$ ]] || { echo "Not a number."; continue; }
|
||
(( choice>=1 && choice<=${#DISKS[@]} )) || { echo "Out of range."; continue; }
|
||
DISK_CHOSEN="${DISKS_DIR}/${DISKS[$((choice-1))]}"
|
||
break
|
||
done
|
||
echo "Selected: ${BOLD}${DISK_CHOSEN}${RESET}"
|
||
}
|
||
|
||
pick_mode(){
|
||
echo
|
||
echo "${BOLD}Header mode:${RESET}"
|
||
echo " 1) strict — require \"(-\" at byte 0"
|
||
echo " 2) loose — allow BOM/whitespace before \"(-\""
|
||
local choice
|
||
while :; do
|
||
read -r -p "Choose mode [1-2, default 1]: " choice || true
|
||
choice="${choice:-1}"
|
||
case "$choice" in
|
||
1) MODE_FLAG=""; MODE="strict"; break ;;
|
||
2) MODE_FLAG="--loose"; MODE="loose"; break ;;
|
||
*) echo "Pick 1 or 2."; continue ;;
|
||
esac
|
||
done
|
||
echo "Mode: ${BOLD}${MODE}${RESET}"
|
||
}
|
||
|
||
ask_int(){
|
||
local prompt="$1" default="$2" __outvar="$3" val
|
||
read -r -p "$prompt (default ${default}): " val || true
|
||
val="${val:-$default}"
|
||
[[ "$val" =~ ^-?[0-9]+$ ]] || { echo "Not a valid integer: $val"; ask_int "$prompt" "$default" "$__outvar"; return; }
|
||
printf -v "$__outvar" "%s" "$val"
|
||
}
|
||
|
||
collect_params(){
|
||
echo
|
||
ask_int "Start block" "$START_DEFAULT" START_BLOCK
|
||
ask_int "End block (-1 for EOF)" "$END_DEFAULT" END_BLOCK
|
||
|
||
echo
|
||
if ask_yes_no "Require closing ')' for the opening '(-' in the same block?" "y"; then
|
||
REQUIRE_CLOSE="--require-close"
|
||
else
|
||
REQUIRE_CLOSE=""
|
||
fi
|
||
|
||
echo
|
||
if ask_yes_no "Set a max number of picked blocks?" "n"; then
|
||
ask_int "Max picks" "1" MAX_PICKS_VAL
|
||
MAX_PICKS="--max=${MAX_PICKS_VAL}"
|
||
else
|
||
MAX_PICKS=""
|
||
fi
|
||
|
||
echo
|
||
ask_int "Forth block size (FBS)" "$FBS_DEFAULT" FBS
|
||
FBS_FLAG="--fbs=${FBS}"
|
||
|
||
echo
|
||
read -r -p "Output path for init.4th [default ${OUT_DEFAULT}]: " OUT_PATH || true
|
||
OUT_PATH="${OUT_PATH:-$OUT_DEFAULT}"
|
||
mkdir -p "$(dirname "$OUT_PATH")"
|
||
}
|
||
|
||
build_command(){
|
||
CMD=( "$BIN_PRIMARY"
|
||
"--img=${DISK_CHOSEN}"
|
||
"--out=${OUT_PATH}"
|
||
"$FBS_FLAG"
|
||
"--start=${START_BLOCK}"
|
||
"--end=${END_BLOCK}"
|
||
)
|
||
[[ -n "$MODE_FLAG" ]] && CMD+=( "$MODE_FLAG" )
|
||
[[ -n "$REQUIRE_CLOSE" ]] && CMD+=( "$REQUIRE_CLOSE" )
|
||
[[ -n "${MAX_PICKS:-}" ]] && CMD+=( "$MAX_PICKS" )
|
||
}
|
||
|
||
main(){
|
||
ensure_layout
|
||
pick_disk
|
||
pick_mode
|
||
collect_params
|
||
build_command
|
||
|
||
echo
|
||
echo "${BOLD}About to run:${RESET}"
|
||
printf " %q " "${CMD[@]}"; echo
|
||
echo
|
||
if ask_yes_no "Proceed?" "y"; then
|
||
"${CMD[@]}"
|
||
echo
|
||
echo "${BOLD}Done.${RESET} Wrote: ${OUT_PATH}"
|
||
else
|
||
echo "Aborted."
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
main "$@"
|