--- /dev/null
+;;; -*-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.
+
+(export 'push-operator)
+(defgeneric push-operator (operator state)
+ (:documentation
+ "Push an OPERATOR onto the STATE's operator stack.
+
+ This should apply existing stacked operators as necessary to obey the
+ language's precedence rules."))
+
+(export 'push-value)
+(defgeneric push-value (value state)
+ (:documentation
+ "Push VALUE onto the STATE's value stack.
+
+ The default message just does that without any fuss. It's unlikely that
+ this will need changing unless you invent some really weird values."))
+
+(export 'apply-operator)
+(defgeneric apply-operator (operator state)
+ (:documentation
+ "Apply the OPERATOR to argument on the STATE's value stack.
+
+ This should pop any necessary arguments, and push the result."))
+
+(export 'operator-push-action)
+(defgeneric operator-push-action (left right)
+ (:documentation
+ "Determine relative precedence between LEFT and RIGHT operators.
+
+ Returns one of three possible values:
+
+ * `:push' means to push the RIGHT operator onto the stack, above the
+ LEFT operator -- i.e., RIGHT has higher precedence than LEFT.
+
+ * `:apply' means to apply the LEFT operator to arguments immediately
+ and try again, comparing RIGHT to the new topmost operator -- i.e.,
+ LEFT has higher precedence than RIGHT.
+
+ * `:error' means that the situation is erroneous: a continuable error is
+ signalled and the situation resolved by applying the LEFT operator and
+ then pushing the RIGHT one -- i.e., treating them as having similar
+ precedence and left associativity).
+
+ There is a default method which decides between `:push' and `:apply' by
+ comparing numerical precedence values."))
+
+(export 'expr)
+(defparse expr ((&key (nestedp (gensym "NESTEDP-")))
+ operand binop preop postop)
+ "Parse an expression involving unary and binary operators.
+
+ Within the parsers for operands and operators, the variable NESTEDP is
+ bound to a generalized boolean which is true if an unmatched open-
+ parenthesis has been seen.
+
+ The OPERAND parser should produce a value; the various operator parsers
+ (BINOP, PREOP, and POSTOP) should produce objects obeying the `operator'
+ protocol. The final output of the `expr' parse is the result of
+ evaluating the parsed expression. (Of course, the definition of
+ `evaluation' here is determined entirely by the methods on
+ `apply-operator', so the final value may be a parse tree, for example.)"
+
+ (flet ((wrap (parser)
+ `(parser (,nestedp)
+ (declare (ignorable ,nestedp))
+ ,parser)))
+ `(parse-expression ,(wrap operand)
+ ,(wrap binop)
+ ,(wrap preop)
+ ,(wrap postop))))
+
+;;;--------------------------------------------------------------------------
+;;; Numerical precedence.
+
+(export '(operator-left-precedence operator-right-precedence))
+(defgeneric operator-left-precedence (operator)
+ (:documentation
+ "Return the OPERATOR's left-precedence.
+
+ Higher precedence numbers indicate tighter leftward binding. Under the
+ default method for `operator-push-action', the OPERATOR's left precedence
+ is compared to the existing operators' right precedences to determine the
+ parser's behaviour: if it is higher, then the OPERATOR is pushed;
+ otherwise the existing operator is applied. Thus, equal precedences cause
+ left-associative parsing."))
+(defgeneric operator-right-precedence (operator)
+ (:documentation
+ "Return the OPERATOR's right-precedence.
+
+ Higher precedence numbers indicate tighter rightward binding. Under the
+ default method for `operator-push-action', a new operator's left
+ precedence may be compared to the existing OPERATOR's right precedences to
+ determine the parser's behaviour: if it is higher, then the new operator
+ is pushed; otherwise the existing OPERATOR is applied. Thus, equal
+ precedences cause left-associative parsing."))
+
+(defgeneric operator-associativity (operator)
+ (:documentation
+ "Returns an OPERATOR's associativity, as a symbol.
+
+ The return value is one of `:left', `:right' or `nil'. If two adjacent
+ operators have the same precedence, their associativities are compared.
+ If both associativities are `:left' then the left-hand operator is
+ considered to have higher precedence; if both are `:right' then the
+ right-hand operator is considered to have higher precedence. If they're
+ inconsistent or `nil', then an error is reported and the behaviour is as
+ if both were `:left'.")
+ (:method (operator) :left))
+
+;;;--------------------------------------------------------------------------
+;;; Basic operator protocol.
+
+(export 'prefix-operator)
+(defclass prefix-operator ()
+ ()
+ (:documentation
+ "Prefix operator base class.
+
+ Prefix operators are special because they are pushed at a time when the
+ existing topmost operator on the stack may not have its operand
+ available. It is therefore incorrect to attempt to apply any existing
+ operators without careful checking. This class provides a method on
+ `push-operator' which immediately pushes the new operator without
+ inspecting the existing stack."))
+
+(export 'simple-operator)
+(defclass simple-operator ()
+ ((function :initarg :function :reader operator-function)
+ (name :initarg :name :initform "<unnamed operator>"
+ :reader operator-name))
+ (:documentation
+ "A simple operator applies a FUNCTION to arguments when it is applied.
+
+ The precise details of the function are left to subclasses to sort out."))
+
+(export 'simple-unary-operator)
+(defclass simple-unary-operator (simple-operator)
+ ()
+ (:documentation
+ "A unary operator works on the topmost value on the value stack.
+
+ The topmost item is popped, the FUNCTION is applied to it, and the result
+ is pushed back on."))
+
+(export 'simple-binary-operator)
+(defclass simple-binary-operator (simple-operator)
+ ((lprec :initarg :left-precedence :initarg :precedence
+ :reader operator-left-precedence)
+ (rprec :initarg :right-precedence :reader operator-right-precedence)
+ (associativity :initarg :associative :initform :left
+ :reader operator-associativity))
+ (:documentation
+ "A binary operator works on the two topmost values on the value stack.
+
+ The function's arguments are the two topmost items in /reverse/ order --
+ so the topmost item is second. This is usually what you want.
+
+ The left and right precedences are settable independently. Usually (and
+ this is the default) you will set them equal, and use the `:associativity'
+ initarg to determine associativity; however, right-associativity can also
+ be obtained by setting the right-precedence lower than the left. Special
+ effects can be obtained by setting them in other ways. Use your
+ imagination."))
+
+(export 'simple-postfix-operator)
+(defclass simple-postfix-operator (simple-unary-operator)
+ ((lprec :initarg :left-precedence :initarg :precedence
+ :reader operator-left-precedence)
+ (rprec :initarg :right-precedence :reader operator-right-precedence))
+ (:documentation
+ "A postfix operator is applied to a single operand.
+
+ The left and right precedences are settable independently. Usually you
+ will want to set them equal (this is the default) and quite high. Special
+ effects can be obtained by doing other things instead; but note that you
+ will get an incorrect parse if the right precedence is lower than the left
+ precedence of a binary operator because the postfix operator will be
+ applied to the result of the binary operator."))
+
+(export 'simple-prefix-operator)
+(defclass simple-prefix-operator (prefix-operator simple-unary-operator)
+ ((rprec :initarg :precedence :reader operator-right-precedence))
+ (:documentation
+ "A prefix operator is applied to a single operand.
+
+ There is only one precedence value for a prefix operator: the
+ `prefix-operator' superclass arranges that the left precedence is
+ effectively minus infinity."))
+
+(export 'preop)
+(defmacro preop (name (x prec) &body body)
+ "Define a prefix operator.
+
+ The operator will be called NAME in error messages, and have right
+ precedence PREC. To apply the operator, BODY is evaluated with X bound to
+ the operand."
+
+ `(make-instance 'simple-prefix-operator
+ :name ,name
+ :precedence ,prec
+ :function (lambda (,x) ,@body)))
+
+(export 'postop)
+(defmacro postop (name (x prec &key rprec) &body body)
+ "Define a postfix operator.
+
+ The operator will be called NAME in error messages, and have left
+ precedence PREC and right precendence RPREC (defaulting to PREC). To
+ apply the operator, BODY is evaluated with X bound to the operand."
+
+ (once-only (name prec rprec)
+ `(make-instance 'simple-postfix-operator
+ :name ,name
+ :left-precedence ,prec
+ :right-precedence ,(or rprec prec)
+ :function (lambda (,x) ,@body))))
+
+(export 'binop)
+(defmacro binop (name (x y prec &key rprec (assoc :left)) &body body)
+ "Define a binary operator.
+
+ The operator will be called NAME in error messages, and have left
+ precedence PREC and right precedence RPREC (defaulting to PREC, implying
+ left associativity under the default `operator-push-action'
+ implementation. To apply the operator, BODY is evaluated with X and Y
+ bound to the operands in the order they were parsed"
+
+ (once-only (name prec rprec assoc)
+ `(make-instance 'simple-binary-operator
+ :name ,name
+ :left-precedence ,prec
+ :right-precedence ,(or rprec prec)
+ :associative ,assoc
+ :function (lambda (,x ,y) ,@body))))
+
+;;;--------------------------------------------------------------------------
+;;; Parentheses.
+
+(defclass parenthesis ()
+ ((tag :initarg :tag :initform nil))
+ (:documentation
+ "Base class for parenthesis operators."))
+
+(export 'open-parenthesis)
+(defclass open-parenthesis (parenthesis prefix-operator) ())
+
+(export 'close-parenthesis)
+(defclass close-parenthesis (parenthesis) ())
+
+(export '(lparen rparen))
+(defun lparen (tag)
+ (make-instance 'open-parenthesis :tag tag))
+(defun rparen (tag)
+ (make-instance 'close-parenthesis :tag tag))
+
+;;;----- That's all, folks --------------------------------------------------