#!MUNGERPATH

; Copyright (c) 2004, 2008, James Bailie <jimmy@mammothcheese.ca>.
; All rights reserved.
; 
; Redistribution and use in source form, with or without
; modification, are permitted provided that the following conditions are met:
; 
;     * Redistributions of source code must retain the above copyright
; notice, this list of conditions and the following disclaimer.
;     * The name of James Bailie may not be used to endorse or promote 
; products derived from this software without specific prior written permission.
; 
; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS"
; AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
; IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
; ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
; LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
; CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
; SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
; CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
; ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
; POSSIBILITY OF SUCH DAMAGE.

# Embedded Munger Processor

# This filter outputs all lines it reads excepting lines beginning with a
# semicolon in column zero, an octothorpe in column zero, and ranges of
# contiguous lines beginning with a line containing an opening parenthesis in
# column zero, and ending with a line containing a semicolon in column zero.

# Lines beginning with a semicolon are considered comments and discarded.

# Lines beginning with an octothorpe are considered comments unless the
# line is recognized as an "include" directive.  Include directives name a
# file whose content is to be inserted verbatim into the output stream at
# that point.  Include lines must be of the form:  

# #include "/path/to/filename"

# Blocks of lines beginning with an open parenthesis and terminated with a
# semicolon line, are evaluated.  If they produce output via print,
# println, or newline, the output is inserted into the document.  The
# return expression is discarded.

# There is no means whereby the filter can be made to escape or pass-through
# lines starting with (, #, or ;.  To cause the filter to output lines
# starting with these characters, one must write code to do so,  like this:  

# (println "; comment")
# ;

# The terminator(s) of a line of literal text may be suppressed by appending a
# backslash to the line.  This can be used to inline lisp output:  

# Version \
# (letn ((v (version))
#        (maj (car v))
#        (min (cadr v)))
#  (print maj "." min))
# ;
# of \
# (print (date))
# ;
#
# Will result in the following output:
#
# Version 3.67 of Tue, 15 Jun 2004 13:33:25 EDT.

# To output a line with a trailing backslash, use code:
#
# (println "line of text\\")
# ;

(fatal)

(let ((code_rx (regcomp "^\(.*"))
      (comment_rx (regcomp "^[;#]"))
      (include_rx (regcomp "^#include[\b\t]*\"(([^\"]|\\\\\")+)\""))
      (suppress_rx (regcomp (concat "\\\\[" (char 10) (char 13) "]+$")))
      (m ())
      (current_expr ())
      (process_expr ())
      (include ())
      (scan_line ()))

   (defun process_expr (line)
      (setq current_expr (append current_expr (list line)))
      (setq line (getline))
   
      (unless (or (eq line 0) (match comment_rx line))
         (tailcall 0 line))
   
      (blind_eval_string (concat current_expr))
      (setq current_expr ()))
   
   (defun include (line)
      (setq m (matches include_rx line))
      (unless (and m (cadr m))
         (warn "filter.munger: malformed #include line.")
         (exit 1))

      (with_input_file (cadr m)
         (while (setq m (getline))
            (print m))))

   (defun scan_line (line)
      (cond ((match code_rx line) (process_expr line))
            ((match include_rx line) (include line))
            ((not (match comment_rx line)) 
             (print (substitute suppress_rx "" line)))))

   (next)
   (foreach_line scan_line)
   (quit))
