infix: Reader macros for infix expressions.
authorMark Wooding <mdw@distorted.org.uk>
Thu, 20 Apr 2006 14:00:08 +0000 (15:00 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Thu, 20 Apr 2006 14:04:46 +0000 (15:04 +0100)
factorial.lisp [new file with mode: 0644]
infix-ext.lisp [new file with mode: 0644]
infix.lisp [new file with mode: 0644]
mdw.asd

diff --git a/factorial.lisp b/factorial.lisp
new file mode 100644 (file)
index 0000000..0155e07
--- /dev/null
@@ -0,0 +1,49 @@
+;;; -*-lisp-*-
+;;;
+;;; Compute factorials
+;;;
+;;; (c) 2006 Mark Wooding
+;;;
+
+;;;----- Licensing notice ---------------------------------------------------
+;;;
+;;; This program 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 2 of the License, or
+;;; (at your option) any later version.
+;;; 
+;;; This program 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 this program; if not, write to the Free Software Foundation,
+;;; Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+(defpackage #:mdw.factorial
+  (:use #:common-lisp)
+  (:export #:factorial))
+(in-package #:mdw.factorial)
+
+(defun factorial (n)
+  "Compute a factorial.  This is a little bit optimized: we try to multiply
+values which are similar in size."
+  (when (minusp n)
+    (error "negative factorial argument ~A" n))
+  (let ((stack nil))
+    (do ((i 2 (1+ i)))
+       ((> i n))
+      (let ((f i))
+       (loop
+         (unless stack (return))
+         (let ((top (car stack)))
+           (when (< f top) (return))
+           (setf f (* f top))
+           (pop stack)))
+       (push f stack)))
+    (do ((stack stack (cdr stack))
+        (a 1 (* a (car stack))))
+       ((null stack) a))))
+
+;;;----- That's all, folks --------------------------------------------------
diff --git a/infix-ext.lisp b/infix-ext.lisp
new file mode 100644 (file)
index 0000000..34d5981
--- /dev/null
@@ -0,0 +1,46 @@
+;;; -*-lisp-*-
+;;;
+;;; Extensions for more infix operators
+;;;
+;;; (c) 2006 Mark Wooding
+;;;
+
+;;;----- Licensing notice ---------------------------------------------------
+;;;
+;;; This program 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 2 of the License, or
+;;; (at your option) any later version.
+;;; 
+;;; This program 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 this program; if not, write to the Free Software Foundation,
+;;; Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+(defpackage #:infix-ext
+  (:use #:common-lisp #:mdw.base #:mdw.factorial #:infix-keywords #:infix))
+(in-package #:infix-ext)
+
+(defun assignop-apply (op)
+  (let ((y (popval))
+       (x (popval)))
+    (pushval (list 'update-place op x y))))
+
+(definfix *= (120 . 5) (assignop-apply '*))
+(definfix %= (120 . 5) (assignop-apply 'mod))
+(definfix //= (120 . 5) (assignop-apply 'floor))
+(definfix &= (120 . 5) (assignop-apply 'logand))
+(definfix \|= (120 . 5) (assignop-apply 'logior))
+(definfix <<= (120 . 5) (assignop-apply 'ash))
+(definfix >>= (120 . 5) (unop-apply-toggle '-) (assignop-apply '*))
+
+(defpostfix ++ 120 (unop-apply 'incf-after))
+(defpostfix -- 120 (unop-apply 'decf-after))
+
+(defpostfix ! 120 (unop-apply 'factorial))
+
+;;;----- That's all, folks --------------------------------------------------
diff --git a/infix.lisp b/infix.lisp
new file mode 100644 (file)
index 0000000..f71758c
--- /dev/null
@@ -0,0 +1,826 @@
+;;; -*-lisp-*-
+;;;
+;;; Infix-to-S-exp translation
+;;;
+;;; (c) 2006 Mark Wooding
+;;;
+
+;;;----- Licensing notice ---------------------------------------------------
+;;;
+;;; This program 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 2 of the License, or
+;;; (at your option) any later version.
+;;; 
+;;; This program 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 this program; if not, write to the Free Software Foundation,
+;;; Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+;;;--------------------------------------------------------------------------
+;;; Packages.
+
+(defpackage #:infix-keywords
+  (:use #:common-lisp)
+  (:export #:|(| #:|)| #:{ #:} #:|,| #:@ #:|$| #:& #:\| #:~
+          #:and #:or #:not #:xor
+          #:== #:/= #:< #:<= #:> #:>= #:eq #:eql #:equal #:equalp
+          #:+ #:- #:* #:/ #:// #:% #:^ #:= #:!
+          #:+= #:-= #:*= #:%= #:&= #:\|= #:xor= #:<<= #:>>=
+          #:++ #:--
+          #:<< #:>>
+          #:if #:then #:else
+          #:let #:let* #:in))
+
+(defpackage #:infix
+  (:use #:common-lisp #:infix-keywords)
+  (:export #:operator #:operatorp
+          #:*token* #:get-token #:*get-token*
+          #:pushval #:popval #:flushops #:pushop
+          #:infix-done #:parse-infix
+          #:defopfunc #:definfix #:defprefix #:defpostfix
+          #:infix #:prefix #:postfix #:operand
+          #:delim #:errfunc
+          #:binop-apply #:binop-apply-append
+          #:unop-apply #:unop-apply-toggle
+          #:strip-progn
+          #:read-infix #:install-infix-reader))
+
+(in-package #:infix)
+
+;;;--------------------------------------------------------------------------
+;;; Data structures.
+
+(defstruct (operator (:predicate operatorp)
+                    (:conc-name op-))
+  "An operator object.  The name serves mainly for documentation.  The left
+and right precedences control operator stacking behaviour.  The function is
+called when this operator is popped off the stack.
+
+If the left precedence is not nil, then operators currently on the stack
+whose /right/-precedence is greater than or equal to this operator's
+/left/-precedence are popped before this operator can be pushed.  If the
+right precedence is nil, then this operator is not in fact pushed, but
+processed immediately."
+  (name nil :type symbol)
+  (lprec nil :type (or fixnum null))
+  (rprec nil :type (or fixnum null))
+  (func (lambda () nil) :type (function () t)))
+
+;;;--------------------------------------------------------------------------
+;;; Global parser state.
+
+(defvar *stream* nil
+  "The parser input stream.  Bound automatically by `read-infix'.")
+
+;;;--------------------------------------------------------------------------
+;;; State for one level of `parse-infix'.
+
+(defvar *valstk* nil
+  "Value stack.  Contains (partially constructed) Lisp forms.")
+(defvar *opstk* nil
+  "Operator stack.  Contains operator objects.")
+(defvar *token* nil
+  "The current token.  Could be any Lisp object.")
+(defvar *paren-depth* 0
+  "Depth of parentheses in the current `parse-infix'.  Used to override the
+minprec restriction.")
+
+;;;--------------------------------------------------------------------------
+;;; The tokenizer.
+
+(defconstant eof (cons :eof nil)
+  "A magical object which `get-token' returns at end-of-file.")
+
+(defun default-get-token ()
+  "Read a token from *stream* and store it in *token*."
+  (flet ((whitespacep (ch)
+          (member ch '(#\newline #\space #\tab #\page)))
+        (self-delim-p (ch)
+          (member ch '(#\; #\, #\: #\( #\) #\@ #\$ #\[ #\] #\{ #\})))
+        (macro-char-p (ch)
+          (member ch '(#\# #\| #\\ #\" #\' #\`)))
+        (done (token)
+          (setf *token* token)
+          (return-from default-get-token)))
+    (let (ch)
+      (tagbody
+       top
+        (setf ch (read-char *stream* nil nil t))
+        (cond ((null ch) (done eof))
+              ((whitespacep ch) (go top))
+              ((eql ch #\;) (go comment))
+              ((self-delim-p ch) (done (intern (string ch)
+                                               'infix-keywords)))
+              ((or (macro-char-p ch) (alphanumericp ch)) (go read))
+              (t (go read-sym)))
+       read
+        (unread-char ch *stream*)
+        (done (read *stream* t nil t))
+       read-sym
+        (done (intern (with-output-to-string (out)
+                        (write-char ch out)
+                        (loop
+                          (setf ch (read-char *stream* nil nil t))
+                          (cond ((or (null ch)
+                                     (whitespacep ch))
+                                 (return))
+                                ((or (self-delim-p ch)
+                                     (macro-char-p ch)
+                                     (alphanumericp ch))
+                                 (unread-char ch *stream*)
+                                 (return))
+                                (t
+                                 (write-char ch out)))))
+                      'infix-keywords))
+
+       comment
+        (case (setf ch (read-char *stream* nil nil t))
+          ((nil) (done eof))
+          ((#\newline) (go top))
+          (t (go comment)))))))
+
+(defvar *get-token* #'default-get-token
+  "The current tokenizing function.")
+
+(defun get-token ()
+  "Read a token, and store it in *token*.  Indirects via *get-token*."
+  (funcall *get-token*))
+
+;;;--------------------------------------------------------------------------
+;;; Stack manipulation.
+
+(defun pushval (val)
+  "Push VAL onto the value stack."
+  (push val *valstk*))
+
+(defun popval ()
+  "Pop a value off the value stack and return it."
+  (pop *valstk*))
+
+(defun flushops (prec)
+  "Flush out operators on the operator stack with precedecnce higher than or
+equal to PREC.  This is used when a new operator is pushed, to ensure that
+higher-precedence operators snarf their arguments."
+  (loop
+    (when (null *opstk*)
+      (return))
+    (let ((head (car *opstk*)))
+      (when (> prec (op-rprec head))
+       (return))
+      (pop *opstk*)
+      (funcall (op-func head)))))
+
+(defun pushop (op)
+  "Push the operator OP onto the stack.  If the operator has a
+left-precedence, then operators with higher precedence are flushed (see
+`flushops').  If the operator has no left-precedence, the operator is invoked immediately."
+  (let ((lp (op-lprec op)))
+    (when lp
+      (flushops lp)))
+  (if (op-rprec op)
+      (push op *opstk*)
+      (funcall (op-func op))))
+
+;;;--------------------------------------------------------------------------
+;;; The main parser.
+
+(defun infix-done ()
+  "Signal that `parse-infix' has reached the end of an expression.  This is
+primarily used by the `)' handler function if it finds there are no
+parentheses."
+  (throw 'infix-done nil))
+
+(defun parse-infix (&optional minprec)
+  "Parses an infix expression and return the resulting Lisp form.  This is
+the heart of the whole thing.
+
+Expects a token to be ready in *token*; leaves *token* as the first token
+which couldn't be parsed.
+
+The syntax parsed by this function doesn't fit nicely into a BNF, since we
+parsing is effected by the precedences of the various operators.  We have
+low-precedence prefix operators such as `not', for example."
+  (flet ((lookup (items)
+          (dolist (item items (values nil nil))
+            (let ((op (get *token* (car item))))
+              (when op (return (values op (cdr item))))))))
+    (let ((*valstk* nil)
+         (*opstk* nil)
+         (*paren-depth* 0)
+         (state :operand))
+      (catch 'infix-done
+       (loop
+         (ecase state
+           (:operand
+            (when (eq *token* eof)
+              (error "operand expected; found eof"))
+            (typecase *token*
+              (symbol
+               (multiple-value-bind (op newstate)
+                   (lookup '((prefix . :operand)
+                             (operand . :operator)))
+                 (etypecase op
+                   (null
+                    (pushval *token*)
+                    (get-token)
+                    (setf state :operator))
+                   (function
+                    (funcall op)
+                    (setf state newstate))
+                   (operator
+                    (get-token)
+                    (pushop op)))))
+              (t
+               (pushval *token*)
+               (get-token)
+               (setf state :operator))))
+           (:operator
+            (typecase *token*
+              (symbol
+               (multiple-value-bind (op newstate)
+                   (lookup '((infix . :operand)
+                             (postfix . :operator)))
+                 (etypecase op
+                   (null
+                    (return))
+                   (function
+                    (funcall op))
+                   (operator
+                    (when (and minprec
+                               (zerop *paren-depth*)
+                               (op-lprec op)
+                               (< (op-lprec op) minprec))
+                      (return))
+                    (get-token)
+                    (pushop op)))
+                 (setf state newstate)))
+              (t
+               (return)))))))
+      (flushops most-negative-fixnum)
+      (assert (and (consp *valstk*)
+                  (null (cdr *valstk*))))
+      (car *valstk*))))
+
+;;;--------------------------------------------------------------------------
+;;; Machinery for defining operators.
+
+(defmacro defopfunc (op kind &body body)
+  "Defines a magical operator.  The operator's name is the symbol OP.  The
+KIND must be one of the symbols `infix', `prefix' or `postfix'.  The body is
+evaluated when the operator is parsed, and must either push appropriate
+things on the operator stack or do its own parsing and push a result on the
+value stack."
+  `(progn
+     (setf (get ',op ',kind)
+           (lambda () ,@body))
+    ',op))
+
+(defmacro definfix (op prec &body body)
+  "Defines an infix operator.  The operator's name is the symbol OP.  The
+operator's precedence is specified by PREC, which may be one of the
+following:
+
+  * PREC -- equivalent to (:lassoc PREC)
+  * (:lassoc PREC) -- left-associative with precedence PREC
+  * (:rassoc PREC) -- right-associative with precedence PREC
+  * (LPREC . RPREC) -- independent left- and right-precedences
+  * (LPREC RPREC) -- synonym for the dotted form
+
+In fact, (:lassoc PREC) is the same as (PREC . PREC), and (:rassoc PREC) is
+the same as (PREC . (1- PREC)).
+
+The BODY is evaluated when the operator's arguments are fully resolved.  It
+should pop off two arguments and push one result.  Nobody will check that
+this is done correctly."
+  (multiple-value-bind
+      (lprec rprec)
+      (flet ((bad ()
+              (error "bad precedence spec ~S" prec)))
+       (cond ((integerp prec)
+              (values prec prec))
+             ((not (consp prec))
+              (bad))
+             ((and (integerp (car prec))
+                   (integerp (cdr prec)))
+              (values (car prec) (cdr prec)))
+             ((or (not (consp (cdr prec)))
+                  (not (integerp (cadr prec)))
+                  (not (null (cddr prec))))
+              (bad))
+             ((integerp (car prec))
+              (values (car prec) (cadr prec)))
+             ((eq (car prec) :lassoc)
+              (values (cadr prec) (cadr prec)))
+             ((eq (car prec) :rassoc)
+              (values (cadr prec) (1- (cadr prec))))
+             (t
+              (bad))))
+    `(progn
+       (setf (get ',op 'infix)
+            (make-operator :name ',op
+                           :lprec ,lprec :rprec ,rprec
+                           :func (lambda () ,@body)))
+       ',op)))
+
+(eval-when (:compile-toplevel :load-toplevel)
+  (defun do-defunary (kind op prec body)
+    (unless (integerp prec)
+      (error "bad precedence spec ~S" prec))
+    `(progn
+       (setf (get ',op ',kind)
+            (make-operator :name ',op
+                           ,(ecase kind
+                              (prefix :rprec)
+                              (postfix :lprec)) ,prec
+                           :func (lambda () ,@body)))
+       ',op)))
+(defmacro defprefix (op prec &body body)
+  "Defines a prefix operator.  The operator's name is the symbol OP.  The
+operator's (right) precedence is PREC.  The body is evaluated with the
+operator's argument is fully determined.  It should pop off one argument and
+push one result."
+  (do-defunary 'prefix op prec body))
+(defmacro defpostfix (op prec &body body)
+  "Defines a postfix operator.  The operator's name is the symbol OP.  The
+operator's (left) precedence is PREC.  The body is evaluated with the
+operator's argument is fully determined.  It should pop off one argument and
+push one result."
+  (do-defunary 'postfix op prec body))
+
+;;;--------------------------------------------------------------------------
+;;; Infrastructure for operator definitions.
+
+(defun delim (delim &key (requiredp t))
+  "Parse DELIM, and read the next token.  Returns t if the DELIM was found,
+or nil if not (and requiredp was nil)."
+  (cond ((eq *token* delim) (get-token) t)
+       (requiredp (error "expected `~(~A~)'; found ~S" delim *token*))
+       (t nil)))
+
+(defun errfunc (&rest args)
+  "Returns a function which reports an error.  Useful when constructing
+operators by hand."
+  (lambda () (apply #'error args)))
+
+(defun binop-apply (name)
+  "Apply the Lisp binop NAME to the top two items on the value stack; i.e.,
+if the top two items are Y and X, then we push (NAME X Y)."
+  (let ((y (popval)) (x (popval)))
+    (pushval (list name x y))))
+
+(defun binop-apply-append (name)
+  "As for `binop-apply' but if the second-from-top item on the stack has the
+form (NAME SOMETHING ...) then fold the top item into the form rather than
+buidling another."
+  (let ((y (popval)) (x (popval)))
+    (pushval (if (and (consp x) (eq (car x) name))
+                (append x (list y))
+                (list name x y)))))
+
+(defun unop-apply (name)
+  "Apply the Lisp unop NAME to the top item on the value stack; i.e., if the
+top item is X, then push (NAME X)."
+  (pushval (list name (popval))))
+(defun unop-apply-toggle (name)
+  "As for `unop-apply', but if the top item has the form (NAME X) already,
+then push just X."
+  (let ((x (popval)))
+    (pushval (if (and (consp x)
+                     (eq (car x) name)
+                     (consp (cdr x))
+                     (null (cddr x)))
+                (cadr x)
+                (list name x)))))
+
+(defun strip-progn (form)
+  "Return a version of FORM suitable for putting somewhere where there's an
+implicit `progn'.  If FORM has the form (PROGN . FOO) then return FOO,
+otherwise return (FORM)."
+  (if (and (consp form)
+          (eq (car form) 'progn))
+      (cdr form)
+      (list form)))
+
+(defun parse-expr-list ()
+  "Parse a list of expressions separated by commas."
+  (let ((stuff nil))
+    (loop
+      (push (parse-infix 0) stuff)
+      (unless (delim '|,| :requiredp nil)
+       (return)))
+    (nreverse stuff)))
+
+(defun parse-ident-list ()
+  "Parse a list of symbols separated by commas."
+  (let ((stuff nil))
+    (loop
+      (unless (symbolp *token*)
+        (error "expected symbol; found ~S" *token*))
+      (push *token* stuff)
+      (get-token)
+      (unless (delim '|,| :requiredp nil)
+       (return)))
+    (nreverse stuff)))
+
+;;;--------------------------------------------------------------------------
+;;; Various simple operators.
+
+(definfix |,| (:lassoc -1) (binop-apply-append 'progn))
+
+(definfix or (:lassoc 10) (binop-apply-append 'or))
+(definfix and (:lassoc 15) (binop-apply-append 'and))
+
+(defprefix not 19 (unop-apply-toggle 'not))
+
+(definfix == (:lassoc 20) (binop-apply-append '=))
+(definfix /= (:lassoc 20) (binop-apply-append '/=))
+(definfix < (:lassoc 20) (binop-apply-append '<))
+(definfix <= (:lassoc 20) (binop-apply-append '<=))
+(definfix >= (:lassoc 20) (binop-apply-append '>=))
+(definfix > (:lassoc 20) (binop-apply-append '>))
+(definfix eq (:lassoc 20) (binop-apply-append 'eq))
+(definfix eql (:lassoc 20) (binop-apply-append 'eql))
+(definfix equal (:lassoc 20) (binop-apply-append 'equal))
+(definfix equalp (:lassoc 20) (binop-apply-append 'equalp))
+
+(definfix \| (:lassoc 30) (binop-apply-append 'logior))
+(definfix xor (:lassoc 30) (binop-apply-append 'logxor))
+(definfix & (:lassoc 35) (binop-apply-append 'logand))
+
+(definfix << (:lassoc 40) (binop-apply 'ash))
+(definfix >> (:lassoc 40) (unop-apply-toggle '-) (binop-apply 'ash))
+
+(definfix + (:lassoc 50) (binop-apply-append '+))
+(definfix - (:lassoc 50) (binop-apply-append '-))
+
+(definfix * (:lassoc 60) (binop-apply-append '*))
+(definfix / (:lassoc 60) (binop-apply '/))
+(definfix // (:lassoc 60) (binop-apply 'floor))
+(definfix % (:lassoc 60) (binop-apply 'mod))
+
+(definfix ^ (:rassoc 70) (binop-apply 'expt))
+
+(definfix = (120 . 5) (binop-apply 'setf))
+(definfix += (120 . 5) (binop-apply 'incf))
+(definfix -= (120 . 5) (binop-apply 'decf))
+
+(defprefix + 100 nil)
+(defprefix - 100 (unop-apply-toggle '-))
+(defprefix ~ 100 (unop-apply-toggle 'lognot))
+
+(defprefix ++ 100 (unop-apply 'incf))
+(defprefix -- 100 (unop-apply 'decf))
+
+;;(defpostfix ! 110 (unop-apply 'factorial))
+
+(defopfunc @ operand
+  "An escape to the standard Lisp reader."
+  (pushval (read *stream* t nil t))
+  (get-token))
+
+;;;--------------------------------------------------------------------------
+;;; Parentheses, for grouping and function-calls.
+
+(defun push-paren (right)
+  "Pushes a funny parenthesis operator.  Since this operator has no left
+precedence, and very low right precedence, it is pushed over any stack of
+operators and can only be popped by magic or end-of-file.  In the latter
+case, cause an error."
+  (pushop (make-operator :name right
+                        :lprec nil :rprec -1000
+                        :func (errfunc "missing `~A'" right)))
+  (incf *paren-depth*)
+  (get-token))
+
+(defun pop-paren (right)
+  "Pops a parenthesis.  If there are no parentheses, maybe they belong to the
+caller's syntax.  Otherwise, pop off operators above the current funny
+parenthesis operator, and then remove it."
+  (when (zerop *paren-depth*)
+    (infix-done))
+  (flushops -999)
+  (assert *opstk*)
+  (unless (eq (op-name (car *opstk*)) right)
+    (error "spurious `~A'" right))
+  (assert (plusp *paren-depth*))
+  (decf *paren-depth*)
+  (pop *opstk*)
+  (get-token))
+
+(defopfunc |(| prefix (push-paren '\)))
+(defopfunc |)| postfix (pop-paren '\)))
+(defopfunc |{| prefix (push-paren '\}))
+(defopfunc |}| postfix (pop-paren '\}))
+
+(defopfunc |(| postfix
+  (get-token)
+  (pushval (cons (popval) (and (not (eq *token* '|)|)) (parse-expr-list))))
+  (delim '|)|))
+
+;;;--------------------------------------------------------------------------
+;;; Various bits of special syntax.
+
+(defopfunc if operand
+  "Parse an `if' form.  Syntax:
+
+  IF ::= `if' CONDITION `then' CONSEQUENCE [`else' ALTERNATIVE]
+
+We parse this into an `if' where sensible, or into a `cond' if we see an
+`else if' pair.  The usual `dangling else' rule is followed."
+  (get-token)
+  (let (cond cons)
+    (setf cond (parse-infix))
+    (delim 'then)
+    (setf cons (parse-infix 0))
+    (if (not (eq *token* 'else))
+       (pushval (list 'if cond cons))
+       (progn
+         (get-token)
+         (cond ((not (eq *token* 'if))
+                (pushval (list 'if cond cons (parse-infix 0))))
+               (t
+                (let ((clauses nil))
+                  (flet ((clause (cond cons)
+                           (push (cons cond (strip-progn cons)) clauses)))
+                    (clause cond cons)
+                    (loop
+                      (get-token)
+                      (setf cond (parse-infix))
+                      (delim 'then)
+                      (setf cons (parse-infix 0))
+                      (clause cond cons)
+                      (unless (eq *token* 'else) (return))
+                      (get-token)
+                      (if (eq *token* 'if)
+                          (get-token)
+                          (progn
+                            (clause t (parse-infix 0))
+                            (return))))
+                    (pushval (cons 'cond (nreverse clauses)))))))))))
+
+(defun do-letlike (kind)
+  "Parse a `let' form.  Syntax:
+
+  LET ::= `let' | `let*' VARS `in' EXPR
+  VARS ::= VAR | VARS `,' VAR
+  VAR ::= NAME [`=' VALUE]
+
+Translates into the obvious Lisp code."
+  (let ((clauses nil) name value)
+    (get-token)
+    (loop
+      (unless (symbolp *token*)
+       (error "symbol expected, found ~S" *token*))
+      (setf name *token*)
+      (get-token)
+      (if (eq *token* '=)
+         (progn
+           (get-token)
+           (setf value (parse-infix 0))
+           (push (list name value) clauses))
+         (push name clauses))
+      (unless (eq *token* '|,|)
+       (return))
+      (get-token))
+    (delim 'in)
+    (pushval `(,kind ,(nreverse clauses) ,@(strip-progn (parse-infix 0))))))
+(defopfunc let operand (do-letlike 'let))
+(defopfunc let* operand (do-letlike 'let*))
+
+(defopfunc when operand
+  (get-token)
+  (pushval `(when ,(parse-infix)
+             ,@(progn (delim 'do) (strip-progn (parse-infix 0))))))
+
+(defopfunc unless operand
+  (get-token)
+  (pushval `(unless ,(parse-infix)
+             ,@(progn (delim 'do) (strip-progn (parse-infix 0))))))
+
+(defopfunc loop operand
+  (get-token)
+  (pushval `(loop ,@(progn (strip-progn (parse-infix 0))))))
+
+(defopfunc multiple-value-bind operand
+  (get-token)
+  (pushval `(multiple-value-bind
+               ,(parse-ident-list)
+               ,(progn (delim '=) (parse-infix))
+             ,@(progn (delim 'in) (strip-progn (parse-infix 0))))))
+
+(defopfunc multiple-value-setq operand
+  (get-token)
+  (pushval `(multiple-value-setq
+               ,(parse-ident-list)
+               ,(progn (delim '=) (parse-infix 0)))))
+
+;;;--------------------------------------------------------------------------
+;;; Parsing function bodies and lambda lists.
+
+(defun parse-lambda-list ()
+  "Parse an infix-form lambda list and return the Lisp equivalent."
+  (flet ((ampersand-symbol-p (thing)
+          (and (symbolp thing)
+               (let ((name (symbol-name thing)))
+                 (plusp (length name))
+                 (char= (char name 0) #\&))))
+        (get-lambda-token ()
+          (default-get-token)
+          (when (or (eq *token* '&)
+                    (eq *token* '|(|))
+            (unread-char #\& *stream*)
+            (setf *token* (read *stream* t nil t)))))
+    (let ((args nil))
+      (let ((*get-token* #'get-lambda-token))
+       (delim '|(|)
+       (unless (eq *token* '|)|)
+         (tagbody
+          loop
+            (cond ((ampersand-symbol-p *token*)
+                   (push *token* args)
+                   (get-token)
+                   (when (eq *token* '|)|)
+                     (go done))
+                   (delim '|,| :requiredp nil)
+                   (go loop))
+                  ((symbolp *token*)
+                   (let ((name *token*))
+                     (get-token)
+                     (if (delim '= :requiredp nil)
+                         (push (list name (parse-infix 0)) args)
+                         (push name args))))
+                  (t
+                   (push *token* args)
+                   (get-token)))
+            (when (delim '|,| :requiredp nil)
+              (go loop))
+          done)))
+      (delim '|)|)
+      (nreverse args))))
+
+(defun parse-func-name ()
+  "Parse a function name and return its Lisp equivalent."
+  (cond ((delim '|(| :requiredp nil)
+        (prog1 (parse-infix) (delim '|)|)))
+       (t (prog1 *token* (get-token)))))
+        
+(defopfunc lambda operand
+  (get-token)
+  (pushval `(lambda ,(parse-lambda-list) ,@(strip-progn (parse-infix 0)))))
+
+(defun do-defunlike (kind)
+  "Process a defun-like form."
+  (get-token)
+  (pushval `(,kind ,(parse-func-name) ,(parse-lambda-list)
+             ,@(strip-progn (parse-infix 0)))))
+
+(defopfunc defun operand (do-defunlike 'defun))
+(defopfunc defmacro operand (do-defunlike 'defmacro))
+
+(defun do-fletlike (kind)
+  "Process a flet-like form."
+  (get-token)
+  (let ((clauses nil))
+    (loop
+      (push `(,(parse-func-name) ,(parse-lambda-list)
+              ,@(strip-progn (parse-infix 0)))
+           clauses)
+      (unless (delim '|,| :requiredp nil)
+       (return)))
+    (delim 'in)
+    (pushval `(,kind ,(nreverse clauses) ,@(strip-progn (parse-infix 0))))))
+
+(defopfunc flet operand (do-fletlike 'flet))
+(defopfunc labels operand (do-fletlike 'labels))
+
+;;;--------------------------------------------------------------------------
+;;; User-interface stuff.
+
+(defun read-infix (&optional (*stream* *standard-input*) &key (delim eof))
+  "Reads an infix expression from STREAM and returns the corresponding Lisp.
+Requires the expression to be delimited properly by DELIM (by default
+end-of-file)."
+  (let (*token*)
+    (prog2
+       (get-token)
+       (parse-infix)
+      (unless (eq *token* delim)
+       (error "expected ~S; found ~S" delim *token*)))))
+
+(defun install-infix-reader (&optional (char #\$))
+  "Installs a macro character `$ INFIX...  $' for translating infix notation
+to Lisp forms.  You also want to (use-package :infix-keywords) if you do
+this."
+  (let ((delim (intern (string #\$) 'infix-keywords)))
+    (set-macro-character char (lambda (stream ch)
+                               (declare (ignore ch))
+                               (read-infix stream :delim delim)))))
+
+;;;--------------------------------------------------------------------------
+;;; Testing things.
+
+(defun test-infix (string)
+  (with-input-from-string (in string)
+    (read-infix in)))
+
+(defun test-tokenize (string &optional (get-token #'get-token))
+  (with-input-from-string (*stream* string)
+    (loop with *token* = nil
+         do (funcall get-token)
+         until (eq *token* eof)
+         collect *token*)))
+
+(defun testrig (what run tests)
+  (loop with ok = t
+       with error = nil
+       for (input . output) in tests
+       for result = (handler-case (funcall run input)
+                      (error (err)
+                        (setf error (format nil "~A" err))
+                        'fail))
+       unless (equal result output)
+       do (format t "~&~
+*** ~S test failure
+    input    = ~S
+    result   = ~:[~S~*~;~*error ~A~]
+    expected = ~S~%"
+                  what
+                  input
+                  (eq result 'fail) result error
+                  output)
+          (setf ok nil)
+          finally (return ok)))
+
+#+notdef
+(testrig "tokenize" #'test-tokenize
+  '(("++z" . (++ z))
+    ("z++" . (z++))
+    ("z ++" . (z ++))
+    ("-5" . (- 5))
+    ("&optional" . (& optional))
+    ("(4)" . (|(| 4 |)|))))
+
+#+notdef
+(testrig "infix" #'test-infix
+  '(("5" . 5)
+    ("-5" . (- 5))
+    ("-" . fail)
+    ("1 + 1" . (+ 1 1))
+    ("(1" . fail)
+    ("1)" . fail)
+    ("1 + 2 + 3" . (+ 1 2 3))
+    ("++x" . (incf x))
+    ("x += 5" . (incf x 5))
+    ("1 << 5" . (ash 1 5))
+    ("1 >> 5" . (ash 1 (- 5)))
+    ("1 & 5" . (logand 1 5))
+    ("lambda (x, y) x + y" . (lambda (x y) (+ x y)))
+    ("lambda (x, y) (x += y, x - 1)" . (lambda (x y) (incf x y) (- x 1)))
+    ("lambda (x, &optional y = 1) x - y" .
+     (lambda (x &optional (y 1)) (- x y)))
+    ("foo(x, y)" . (foo x y))
+    ("if a == b then x + y" . (if (= a b) (+ x y)))
+    ("if a == b then x + y else x - y" . (if (= a b) (+ x y) (- x y)))
+    ("if a == b then x + y else if a == -b then x - y" .
+     (cond ((= a b) (+ x y)) ((= a (- b)) (- x y))))
+    ("let x = 1 in x ^ 4" . (let ((x 1)) (expt x 4)))
+    ("x ^ y ^ z" . (expt x (expt y z)))
+    ("a < b and not b < c or c > d" .
+     (or (and (< a b) (not (< b c))) (> c d)))
+    ("cdr(x) = nil" . (setf (cdr x) nil))
+    ("labels foo (x) x + 1, bar (x) x - 1 in foo(bar(y))".
+     (labels ((foo (x) (+ x 1)) (bar (x) (- x 1))) (foo (bar y))))
+    ("defun foo (x) x - 6" .
+     (defun foo (x) (- x 6)))
+    ("multiple-value-bind x, y, z = values(4, 6, 8) in x + y + z" .
+     (multiple-value-bind (x y z) (values 4 6 8) (+ x y z)))))
+
+;;;--------------------------------------------------------------------------
+;;; Debugging guff.
+
+#+notdef
+(flet ((dotrace (func)
+        (and func
+             (trace :function func
+                    :encapsulate nil
+                    :print-all *token*
+                    :print-all *opstk*
+                    :print-all *valstk*))))
+  (untrace)
+  (dolist (s '(if \( \) \:))
+    (dolist (p '(infix prefix postfix))
+      (let ((op (get s p)))
+       (dotrace (etypecase op
+                  (function op)
+                  (operator (op-func op))
+                  (null nil))))))
+  (dolist (f '(read-infix parse-infix binop-apply unop-apply pushval popval
+              pushop flushops push-paren get-token))
+    (dotrace f)))
+
+;;;--------------------------------------------------------------------------
diff --git a/mdw.asd b/mdw.asd
index 32ad40d..3ad147d 100644 (file)
--- a/mdw.asd
+++ b/mdw.asd
@@ -8,9 +8,12 @@
   :components ((:file "mdw-base")
               (:file "anaphora")
               (:file "sys-base")
+              (:file "factorial")
               (:file "str")
               (:file "collect")
               (:file "unix")
               (:file "safely")
+              (:file "infix")
+              (:file "infix-ext")
               (:file "optparse"))
   :serial t)