rpn/_parse.ss
#lang scheme/base

(require
 "../ns.ss"
 (for-syntax
  scheme/base
  scheme/local
  "../ns-tx.ss"
  "parse-tx.ss"))

(provide (all-defined-out))

;; An RPN transformer is a primitive taking arguments
;;   W : code stack
;;   D : dictionary (parser output)
;;   K : parser continuation



;; RPN PARSER

;; (rpn-parse (mk semantics ...) code ...)

;; The rpn syntax is currently implemented as a single transformer to
;; be able to get at the provided semantics macros through lexical
;; scope.  (Previous implementation used compile-time paramters, which
;; became hard to understand.)

;; The parser can be parameterized as follows:
;;   * semantics for built-in in RPN language constructs.
;;   * prefix parsers bound to local syntax
;;   * prefix parsers found in the input stream

(define-syntax (rpn-parse stx)
  (let ((args (stx-args stx)))
    (syntax-case (car args) ()
      ((tx-dict    ;; macro continuation <- dictionary output form
        (ns ...)   ;; identifier namespace
        function   ;; semantics macros for different forms
        immediate
        immediate-program
        program:
        init-dict)
       
       (local
        ((define (map-id id)
           (ns-prefixed #'(ns ...) id))
         (define (->parse it)
           (and (rpn-transformer? it)
                (rpn-transformer-tx it)))
         (define (syntax-local stx)
           (and (identifier? stx)
                (syntax-local-value (map-id stx) (lambda () #f))))
         
         ;; Build a quasiquoted immediate by traverseing an sexp tree
         ;; and performing proper unquotes.
         (define (qq unquote-tx)
           (lambda (atom-stx)
             (define (uq stx)
               (syntax-case stx (unquote)
                 ((unquote atom) (unquote-tx #'atom))
                 ((car . cdr)    #`(#,(uq #'car) . #,(uq #'cdr)))
                 (atom           #'atom)))
             #`(immediate (quasiquote #,(uq atom-stx)))))
         
         ;; All quoters take one arguement.
         (define (quoter fn stx)
           (syntax-case stx ()
             ((_ atom) (fn #'atom))
             (other    (raise-syntax-error
                        #f "takes a single argument" stx))))
         
         ;; Quote supports unquote as a way to introduce arbitrary
         ;; scheme values into s-expressions.
         (define quoted
           (qq (lambda (atom) #`(unquote #,atom))))
         
         ;; Quasiquotation is intended to build datastructures
         ;; containing function objects, not to substitute scheme
         ;; values.  It supports both identifiers and compositions.
         (define quasiquoted 
           (qq (lambda (atom)
                 (syntax-case atom ()
                   ((e ...) #`(unquote (program: e ...)))
                   (e       #`(unquote #,(map-id #'e)))))))
         
         ;; Unquote takes an expression from the surrounding
         ;; Scheme environment and uses it as a function.
         (define (unquoted atom-stx)
           #`(function #,atom-stx))

         ;; Primitive syntax forms.
         (define (primitive element)
           (syntax-case element
               (quote quasiquote unquote) 
             ((quote . e)        (quoter quoted element))
             ((quasiquote . e)   (quoter quasiquoted element))
             ((unquote . e)      (quoter unquoted element))
             ((e ...)            #`(immediate-program (program: e ...)))
             (e (if (identifier? #'e)
                    #`(function (ns ... e))
                    #`(immediate #,element))))))

        
        ;; * MAIN LOOP *
        
        ;; Read elements from the list of syntax elements, parse
        ;; and compile.  When done, pass the dictionary to the
        ;; dictionary transformer macro.
        (let next ((w (cdr args))
                   (d (foldl d-compile
                             (d-create)
                             (syntax->list #'init-dict))))
          (if (w-null? w)
              #`(tx-dict #,@(d->forms d))
              (let* ((element (w-car w))
                     (default-parse
                       ;; The default parser interprets the element and
                       ;; compiles it to the dictionary, moving on with
                       ;; the following element in the stream.
                       (lambda (w d next)
                         (next (w-cdr w)
                               (d-compile
                                (primitive element) d))))
                     (parse 
                      ;; Determine if the element represents a parser
                      ;; extension.  Either directly in the input
                      ;; stream (possibly wrapped in a syntax object)
                      ;; or bound to a transformer binding.
                      (or (->parse element)
                          (->parse (syntax->datum element))
                          (->parse (syntax-local element))
                          default-parse)))
                ;; All parsers are invoked in tail position and need
                ;; to call 'next to continue looping with updated
                ;; state.
                (parse w d next)))))))))


;; Notes
;;
;; (1) In order to access transformer bindings containing
;;     rpn-transformer instances, compile time identifiers specified
;;     by the form (namespace ... id) are _interpreted_ : the form
;;     which is a valid macro form is _not_ expanded.  This is to
;;     prevend recursive macro expansion inside the rpn-parse macro,
;;     which I've not been able to figure out how to do correctly.
;;     (i.e. using 'local-expand ...).  However, if this process fails
;;     and no transformer binding is found, the identifier is replaced
;;     with the form in the output of rpn-parse, which allows for
;;     abstract identifier mapping.