fixes for while :
ober
85c092277b19c5212f9dbeb21657a51217ec9f28
--- a/.build.yml +++ b/.build.yml @@ -14,6 +14,7 @@ artifacts: - jerboa-shell/jerboa-shell-0.2.0.jpkg tasks: - build-jerboa-tools: | + umask 022 cd jerboa sudo fallocate -l 4G /swapfile && sudo chmod 600 /swapfile && sudo mkswap /swapfile && sudo swapon /swapfile export CARGO_BUILD_JOBS=1 @@ -26,6 +27,7 @@ tasks: cd jerboa-shell JERBUILD=$HOME/jerboa/dist/jerbuild make test - package: | + umask 022 cd jerboa-shell J="$HOME/jerboa/dist/jerboa" mkdir -p "$HOME/.cache" && chmod -R go-w "$HOME/.cache" --- a/main.ss +++ b/main.ss @@ -612,6 +612,10 @@ (parse-complete-command input (env-shopt? env "extglob") alias-fn))))]) (cond ((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) ((not cmd) 0) (else (execute-command cmd env)))))))) --- a/parser.ss +++ b/parser.ss @@ -23,20 +23,28 @@ ;; Parse a complete command from input string ;; Returns an AST node (command-list, and-or-list, etc.) or #f for empty input +;; Returns 'need-more if the command is incomplete (e.g., "while :" without "do") (def (parse-complete-command input (extglob? #f) (alias-fn #f)) (let* ((lex (if (string? input) (make-shell-lexer input extglob?) input)) (ps (make-parser-state lex #f #f [] alias-fn))) - (let ((result (parse-list ps))) - (when (lexer-needs-more? lex) - (set! (parser-state-needs-more? ps) #t)) - ;; Check for unconsumed tokens — syntax error if non-NEWLINE/EOF remain - (let ((tok (parser-peek ps))) - (when (and (token? tok) (not (eq? (token-type tok) 'NEWLINE))) - (error #f (string-append "parse error near `" - (if (token-value tok) (token-value tok) - (symbol->string (token-type tok))) - "'")))) - result))) + (with-catch + (lambda (e) + ;; Check if this is an incomplete command that needs more input + (if (or (lexer-want-more? lex) (parser-state-needs-more? ps)) + 'need-more + (raise e))) + (lambda () + (let ((result (parse-list ps))) + (when (lexer-needs-more? lex) + (set! (parser-state-needs-more? ps) #t)) + ;; Check for unconsumed tokens — syntax error if non-NEWLINE/EOF remain + (let ((tok (parser-peek ps))) + (when (and (token? tok) (not (eq? (token-type tok) 'NEWLINE))) + (error #f (string-append "parse error near `" + (if (token-value tok) (token-value tok) + (symbol->string (token-type tok))) + "'")))) + result))))) ;; Parse one "line" — semicolon-separated commands, stopping at newline. ;; This allows execution between lines so shopt changes take effect. @@ -142,6 +150,10 @@ (unless (and (token? tok) (eq? (token-type tok) 'WORD) (string=? (token-value tok) word)) + ;; If we got eof, mark that we need more input (for multi-line compound commands) + (when (eq? tok 'eof) + (set! (parser-state-needs-more? ps) #t) + (set! (lexer-want-more? (parser-state-lexer ps)) #t)) (error #f (format "parse error: expected '~a', got ~a" word (if (token? tok) (token-value tok) tok)))))) --- a/script.ss +++ b/script.ss @@ -88,27 +88,35 @@ (let ((lines (string-split input #\newline))) (let line-loop ((remaining-lines lines) (status 0) (shell-buffer '())) (cond - ;; No more lines - execute any pending shell commands - ((null? remaining-lines) - (if (null? shell-buffer) - status - (let ((shell-input (string-join (reverse shell-buffer) "\n"))) - (execute-shell-lines shell-input env interactive? status)))) - ;; Comma meta-command line - execute pending shell input first, then - ;; dispatch through the shared meta-command registry. - ((and (> (string-length (car remaining-lines)) 0) - (char=? (string-ref (car remaining-lines) 0) #\,)) - (let* ((pending-status - (if (null? shell-buffer) - status - (let ((shell-input (string-join (reverse shell-buffer) "\n"))) - (execute-shell-lines shell-input env interactive? status)))) - (line (car remaining-lines)) - (meta-status - (execute-meta-command - (substring line 1 (string-length line))))) - (env-set-last-status! env meta-status) - (line-loop (cdr remaining-lines) meta-status '()))) + ;; No more lines - execute any pending shell commands + ((null? remaining-lines) + (if (null? shell-buffer) + status + (let ((shell-input (string-join (reverse shell-buffer) "\n"))) + (let ((result (execute-shell-lines shell-input env interactive? status))) + ;; Check for incomplete command at end of script + (if (eq? result 'need-more) + (begin + (fprintf (current-error-port) + "jsh: syntax error: unexpected end of file~n") + 2) + result))))) + ;; Comma meta-command line - execute pending shell input first, then + ;; dispatch through the shared meta-command registry. + ((and (> (string-length (car remaining-lines)) 0) + (char=? (string-ref (car remaining-lines) 0) #\,)) + (let* ((pending-result + (if (null? shell-buffer) + status + (let ((shell-input (string-join (reverse shell-buffer) "\n"))) + (execute-shell-lines shell-input env interactive? status)))) + (pending-status (if (eq? pending-result 'need-more) 2 pending-result)) + (line (car remaining-lines)) + (meta-status + (execute-meta-command + (substring line 1 (string-length line))))) + (env-set-last-status! env meta-status) + (line-loop (cdr remaining-lines) meta-status '()))) ;; Regular shell line - accumulate (else (line-loop (cdr remaining-lines) status (cons (car remaining-lines) shell-buffer))))))) @@ -117,20 +125,27 @@ (def (execute-shell-lines input env interactive? initial-status) (let ((lexer (make-shell-lexer input (env-shopt? env "extglob")))) (let loop ((status initial-status)) - (let ((cmd (with-catch - (lambda (e) - (fprintf (current-error-port) "jsh: syntax error: ~a~n" - (exception-message e)) - 'error) - (lambda () - ;; Update lexer extglob flag in case shopt changed it - (set! (lexer-extglob? lexer) (env-shopt? env "extglob")) - ;; Build alias lookup: checks expand_aliases shopt, returns value or #f - (let ((alias-fn (and (env-shopt? env "expand_aliases") - (lambda (word) (alias-get env word))))) - (parse-one-line lexer (env-shopt? env "extglob") alias-fn)))))) + (let ((cmd (with-catch + (lambda (e) + ;; Check if lexer needs more input (e.g., incomplete compound command) + (if (lexer-want-more? lexer) + 'need-more + (begin + (fprintf (current-error-port) "jsh: syntax error: ~a~n" + (exception-message e)) + 'error))) + (lambda () + ;; Update lexer extglob flag in case shopt changed it + (set! (lexer-extglob? lexer) (env-shopt? env "extglob")) + ;; Build alias lookup: checks expand_aliases shopt, returns value or #f + (let ((alias-fn (and (env-shopt? env "expand_aliases") + (lambda (word) (alias-get env word))))) + (parse-one-line lexer (env-shopt? env "extglob") alias-fn)))))) (cond ((eq? cmd 'error) 2) ;; syntax error + ((eq? cmd 'need-more) + ;; Incomplete command (e.g., "while :" without "do") — signal need for more input + 'need-more) ((not cmd) status) ;; end of input ;; Unterminated quote/construct after parsing — syntax error ((lexer-want-more? lexer) --- a/support/adversarial-corpus-evidence.ss +++ b/support/adversarial-corpus-evidence.ss @@ -38,7 +38,11 @@ (define (try-parse source extglob?) (guard (exn (else (list 'error exn))) - (list 'ok (parse-complete-command source extglob? #f)))) + (let ((result (parse-complete-command source extglob? #f))) + ;; Treat incomplete commands (need-more) as parse errors + (if (eq? result 'need-more) + (list 'error 'incomplete-command) + (list 'ok result))))) (define (sum-list xs f) (let loop ((xs xs) (n 0))