updates
ober
804643109749c9cbbadab9aac8f4456221ccf9b8
--- a/Makefile +++ b/Makefile @@ -539,22 +539,40 @@ yubikey-native-linux-arm64: rust-musl-target-arm64 --release --target aarch64-unknown-linux-musl --no-default-features @test -f $(XC_YUBIKEY_NATIVE_MUSL_ARM64_LIB) -linux-amd64: stage-jsqlite ffi-shim-symbols.list vendor-deps-linux jerboa gen-embed chez-cross-amd64 rust-coreutils-linux-amd64 jerboa-native-linux-amd64 vendor-staged-libs yubikey-native-linux-amd64 +linux-amd64: ensure-shell-extras stage-jsqlite ffi-shim-symbols.list vendor-deps-linux jerboa gen-embed chez-cross-amd64 rust-coreutils-linux-amd64 jerboa-native-linux-amd64 vendor-staged-libs yubikey-native-linux-amd64 @echo "=== Wiping stale per-arch compiled artifacts (.so/.wpo/boot) ===" @$(XC_WIPE) @features="$(JSH_CROSS_FEATURES)"; \ - echo "=== Cross-building jsh-linux-amd64 (x86_64 Linux static, JSH_FEATURES=$$features) ==="; \ - JERBOA_HOME=$(JERBOA_HOME) TARGET_ARCH=amd64 JSH_FEATURES="$$features" JERBOA_NATIVE_FEATURES="$(JERBOA_NATIVE_FEATURES)" JERBOA_NATIVE_A="$(XC_NATIVE_MUSL_AMD64_LIB)" YUBIKEY_NATIVE_A="$(XC_YUBIKEY_NATIVE_MUSL_AMD64_LIB)" $(SCHEME) -q --libdirs "$(XC_LIBDIRS)" --script build-jsh-cross.ss + if [ -f "$(JERBOA_SHELL_EXTRAS_DIR)/Makefile" ] && echo "$$features" | grep -qE '^(all|.*embed|.*pass|.*wireguard|.*harden|.*awk|.*sed)'; then \ + echo "=== Extras features detected — delegating to jerboa-shell-extras ==="; \ + $(MAKE) -C "$(JERBOA_SHELL_EXTRAS_DIR)" linux-amd64 \ + JERBOA_SHELL_DIR="$(CURDIR)" \ + JSH_CROSS_FEATURES="$(JSH_CROSS_FEATURES)" \ + JSH_FEATURES="$(JSH_FEATURES)"; \ + cp -f "$(JERBOA_SHELL_EXTRAS_DIR)/jsh-linux-amd64" "$(CURDIR)/jsh-linux-amd64"; \ + else \ + echo "=== Cross-building jsh-linux-amd64 (x86_64 Linux static, JSH_FEATURES=$$features) ==="; \ + JERBOA_HOME=$(JERBOA_HOME) TARGET_ARCH=amd64 JSH_FEATURES="$$features" JERBOA_NATIVE_FEATURES="$(JERBOA_NATIVE_FEATURES)" JERBOA_NATIVE_A="$(XC_NATIVE_MUSL_AMD64_LIB)" YUBIKEY_NATIVE_A="$(XC_YUBIKEY_NATIVE_MUSL_AMD64_LIB)" $(SCHEME) -q --libdirs "$(XC_LIBDIRS)" --script build-jsh-cross.ss; \ + fi @echo "=== jsh-linux-amd64 ready ===" @ls -lh jsh-linux-amd64 @file jsh-linux-amd64 -linux-arm64: stage-jsqlite ffi-shim-symbols.list vendor-deps-linux jerboa gen-embed chez-cross-arm64 rust-coreutils-linux-arm64 jerboa-native-linux-arm64 vendor-staged-libs yubikey-native-linux-arm64 +linux-arm64: ensure-shell-extras stage-jsqlite ffi-shim-symbols.list vendor-deps-linux jerboa gen-embed chez-cross-arm64 rust-coreutils-linux-arm64 jerboa-native-linux-arm64 vendor-staged-libs yubikey-native-linux-arm64 @echo "=== Wiping stale per-arch compiled artifacts (.so/.wpo/boot) ===" @$(XC_WIPE) @features="$(JSH_CROSS_FEATURES)"; \ - echo "=== Cross-building jsh-linux-arm64 (aarch64 Linux static, JSH_FEATURES=$$features) ==="; \ - JERBOA_HOME=$(JERBOA_HOME) TARGET_ARCH=arm64 JSH_FEATURES="$$features" JERBOA_NATIVE_FEATURES="$(JERBOA_NATIVE_FEATURES)" JERBOA_NATIVE_A="$(XC_NATIVE_MUSL_ARM64_LIB)" YUBIKEY_NATIVE_A="$(XC_YUBIKEY_NATIVE_MUSL_ARM64_LIB)" $(SCHEME) -q --libdirs "$(XC_LIBDIRS)" --script build-jsh-cross.ss + if [ -f "$(JERBOA_SHELL_EXTRAS_DIR)/Makefile" ] && echo "$$features" | grep -qE '^(all|.*embed|.*pass|.*wireguard|.*harden|.*awk|.*sed)'; then \ + echo "=== Extras features detected — delegating to jerboa-shell-extras ==="; \ + $(MAKE) -C "$(JERBOA_SHELL_EXTRAS_DIR)" linux-arm64 \ + JERBOA_SHELL_DIR="$(CURDIR)" \ + JSH_CROSS_FEATURES="$(JSH_CROSS_FEATURES)" \ + JSH_FEATURES="$(JSH_FEATURES)"; \ + cp -f "$(JERBOA_SHELL_EXTRAS_DIR)/jsh-linux-arm64" "$(CURDIR)/jsh-linux-arm64"; \ + else \ + echo "=== Cross-building jsh-linux-arm64 (aarch64 Linux static, JSH_FEATURES=$$features) ==="; \ + JERBOA_HOME=$(JERBOA_HOME) TARGET_ARCH=arm64 JSH_FEATURES="$$features" JERBOA_NATIVE_FEATURES="$(JERBOA_NATIVE_FEATURES)" JERBOA_NATIVE_A="$(XC_NATIVE_MUSL_ARM64_LIB)" YUBIKEY_NATIVE_A="$(XC_YUBIKEY_NATIVE_MUSL_ARM64_LIB)" $(SCHEME) -q --libdirs "$(XC_LIBDIRS)" --script build-jsh-cross.ss; \ + fi @echo "=== jsh-linux-arm64 ready ===" @ls -lh jsh-linux-arm64 @file jsh-linux-arm64 --- a/main.ss +++ b/main.ss @@ -310,6 +310,9 @@ (substring prompt-str nl (string-length prompt-str))) (string-append prompt-str sfx)))))] ;; Create completion function + [ps2-str (expand-prompt (or (env-get env "PS2") "> ") + (lambda (name) (env-get env name)) + 0 0 0 #f 0)] [complete-fn (lambda (line cursor) (complete-word line cursor env))] ;; Read input with line editor @@ -350,31 +353,44 @@ ;; Show expanded line if it changed (when (not (string=? expanded input)) (displayln expanded (current-error-port))) - ;; Add to history when enabled. - (when (*jsh-history-enabled*) - (history-add! expanded)) ;; Parse and execute (unless :p modifier was used) (when execute? - (let ([status (with-catch - (lambda (e) - (cond - ((break-exception? e) 0) - ((continue-exception? e) 0) - ((return-exception? e) (return-exception-status e)) - (else (raise e)))) - (lambda () (execute-input expanded env)))]) - (let ([hook (*command-trace-hook*)]) - (when hook - (with-catch (lambda (e) #!void) - (lambda () (hook expanded status env))))) - (env-set-last-status! env status) - ;; Process pending signals and execute trap commands - (process-traps! env) - ;; ERR trap: execute if last command failed - (when (and (not (= status 0)) (trap-get "ERR")) - (let ([action (trap-get "ERR")]) - (when (string? action) - (execute-input action env))))))))) + (let loop ([buffer expanded] [history-added? #f]) + (let ([status (with-catch + (lambda (e) + (cond + ((break-exception? e) 0) + ((continue-exception? e) 0) + ((return-exception? e) (return-exception-status e)) + (else (raise e)))) + (lambda () (execute-input buffer env #t)))]) + (cond + ((eq? status 'need-more) + (let ([more (line-edit ps2-str complete-fn edit-mode)]) + (if (eq? more 'eof) + (begin + (fprintf (current-error-port) "jsh: syntax error: unexpected end of file~n") + (unless history-added? + (when (*jsh-history-enabled*) + (history-add! buffer))) + (env-set-last-status! env 2)) + (loop (string-append buffer "\n" more) history-added?)))) + (else + (unless history-added? + (when (*jsh-history-enabled*) + (history-add! buffer))) + (let ([hook (*command-trace-hook*)]) + (when hook + (with-catch (lambda (e) #!void) + (lambda () (hook buffer status env))))) + (env-set-last-status! env status) + ;; Process pending signals and execute trap commands + (process-traps! env) + ;; ERR trap: execute if last command failed + (when (and (not (= status 0)) (trap-get "ERR")) + (let ([action (trap-get "ERR")]) + (when (string? action) + (execute-input action env)))))))))))) ;; Restore terminal to sane state after command ;; (programs like top/vim may leave echo off or raw mode on). ;; Must use Gambit's tty-mode-set! to keep port state in sync; @@ -578,7 +594,7 @@ (let ([expr (string-trim-whitespace (substring input 1 (string-length input)))]) (execute-meta-command expr))) -(def (execute-input input env) +(def (execute-input input env (allow-incomplete? #f)) ;; Parse and execute a line of input (with-catch (lambda (e) @@ -614,8 +630,11 @@ ((eq? cmd 'error) 2) ((eq? cmd 'need-more) ;; Incomplete command (e.g., "while :" without "do") - (fprintf (current-error-port) "jsh: syntax error: unexpected end of file~n") - 2) + (if allow-incomplete? + 'need-more + (begin + (fprintf (current-error-port) "jsh: syntax error: unexpected end of file~n") + 2))) ((not cmd) 0) (else (execute-command cmd env)))))))) --- a/test/test-binary.sh +++ b/test/test-binary.sh @@ -82,6 +82,68 @@ else fi rm -rf "$use_tmp" +# Interactive REPL continuation tests require a pseudo-tty; skip if python3 is unavailable. +if command -v python3 >/dev/null 2>&1; then + if python3 - "$BINARY" <<'PY' +import pty, os, sys, select, re + +binary = sys.argv[1] +master, slave = pty.openpty() +pid = os.fork() +if pid == 0: + os.close(master) + os.setsid() + os.dup2(slave, 0) + os.dup2(slave, 1) + os.dup2(slave, 2) + os.close(slave) + os.execv(binary, [binary]) + sys.exit(1) +os.close(slave) + +def read_all(timeout=1.0): + out = b'' + while True: + r, _, _ = select.select([master], [], [], timeout) + if not r: + break + try: + data = os.read(master, 4096) + if not data: + break + out += data + except OSError: + break + return out + +def send(cmd): + os.write(master, (cmd + '\n').encode()) + return read_all(0.5) + +read_all(1.0) +send('for x in a b; do') +send('echo "x=$x"') +out = send('done') +text = re.sub(r'\x1b\[[0-9;?]*[a-zA-Z]', '', out.decode('utf-8', errors='replace')) +os.write(master, b'exit\n') +read_all(0.5) +os.waitpid(pid, 0) +if 'x=a' in text and 'x=b' in text: + sys.exit(0) +else: + print(text, file=sys.stderr) + sys.exit(1) +PY + then + pass=$((pass + 1)) + else + fail=$((fail + 1)) + printf 'FAIL: interactive REPL multi-line continuation (for loop)\n' + fi +else + echo "SKIP: interactive REPL tests require python3" +fi + rm -f /tmp/jsh-test.out /tmp/jsh-test.err echo "Binary tests ($BINARY): $pass passed, $fail failed" [ "$fail" -eq 0 ]