Massive reorganization in progress.
[sod] / pre-reorg / pset.lisp
diff --git a/pre-reorg/pset.lisp b/pre-reorg/pset.lisp
new file mode 100644 (file)
index 0000000..20f0ff9
--- /dev/null
@@ -0,0 +1,272 @@
+;;; -*-lisp-*-
+;;;
+;;; Collections of properties
+;;;
+;;; (c) 2009 Straylight/Edgeware
+;;;
+
+;;;----- Licensing notice ---------------------------------------------------
+;;;
+;;; This file is part of the Simple Object Definition system.
+;;;
+;;; 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)
+
+;;;--------------------------------------------------------------------------
+;;; Expression parser.
+
+(defun parse-expression (lexer)
+  "Parse an expression from the LEXER.
+
+   The return values are the expression's VALUE and TYPE; currently the types
+   are :ID, :INTEGER, :STRING, and :CHAR.  If an error prevented a sane value
+   being produced, the TYPE :INVALID is returned.
+
+   Expression syntax is rather limited at the moment:
+
+   expression : term | expression `+' term | expression `-' term
+   term : factor | term `*' factor | term `/' factor
+   factor : primary | `+' factor | `-' factor
+   primary : integer | identifier | string
+          | `(' expression `)'
+          | `?' lisp-expression
+
+   Identifiers are just standalone things.  They don't name values.  The
+   operators only work on integer values at the moment.  (Confusingly, you
+   can manufacture rational numbers using the division operator, but they
+   still get called integers.)"
+
+  (let ((valstack nil)
+       (opstack nil))
+
+    ;; The following is a simple operator-precedence parser: the
+    ;; recursive-descent parser I wrote the first time was about twice the
+    ;; size and harder to extend.
+    ;;
+    ;; The parser flips between two states, OPERAND and OPERATOR.  It starts
+    ;; out in OPERAND state, and tries to parse a sequence of prefix
+    ;; operators followed by a primary expression.  Once it's found one, it
+    ;; pushes the operand onto the value stack and flips to OPERATOR state;
+    ;; if it fails, it reports a syntax error and exits.  The OPERAND state
+    ;; tries to read a sequence of postfix operators followed by an infix
+    ;; operator; if it fails, it assumes that it hit the stuff following the
+    ;; expression and stops.
+    ;;
+    ;; Each operator is pushed onto a stack consisting of lists of the form
+    ;; (FUNC PREC TY*).  The PREC is a precedence -- higher numbers mean
+    ;; tighter binding.  The TY* are operand types; operands are popped off
+    ;; the operand stack, checked against the requested types, and passed to
+    ;; the FUNC, which returns a new operand to be pushed in their place.
+    ;;
+    ;; Usually, when a binary operator is pushed, existing stacked operators
+    ;; with higher precedence are applied.  Whether operators with /equal/
+    ;; precedence are also applied depends on the associativity of the
+    ;; operator: apply equal precedence operators for left-associative
+    ;; operators, don't apply for right-associative.  When we reach the end
+    ;; of the expression, all the remaining operators on the stack are
+    ;; applied.
+    ;;
+    ;; Parenthesized subexpressions are implemented using a hack: when we
+    ;; find an open paren in operand position, a fake operator is pushed with
+    ;; an artificially low precedece, which protects the operators beneath
+    ;; from premature application.  The fake operator's function reports an
+    ;; error -- this will be triggered only if we reach the end of the
+    ;; expression before a matching close-paren, because the close-paren
+    ;; handler will pop the fake operator before it does any harm.
+
+    (restart-case
+       (labels ((apply-op (op)
+                  ;; Apply the single operator list OP to the values on the
+                  ;; value stack.
+                  (let ((func (pop op))
+                        (args nil))
+                    (dolist (ty (reverse (cdr op)))
+                      (let ((arg (pop valstack)))
+                        (cond ((eq (car arg) :invalid)
+                               (setf func nil))
+                              ((eq (car arg) ty)
+                               (push (cdr arg) args))
+                              (t
+                               (cerror* "Type mismatch: wanted ~A; found ~A"
+                                        ty (car arg))
+                               (setf func nil)))))
+                    (if func
+                        (multiple-value-bind (type value) (apply func args)
+                          (push (cons type value) valstack))
+                        (push '(:invalid . nil) valstack))))
+
+                (apply-all (prec)
+                  ;; Apply all operators with precedence PREC or higher.
+                  (loop
+                    (when (or (null opstack) (< (cadar opstack) prec))
+                      (return))
+                    (apply-op (pop opstack)))))
+
+         (tagbody
+
+          operand
+            ;; Operand state.  Push prefix operators, and try to read a
+            ;; primary operand.
+            (case (token-type lexer)
+
+              ;; Aha.  A primary.  Push it onto the stack, and see if
+              ;; there's an infix operator.
+              ((:integer :id :string :char)
+               (push (cons (token-type lexer)
+                           (token-value lexer))
+                     valstack)
+               (go operator))
+
+              ;; Look for a Lisp S-expression.
+              (#\?
+               (with-lexer-stream (stream lexer)
+                 (let ((value (eval (read stream t))))
+                   (push (cons (property-type value) value) valstack)))
+               (go operator))
+
+              ;; Arithmetic unary operators.  Push an operator for `+' for
+              ;; the sake of type-checking.
+              (#\+
+               (push (list (lambda (x) (values :integer x))
+                           10 :integer)
+                     opstack))
+              (#\-
+               (push (list (lambda (x) (values :integer (- x)))
+                           10 :integer)
+                     opstack))
+
+              ;; The open-paren hack.  Push a magic marker which will
+              ;; trigger an error if we hit the end of the expression.
+              ;; Inside the paren, we're still looking for an operand.
+              (#\(
+               (push (list (lambda ()
+                             (error "Expected `)' but found ~A"
+                                    (format-token lexer)))
+                           -1)
+                     opstack))
+
+              ;; Failed to find anything.  Report an error and give up.
+              (t
+               (error "Expected expression but found ~A"
+                      (format-token lexer))))
+
+            ;; Assume prefix operators as the default, so go round for more.
+            (next-token lexer)
+            (go operand)
+
+          operator
+            ;; Operator state.  Push postfix operators, and try to read an
+            ;; infix operator.  It turns out that we're always a token
+            ;; behind here, so catch up.
+            (next-token lexer)
+            (case (token-type lexer)
+
+              ;; Binary operators.
+              (#\+ (apply-all 3)
+                   (push (list (lambda (x y) (values :integer (+ x y)))
+                               3 :integer :integer)
+                         opstack))
+              (#\- (apply-all 3)
+                   (push (list (lambda (x y) (values :integer (- x y)))
+                               3 :integer :integer)
+                         opstack))
+              (#\* (apply-all 5)
+                   (push (list (lambda (x y) (values :integer (* x y)))
+                               5 :integer :integer)
+                         opstack))
+              (#\/ (apply-all 5)
+                   (push (list (lambda (x y)
+                                 (if (zerop y)
+                                     (progn (cerror* "Division by zero")
+                                            (values nil :invalid))
+                                     (values (/ x y) :integer)))
+                               5 :integer :integer)
+                         opstack))
+
+              ;; The close-paren hack.  Finish off the operators pushed
+              ;; since the open-paren.  If the operator stack is now empty,
+              ;; this is someone else's paren, so exit.  Otherwise pop our
+              ;; magic marker, and continue looking for an operator.
+              (#\) (apply-all 0)
+                   (when (null opstack)
+                     (go done))
+                   (pop opstack)
+                   (go operator))
+
+              ;; Nothing useful.  Must have hit the end, so leave.
+              (t (go done)))
+
+            ;; Assume we found the binary operator as a default, so snarf a
+            ;; token and head back.
+            (next-token lexer)
+            (go operand)
+
+          done)
+
+         ;; Apply all the pending operators.  If there's an unmatched
+         ;; open paren, this will trigger the error message.
+         (apply-all -99)
+
+         ;; If everything worked out, we should have exactly one operand
+         ;; left.  This is the one we want.
+         (assert (and (consp valstack)
+                      (null (cdr valstack))))
+         (values (cdar valstack) (caar valstack)))
+      (continue ()
+       :report "Return an invalid value and continue."
+       (values nil :invalid)))))
+
+;;;--------------------------------------------------------------------------
+;;; Property set parsing.
+
+(defun parse-property (lexer pset)
+  "Parse a single property from LEXER; add it to PSET."
+  (let ((name (require-token lexer :id)))
+    (require-token lexer #\=)
+    (multiple-value-bind (value type) (parse-expression lexer)
+      (unless (eq type :invalid)
+       (add-property pset name value :type type :location lexer)))))
+
+(defun parse-property-set (lexer)
+  "Parse a property set from LEXER.
+
+   If there wasn't one to parse, return nil; this isn't considered an error,
+   and GET-PROPERTY will perfectly happily report defaults for all requested
+   properties."
+
+  (when (require-token lexer #\[ :errorp nil)
+    (let ((pset (make-pset)))
+      (loop
+       (parse-property lexer pset)
+       (unless (require-token lexer #\, :errorp nil)
+         (return)))
+      (require-token lexer #\])
+      pset)))
+
+;;;--------------------------------------------------------------------------
+;;; Testing cruft.
+
+#+test
+(with-input-from-string (raw "[role = before, integer = 42 * (3 - 1)]")
+  (let* ((in (make-instance 'position-aware-input-stream :stream raw))
+        (lexer (make-instance 'sod-lexer :stream in)))
+    (next-char lexer)
+    (next-token lexer)
+    (multiple-value-call #'values
+      (parse-property-set lexer)
+      (token-type lexer))))
+
+;;;----- That's all, folks --------------------------------------------------