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