A Lisp implemented in AWK
;; SPDX-License-Identifier: BSD-2-Clause

(macro while
    (lambda (args)
      `(label ((loop (lambda ()
                       ,@(cdr args)
                       (cond (,(car args) (loop))))))
              (cond (,(car args) (loop))))))
(macro if (lambda (args)
            `(cond (,(car args) ,(cadr args))
                   (true ,@(cddr args)))))
(macro when (lambda (args)
              `(cond (,(car args)
                      ,@(cdr args)))))

(defun dolines-until (pred op)
  (let* ((result (getline))
         (line (car result))
         (rv (cadr result)))
    (cond
      ((pred line) true)
      ((> rv 0) (op line)
       (dolines-until pred op))
      ((= rv 0) nil)
      ((< rv 0) nil))))

(defun any (pred lis)
  (cond ((null lis) false)
        ((pred (car lis)) true)
        (true (any pred (cdr lis)))))

(defun mapcars args
  (let ((f (car args))
        (as (cdr args)))
    (label ((map
             (lambda (f as res)
               (cond
                ((any null as) (nreverse res))
                (true (map f (mapcar cdr as)
                           (cons (apply f (mapcar car as)) res)))))))
           (map f as nil))))

(defun zip args (apply mapcars list args))
                 
(defun dolines (fn)
  (dolines-until (lambda (line) false) fn))

(defun getlines ()
  (let ((lines '()))
    (dolines (lambda (line)
               (setq lines (cons line lines))))
    (nreverse lines)))

(defun output-lines-of argv
  (let ((command (apply make-safe-shell-command argv)))
    (with-input-from "|" command
      (prog1
          (getlines)
        (close command)))))

(defun output-of argv
  (apply string-join "\n" (apply output-lines-of argv)))

(defun split-at-first (pred lis)
  (label ((spla (lambda (pred lis first-lis)
                  (cond
                    ((pred (car lis))
                     (list (nreverse first-lis) lis))
                    (true (spla pred
                                (cdr lis)
                                (cons (car lis) first-lis)))))))
         (spla pred lis nil)))

(defun *rooted* (file-path) (if file-path file-path nil))

(defun trim (s) (sub "[[:space:]]*$" "" (sub "^[[:space:]]*" "" s)))

(defun path-join argv
  (gsub "/+" "/" (apply string-join "/" argv)))