indent.lsp revision 5dfecf96
1;
2;; Copyright (c) 2002 by The XFree86 Project, Inc.
3;;
4;; Permission is hereby granted, free of charge, to any person obtaining a
5;; copy of this software and associated documentation files (the "Software"),
6;; to deal in the Software without restriction, including without limitation
7;; the rights to use, copy, modify, merge, publish, distribute, sublicense,
8;; and/or sell copies of the Software, and to permit persons to whom the
9;; Software is furnished to do so, subject to the following conditions:
10;;
11;; The above copyright notice and this permission notice shall be included in
12;; all copies or substantial portions of the Software.
13;;
14;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
17;; THE XFREE86 PROJECT BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
18;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
19;; OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20;; SOFTWARE.
21;;
22;; Except as contained in this notice, the name of the XFree86 Project shall
23;; not be used in advertising or otherwise to promote the sale, use or other
24;; dealings in this Software without prior written authorization from the
25;; XFree86 Project.
26;;
27;; Author: Paulo César Pereira de Andrade
28;;
29;;
30;; $XFree86: xc/programs/xedit/lisp/modules/indent.lsp,v 1.6 2003/01/16 03:50:46 paulo Exp $
31;;
32
33(provide "indent")
34(require "xedit")
35(in-package "XEDIT")
36
37(defconstant indent-spaces '(#\Tab #\Space))
38
39;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
40;; The final indentation function.
41;; Parameters:
42;;	indent
43;;		Number of spaces to insert
44;;	offset
45;;		Offset to where indentation should be added
46;;	no-tabs
47;;		If set, tabs aren't inserted
48;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
49(defun indent-text (indent offset &optional no-tabs
50		    &aux start line length index current tabs spaces string
51			 barrier base result (point (point))
52		   )
53
54    ;; Initialize
55    (setq
56	start	(scan offset :eol :left)
57	line	(read-text start (- offset start))
58	length	(length line)
59	index	(1- length)
60	current	0
61	base	0
62    )
63
64    (and (minusp indent) (setq indent 0))
65
66    ;; Skip any spaces after offset, "paranoia check"
67    (while (member (char-after offset) indent-spaces)
68	(incf offset)
69    )
70
71    ;; Check if there are only spaces before `offset' and the line `start'
72    (while (and (>= index 0) (member (char line index) indent-spaces))
73	(decf index)
74    )
75
76    ;; `index' will be zero if there are only spaces in the `line'
77    (setq barrier (+ start (incf index)))
78
79    ;; Calculate `base' unmodifiable indentation, if any
80    (dotimes (i index)
81	(if (char= (char line i) #\Tab)
82	    (incf base (- 8 (rem base 8)))
83	    (incf base)
84	)
85    )
86
87    ;; If any non blank character would need to be deleted
88    (and (> base indent) (return-from indent-text nil))
89
90    ;; Calculate `current' indentation
91    (setq current base)
92    (while (< index length)
93	(if (char= (char line index) #\Tab)
94	    (incf current (- 8 (rem current 8)))
95	    (incf current)
96	)
97	(incf index)
98    )
99
100    ;; Maybe could also "optimize" the indentation even if it is already
101    ;; correct, removing spaces "inside" tabs.
102    (when (/= indent current)
103	(if no-tabs
104	    (setq
105		length	(- indent base)
106		result	(+ barrier length)
107		string	(make-string length :initial-element #\Space)
108	    )
109	    (progn
110		(multiple-value-setq (tabs spaces) (floor (- indent base) 8))
111		(setq
112		    length	(+ tabs spaces)
113		    result	(+ barrier length)
114		    string	(make-string length :initial-element #\Tab)
115		)
116		(fill string #\Space :start tabs)
117	    )
118	)
119
120	(replace-text barrier offset string)
121	(and (>= offset point) (>= point barrier) (goto-char result))
122    )
123)
124(compile 'indent-text)
125
126;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
127;; Helper function, returns indentation of a given offset
128;; If `align' is set, stop once a non blank character is seen, that
129;; is, use `offset' only as a line identifier
130;; If `resolve' is set, it means that the offset is just a hint, it
131;; maybe anywhere in the line
132;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
133(defun offset-indentation (offset &key resolve align
134			   &aux
135			   char
136			   line
137			   (start (scan offset :eol :left))
138			   (indent 0))
139    (if resolve
140	(loop
141	    (if (characterp (setq char (char-after start)))
142		(if (char= char #\Tab)
143		    (incf indent (- 8 (rem indent 8)))
144		    ;; Not a tab, check if is a space
145		    (if (char= char #\Space)
146			(incf indent)
147			;; Not a tab neither a space
148			(return indent)
149		    )
150		)
151		;; EOF found
152		(return indent)
153	    )
154	    ;; Increment offset to check next character
155	    (incf start)
156	)
157	(progn
158	    (setq line (read-text start (- offset start)))
159	    (dotimes (i (length line) indent)
160		(if (char= (setq char (char line i)) #\Tab)
161		    (incf indent (- 8 (rem indent 8)))
162		    (progn
163			(or align (member char indent-spaces)
164			    (return indent)
165			)
166			(incf indent)
167		    )
168		)
169	    )
170	)
171    )
172)
173(compile 'offset-indentation)
174
175;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
176;;  A default/fallback indentation function, just copy indentation
177;; of previous line.
178;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
179(defun default-indent (syntax syntable)
180    (let
181	(
182	(offset (scan (point) :eol :left))
183	start
184	left
185	right
186	)
187
188	syntable	;; XXX hack to not generate warning about unused
189			;; variable, should be temporary (until unused
190			;; variables can be declared as such)
191
192	(if
193	    (or
194		;; if indentation is disabled
195		(and
196		    (hash-table-p (syntax-options syntax))
197		    (gethash :disable-indent (syntax-options syntax))
198		)
199		;; or if not at the start of a new line
200		(> (scan offset :eol :right) offset)
201	    )
202	    (return-from default-indent)
203	)
204
205	(setq left offset)
206	(loop
207	    (setq
208		start left
209		left (scan start :eol :left :count 2)
210		right (scan left :eol :right)
211	    )
212	    ;; if start of file reached
213	    (and (>= left start) (return))
214	    (when
215		(setq
216		    start
217		    (position-if-not
218			#'(lambda (char) (member char indent-spaces))
219			(read-text left (- right left))
220		    )
221		)
222
223		;; indent the current line
224		(indent-text (offset-indentation (+ left start) :align t) offset)
225		(return)
226	    )
227	)
228    )
229)
230(compile 'default-indent)
231
232;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
233;; Helper function
234;;   Clear line before cursor if it is empty
235;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
236(defun indent-clear-empty-line (&aux left offset right line index)
237    (setq
238	offset	(scan (point) :eol :left)
239	left	(scan offset :eol :left :count 2)
240	right	(scan left :eol :right)
241    )
242
243    ;; If not at the first line in the file and line is not already empty
244    (when (and (/= offset left) (/= left right))
245	(setq
246	    line	(read-text left (- right left))
247	    index	(1- (length line))
248	)
249	(while (and (>= index 0) (member (char line index) indent-spaces))
250	    (decf index)
251	)
252	;; If line was only spaces
253	(and (minusp index) (replace-text left right ""))
254    )
255)
256
257;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
258;;  Macro to be called whenever an indentation rule decides that
259;; the parser is done.
260;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
261(defmacro indent-macro-terminate (&optional result)
262    `(return-from ind-terminate-block ,result)
263)
264
265;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
266;; Like indent-terminate, but "rejects" the input for the current line
267;; and terminates the loop.
268;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
269(defmacro indent-macro-reject (&optional result)
270   `(progn
271	(setq ind-state ind-prev-state)
272	(return-from ind-terminate-block ,result)
273    )
274)
275
276;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
277;; Like indent-reject, but "rejects" anything before the current token
278;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
279(defmacro indent-macro-reject-left (&optional result)
280   `(progn
281	(setq ind-state ind-matches)
282	(return-from ind-terminate-block ,result)
283    )
284)
285
286
287(defstruct indtoken
288    regex			;; a string, character or regex
289    token			;; the resulting token, nil or a keyword
290    begin			;; begin a new table
291    switch			;; switch to another table
292    ;; begin and switch fields are used like the ones for the syntax highlight
293    ;; syntoken structure.
294    label			;; filed at compile time
295    code			;; code to execute when it matches
296)
297
298(defstruct indtable
299    label			;; a keyword, name of the table
300    tokens			;; list of indtoken structures
301    tables			;; list of indtable structures
302    augments			;; augment list
303)
304
305(defstruct indaugment
306    labels			;; list of keywords labeling tables
307)
308
309(defstruct indinit
310    variables			;; list of variables and optional initialization
311    ;; Format of variables must be suitable to LET*, example of call:
312    ;;	(indinit
313    ;;	    var1		;; initialized to NIL
314    ;;	    (var2 (afun))	;; initialized to the value returned by AFUN
315    ;;	)
316)
317
318(defstruct indreduce
319    token			;; reduced token
320    rules			;; list of rules
321    label			;; unique label associated with rule, this
322				;; field is automatically filled in the
323				;; compilation process. this field exists
324				;; to allow several indreduce definitions
325				;; that result in the same token
326    check			;; FORM evaluated, if T apply reduce rule
327    code			;; PROGN to be called when a rule matches
328)
329
330;; NOTE, unlike "reduce" rules, "resolve" rules cannot be duplicated
331(defstruct indresolve
332    match			;; the matched token (or a list of tokens)
333    code			;; PROGN to apply for this token
334)
335
336(defstruct indent
337    reduces			;; list of indreduce structures
338    tables			;; list of indtable structures
339    inits			;; initialization list
340    resolves			;; list of indresolve structures
341    token-code			;; code to execute when a token matches
342    check-code			;; code to execute before applying a reduce rule
343    reduce-code			;; code to execute after reduce rule
344    resolve-code		;; code to execute when matching a token
345)
346
347(defmacro defindent (variable label &rest lists)
348   `(if (boundp ',variable)
349	,variable
350	(progn
351	    (proclaim '(special ,variable))
352	    (setq ,variable (compile-indent-table ,label ,@lists))
353	)
354    )
355)
356
357
358;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
359;; Create an indent token.
360;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
361(defmacro indtoken (pattern token
362		    &key icase nospec begin switch code (nosub t))
363    (setq pattern (re-comp (eval pattern) :icase icase :nospec nospec :nosub nosub))
364    (when (consp (re-exec pattern "" :notbol t :noteol t))
365	(error "INDTOKEN: regex ~A matches empty string" pattern)
366    )
367
368    ;; result of macro, return token structure
369    (make-indtoken
370	:regex	pattern
371	:token	token
372	:begin	begin
373	:switch	switch
374	:code	code
375    )
376)
377
378
379;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
380;; Create an indentation table. Basically a list of indentation tokens.
381;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
382(defun indtable (label &rest definitions)
383    ;; check for simple errors
384    (unless (keywordp label)
385	(error "INDTABLE: ~A is not a keyword" label)
386    )
387    (dolist (item definitions)
388	(unless
389	    (or
390		(atom item)
391		(indtoken-p item)
392		(indtable-p item)
393		(indaugment-p item)
394	    )
395	    (error "INDTABLE: invalid indent table argument ~A" item)
396	)
397    )
398
399    ;; return indent table structure
400    (make-indtable
401	:label		label
402	:tokens		(remove-if-not #'indtoken-p definitions)
403	:tables		(remove-if-not #'indtable-p definitions)
404	:augments	(remove-if-not #'indaugment-p definitions)
405    )
406)
407
408
409;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
410;; Add identifier to list of augment tables.
411;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
412(defun indaugment (&rest keywords)
413    (dolist (keyword keywords)
414	(unless (keywordp keyword)
415	    (error "INDAUGMENT: bad indent table label ~A" keyword)
416	)
417    )
418
419    ;; return augment list structure
420    (make-indaugment :labels keywords)
421)
422
423
424;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
425;; Add variables to initialization list
426;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
427(defmacro indinit (&rest variables)
428    (make-indinit :variables variables)
429)
430
431
432;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
433;; Create a "reduction rule"
434;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
435(defmacro indreduce (token check rules &rest code &aux nullp consp)
436    ;; check for simple errors
437    (unless (or (keywordp token) (null token))
438	(error "INDREDUCE: ~A is not a keyword" token)
439    )
440    (dolist (rule rules)
441	(or (listp rule) (error "INDREDUCE: invalid indent rule ~A" rule))
442	;; XXX This test is not enough, maybe should add some sort of
443	;; runtime check to avoid circularity.
444	(and (eq token (car rule)) (null (cdr rule))
445	    (error "INDREDUCE: ~A reduces to ~A" token)
446	)
447	(dolist (item rule)
448	    (and (or nullp consp) (not (keywordp item))
449		(error "INDREDUCE: a keyword must special pattern")
450	    )
451	    (if (consp item)
452		(progn
453		    (unless
454			(or
455			    (and
456				(eq (car item) 'not)
457				(keywordp (cadr item))
458				(null (cddr item))
459			    )
460			    (and
461				(eq (car item) 'or)
462				(null (member-if-not #'keywordp (cdr item)))
463			    )
464			)
465			(error "INDREDUCE: syntax error parsing ~A" item)
466		    )
467		    (setq consp t)
468		)
469		(progn
470		    (setq nullp (null item) consp nil)
471		    (unless (or (keywordp item) nullp (eq item t))
472			(error "INDREDUCE: ~A is not a keyword" item)
473		    )
474		)
475	    )
476	)
477;	(and consp
478;	    (error "INDREDUCE: pattern must be followed by keyword")
479;	)
480    )
481
482    ;; result of macro, return indent reduce structure
483    (make-indreduce
484	:token	token
485	:check	check
486	:rules	(remove-if #'null rules)
487	:code	code
488    )
489)
490
491;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
492;; Create a "resolve rule"
493;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
494(defmacro indresolve (match &rest code)
495    ;; check for simple errors
496    (if (consp match)
497	(dolist (token match)
498	    (or (keywordp token) (error "INDRESOLVE: ~A is not a keyword" token))
499	)
500	(or (keywordp match) (error "INDRESOLVE: ~A is not a keyword" match))
501    )
502
503    ;; result of macro, return indent resolve structure
504    (make-indresolve
505	:match	match
506	:code	code
507    )
508)
509
510
511;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
512;; Helper function for compile-indent-table. Returns a list of all
513;; tables and tokens for a given table, including tokens and tables
514;; of children.
515;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
516(defun list-indtable-elements (table &aux result sub-result)
517    (setq result (cons (indtable-tokens table) (indtable-tables table)))
518    (dolist (child (indtable-tables table))
519	(setq sub-result (list-indtable-elements child))
520	(rplaca result (append (car result) (car sub-result)))
521	(rplacd result (append (cdr result) (cdr sub-result)))
522    )
523    ;; Return pair of all nested tokens and tables
524    result
525)
526
527
528;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
529;; First pass adding augumented tokens to a table, done in two passes
530;; to respect inheritance order.
531;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
532(defun compile-indent-augment-list (table table-list &aux labels augment tokens)
533
534    ;; Create a list of all augment tables.
535    (dolist (augment (indtable-augments table))
536	(setq labels (append labels (indaugment-labels augment)))
537    )
538
539    ;;  Remove duplicates and references to "itself", without warnings?
540    (setq
541	labels
542	(remove (indtable-label table) (remove-duplicates labels :from-end t))
543    )
544
545    ;; Check if the specified indent tables exists!
546    (dolist (label labels)
547	(unless
548	    (setq augment (car (member label table-list :key #'indtable-label)))
549	    (error "COMPILE-INDENT-AUGMENT-LIST: Cannot augment ~A in ~A"
550		label
551		(indtable-label table)
552	    )
553	)
554
555	;; Increase list of tokens.
556	(setq tokens (append tokens (indtable-tokens augment)))
557    )
558
559    ;;  Store the tokens in the augment list. They will be added
560    ;; to the indent table in the second pass.
561    (setf (indtable-augments table) tokens)
562
563    ;;  Recurse on every child table.
564    (dolist (child (indtable-tables table))
565	(compile-indent-augment-list child table-list)
566    )
567)
568
569
570;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
571;; Last pass adding augmented tokens to a table.
572;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
573(defun link-indent-augment-list (table)
574    (setf
575	(indtable-tokens table)
576	(remove-duplicates
577	    (nconc (indtable-tokens table) (indtable-augments table))
578	    :key	#'indtoken-regex
579	    :test	#'equal
580	    :from-end	t
581	)
582
583	;;  Don't need to keep this list anymore.
584	(indtable-augments table)
585	()
586    )
587
588    (dolist (child (indtable-tables table))
589	(link-indent-augment-list child)
590    )
591)
592
593;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
594;; Compile the indent reduction rules
595;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
596(defun compile-indent-reduces (reduces
597			       &aux need label check rules reduce
598				    check-code reduce-code)
599    (dolist (item reduces)
600	(setq
601	    label	(indreduce-label item)
602	    check	(indreduce-check item)
603	    rules	(indreduce-rules item)
604	    reduce	(indreduce-code  item)
605	    need	(and
606			    rules
607			    (not label)
608			    (or
609				reduce
610				(null check)
611				(not (constantp check))
612			    )
613			)
614	)
615	(when need
616	    (and (null label) (setq label (intern (string (gensym)) 'keyword)))
617
618	    (setf (indreduce-label item) label)
619
620	    (and
621		(or (null check)
622		    (not (constantp check))
623		)
624		(setq
625		    check	(list (list 'eq '*ind-label* label) check)
626		    check-code	(nconc check-code (list check))
627		)
628	    )
629
630	    (and reduce
631		(setq
632		    reduce	(cons (list 'eq '*ind-label* label) reduce)
633		    reduce-code	(nconc reduce-code (list reduce))
634		)
635	    )
636	)
637    )
638
639    ;; XXX Instead of using COND, could/should use CASE
640    ;; TODO Implement a smart CASE in the bytecode compiler, if
641    ;;	    possible, should generate a hashtable, or a table
642    ;;	    of indexes (for example when all elements in the cases
643    ;;	    are characters) and then jump directly to the code.
644    (if check-code
645	(setq check-code (cons 'cond (nconc check-code '((t t)))))
646	(setq check-code t)
647    )
648    (and reduce-code (setq reduce-code (cons 'cond reduce-code)))
649
650    (values check-code reduce-code)
651)
652
653
654;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
655;; Compile the indent resolve code
656;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
657(defun compile-indent-resolves (resolves &aux match resolve resolve-code)
658    (and
659	(/=
660	    (length resolves)
661	    (length (remove-duplicates resolves :key #'indresolve-match))
662	)
663	;; XXX Could do a more complete job and tell what is wrong...
664	(error "COMPILE-INDENT-RESOLVES: duplicated labels")
665    )
666
667    (dolist (item resolves)
668	(when (setq resolve (indresolve-code item))
669	    (setq
670		match
671		(indresolve-match item)
672
673		resolve
674		(cons
675		    (if (listp match)
676			(list 'member '*ind-token* `',match :test `#'eq)
677			(list 'eq '*ind-token* match)
678		    )
679		    resolve
680		)
681
682		resolve-code
683		(nconc resolve-code (list resolve))
684	    )
685	)
686    )
687
688    (and resolve-code (cons 'cond resolve-code))
689)
690
691
692;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
693;; Create an indentation table
694;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
695(defun compile-indent-table (name &rest lists
696			     &aux main elements switches begins tables symbols
697				  label code token-code check-code reduce-code
698				  (inits (remove-if-not #'indinit-p lists))
699				  (reduces (remove-if-not #'indreduce-p lists))
700				  (resolves (remove-if-not #'indresolve-p lists))
701			    )
702    (setq
703	lists	 (delete-if
704		    #'(lambda (object)
705			(or
706			    (indinit-p object)
707			    (indreduce-p object)
708			    (indresolve-p object)
709			)
710		    )
711		    lists)
712	main	 (apply #'indtable name lists)
713	elements (list-indtable-elements main)
714	switches (remove-if #'null (car elements) :key #'indtoken-switch)
715	begins   (remove-if #'null (car elements) :key #'indtoken-begin)
716	tables	 (cons main (cdr elements))
717    )
718
719    ;; Check for typos in the keywords, or for not defined indent tables.
720    (dolist (item (mapcar #'indtoken-switch switches))
721	(unless
722	    (or	(and (integerp item) (minusp item))
723		(member item tables :key #'indtable-label)
724	    )
725	    (error "COMPILE-INDENT-TABLE: SWITCH ~A cannot be matched" item)
726	)
727    )
728    (dolist (item (mapcar #'indtoken-begin begins))
729	(unless (member item tables :key #'indtable-label)
730	    (error "COMPILE-INDENT-TABLE: BEGIN ~A cannot be matched" item)
731	)
732    )
733
734    ;; Build augment list.
735    (compile-indent-augment-list main tables)
736    (link-indent-augment-list main)
737
738    ;; Change switch and begin fields to point to the indent table
739    (dolist (item switches)
740	(if (keywordp (indtoken-switch item))
741	    (setf
742		(indtoken-switch item)
743		(car (member (indtoken-switch item) tables :key #'indtable-label))
744	    )
745	)
746    )
747    (dolist (item begins)
748	(setf
749	    (indtoken-begin item)
750	    (car (member (indtoken-begin item) tables :key #'indtable-label))
751	)
752    )
753
754    ;; Build initialization list
755    (dolist (init inits)
756	(setq symbols (nconc symbols (indinit-variables init)))
757    )
758
759    ;; Build token code
760    (dolist (item (car elements))
761	(when (setq code (indtoken-code item))
762	    (setf
763		label
764		(intern (string (gensym)) 'keyword)
765
766		(indtoken-label item)
767		label
768
769		code
770		(list (list 'eq '*ind-label* label) code)
771
772		token-code
773		(nconc token-code (list code))
774	    )
775	)
776    )
777
778    (multiple-value-setq
779	(check-code reduce-code)
780	(compile-indent-reduces reduces)
781    )
782
783    (make-indent
784	:tables		tables
785	:inits		symbols
786	:reduces	reduces
787	:resolves	resolves
788	:token-code	(and token-code (cons 'cond token-code))
789	:check-code	check-code
790	:reduce-code	reduce-code
791	:resolve-code	(compile-indent-resolves resolves)
792    )
793)
794
795
796;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
797;; Search rule-pattern in match-pattern
798;; Returns offset of match, and it's length, if any
799;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
800(defun indent-search-rule (rule-pattern match-pattern
801			   &aux start rule rulep matchp test offset length)
802    (if (member-if-not #'keywordp rule-pattern)
803	;; rule has wildcards
804	(progn
805	    (setq
806		rulep	rule-pattern
807		matchp	match-pattern
808		start	match-pattern
809	    )
810	    (loop
811		(setq rule (car rulep))
812		(cond
813		    ;; Special pattern
814		    ((consp rule)
815			(if (eq (car rule) 'not)
816			    (progn
817				(setq
818				    test	(cadr rule)
819				    rulep	(cdr rulep)
820				    rule	(car rulep)
821				)
822				(while
823				    (and
824					;; something to match
825					matchp
826					;; NOT match is true
827					(not (eq (car matchp) test))
828					;; next match is not true
829					(not (eq (car matchp) rule))
830				    )
831				    (setq matchp (cdr matchp))
832				)
833				(if (eq (car matchp) rule)
834				    ;; rule matched
835				    (setq
836					matchp	(cdr matchp)
837					rulep	(cdr rulep)
838				    )
839				    ;; failed
840				    (setq
841					rulep	rule-pattern
842					matchp	(cdr start)
843					start	matchp
844				    )
845				)
846			    )
847			    ;; (eq (car rule) 'or)
848			    (progn
849				(if (member (car matchp) (cdr rule) :test #'eq)
850				    (setq rulep (cdr rulep) matchp (cdr matchp))
851				    ;; failed
852				    (progn
853					;; end of match found!
854					(and (null matchp) (return))
855					;; reset search
856					(setq
857					    rulep	rule-pattern
858					    matchp	(cdr start)
859					    start	matchp
860					)
861				    )
862				)
863			    )
864			)
865		    )
866
867		    ;; Skip until end of match-pattern or rule is found
868		    ((null rule)
869			(setq rulep (cdr rulep))
870			;; If matches everything
871			(if (null rulep)
872			    (progn (setq matchp nil) (return))
873			    ;; If next token cannot be matched
874			    (unless
875				(setq
876				    matchp
877				    (member (car rulep) matchp :test #'eq)
878				)
879				(setq rulep rule-pattern)
880				(return)
881			    )
882			)
883			(setq rulep (cdr rulep) matchp (cdr matchp))
884		    )
885
886		    ;; Matched
887		    ((eq rule t)
888			;; If there isn't a rule to skip
889			(and (null matchp) (return))
890			(setq rulep (cdr rulep) matchp (cdr matchp))
891		    )
892
893		    ;; Matched
894		    ((eq rule (car matchp))
895			(setq rulep (cdr rulep) matchp (cdr matchp))
896		    )
897
898		    ;; No match
899		    (t
900			;; end of match found!
901			(and (null matchp) (return))
902			;; reset search
903			(setq
904			    rulep	rule-pattern
905			    matchp	(cdr start)
906			    start	matchp
907			)
908		    )
909		)
910
911		;; if everything matched
912		(or rulep (return))
913	    )
914
915	    ;; All rules matched
916	    (unless rulep
917		;; Calculate offset and length of match
918		(setq offset 0 length 0)
919		(until (eq match-pattern start)
920		    (setq
921			offset		(1+ offset)
922			match-pattern	(cdr match-pattern)
923		    )
924		)
925		(until (eq match-pattern matchp)
926		    (setq
927			length		(1+ length)
928			match-pattern	(cdr match-pattern)
929		    )
930		)
931	    )
932	)
933	;; no wildcards
934	(and (setq offset (search rule-pattern match-pattern :test #'eq))
935	     (setq length (length rule-pattern))
936	)
937    )
938
939    (values offset length)
940)
941(compile 'indent-search-rule)
942
943;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
944;; Indentation parser
945;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
946(defmacro indent-macro (ind-definition ind-offset &optional ind-no-tabs)
947   `(prog*
948	(
949	;; Current indentation table
950	(ind-table (car (indent-tables ,ind-definition)))
951
952	;; The parser rules
953	(ind-reduces (indent-reduces ,ind-definition))
954
955	;; Token list for the table
956	(ind-tokens (indtable-tokens ind-table))
957
958	;; Stack of nested tables/states
959	ind-stack
960
961	;; indentation to be used
962	(*indent* 0)
963
964	;; offset to apply indentation
965	*offset*
966
967	;; Number of lines read
968	(*ind-lines* 1)
969
970	;; Matched token
971	*ind-token*
972
973	;; list of tokens after current match, should not be changed
974	*ind-token-list*
975
976	;; label associated with rule
977	*ind-label*
978
979	;; offset of match
980	*ind-offset*
981
982	;; length of match
983	*ind-length*
984
985	;; insert position
986	(*ind-point* (point))
987
988	(ind-from (scan ,ind-offset :eol :left))
989	(ind-to ,ind-offset)
990	(ind-line (read-text ind-from (- ind-to ind-from)))
991
992	;; start of current line
993	(*ind-start* ind-from)
994
995	;; State information
996	ind-state
997
998	;; For use with (indent-macro-reject)
999	ind-prev-state
1000
1001	;; Matches for the current line
1002	ind-matches
1003
1004	;; Matched tokens not yet used
1005	ind-cache
1006
1007	;; Pattern being tested
1008	ind-token
1009
1010	;; Used when searching for a regex
1011	ind-match
1012
1013	;; Table to change
1014	ind-change
1015
1016	;; Length of ind-line
1017	(ind-length (length ind-line))
1018
1019	;; Don't parse after this offset
1020	(ind-end ind-length)
1021
1022	;; Temporary variables used during loops
1023	ind-left
1024	ind-right
1025	ind-tleft
1026	ind-tright
1027
1028	;; Set  when start of file is found
1029	ind-startp
1030
1031	;; Flag for regex search
1032	(ind-noteol (< ind-to (scan ind-from :eol :right)))
1033
1034	;; Initialization variables expanded here
1035	,@(indent-inits (eval ind-definition))
1036	)
1037
1038	;; Initial input already read
1039	(go :ind-loop)
1040
1041;------------------------------------------------------------------------
1042; Read a text line
1043:ind-read
1044	(setq
1045	    ind-to	ind-from
1046	    ind-from	(scan ind-from :eol :left :count 2)
1047	)
1048	;; If start of file reached
1049	(and (= ind-to ind-from) (setq ind-startp t) (go :ind-process))
1050
1051	(setq
1052	    *ind-lines*		(1+ *ind-lines*)
1053	    ind-to		(scan ind-from :eol :right)
1054	    ind-line		(read-text ind-from (- ind-to ind-from))
1055	    ind-length		(length ind-line)
1056	    ind-end		ind-length
1057	    ind-noteol		nil
1058	    ind-cache		nil
1059	    ind-prev-state	ind-state
1060	)
1061
1062;------------------------------------------------------------------------
1063; Loop parsing backwards
1064:ind-loop
1065	(setq ind-matches nil)
1066	(dolist (token ind-tokens)
1067	    ;; Prepare to loop
1068	    (setq
1069		ind-token	(indtoken-regex token)
1070		ind-left	0
1071	    )
1072	    ;; While the pattern matches
1073	    (loop
1074		(setq ind-right ind-left)
1075		(if
1076		    (consp
1077			(setq
1078			    ind-match
1079			    (re-exec
1080				ind-token
1081				ind-line
1082				:start	ind-left
1083				:end	ind-end
1084				:notbol (> ind-left 0)
1085				:noteol ind-noteol
1086			    )
1087			)
1088		    )
1089
1090		    ;; Remember about match
1091		    (setq
1092			ind-match   (car ind-match)
1093			ind-left    (cdr ind-match)
1094			ind-matches (cons (cons token ind-match) ind-matches)
1095		    )
1096
1097		    ;; No match
1098		    (return)
1099		)
1100		;; matched an empty string
1101		(and (= ind-left ind-right) (incf ind-left))
1102
1103		;; matched a single eol or bol
1104		(and (>= ind-left ind-end) (return))
1105	    )
1106	)
1107
1108	;; Add new matches to cache
1109	(when ind-matches
1110	    (setq
1111		ind-cache
1112		(stable-sort
1113		    (nconc (nreverse ind-matches) ind-cache) #'< :key #'cadr
1114		)
1115	    )
1116	)
1117
1118	;; If nothing in the cache
1119	(or ind-cache (go :ind-process))
1120
1121	(setq
1122	    ind-left	(cadar ind-cache)
1123	    ind-right	(cddar ind-cache)
1124	    ind-matches	(cdr ind-cache)
1125	)
1126
1127	;; If only one element in the cache
1128	(or ind-matches	(go :ind-parse))
1129
1130	(setq
1131	    ind-tleft	(cadar ind-matches)
1132	    ind-tright	(cddar ind-matches)
1133	)
1134
1135	;; Remove overlaps
1136	(loop
1137	    (if (or (>= ind-tleft ind-right) (<= ind-tright ind-left))
1138		;; No overlap
1139		(progn
1140		    (setq
1141			ind-left    ind-tleft
1142			ind-right   ind-tright
1143			ind-matches (cdr ind-matches)
1144		    )
1145		    ;; If everything checked
1146		    (or ind-matches (return))
1147		)
1148		;; Overlap found
1149		(progn
1150		    (if (consp (cdr ind-matches))
1151			;; There are yet items to be checked
1152			(progn
1153			    (rplaca ind-matches (cadr ind-matches))
1154			    (rplacd ind-matches (cddr ind-matches))
1155			)
1156			;; Last item
1157			(progn
1158			    (rplacd (last ind-cache 2) nil)
1159			    (return)
1160			)
1161		    )
1162		)
1163	    )
1164
1165	    ;; Prepare for next check
1166	    (setq
1167		ind-tleft   (cadar ind-matches)
1168		ind-tright  (cddar ind-matches)
1169	    )
1170	)
1171
1172;------------------------------------------------------------------------
1173; Process the matched tokens
1174:ind-parse
1175	(setq ind-cache (nreverse ind-cache))
1176
1177:ind-parse-loop
1178	(or (setq ind-match (car ind-cache)) (go :ind-process))
1179
1180	(setq
1181	    ind-cache (cdr ind-cache)
1182	    ind-token (car ind-match)
1183	)
1184
1185	(or (member ind-token ind-tokens :test #'eq)
1186	    (go :ind-parse-loop)
1187	)
1188
1189	;; If a state should be added
1190	(when (setq ind-change (indtoken-token ind-token))
1191	    (setq
1192		ind-left    (cadr ind-match)
1193		ind-right   (cddr ind-match)
1194
1195		*ind-offset*
1196		(+ ind-from ind-left)
1197
1198		*ind-length*
1199		(- ind-right ind-left)
1200
1201		ind-state
1202		(cons
1203		    (cons ind-change (cons *ind-offset* *ind-length*))
1204		    ind-state
1205		)
1206
1207		*ind-label*
1208		(indtoken-label ind-token)
1209	    )
1210
1211	    ;; Expand token code
1212	    ,(indent-token-code (eval ind-definition))
1213	)
1214
1215	;; Check if needs to switch to another table
1216	(when (setq ind-change (indtoken-switch ind-token))
1217	    ;; Need to switch to a previous table
1218	    (if (integerp ind-change)
1219		;; Relative switch
1220		(while (and ind-stack (minusp ind-change))
1221		    (setq
1222			ind-table	(pop ind-stack)
1223			ind-change	(1+ ind-change)
1224		    )
1225		)
1226		;; Search table in the stack
1227		(until
1228		    (or
1229			(null ind-stack)
1230			(eq
1231			    (setq ind-table (pop ind-stack))
1232			    ind-change
1233			)
1234		    )
1235		)
1236	    )
1237
1238	    ;; If no match or stack became empty
1239	    (and (null ind-table)
1240		(setq
1241		    ind-table
1242		    (car (indent-tables ,ind-definition))
1243		)
1244	    )
1245	)
1246
1247	;; Check if needs to start a new table
1248	;; XXX use ind-tleft to reduce number of local variables
1249	(when (setq ind-tleft (indtoken-begin ind-token))
1250	    (setq
1251		ind-change  ind-tleft
1252		ind-stack   (cons ind-table ind-stack)
1253		ind-table   ind-change
1254	    )
1255	)
1256
1257	;; If current "indent pattern table" changed
1258	(when ind-change
1259	    (setq
1260		ind-tokens  (indtable-tokens ind-table)
1261		ind-cache   (nreverse ind-cache)
1262		ind-end     (cadr ind-match)
1263		ind-noteol  (> ind-length ind-end)
1264	    )
1265	    (go :ind-loop)
1266	)
1267
1268	(and ind-cache (go :ind-parse-loop))
1269
1270;------------------------------------------------------------------------
1271; Everything checked, process result
1272:ind-process
1273
1274	;; If stack is not empty, don't apply rules
1275	(and ind-stack (not ind-startp) (go :ind-read))
1276
1277	(block ind-terminate-block
1278	    (setq ind-cache nil ind-tleft 0 ind-change (mapcar #'car ind-state))
1279	    (dolist (entry ind-reduces)
1280		(setq
1281		    *ind-token* (indreduce-token entry)
1282		    *ind-label* (indreduce-label entry)
1283		)
1284		(dolist (rule (indreduce-rules entry))
1285		    (loop
1286			;; Check if reduction can be applied
1287			(or
1288			    (multiple-value-setq
1289				(ind-match ind-length)
1290				(indent-search-rule rule ind-change)
1291			    )
1292			    (return)
1293			)
1294
1295			(setq
1296			    ;; First element matched
1297			    ind-matches		(nthcdr ind-match ind-state)
1298
1299			    ;; Offset of match
1300			    *ind-offset*	(cadar ind-matches)
1301
1302			    *ind-token-list*	(nthcdr ind-match ind-change)
1303
1304			    ;; Length of match, note that *ind-length*
1305			    ;; Will be transformed to zero bellow if
1306			    ;; the rule is deleting entries.
1307			    *ind-length*
1308			    (if (> ind-length 1)
1309				(progn
1310				    (setq
1311					;; XXX using ind-tright, to reduce
1312					;; number of local variables...
1313					ind-tright
1314					(nth (1- ind-length) ind-matches)
1315
1316					ind-right
1317					(+  (cadr ind-tright)
1318					    (cddr ind-tright)
1319					)
1320				    )
1321				    (- ind-right *ind-offset*)
1322				)
1323				(cddar ind-matches)
1324			    )
1325			)
1326
1327			;; XXX using ind-tleft as a counter, to reduce
1328			;; number of used variables...
1329			(and (>= (incf ind-tleft) 1000)
1330			    ;; Should never apply so many reduce rules on
1331			    ;; every iteration, if needs to, something is
1332			    ;; wrong in the indentation definition...
1333			    (error "~D INDREDUCE iterations, ~
1334				   now checking (~A ~A)"
1335				ind-tleft *ind-token* rule
1336			    )
1337			)
1338
1339			;; Check if should apply the reduction
1340			(or
1341			    ;; Expand check code
1342			    ,(indent-check-code (eval ind-definition))
1343			    (return)
1344			)
1345
1346			(if (null *ind-token*)
1347			    ;; Remove match
1348			    (progn
1349				(setq *ind-length* 0)
1350				(if (= ind-match 0)
1351				    ;; Matched the first entry
1352				    (setq
1353					ind-state
1354					(nthcdr ind-length ind-matches)
1355				    )
1356				    (progn
1357					(setq
1358					    ind-matches
1359					    (nthcdr (1- ind-match) ind-state)
1360					)
1361					(rplacd
1362					    ind-matches
1363					    (nthcdr (1+ ind-length) ind-matches)
1364					)
1365				    )
1366				)
1367			    )
1368
1369			    ;; Substitute/simplify
1370			    (progn
1371				(rplaca (car ind-matches) *ind-token*)
1372				(when (> ind-length 1)
1373				    (rplacd (cdar ind-matches) *ind-length*)
1374				    (rplacd
1375					ind-matches
1376					(nthcdr ind-length ind-matches)
1377				    )
1378				)
1379			    )
1380			)
1381			(setq
1382			    ind-cache	    t
1383			    ind-change	    (mapcar #'car ind-state)
1384			)
1385
1386			;; Expand reduce code
1387			,(indent-reduce-code (eval ind-definition))
1388		    )
1389		)
1390	    )
1391
1392	    ;; ind-cache will be T if at least one change was done
1393	    (and ind-cache (go :ind-process))
1394
1395	    ;; Start of file reached
1396	    (or ind-startp (go :ind-read))
1397
1398	)    ;; end of ind-terminate-block
1399
1400
1401	(block ind-terminate-block
1402	    (setq *ind-token-list* (mapcar #'car ind-state))
1403	    (dolist (item ind-state)
1404		(setq
1405		    *ind-token*		(car item)
1406		    *ind-offset*	(cadr item)
1407		    *ind-length*	(cddr item)
1408		)
1409		;; Expand resolve code
1410		,(indent-resolve-code (eval ind-definition))
1411		(setq *ind-token-list* (cdr *ind-token-list*))
1412	    )
1413	)
1414
1415	(and (integerp *indent*)
1416	     (integerp *offset*)
1417	    (indent-text *indent* *offset* ,ind-no-tabs)
1418	)
1419    )
1420)
1421