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