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