;;; The Geesh Shell Interpreter ;;; Copyright 2018 Timothy Sample ;;; ;;; This file is part of Geesh. ;;; ;;; Geesh is free software: you can redistribute it and/or modify ;;; it under the terms of the GNU General Public License as published by ;;; the Free Software Foundation, either version 3 of the License, or ;;; (at your option) any later version. ;;; ;;; Geesh is distributed in the hope that it will be useful, ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;;; GNU General Public License for more details. ;;; ;;; You should have received a copy of the GNU General Public License ;;; along with Geesh. If not, see . (define-module (geesh eval) #:use-module (geesh environment) #:use-module (geesh shell) #:use-module (geesh word) #:use-module (ice-9 match) #:use-module (srfi srfi-1) #:use-module (srfi srfi-26) #:export (eval-sh)) ;;; Commentary: ;;; ;;; This module provides an interpreter for the Shell language. ;;; ;;; Code: (define* (eval-word env word #:key (output 'fields) (rhs-tildes? #f)) (parameterize ((eval-cmd-sub (lambda (exps) (sh:substitute-command env (lambda () (for-each (cut eval-sh env <>) exps)))))) (expand-word env word #:output output #:rhs-tildes? rhs-tildes?))) (define (eval-redir env redir) "Evaluate the redirect @var{redir} in environment @var{env}." (match-let* (((op fd word) redir) (field (eval-word env word #:output 'string))) (match op ((or '>& '<&) (let ((n (string->number field))) (cond ((and n (exact-integer? n)) `(,op ,fd ,n)) ((string=? field "-") `(,op ,fd -)) (else (throw 'bad-dup))))) (_ `(,op ,fd ,field))))) (define (exp->thunk env exp) (lambda () (eval-sh env exp))) (define (exps->thunk env exps) (lambda () (eval-sh env `( ,@exps)))) (define (eval-sh env exp) "Evaluate the Shell expression @var{exp} in the context of the Shell environment @var{env}." (match exp ((' exp1 exp2) (sh:and env (exp->thunk env exp1) (exp->thunk env exp2))) ((' . sub-exps) (for-each (cut eval-sh env <>) sub-exps)) ((' word (pattern-lists . sub-exp-lists) ...) (let ((value (eval-word env word #:output 'string))) (apply sh:case env value (map (lambda (patterns sub-exps) `(,(map (cut eval-word env <> #:output 'pattern) patterns) ,(exps->thunk env sub-exps))) pattern-lists sub-exp-lists)))) ((' (test-exps . sub-exp-lists) ..1) (apply sh:cond env (map (lambda (test-exp sub-exps) `(,(match test-exp (' #t) (exp (exp->thunk env exp))) ,(exps->thunk env sub-exps))) test-exps sub-exp-lists))) ((' name . sub-exps) (let ((proc (lambda (env . args) (eval-sh env `( ,@sub-exps))))) (define-environment-function! env name proc))) ((' words ..1) (let ((args (append-map (cut eval-word env <>) words))) (match args ((name . args) (apply sh:exec env name args)) (() #f)))) ((' ((names var-words) ..1) cmd-words ..1) (let* ((args (append-map (cut eval-word env <>) cmd-words)) (bindings (map (lambda (name word) `(,name . ,(eval-word env word #:output 'string #:rhs-tildes? #t))) names var-words))) (match args ((name . args) (apply sh:exec-let env bindings name args)) (() (for-each (match-lambda ((name . value) (set-var! env name value))) bindings))))) ((' (name (words ...)) . sub-exps) (sh:for env `(,name ,(append-map (cut eval-word env <>) words)) (exps->thunk env sub-exps))) ((' exp) (sh:not env (exp->thunk env exp))) ((' exp1 exp2) (sh:or env (exp->thunk env exp1) (exp->thunk env exp2))) ((' cmd*s ..1) (apply sh:pipeline env (map (cut exp->thunk env <>) cmd*s))) ((' (names words) ..1) (for-each (lambda (name word) (set-var! env name (eval-word env word #:output 'string #:rhs-tildes? #t))) names words)) ((' . sub-exps) (sh:subshell env (exps->thunk env sub-exps))) ((' test-exp sub-exps ..1) (sh:while env (exp->thunk env test-exp) (exps->thunk env sub-exps))) ((' (redirs ..1) sub-exp) (match sub-exp ;; For "simple commands" we have to observe a special order of ;; evaluation: first command words, then redirects, and finally ;; assignment words. ((' words ..1) (let ((args (append-map (cut eval-word env <>) words))) (match (false-if-exception (map (cut eval-redir env <>) redirs)) (#f (set-environment-status! env 1)) (redirs (match args ;; This built-in, called with no arguments, is a very ;; special case. We need to treat the redirects ;; directly rather than pass them to ;; 'sh:with-redirects'. (("exec") (sh:set-redirects env redirs)) ((name . args) (sh:with-redirects env redirs (lambda () (apply sh:exec env name args)))) (() #f)))))) ((' ((names var-words) ..1) cmd-words ..1) (let ((args (append-map (cut eval-word env <>) cmd-words))) (match (false-if-exception (map (cut eval-redir env <>) redirs)) (#f (set-environment-status! env 1)) (redirs (let ((bindings (map (lambda (name word) `(,name . ,(eval-word env word #:output 'string #:rhs-tildes? #t))) names var-words))) (match args ;; See the '' case for why this built-in is ;; treated specially. (("exec") (sh:set-redirects env redirs)) ((name . args) (sh:with-redirects env redirs (lambda () (apply sh:exec-let env bindings name args)))) (() (for-each (match-lambda ((name . value) (set-var! env name value))) bindings)))))))) (_ (match (false-if-exception (map (cut eval-redir env <>) redirs)) (#f (set-environment-status! env 1)) (redirs (sh:with-redirects env redirs (exp->thunk env sub-exp))))))) ((' test-exp sub-exps ..1) (sh:until env (exp->thunk env test-exp) (exps->thunk env sub-exps)))))