Change naming convention around.
[sod] / src / parser / parser-expr-impl.lisp
diff --git a/src/parser/parser-expr-impl.lisp b/src/parser/parser-expr-impl.lisp
new file mode 100644 (file)
index 0000000..89b0f58
--- /dev/null
@@ -0,0 +1,219 @@
+;;; -*-lisp-*-
+;;;
+;;; Parsers for expressions with binary operators
+;;;
+;;; (c) 2009 Straylight/Edgeware
+;;;
+
+;;;----- Licensing notice ---------------------------------------------------
+;;;
+;;; This file is part of the Sensble Object Design, an object system for C.
+;;;
+;;; SOD 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.
+;;;
+;;; SOD 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 SOD; if not, write to the Free Software Foundation,
+;;; Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+(cl:in-package #:sod-parser)
+
+;;;--------------------------------------------------------------------------
+;;; Basic protocol implementation.
+
+(defclass expression-parse-state ()
+  ((opstack :initform nil :type list)
+   (valstack :initform nil :type list)
+   (nesting :initform 0 :type fixnum))
+  (:documentation
+   "State for the expression parser.  Largely passive."))
+
+(defmethod push-value (value (state expression-parse-state))
+  (with-slots (valstack) state
+    (push value valstack)))
+
+(defmethod push-operator (operator (state expression-parse-state))
+  (with-slots (opstack) state
+    (loop
+      (when (null opstack) (return))
+      (let ((head (car opstack)))
+       (ecase (operator-push-action head operator)
+         (:push (return))
+         (:error (cerror* "Parse error: ... ~A ... ~A ... forbidden; ~
+                           operators aren't associative"
+                          head operator))
+         (:apply (apply-operator head state)
+                 (setf opstack (cdr opstack))))))
+    (push operator opstack)))
+
+(defgeneric apply-pending-operators (state)
+  (:documentation
+   "Apply all of the pending operators to their arguments.
+
+   The return value is the final result of the parse.  By the time all of the
+   operators have been applied, of course, there ought to be exactly one
+   operand remaining.")
+  (:method ((state expression-parse-state))
+    (with-slots (opstack valstack) state
+      (dolist (operator opstack)
+       (apply-operator operator state))
+      (assert (and (consp valstack) (null (cdr valstack))))
+      (pop valstack))))
+
+;;;--------------------------------------------------------------------------
+;;; Basic operator implementation.
+
+(defmethod operator-push-action (left right)
+  (let ((lprec (operator-right-precedence left))
+       (rprec (operator-left-precedence right)))
+    (cond ((< lprec rprec) :push)
+         ((> lprec rprec) :apply)
+         (t (let ((lassoc (operator-associativity left))
+                  (rassoc (operator-associativity right)))
+              (cond ((not (eq lassoc rassoc))
+                     (cerror* "Parse error: ... ~A ... ~A ...: ~
+                               inconsistent associativity: ~
+                               ~(~A~) versus ~(~A~))"
+                              left right
+                              (or lassoc "none") (or rassoc "none"))
+                     :apply)
+                    ((not lassoc)
+                     (cerror* "Parse error: ... ~A ... ~A ...: ~
+                               operators are not associative"
+                              left right)
+                     :apply)
+                    ((eq lassoc :left) :apply)
+                    ((eq lassoc :right) :push)
+                    (t (error "Invalid associativity ~S ~
+                               for operators ~A and ~A"
+                              lassoc left right))))))))
+
+(defmethod print-object ((operator simple-operator) stream)
+  (maybe-print-unreadable-object (operator stream :type t)
+    (princ (operator-name operator) stream)))
+
+(defmethod shared-initialize :after
+    ((operator simple-binary-operator) slot-names &key)
+  (when (slot-boundp operator 'lprec)
+    (default-slot (operator 'rprec slot-names)
+      (slot-value operator 'lprec))))
+
+(defmethod push-operator
+    ((operator prefix-operator) (state expression-parse-state))
+
+  ;; It's not safe to apply stacked operators here.  Already-stacked prefix
+  ;; operators won't have their operands yet, so we'll end up in an
+  ;; inconsistent state.
+  (with-slots (opstack) state
+    (push operator opstack)))
+
+(defmethod apply-operator
+    ((operator simple-unary-operator) (state expression-parse-state))
+  (with-slots (function) operator
+    (with-slots (valstack) state
+      (assert (not (null valstack)))
+      (push (funcall function (pop valstack)) valstack))))
+
+(defmethod apply-operator
+    ((operator simple-binary-operator) (state expression-parse-state))
+  (with-slots (function) operator
+    (with-slots (valstack) state
+      (assert (not (or (null valstack)
+                      (null (cdr valstack)))))
+      (let ((second (pop valstack))
+           (first (pop valstack)))
+       (push (funcall function first second) valstack)))))
+
+;;;--------------------------------------------------------------------------
+;;; Parenthesis protocol implementation.
+
+(defmethod push-operator :after
+    ((paren open-parenthesis) (state expression-parse-state))
+  (with-slots (nesting) state
+    (incf nesting)))
+
+(defmethod push-operator
+    ((paren close-parenthesis) (state expression-parse-state))
+  (with-slots (opstack nesting) state
+    (with-slots (tag) paren
+      (flet ((fail ()
+              (cerror* "Parse error: spurious `~A'" tag)
+              (return-from push-operator)))
+       (loop
+         (when (null opstack) (fail))
+         (let ((head (car opstack)))
+           (cond ((not (typep head 'open-parenthesis))
+                  (apply-operator head state))
+                 ((not (eq (slot-value head 'tag) tag))
+                  (fail))
+                 (t
+                  (return)))
+           (setf opstack (cdr opstack))))
+       (setf opstack (cdr opstack))
+       (decf nesting)))))
+
+(defmethod apply-operator
+    ((paren open-parenthesis) (state expression-parse-state))
+  (with-slots (tag) paren
+    (cerror* "Parse error: missing `~A'" tag)))
+
+(defmethod operator-push-action (left (right open-parenthesis))
+  :push)
+
+(defmethod operator-push-action ((left open-parenthesis) right)
+  :push)
+
+;;;--------------------------------------------------------------------------
+;;; Main expression parser implementation.
+
+(defun parse-expression (p-operand p-binop p-preop p-postop)
+  "Parse an expression consisting of operands and various kinds of operators.
+
+   The arguments are all parser functions: they will be called with one
+   argument NESTEDP, which indicates whether the parse has encountered an
+   unmatched parenthesis."
+
+  (let ((state (make-instance 'expression-parse-state))
+       (consumed-any-p nil))
+
+    (labels ((fail (expected)
+              (return-from parse-expression
+                (values expected nil consumed-any-p)))
+
+            (parse (parser)
+              (unless parser
+                (return-from parse (values nil nil)))
+              (multiple-value-bind (value winp consumedp)
+                  (funcall parser (plusp (slot-value state 'nesting)))
+                (when consumedp (setf consumed-any-p t))
+                (unless (or winp (not consumedp)) (fail value))
+                (values value winp)))
+
+            (get-operand ()
+              (loop (multiple-value-bind (value winp) (parse p-preop)
+                      (unless winp (return))
+                      (push-operator value state)))
+              (multiple-value-bind (value winp) (parse p-operand)
+                (unless winp (fail value))
+                (push-value value state))
+              (loop (multiple-value-bind (value winp) (parse p-postop)
+                      (unless winp (return))
+                      (push-operator value state)))))
+
+      (get-operand)
+      (loop
+       (multiple-value-bind (value winp) (parse p-binop)
+         (unless winp (return))
+         (push-operator value state))
+       (get-operand))
+
+      (values (apply-pending-operators state) t consumed-any-p))))
+
+;;;----- That's all, folks --------------------------------------------------