source: CGBLisp/src/infix.lisp@ 1

Last change on this file since 1 was 1, checked in by Marek Rychlik, 15 years ago

First import of a version circa 1997.

File size: 40.3 KB
Line 
1;;; -*- Mode: Lisp -*-
2;;; $Id: infix.lisp,v 1.4 2009/01/22 04:02:59 marek Exp $
3;;; ****************************************************************
4;;; CHANGE LOG:
5;;; * (1997/12/6) Fixed code for valid-numberp, so that "" is no longer
6;;; recognized as valid number
7;;; * (1997/11/29) Modified by M. Rychlik(rychlik@math.arizona.edu):
8;;; The role of operators ^^ (exponentiation) and ^ (logxior)
9;;; of the original version was reversed
10;;; ****************************************************************
11;;;
12
13;;; Wed Jan 18 13:13:59 1995 by Mark Kantrowitz <mkant@FLATHEAD.OZ.CS.CMU.EDU>
14;;; infix.cl -- 40545 bytes
15
16;;; **************************************************************************
17;;; Infix ********************************************************************
18;;; **************************************************************************
19;;;
20;;; This is an implementation of an infix reader macro. It should run in any
21;;; valid Common Lisp and has been tested in Allegro CL 4.1, Lucid CL 4.0.1,
22;;; MCL 2.0 and CMU CL. It allows the user to type arithmetic expressions in
23;;; the traditional way (e.g., 1+2) when writing Lisp programs instead of
24;;; using the normal Lisp syntax (e.g., (+ 1 2)). It is not intended to be a
25;;; full replacement for the normal Lisp syntax. If you want a more complete
26;;; alternate syntax for Lisp, get a copy Apple's MLisp or Pratt's CGOL.
27;;;
28;;; Although similar in concept to the Symbolics infix reader (#<DIAMOND>),
29;;; no real effort has been made to ensure compatibility beyond coverage
30;;; of at least the same set of basic arithmetic operators. There are several
31;;; differences in the syntax beyond just the choice of #I as the macro
32;;; character. (Our syntax is a little bit more C-like than the Symbolics
33;;; macro in addition to some more subtle differences.)
34;;;
35;;; We initially chose $ as a macro character because of its association
36;;; with mathematics in LaTeX, but unfortunately that character is already
37;;; used in MCL. We switched to #I() because it was one of the few options
38;;; remaining.
39;;;
40;;; Written by Mark Kantrowitz, School of Computer Science,
41;;; Carnegie Mellon University, March 1993.
42;;;
43;;; Copyright (c) 1993 by Mark Kantrowitz. All rights reserved.
44;;;
45;;; Use and copying of this software and preparation of derivative works
46;;; based upon this software are permitted, so long as the following
47;;; conditions are met:
48;;; o no fees or compensation are charged for use, copies,
49;;; distribution or access to this software
50;;; o this copyright notice is included intact.
51;;; This software is made available AS IS, and no warranty is made about
52;;; the software or its performance.
53;;;
54;;; In no event will the author(s) or their institutions be liable to you for
55;;; damages, including lost profits, lost monies, or other special, incidental
56;;; or consequential damages, arising out of or in connection with the use or
57;;; inability to use (including but not limited to loss of data or data being
58;;; rendered inaccurate or losses sustained by third parties or a failure of
59;;; the program to operate as documented) the program, or for any claim by
60;;; any other party, whether in an action of contract, negligence, or
61;;; other tortious action.
62;;;
63;;; Please send bug reports, comments and suggestions to mkant@cs.cmu.edu.
64;;;
65;;; The current version of this software and a variety of related utilities
66;;; may be obtained from the Lisp Repository by anonymous ftp
67;;; from ftp.cs.cmu.edu [128.2.206.173] in the directory
68;;; user/ai/lang/lisp/code/syntax/infix/
69;;; If your site runs the Andrew File System, you can cd to the AFS directory
70;;; /afs/cs.cmu.edu/project/ai-repository/ai/lang/lisp/code/syntax/infix/
71;;;
72;;; If you wish to be added to the Lisp-Utilities@cs.cmu.edu mailing list,
73;;; send email to Lisp-Utilities-Request@cs.cmu.edu with your name, email
74;;; address, and affiliation. This mailing list is primarily for
75;;; notification about major updates, bug fixes, and additions to the Lisp
76;;; Utilities Repository. The mailing list is intended to have low traffic.
77;;;
78
79
80;;; ********************************
81;;; Documentation ******************
82;;; ********************************
83;;;
84;;; Syntax:
85;;;
86;;; Begin the reader macro with #I( and end it with ). For example,
87;;; #I( x^2 + y^2 )
88;;; is equivalent to the Lisp form
89;;; (+ (expt x 2) (expt y 2))
90;;; but much easier to read according to some folks.
91;;;
92;;; If you want to see the expansion, type a quote before the #I form
93;;; at the Lisp prompt:
94;;; > '#I(if x<y<=z then f(x)=x^2+y^2 else f(x)=x^2-y^2)
95;;; (IF (AND (< X Y) (<= Y Z))
96;;; (SETF (F X) (+ (EXPT X 2) (EXPT Y 2)))
97;;; (SETF (F X) (- (EXPT X 2) (EXPT Y 2))))
98;;;
99;;;
100;;; Operators:
101;;;
102;;; NOTE: == is equality, = is assignment (C-style).
103;;;
104;;; \ quoting character: x\-y --> x-y
105;;; ! lisp escape !(foo bar) --> (foo bar)
106;;; ; comment
107;;; x = y assignment (setf x y)
108;;; x += y increment (incf x y)
109;;; x -= y decrement (decf x y)
110;;; x *= y multiply and store (setf x (* x y))
111;;; x /= y divide and store (setf x (/ x y))
112;;; x|y bitwise logical inclusive or (logior x y)
113;;; x^^y bitwise logical exclusive or (logxor x y)
114;;; x&y bitwise logical and (logand x y)
115;;; x<<y left shift (ash x y)
116;;; x>>y right shift (ash x (- y))
117;;; ~x ones complement (unary) (lognot x)
118;;; x and y conjunction (and x y)
119;;; x && y conjunction (and x y)
120;;; x or y disjunction (or x y)
121;;; x || y disjunction (or x y)
122;;; not x negation (not x)
123;;; x^y exponentiation (expt x y)
124;;; x,y sequence (progn x y)
125;;; (x,y) sequence (progn x y)
126;;; also parenthesis (x+y)/z --> (/ (+ x y) z)
127;;; f(x,y) functions (f x y)
128;;; a[i,j] array reference (aref a i j)
129;;; x+y x*y arithmetic (+ x y) (* x y)
130;;; x-y x/y arithmetic (- x y) (/ x y)
131;;; -y value negation (- y)
132;;; x % y remainder (mod x y)
133;;; x<y x>y inequalities (< x y) (> x y)
134;;; x <= y x >= y inequalities (<= x y) (>= x y)
135;;; x == y equality (= x y)
136;;; x != y equality (not (= x y))
137;;; if p then q conditional (when p q)
138;;; if p then q else r conditional (if p q r)
139;;;
140
141
142;;; Precedence:
143;;;
144;;; The following precedence conventions are obeyed by the infix operators:
145;;; [ ( !
146;;; ^
147;;; ~
148;;; * / %
149;;; + -
150;;; << >>
151;;; < == > <= != >=
152;;; &
153;;; ^^
154;;; |
155;;; not
156;;; and
157;;; or
158;;; = += -= *= /=
159;;; ,
160;;; if
161;;; then else
162;;; ] )
163;;;
164;;; Note that logical negation has lower precedence than numeric comparison
165;;; so that "not a<b" becomes (not (< a b)), which is different from the
166;;; C precedence conventions. You can change the precedence conventions by
167;;; modifying the value of the variable *operator-ordering*.
168;;;
169
170
171;;; ********************************
172;;; To Do **************************
173;;; ********************************
174;;;
175;;; Write some more test cases.
176;;; Write some more syntactic optimizations.
177;;; Would really like ~x to be (not x), but need it for (lognot x).
178;;; Support for multiple languages, such as a Prolog parser, a
179;;; strictly C compatible parser, etc.
180
181;;; Create a more declarative format, where there is one big table of
182;;; operators with all the info on them, and also NOT have the list of
183;;; operators in the comment, where they are likely to become wrong when
184;;; changes are made to the code. For example, something like:
185#|
186(define-infix-operators
187 ([ 30 :matchfix aref :end ])
188 (* 20 :infix * )
189 (+ 10 :infix + :prefix + )
190 (& 10 :infix and )
191 (+= 10 :infix #'+=-operator )
192 ...)
193|#
194
195;;; ********************************
196;;; Change Log *********************
197;;; ********************************
198;;;
199;;; 9-MAR-93 mk Created
200;;; 12-MAR-93 mk Fixed defpackage form for Lucid.
201;;; 1.1:
202;;; 14-OCT-93 mk Changed macro character from #$ to #I(). Suggested by
203;;; Scott McKay.
204;;; 1.2:
205;;; 18-JAN-95 norvig Added *print-infix-copyright*, string->prefix, support
206;;; for #I"..." in addition to #i(...) which lets one
207;;; type #i"a|b" which doesn't confuse editors that aren't
208;;; |-aware. Also added := as a synonym for =, so that
209;;; '#i"car(a) := b" yields (SETF (CAR A) B).
210;;;
211;;; 1.3:
212;;; 28-JUN-96 mk Modified infix reader to allow whitespace between the #I
213;;; and the start of the expression.
214
215
216
217
218;;; ********************************
219;;; Implementation Notes ***********
220;;; ********************************
221;;;
222;;; Initially we tried implementing everything within the Lisp reader,
223;;; but found this to not be workable. Parameters had to be passed in
224;;; global variables, and some of the processing turned out to be
225;;; indelible, so it wasn't possible to use any kind of lookahead.
226;;; Center-embedded constructions were also a problem, due to the lack
227;;; of an explicit stack.
228;;;
229;;; So we took another tack, that used below. The #I macro binds the
230;;; *readtable* to a special readtable, which is used solely for tokenization
231;;; of the input. Then the problem is how to correctly parenthesize the input.
232;;; We do that with what is essentially a recursive-descent parser. An
233;;; expression is either a prefix operator followed by an expression, or an
234;;; expression followed by an infix operator followed by an expression. When
235;;; the latter expression is complex, the problem becomes a little tricky.
236;;; For example, suppose we have
237;;; exp1 op1 exp2 op2
238;;; We need to know whether to parenthesize it as
239;;; (exp1 op1 exp2) op2
240;;; or as
241;;; exp1 op1 (exp2 op2 ...)
242;;; The second case occurs either when op2 has precedence over op1 (e.g.,
243;;; * has precedence over +) or op2 and op1 are the same right-associative
244;;; operator (e.g., exponentiation). Thus the algorithm is as follows:
245;;; When we see op1, we want to gobble up exp2 op2 exp3 op3 ... opn expn+1
246;;; into an expression where op2 through opn all have higher precedence
247;;; than op1 (or are the same right-associative operator), and opn+1 doesn't.
248;;; This algorithm is implemented by the GATHER-SUPERIORS function.
249;;;
250;;; Because + and - are implemented in the infix readtable as terminating
251;;; macro cahracters, the exponentiation version of Lisp number syntax
252;;; 1e-3 == 0.001
253;;; doesn't work correctly -- it parses it as (- 1e 3). So we add a little
254;;; cleverness to GATHER-SUPERIORS to detect when the tokenizer goofed.
255;;; Since this requires the ability to lookahead two tokens, we use a
256;;; stack to implement the lookahead in PEEK-TOKEN and READ-TOKEN.
257;;;
258;;; Finally, the expression returned by GATHER-SUPERIORS sometimes needs to
259;;; be cleaned up a bit. For example, parsing a<b<c would normally return
260;;; (< (< a b) c), which obviously isn't correct. So POST-PROCESS-EXPRESSION
261;;; detects this and similar cases, replacing the expression with (< a b c).
262;;; For cases like a<b<=c, it replaces it with (and (< a b) (<= b c)).
263;;;
264
265
266;;; ********************************
267;;; Package Cruft ******************
268;;; ********************************
269
270(defpackage "INFIX"
271 (:use #-:lucid "COMMON-LISP"
272 #+:lucid "LISP" #+:lucid "LUCID-COMMON-LISP")
273 (:export test-infix string->prefix))
274
275(in-package "INFIX")
276
277(pushnew :infix *features*)
278
279#+debug(proclaim '(optimize (speed 0) (debug 3)))
280#-debug(proclaim '(optimize (speed 3) (debug 0)))
281
282(eval-when (compile load eval)
283 (defparameter *version* "1.3 28-JUN-96")
284 (defparameter *print-infix-copyright* t
285 "If non-NIL, prints a copyright notice upon loading this file.")
286
287 (defun infix-copyright (&optional (stream *standard-output*))
288 "Prints an INFIX copyright notice and header upon startup."
289 (format stream "~%;;; ~V,,,'*A" 73 "*")
290 (format stream "~%;;; Infix notation for Common Lisp.")
291 (format stream "~%;;; Version ~A." *version*)
292 (format stream "~%;;; Written by Mark Kantrowitz, ~
293 CMU School of Computer Science.")
294 (format stream "~%;;; Copyright (c) 1993-95. All rights reserved.")
295 (format stream "~%;;; May be freely redistributed, provided this ~
296 notice is left intact.")
297 (format stream "~%;;; This software is made available AS IS, without ~
298 any warranty.")
299 (format stream "~%;;; ~V,,,'*A~%" 73 "*")
300 (force-output stream))
301
302 ;; What this means is you can either turn off the copyright notice
303 ;; by setting the parameter, or you can turn it off by including
304 ;; (setf (get :infix :dont-print-copyright) t) in your lisp init file.
305 (when (and *print-infix-copyright*
306 (not (get :infix :dont-print-copyright)))
307 (infix-copyright)))
308
309;;; ********************************
310;;; Readtable **********************
311;;; ********************************
312
313(defparameter *infix-readtable* (copy-readtable nil))
314(defparameter *normal-readtable* (copy-readtable nil))
315
316(defmacro infix-error (format-string &rest args)
317 `(let ((*readtable* *normal-readtable*))
318 (error ,format-string ,@args)))
319
320(defun infix-reader (stream subchar arg)
321 ;; Read either #I(...) or #I"..."
322 (declare (ignore arg subchar))
323 (let ((first-char (peek-char nil stream t nil t)))
324 (cond ((char= first-char #\space)
325 (read-char stream) ; skip over whitespace
326 (infix-reader stream nil nil))
327 ((char= first-char #\")
328 ;; Read double-quote-delimited infix expressions.
329 (string->prefix (read stream t nil t)))
330 ((char= first-char #\()
331 (read-char stream) ; get rid of opening left parenthesis
332 (let ((*readtable* *infix-readtable*)
333 (*normal-readtable* *readtable*))
334 (read-infix stream)))
335 (t
336 (infix-error "Infix expression starts with ~A" first-char)))))
337
338(set-dispatch-macro-character #\# #\I #'infix-reader *readtable*) ; was #\# #\$
339
340(defun string->prefix (string)
341 "Convert a string to a prefix s-expression using the infix reader.
342 If the argument is not a string, just return it as is."
343 (if (stringp string)
344 (with-input-from-string (stream (concatenate 'string "#I(" string ")"))
345 (read stream))
346 string))
347
348
349(defun read-infix (stream)
350 (let* ((result (gather-superiors '\) stream)) ; %infix-end-token%
351 (next-token (read-token stream)))
352 (unless (same-token-p next-token '\)) ; %infix-end-token%
353 (infix-error "Infix expression ends with ~A." next-token))
354 result))
355
356(defun read-regular (stream)
357 (let ((*readtable* *normal-readtable*))
358 (read stream t nil t)))
359
360
361
362;;; ********************************
363;;; Reader Code ********************
364;;; ********************************
365
366(defun same-operator-p (x y)
367 (same-token-p x y))
368
369(defun same-token-p (x y)
370 (and (symbolp x)
371 (symbolp y)
372 (string-equal (symbol-name x) (symbol-name y))))
373
374;;; Peeking Token Reader
375
376(defvar *peeked-token* nil)
377(defun read-token (stream)
378 (if *peeked-token*
379 (pop *peeked-token*)
380 (read stream t nil t)))
381(defun peek-token (stream)
382 (unless *peeked-token*
383 (push (read stream t nil t) *peeked-token*))
384 (car *peeked-token*))
385
386;;; Hack to work around + and - being terminating macro characters,
387;;; so 1e-3 doesn't normally work correctly.
388
389(defun fancy-number-format-p (left operator stream)
390 (when (and (symbolp left)
391 (find operator '(+ -) :test #'same-operator-p))
392 (let* ((name (symbol-name left))
393 (length (length name)))
394 (when (and (valid-numberp (subseq name 0 (1- length)))
395 ;; Exponent, Single, Double, Float, or Long
396 (find (subseq name (1- length))
397 '("e" "s" "d" "f" "l")
398 :test #'string-equal))
399 (read-token stream)
400 (let ((right (peek-token stream)))
401 (cond ((integerp right)
402 ;; it is one of the fancy numbers, so return it
403 (read-token stream)
404 (let ((*readtable* *normal-readtable*))
405 (read-from-string (format nil "~A~A~A"
406 left operator right))))
407 (t
408 ;; it isn't one of the fancy numbers, so unread the token
409 (push operator *peeked-token*)
410 ;; and return nil
411 nil)))))))
412
413(defun valid-numberp (string)
414 (let ((saw-dot nil)
415 (char-list (coerce string 'list)))
416 (unless char-list (return-from valid-numberp nil))
417 (dolist (char char-list t)
418 (cond ((char= char #\.)
419 (if saw-dot
420 (return nil)
421 (setq saw-dot t)))
422 ((not (find char "01234567890" :test #'char=))
423 (return nil))))))
424
425;;; Gobbles an expression from the stream.
426
427(defun gather-superiors (previous-operator stream)
428 "Gathers an expression whose operators all exceed the precedence of
429 the operator to the left."
430 (let ((left (get-first-token stream)))
431 (loop
432 (setq left (post-process-expression left))
433 (let ((peeked-token (peek-token stream)))
434 (let ((fancy-p (fancy-number-format-p left peeked-token stream)))
435 (when fancy-p
436 ;; i.e., we've got a number like 1e-3 or 1e+3 or 1f-1
437 (setq left fancy-p
438 peeked-token (peek-token stream))))
439 (unless (or (operator-lessp previous-operator peeked-token)
440 (and (same-operator-p peeked-token previous-operator)
441 (operator-right-associative-p previous-operator)))
442 ;; The loop should continue when the peeked operator is
443 ;; either superior in precedence to the previous operator,
444 ;; or the same operator and right-associative.
445 (return left)))
446 (setq left (get-next-token stream left)))))
447
448(defun get-first-token (stream)
449 (let ((token (read-token stream)))
450 (if (token-operator-p token)
451 ;; It's an operator in a prefix context.
452 (apply-token-prefix-operator token stream)
453 ;; It's a regular token
454 token)))
455
456(defun apply-token-prefix-operator (token stream)
457 (let ((operator (get-token-prefix-operator token)))
458 (if operator
459 (funcall operator stream)
460 (infix-error "~A is not a prefix operator" token))))
461
462(defun get-next-token (stream left)
463 (let ((token (read-token stream)))
464 (apply-token-infix-operator token left stream)))
465
466(defun apply-token-infix-operator (token left stream)
467 (let ((operator (get-token-infix-operator token)))
468 (if operator
469 (funcall operator stream left)
470 (infix-error "~A is not an infix operator" token))))
471
472;;; Fix to read-delimited-list so that it works with tokens, not
473;;; characters.
474
475(defun infix-read-delimited-list (end-token delimiter-token stream)
476 (do ((next-token (peek-token stream) (peek-token stream))
477 (list nil))
478 ((same-token-p next-token end-token)
479 ;; We've hit the end. Remove the end-token from the stream.
480 (read-token stream)
481 ;; and return the list of tokens.
482 ;; Note that this does the right thing with [] and ().
483 (nreverse list))
484 ;; Ignore the delimiters.
485 (when (same-token-p next-token delimiter-token)
486 (read-token stream))
487 ;; Gather the expression until the next delimiter.
488 (push (gather-superiors delimiter-token stream) list)))
489
490
491
492;;; ********************************
493;;; Precedence *********************
494;;; ********************************
495
496(defparameter *operator-ordering*
497 '(( \[ \( \! ) ; \[ is array reference
498 ( ^ ) ; exponentiation
499 ( ~ ) ; lognot
500 ( * / % ) ; % is mod
501 ( + - )
502 ( << >> )
503 ( < == > <= != >= )
504 ( & ) ; logand
505 ( ^^ ) ; logxor
506 ( \| ) ; logior
507 ( not )
508 ( and )
509 ( or )
510 ;; Where should setf and friends go in the precedence?
511 ( = |:=| += -= *= /= )
512 ( \, ) ; progn (statement delimiter)
513 ( if )
514 ( then else )
515 ( \] \) )
516 ( %infix-end-token% )) ; end of infix expression
517 "Ordered list of operators of equal precedence.")
518
519(defun operator-lessp (op1 op2)
520 (dolist (ops *operator-ordering* nil)
521 (cond ((find op1 ops :test #'same-token-p)
522 (return nil))
523 ((find op2 ops :test #'same-token-p)
524 (return t)))))
525
526(defparameter *right-associative-operators* '(^ =))
527(defun operator-right-associative-p (operator)
528 (find operator *right-associative-operators*))
529
530
531
532;;; ********************************
533;;; Define Operators ***************
534;;; ********************************
535
536(defvar *token-operators* nil)
537(defvar *token-prefix-operator-table* (make-hash-table))
538(defvar *token-infix-operator-table* (make-hash-table))
539(defun token-operator-p (token)
540 (find token *token-operators*))
541(defun get-token-prefix-operator (token)
542 (gethash token *token-prefix-operator-table*))
543(defun get-token-infix-operator (token)
544 (gethash token *token-infix-operator-table*))
545
546(eval-when (compile load eval)
547 (defmacro define-token-operator (operator-name &key
548 (prefix nil prefix-p)
549 (infix nil infix-p))
550 `(progn
551 (pushnew ',operator-name *token-operators*)
552 ,(when prefix-p
553 `(setf (gethash ',operator-name *token-prefix-operator-table*)
554 #'(lambda (stream)
555 ,@(cond ((and (consp prefix)
556 (eq (car prefix) 'infix-error))
557 ;; To avoid ugly compiler warnings.
558 `((declare (ignore stream))
559 ,prefix))
560 (t
561 (list prefix))))))
562 ,(when infix-p
563 `(setf (gethash ',operator-name *token-infix-operator-table*)
564 #'(lambda (stream left)
565 ,@(cond ((and (consp infix)
566 (eq (car infix) 'infix-error))
567 ;; To avoid ugly compiler warnings.
568 `((declare (ignore stream left))
569 ,infix))
570 (t
571 (list infix)))))))))
572
573;;; Readtable definitions for characters, so that the right token is returned.
574(eval-when (compile load eval)
575 (defmacro define-character-tokenization (char function)
576 `(set-macro-character ,char ,function nil *infix-readtable*)))
577
578
579
580;;; ********************************
581;;; Operator Definitions ***********
582;;; ********************************
583
584(define-token-operator and
585 :infix `(and ,left ,(gather-superiors 'and stream)))
586(define-token-operator or
587 :infix `(or ,left ,(gather-superiors 'or stream)))
588(define-token-operator not
589 :prefix `(not ,(gather-superiors 'not stream)))
590
591(define-token-operator if
592 :prefix (let* ((test (gather-superiors 'if stream))
593 (then (cond ((same-token-p (peek-token stream) 'then)
594 (read-token stream)
595 (gather-superiors 'then stream))
596 (t
597 (infix-error "Missing THEN clause."))))
598 (else (when (same-token-p (peek-token stream) 'else)
599 (read-token stream)
600 (gather-superiors 'else stream))))
601 (cond ((and test then else)
602 `(if ,test ,then ,else))
603 ((and test then)
604 ;; no else clause
605 `(when ,test ,then))
606 ((and test else)
607 ;; no then clause
608 `(unless ,test ,else))
609 (t
610 ;; no then and else clauses --> always NIL
611 nil))))
612
613(define-token-operator then
614 :prefix (infix-error "THEN clause without an IF."))
615(define-token-operator else
616 :prefix (infix-error "ELSE clause without an IF."))
617
618(define-character-tokenization #\+
619 #'(lambda (stream char)
620 (declare (ignore char))
621 (cond ((char= (peek-char nil stream t nil t) #\=)
622 (read-char stream t nil t)
623 '+=)
624 (t
625 '+))))
626(define-token-operator +
627 :infix `(+ ,left ,(gather-superiors '+ stream))
628 :prefix (gather-superiors '+ stream))
629(define-token-operator +=
630 :infix `(incf ,left ,(gather-superiors '+= stream)))
631
632(define-character-tokenization #\-
633 #'(lambda (stream char)
634 (declare (ignore char))
635 (cond ((char= (peek-char nil stream t nil t) #\=)
636 (read-char stream t nil t)
637 '-=)
638 (t
639 '-))))
640(define-token-operator -
641 :infix `(- ,left ,(gather-superiors '- stream))
642 :prefix `(- ,(gather-superiors '- stream)))
643(define-token-operator -=
644 :infix `(decf ,left ,(gather-superiors '-= stream)))
645
646(define-character-tokenization #\*
647 #'(lambda (stream char)
648 (declare (ignore char))
649 (cond ((char= (peek-char nil stream t nil t) #\=)
650 (read-char stream t nil t)
651 '*=)
652 (t
653 '*))))
654(define-token-operator *
655 :infix `(* ,left ,(gather-superiors '* stream)))
656(define-token-operator *=
657 :infix `(,(if (symbolp left)
658 'setq
659 'setf)
660 ,left
661 (* ,left ,(gather-superiors '*= stream))))
662
663(define-character-tokenization #\/
664 #'(lambda (stream char)
665 (declare (ignore char))
666 (cond ((char= (peek-char nil stream t nil t) #\=)
667 (read-char stream t nil t)
668 '/=)
669 (t
670 '/))))
671(define-token-operator /
672 :infix `(/ ,left ,(gather-superiors '/ stream))
673 :prefix `(/ ,(gather-superiors '/ stream)))
674(define-token-operator /=
675 :infix `(,(if (symbolp left)
676 'setq
677 'setf)
678 ,left
679 (/ ,left ,(gather-superiors '/= stream))))
680
681(define-character-tokenization #\^
682 #'(lambda (stream char)
683 (declare (ignore char))
684 (cond ((char= (peek-char nil stream t nil t) #\^)
685 (read-char stream t nil t)
686 '^^)
687 (t
688 '^))))
689(define-token-operator ^
690 :infix `(expt ,left ,(gather-superiors '^ stream)))
691(define-token-operator ^^
692 :infix `(logxor ,left ,(gather-superiors '^^ stream)))
693
694(define-character-tokenization #\|
695 #'(lambda (stream char)
696 (declare (ignore char))
697 (cond ((char= (peek-char nil stream t nil t) #\|)
698 (read-char stream t nil t)
699 'or)
700 (t
701 '\|))))
702(define-token-operator \|
703 :infix `(logior ,left ,(gather-superiors '\| stream)))
704
705(define-character-tokenization #\&
706 #'(lambda (stream char)
707 (declare (ignore char))
708 (cond ((char= (peek-char nil stream t nil t) #\&)
709 (read-char stream t nil t)
710 'and)
711 (t
712 '\&))))
713(define-token-operator \&
714 :infix `(logand ,left ,(gather-superiors '\& stream)))
715
716(define-character-tokenization #\%
717 #'(lambda (stream char)
718 (declare (ignore stream char))
719 '\%))
720(define-token-operator \%
721 :infix `(mod ,left ,(gather-superiors '\% stream)))
722
723(define-character-tokenization #\~
724 #'(lambda (stream char)
725 (declare (ignore stream char))
726 '\~))
727(define-token-operator \~
728 :prefix `(lognot ,(gather-superiors '\~ stream)))
729
730(define-character-tokenization #\,
731 #'(lambda (stream char)
732 (declare (ignore stream char))
733 '\,))
734(define-token-operator \,
735 :infix `(progn ,left ,(gather-superiors '\, stream)))
736
737(define-character-tokenization #\=
738 #'(lambda (stream char)
739 (declare (ignore char))
740 (cond ((char= (peek-char nil stream t nil t) #\=)
741 (read-char stream t nil t)
742 '==)
743 (t
744 '=))))
745(define-token-operator ==
746 :infix `(= ,left ,(gather-superiors '== stream)))
747(define-token-operator =
748 :infix `(,(if (symbolp left)
749 'setq
750 'setf)
751 ,left
752 ,(gather-superiors '= stream)))
753
754(define-character-tokenization #\:
755 #'(lambda (stream char)
756 (declare (ignore char))
757 (cond ((char= (peek-char nil stream t nil t) #\=)
758 (read-char stream t nil t)
759 '|:=|)
760 (t
761 '|:|))))
762(define-token-operator |:=|
763 :infix `(,(if (symbolp left)
764 'setq
765 'setf)
766 ,left
767 ,(gather-superiors '|:=| stream)))
768
769(define-character-tokenization #\<
770 #'(lambda (stream char)
771 (declare (ignore char))
772 (cond ((char= (peek-char nil stream t nil t) #\=)
773 (read-char stream t nil t)
774 '<=)
775 ((char= (peek-char nil stream t nil t) #\<)
776 (read-char stream t nil t)
777 '<<)
778 (t
779 '<))))
780(define-token-operator <
781 :infix `(< ,left ,(gather-superiors '< stream)))
782(define-token-operator <=
783 :infix `(<= ,left ,(gather-superiors '<= stream)))
784(define-token-operator <<
785 :infix `(ash ,left ,(gather-superiors '<< stream)))
786
787(define-character-tokenization #\>
788 #'(lambda (stream char)
789 (declare (ignore char))
790 (cond ((char= (peek-char nil stream t nil t) #\=)
791 (read-char stream t nil t)
792 '>=)
793 ((char= (peek-char nil stream t nil t) #\>)
794 (read-char stream t nil t)
795 '>>)
796 (t
797 '>))))
798(define-token-operator >
799 :infix `(> ,left ,(gather-superiors '> stream)))
800(define-token-operator >=
801 :infix `(>= ,left ,(gather-superiors '>= stream)))
802(define-token-operator >>
803 :infix `(ash ,left (- ,(gather-superiors '>> stream))))
804
805(define-character-tokenization #\!
806 #'(lambda (stream char)
807 (declare (ignore char))
808 (cond ((char= (peek-char nil stream t nil t) #\=)
809 (read-char stream t nil t)
810 '!=)
811 (t
812 '!))))
813(define-token-operator !=
814 :infix `(not (= ,left ,(gather-superiors '!= stream))))
815(define-token-operator !
816 :prefix (read-regular stream))
817
818(define-character-tokenization #\[
819 #'(lambda (stream char)
820 (declare (ignore stream char))
821 '\[))
822(define-token-operator \[
823 :infix (let ((indices (infix-read-delimited-list '\] '\, stream)))
824 (if (null indices)
825 (infix-error "No indices found in array reference.")
826 `(aref ,left ,@indices)))
827 :prefix (let ((list-members (infix-read-delimited-list '\] '\, stream)))
828 `(:[ ,@list-members)))
829
830(define-character-tokenization #\(
831 #'(lambda (stream char)
832 (declare (ignore stream char))
833 '\())
834(define-token-operator \(
835 :infix `(,left ,@(infix-read-delimited-list '\) '\, stream))
836 :prefix (let ((list (infix-read-delimited-list '\) '\, stream)))
837 (if (null (rest list))
838 ;; only one element in list. works correctly if list is NIL
839 (first list)
840 ;; several elements in list
841 `(progn ,@list))))
842
843(define-character-tokenization #\]
844 #'(lambda (stream char)
845 (declare (ignore stream char))
846 '\]))
847(define-token-operator \]
848 :infix (infix-error "Extra close brace \"]\" in infix expression"))
849
850(define-character-tokenization #\)
851 #'(lambda (stream char)
852 (declare (ignore stream char))
853 '\)))
854(define-token-operator \)
855 :infix (infix-error "Extra close paren \")\" in infix expression"))
856
857#|
858;;; Commented out because no longer using $ as the macro character.
859(define-character-tokenization #\$
860 #'(lambda (stream char)
861 (declare (ignore stream char))
862 '%infix-end-token%))
863(define-token-operator %infix-end-token%
864 :infix (infix-error "Prematurely terminated infix expression")
865 :prefix (infix-error "Prematurely terminated infix expression"))
866|#
867
868(define-character-tokenization #\;
869 #'(lambda (stream char)
870 (declare (ignore char))
871 (do ((char (peek-char nil stream t nil t)
872 (peek-char nil stream t nil t)))
873 ((or (char= char #\newline) (char= char #\return)
874 ;; was #\$
875; (char= char #\))
876 )
877 ;; Gobble characters until the end of the line or the
878 ;; end of the input.
879 (cond ((or (char= char #\newline) (char= char #\return))
880 (read-char stream)
881 (read stream t nil t))
882 (t
883 ;; i.e., return %infix-end-token%
884 (read stream t nil t))))
885 (read-char stream))))
886
887
888
889;;; ********************************
890;;; Syntactic Modifications ********
891;;; ********************************
892
893;;; Post processes the expression to remove some unsightliness caused
894;;; by the way infix processes the input. Note that it is also required
895;;; for correctness in the a<b<=c case.
896
897(defun post-process-expression (expression)
898 (if (and (consp expression)
899 (= (length expression) 3))
900 (destructuring-bind (operator left right) expression
901 (cond ((and (consp left)
902 (same-operator-p (first left) operator)
903 (find operator '(+ * / - and or < > <= >= progn)
904 :test #'same-operator-p))
905 ;; Flatten the expression if possible
906 (cond ((and (eq operator '-)
907 (= (length left) 2))
908 ;; -a-b --> (+ (- a) (- b)).
909 `(+ ,left (- ,right)))
910 ((and (eq operator '/)
911 (= (length left) 2))
912 ;; ditto with /
913 `(/ (* ,(second left) ,right)))
914 (t
915 ;; merges a+b+c as (+ a b c).
916 (append left (list right)))))
917 ((and (consp left)
918 (eq operator '-)
919 (eq (first left) '+))
920 ;; merges a+b-c as (+ a b (- c)).
921 (append left (list `(- ,right))))
922 ((and (consp left)
923 (find operator '(< > <= >=))
924 (find (first left) '(< > <= >=)))
925 ;; a<b<c --> a<b and b<c
926 `(and ,left
927 (,operator ,(first (last left))
928 ,right)))
929 (t
930 expression)))
931 expression))
932
933
934
935;;; ********************************
936;;; Test Infix *********************
937;;; ********************************
938
939;;; Invoke with (infix:test-infix).
940;;; Prints out all the tests that fail and a count of the number of failures.
941
942(defparameter *test-cases*
943 ;; Note that in strings, we have to slashify \ as \\.
944 '(("1 * +2" (* 1 2))
945 ("1 * -2" (* 1 (- 2)))
946 ("1 * /2" (* 1 (/ 2)))
947 ("/2" (/ 2))
948 ("not true" (not true))
949 ("foo\\-bar" foo-bar)
950 ("a + b-c" (+ a b (- c)))
951 ("a + b\\-c" (+ a b-c))
952 ("f\\oo" |FoO|)
953 ("!foo-bar * 2" (* foo-bar 2))
954 ("!(foo bar baz)" (foo bar baz))
955 ("!foo-bar " foo-bar)
956 ;; The following now longer gives an eof error, since the close
957 ;; parenthesis terminates the token.
958 ("!foo-bar" foo-bar) ; eof error -- ! eats the close $
959 ("a+-b" (+ a (- b)))
960 ("a+b" (+ a b))
961 ("a+b*c" (+ a (* b c)))
962 ("a+b+c" (+ a b c))
963 ("a+b-c" (+ a b (- c)))
964 ("a+b-c+d" (+ a b (- c) d))
965 ("a+b-c-d" (+ a b (- c) (- d)))
966 ("a-b" (- a b))
967 ("a*b" (* a b))
968 ("a*b*c" (* a b c))
969 ("a*b+c" (+ (* a b) c))
970 ("a/b" (/ a b))
971 ("a^b" (expt a b))
972 ("foo/-bar" (/ foo (- bar)))
973 ("1+2*3^4" (+ 1 (* 2 (expt 3 4))))
974 ("1+2*3^4+5" (+ 1 (* 2 (expt 3 4)) 5))
975 ("2*3^4+1" (+ (* 2 (expt 3 4)) 1))
976 ("2+3^4*5" (+ 2 (* (expt 3 4) 5)))
977 ("2^3^4" (expt 2 (expt 3 4)))
978 ("x^2 + y^2" (+ (expt x 2) (expt y 2)))
979 ("(1+2)/3" (/ (+ 1 2) 3))
980 ("(a=b)" (setq a b))
981 ("(a=b,b=c)" (progn (setq a b) (setq b c)))
982 ("1*(2+3)" (* 1 (+ 2 3)))
983 ("1+2/3" (+ 1 (/ 2 3)))
984 ("a,b" (progn a b))
985 ("a,b,c" (progn a b c))
986 ("foo(a,b,(c,d))" (foo a b (progn c d)))
987 ("foo(a,b,c)" (foo a b c))
988 ("(a+b,c)" (progn (+ a b) c))
989 ("1" 1)
990 ("-1" (- 1))
991 ("+1" 1)
992 ("1." 1)
993 ("1.1" 1.1)
994 ("1e3" 1000.0)
995 ("1e-3" 0.001)
996 ("1f-3" 1f-3)
997 ("1e-3e" (- 1e 3e))
998 ("!1e-3 " 0.001)
999 ("a and b and c" (and a b c))
1000 ("a and b or c" (or (and a b) c))
1001 ("a and b" (and a b))
1002 ("a or b and c" (or a (and b c)))
1003 ("a or b" (or a b))
1004 ("a<b and b<c" (and (< a b) (< b c)))
1005 ("if (if a then b else c) then e" (when (if a b c) e))
1006 ("if 1 then 2 else 3+4" (if 1 2 (+ 3 4)))
1007 ("(if 1 then 2 else 3)+4" (+ (if 1 2 3) 4))
1008 ("if a < b then b else a" (if (< a b) b a))
1009 ("if a and b then c and d else e and f"
1010 (if (and a b) (and c d) (and e f)))
1011 ("if a or b then c or d else e or f" (if (or a b) (or c d) (or e f)))
1012 ("if a then (if b then c else d) else e" (if a (if b c d) e))
1013 ("if a then (if b then c) else d" (if a (when b c) d))
1014 ("if a then b else c" (if a b c))
1015 ("if a then b" (when a b))
1016 ("if a then if b then c else d else e" (if a (if b c d) e))
1017 ("if a then if b then c else d" (when a (if b c d)))
1018 ("if if a then b else c then e" (when (if a b c) e))
1019 ("if not a and not b then c" (when (and (not a) (not b)) c))
1020 ("if not a then not b else not c and d"
1021 (if (not a) (not b) (and (not c) d)))
1022 ("not a and not b" (and (not a) (not b)))
1023 ("not a or not b" (or (not a) (not b)))
1024 ("not a<b and not b<c" (and (not (< a b)) (not (< b c))))
1025 ("not a<b" (not (< a b)))
1026 ("a[i,k]*b[j,k]" (* (aref a i k) (aref b j k)))
1027 ("foo(bar)=foo[bar,baz]" (setf (foo bar) (aref foo bar baz)))
1028 ("foo(bar,baz)" (foo bar baz))
1029 ("foo[bar,baz]" (aref foo bar baz))
1030 ("foo[bar,baz]=barf" (setf (aref foo bar baz) barf))
1031 ("max = if a < b then b else a" (setq max (if (< a b) b a)))
1032 ("a < b < c" (< A B C))
1033 ("a < b <= c" (and (< a b) (<= b c)))
1034 ("a <= b <= c" (<= A B C))
1035 ("a <= b <= c" (<= A B C))
1036 ("a!=b and b<c" (and (not (= a b)) (< b c)))
1037 ("a!=b" (not (= a b)))
1038 ("a<b" (< a b))
1039 ("a==b" (= a b))
1040 ("a*b(c)+d" (+ (* a (b c)) d))
1041 ("a+b(c)*d" (+ a (* (b c) d)))
1042 ("a+b(c)+d" (+ a (b c) d))
1043 ("d+a*b(c)" (+ d (* a (b c))))
1044 ("+a+b" (+ a b))
1045 ("-a+b" (+ (- a) b))
1046 ("-a-b" (+ (- a) (- b)))
1047 ("-a-b-c" (+ (- a) (- b) (- c)))
1048 ("a*b/c" (/ (* a b) c))
1049 ("a+b-c" (+ a b (- c)))
1050 ("a-b-c" (- a b c))
1051 ("a/b*c" (* (/ a b) c))
1052 ("a/b/c" (/ a b c))
1053 ("/a/b" (/ (* a b)))
1054 ("a^b^c" (expt a (expt b c)))
1055 ("a(d)^b^c" (expt (a d) (expt b c)))
1056 ("a<b+c<d" (< a (+ b c) d))
1057 ("1*~2+3" (+ (* 1 (lognot 2)) 3))
1058 ("1+~2*3" (+ 1 (* (lognot 2) 3)))
1059 ("1+~2+3" (+ 1 (lognot 2) 3))
1060 ("f(a)*=g(b)" (setf (f a) (* (f a) (g b))))
1061 ("f(a)+=g(b)" (incf (f a) (g b)))
1062 ("f(a)-=g(b)" (decf (f a) (g b)))
1063 ("f(a)/=g(b)" (setf (f a) (/ (f a) (g b))))
1064 ("a&b" (logand a b))
1065 ("a^^b" (logxor a b))
1066 ("a|b" (logior a b))
1067 ("a<<b" (ash a b))
1068 ("a>>b" (ash a (- b)))
1069 ("~a" (lognot a))
1070 ("a&&b" (and a b))
1071 ("a||b" (or a b))
1072 ("a%b" (mod a b))
1073
1074 ;; Comment character -- must have carriage return after semicolon.
1075 ("x^2 ; the x coordinate
1076 + y^2 ; the y coordinate" :error)
1077 ("x^2 ; the x coordinate
1078 + y^2 ; the y coordinate
1079 " (+ (expt x 2) (expt y 2)))
1080
1081 ;; Errors
1082 ("foo(bar,baz" :error) ; premature termination
1083 ;; The following no longer gives an error
1084 ("foo(bar,baz))" (foo bar baz)) ; extra close parenthesis
1085 ("foo[bar,baz]]" :error) ; extra close bracket
1086 ("[foo,bar]" :error) ; AREF is not a prefix operator
1087 ("and a" :error) ; AND is not a prefix operator
1088 ("< a" :error) ; < is not a prefix operator
1089 ("=bar" :error) ; SETF is not a prefix operator
1090 ("*bar" :error) ; * is not a prefix operator
1091 ("a not b" :error) ; NOT is not an infix operator
1092 ("a if b then c" :error) ; IF is not an infix operator
1093 ("" :error) ; premature termination (empty clause)
1094 (")a" :error) ; left parent is not a prefix operator
1095 ("]a" :error) ; left bracket is not a prefix operator
1096 ))
1097
1098(defun test-infix (&optional (tests *test-cases*))
1099 (let ((count 0))
1100 (dolist (test tests)
1101 (destructuring-bind (string result) test
1102 (unless (test-infix-case string result)
1103 (incf count))))
1104 (format t "~&~:(~R~) test~p failed." count count)
1105 (values)))
1106
1107(defun test-infix-case (string result)
1108 (multiple-value-bind (value error)
1109 (let ((*package* (find-package "INFIX")))
1110 (ignore-errors
1111 (values (read-from-string (concatenate 'string "#I(" string ")")
1112 t nil))))
1113 (cond (error
1114 (cond ((eq result :error)
1115 t)
1116 (t
1117 (format t "~&Test #I(~A) failed with ERROR." string)
1118 nil)))
1119 ((eq result :error)
1120 (format t "~&Test #I(~A) failed. ~
1121 ~& Expected ERROR ~
1122 ~& but got ~A."
1123 string value)
1124 nil)
1125 ((not (equal value result))
1126 (format t "~&Test #I(~A) failed. ~
1127 ~& Expected ~A ~
1128 ~& but got ~A."
1129 string result value)
1130 nil)
1131 (t
1132 t))))
1133
1134;;; *EOF*
Note: See TracBrowser for help on using the repository browser.