xenocara/app/xedit/lisp/modules/indent.lsp
2008-10-13 20:53:31 +00:00

1427 lines
35 KiB
Common Lisp

;
;; Copyright (c) 2002 by The XFree86 Project, Inc.
;;
;; Permission is hereby granted, free of charge, to any person obtaining a
;; copy of this software and associated documentation files (the "Software"),
;; to deal in the Software without restriction, including without limitation
;; the rights to use, copy, modify, merge, publish, distribute, sublicense,
;; and/or sell copies of the Software, and to permit persons to whom the
;; Software is furnished to do so, subject to the following conditions:
;;
;; The above copyright notice and this permission notice shall be included in
;; all copies or substantial portions of the Software.
;;
;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
;; THE XFREE86 PROJECT BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
;; OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
;; SOFTWARE.
;;
;; Except as contained in this notice, the name of the XFree86 Project shall
;; not be used in advertising or otherwise to promote the sale, use or other
;; dealings in this Software without prior written authorization from the
;; XFree86 Project.
;;
;; Author: Paulo César Pereira de Andrade
;;
;;
;; $XFree86: xc/programs/xedit/lisp/modules/indent.lsp,v 1.6 2003/01/16 03:50:46 paulo Exp $
;;
(provide "indent")
(require "xedit")
(in-package "XEDIT")
(defconstant indent-spaces '(#\Tab #\Space))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; The final indentation function.
;; Parameters:
;; indent
;; Number of spaces to insert
;; offset
;; Offset to where indentation should be added
;; no-tabs
;; If set, tabs aren't inserted
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun indent-text (indent offset &optional no-tabs
&aux start line length index current tabs spaces string
barrier base result (point (point))
)
;; Initialize
(setq
start (scan offset :eol :left)
line (read-text start (- offset start))
length (length line)
index (1- length)
current 0
base 0
)
(and (minusp indent) (setq indent 0))
;; Skip any spaces after offset, "paranoia check"
(while (member (char-after offset) indent-spaces)
(incf offset)
)
;; Check if there are only spaces before `offset' and the line `start'
(while (and (>= index 0) (member (char line index) indent-spaces))
(decf index)
)
;; `index' will be zero if there are only spaces in the `line'
(setq barrier (+ start (incf index)))
;; Calculate `base' unmodifiable indentation, if any
(dotimes (i index)
(if (char= (char line i) #\Tab)
(incf base (- 8 (rem base 8)))
(incf base)
)
)
;; If any non blank character would need to be deleted
(and (> base indent) (return-from indent-text nil))
;; Calculate `current' indentation
(setq current base)
(while (< index length)
(if (char= (char line index) #\Tab)
(incf current (- 8 (rem current 8)))
(incf current)
)
(incf index)
)
;; Maybe could also "optimize" the indentation even if it is already
;; correct, removing spaces "inside" tabs.
(when (/= indent current)
(if no-tabs
(setq
length (- indent base)
result (+ barrier length)
string (make-string length :initial-element #\Space)
)
(progn
(multiple-value-setq (tabs spaces) (floor (- indent base) 8))
(setq
length (+ tabs spaces)
result (+ barrier length)
string (make-string length :initial-element #\Tab)
)
(fill string #\Space :start tabs)
)
)
(replace-text barrier offset string)
(and (>= offset point) (>= point barrier) (goto-char result))
)
)
(compile 'indent-text)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Helper function, returns indentation of a given offset
;; If `align' is set, stop once a non blank character is seen, that
;; is, use `offset' only as a line identifier
;; If `resolve' is set, it means that the offset is just a hint, it
;; maybe anywhere in the line
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun offset-indentation (offset &key resolve align
&aux
char
line
(start (scan offset :eol :left))
(indent 0))
(if resolve
(loop
(if (characterp (setq char (char-after start)))
(if (char= char #\Tab)
(incf indent (- 8 (rem indent 8)))
;; Not a tab, check if is a space
(if (char= char #\Space)
(incf indent)
;; Not a tab neither a space
(return indent)
)
)
;; EOF found
(return indent)
)
;; Increment offset to check next character
(incf start)
)
(progn
(setq line (read-text start (- offset start)))
(dotimes (i (length line) indent)
(if (char= (setq char (char line i)) #\Tab)
(incf indent (- 8 (rem indent 8)))
(progn
(or align (member char indent-spaces)
(return indent)
)
(incf indent)
)
)
)
)
)
)
(compile 'offset-indentation)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; A default/fallback indentation function, just copy indentation
;; of previous line.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun default-indent (syntax syntable)
(let
(
(offset (scan (point) :eol :left))
start
left
right
)
syntable ;; XXX hack to not generate warning about unused
;; variable, should be temporary (until unused
;; variables can be declared as such)
(if
(or
;; if indentation is disabled
(and
(hash-table-p (syntax-options syntax))
(gethash :disable-indent (syntax-options syntax))
)
;; or if not at the start of a new line
(> (scan offset :eol :right) offset)
)
(return-from default-indent)
)
(setq left offset)
(loop
(setq
start left
left (scan start :eol :left :count 2)
right (scan left :eol :right)
)
;; if start of file reached
(and (>= left start) (return))
(when
(setq
start
(position-if-not
#'(lambda (char) (member char indent-spaces))
(read-text left (- right left))
)
)
;; indent the current line
(indent-text (offset-indentation (+ left start) :align t) offset)
(return)
)
)
)
)
(compile 'default-indent)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Helper function
;; Clear line before cursor if it is empty
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun indent-clear-empty-line (&aux left offset right line index)
(setq
offset (scan (point) :eol :left)
left (scan offset :eol :left :count 2)
right (scan left :eol :right)
)
;; If not at the first line in the file and line is not already empty
(when (and (/= offset left) (/= left right))
(setq
line (read-text left (- right left))
index (1- (length line))
)
(while (and (>= index 0) (member (char line index) indent-spaces))
(decf index)
)
;; If line was only spaces
(and (minusp index) (replace-text left right ""))
)
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Macro to be called whenever an indentation rule decides that
;; the parser is done.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmacro indent-macro-terminate (&optional result)
`(return-from ind-terminate-block ,result)
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Like indent-terminate, but "rejects" the input for the current line
;; and terminates the loop.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmacro indent-macro-reject (&optional result)
`(progn
(setq ind-state ind-prev-state)
(return-from ind-terminate-block ,result)
)
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Like indent-reject, but "rejects" anything before the current token
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmacro indent-macro-reject-left (&optional result)
`(progn
(setq ind-state ind-matches)
(return-from ind-terminate-block ,result)
)
)
(defstruct indtoken
regex ;; a string, character or regex
token ;; the resulting token, nil or a keyword
begin ;; begin a new table
switch ;; switch to another table
;; begin and switch fields are used like the ones for the syntax highlight
;; syntoken structure.
label ;; filed at compile time
code ;; code to execute when it matches
)
(defstruct indtable
label ;; a keyword, name of the table
tokens ;; list of indtoken structures
tables ;; list of indtable structures
augments ;; augment list
)
(defstruct indaugment
labels ;; list of keywords labeling tables
)
(defstruct indinit
variables ;; list of variables and optional initialization
;; Format of variables must be suitable to LET*, example of call:
;; (indinit
;; var1 ;; initialized to NIL
;; (var2 (afun)) ;; initialized to the value returned by AFUN
;; )
)
(defstruct indreduce
token ;; reduced token
rules ;; list of rules
label ;; unique label associated with rule, this
;; field is automatically filled in the
;; compilation process. this field exists
;; to allow several indreduce definitions
;; that result in the same token
check ;; FORM evaluated, if T apply reduce rule
code ;; PROGN to be called when a rule matches
)
;; NOTE, unlike "reduce" rules, "resolve" rules cannot be duplicated
(defstruct indresolve
match ;; the matched token (or a list of tokens)
code ;; PROGN to apply for this token
)
(defstruct indent
reduces ;; list of indreduce structures
tables ;; list of indtable structures
inits ;; initialization list
resolves ;; list of indresolve structures
token-code ;; code to execute when a token matches
check-code ;; code to execute before applying a reduce rule
reduce-code ;; code to execute after reduce rule
resolve-code ;; code to execute when matching a token
)
(defmacro defindent (variable label &rest lists)
`(if (boundp ',variable)
,variable
(progn
(proclaim '(special ,variable))
(setq ,variable (compile-indent-table ,label ,@lists))
)
)
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Create an indent token.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmacro indtoken (pattern token
&key icase nospec begin switch code (nosub t))
(setq pattern (re-comp (eval pattern) :icase icase :nospec nospec :nosub nosub))
(when (consp (re-exec pattern "" :notbol t :noteol t))
(error "INDTOKEN: regex ~A matches empty string" pattern)
)
;; result of macro, return token structure
(make-indtoken
:regex pattern
:token token
:begin begin
:switch switch
:code code
)
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Create an indentation table. Basically a list of indentation tokens.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun indtable (label &rest definitions)
;; check for simple errors
(unless (keywordp label)
(error "INDTABLE: ~A is not a keyword" label)
)
(dolist (item definitions)
(unless
(or
(atom item)
(indtoken-p item)
(indtable-p item)
(indaugment-p item)
)
(error "INDTABLE: invalid indent table argument ~A" item)
)
)
;; return indent table structure
(make-indtable
:label label
:tokens (remove-if-not #'indtoken-p definitions)
:tables (remove-if-not #'indtable-p definitions)
:augments (remove-if-not #'indaugment-p definitions)
)
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Add identifier to list of augment tables.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun indaugment (&rest keywords)
(dolist (keyword keywords)
(unless (keywordp keyword)
(error "INDAUGMENT: bad indent table label ~A" keyword)
)
)
;; return augment list structure
(make-indaugment :labels keywords)
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Add variables to initialization list
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmacro indinit (&rest variables)
(make-indinit :variables variables)
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Create a "reduction rule"
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmacro indreduce (token check rules &rest code &aux nullp consp)
;; check for simple errors
(unless (or (keywordp token) (null token))
(error "INDREDUCE: ~A is not a keyword" token)
)
(dolist (rule rules)
(or (listp rule) (error "INDREDUCE: invalid indent rule ~A" rule))
;; XXX This test is not enough, maybe should add some sort of
;; runtime check to avoid circularity.
(and (eq token (car rule)) (null (cdr rule))
(error "INDREDUCE: ~A reduces to ~A" token)
)
(dolist (item rule)
(and (or nullp consp) (not (keywordp item))
(error "INDREDUCE: a keyword must special pattern")
)
(if (consp item)
(progn
(unless
(or
(and
(eq (car item) 'not)
(keywordp (cadr item))
(null (cddr item))
)
(and
(eq (car item) 'or)
(null (member-if-not #'keywordp (cdr item)))
)
)
(error "INDREDUCE: syntax error parsing ~A" item)
)
(setq consp t)
)
(progn
(setq nullp (null item) consp nil)
(unless (or (keywordp item) nullp (eq item t))
(error "INDREDUCE: ~A is not a keyword" item)
)
)
)
)
; (and consp
; (error "INDREDUCE: pattern must be followed by keyword")
; )
)
;; result of macro, return indent reduce structure
(make-indreduce
:token token
:check check
:rules (remove-if #'null rules)
:code code
)
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Create a "resolve rule"
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmacro indresolve (match &rest code)
;; check for simple errors
(if (consp match)
(dolist (token match)
(or (keywordp token) (error "INDRESOLVE: ~A is not a keyword" token))
)
(or (keywordp match) (error "INDRESOLVE: ~A is not a keyword" match))
)
;; result of macro, return indent resolve structure
(make-indresolve
:match match
:code code
)
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Helper function for compile-indent-table. Returns a list of all
;; tables and tokens for a given table, including tokens and tables
;; of children.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun list-indtable-elements (table &aux result sub-result)
(setq result (cons (indtable-tokens table) (indtable-tables table)))
(dolist (child (indtable-tables table))
(setq sub-result (list-indtable-elements child))
(rplaca result (append (car result) (car sub-result)))
(rplacd result (append (cdr result) (cdr sub-result)))
)
;; Return pair of all nested tokens and tables
result
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; First pass adding augumented tokens to a table, done in two passes
;; to respect inheritance order.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun compile-indent-augment-list (table table-list &aux labels augment tokens)
;; Create a list of all augment tables.
(dolist (augment (indtable-augments table))
(setq labels (append labels (indaugment-labels augment)))
)
;; Remove duplicates and references to "itself", without warnings?
(setq
labels
(remove (indtable-label table) (remove-duplicates labels :from-end t))
)
;; Check if the specified indent tables exists!
(dolist (label labels)
(unless
(setq augment (car (member label table-list :key #'indtable-label)))
(error "COMPILE-INDENT-AUGMENT-LIST: Cannot augment ~A in ~A"
label
(indtable-label table)
)
)
;; Increase list of tokens.
(setq tokens (append tokens (indtable-tokens augment)))
)
;; Store the tokens in the augment list. They will be added
;; to the indent table in the second pass.
(setf (indtable-augments table) tokens)
;; Recurse on every child table.
(dolist (child (indtable-tables table))
(compile-indent-augment-list child table-list)
)
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Last pass adding augmented tokens to a table.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun link-indent-augment-list (table)
(setf
(indtable-tokens table)
(remove-duplicates
(nconc (indtable-tokens table) (indtable-augments table))
:key #'indtoken-regex
:test #'equal
:from-end t
)
;; Don't need to keep this list anymore.
(indtable-augments table)
()
)
(dolist (child (indtable-tables table))
(link-indent-augment-list child)
)
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Compile the indent reduction rules
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun compile-indent-reduces (reduces
&aux need label check rules reduce
check-code reduce-code)
(dolist (item reduces)
(setq
label (indreduce-label item)
check (indreduce-check item)
rules (indreduce-rules item)
reduce (indreduce-code item)
need (and
rules
(not label)
(or
reduce
(null check)
(not (constantp check))
)
)
)
(when need
(and (null label) (setq label (intern (string (gensym)) 'keyword)))
(setf (indreduce-label item) label)
(and
(or (null check)
(not (constantp check))
)
(setq
check (list (list 'eq '*ind-label* label) check)
check-code (nconc check-code (list check))
)
)
(and reduce
(setq
reduce (cons (list 'eq '*ind-label* label) reduce)
reduce-code (nconc reduce-code (list reduce))
)
)
)
)
;; XXX Instead of using COND, could/should use CASE
;; TODO Implement a smart CASE in the bytecode compiler, if
;; possible, should generate a hashtable, or a table
;; of indexes (for example when all elements in the cases
;; are characters) and then jump directly to the code.
(if check-code
(setq check-code (cons 'cond (nconc check-code '((t t)))))
(setq check-code t)
)
(and reduce-code (setq reduce-code (cons 'cond reduce-code)))
(values check-code reduce-code)
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Compile the indent resolve code
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun compile-indent-resolves (resolves &aux match resolve resolve-code)
(and
(/=
(length resolves)
(length (remove-duplicates resolves :key #'indresolve-match))
)
;; XXX Could do a more complete job and tell what is wrong...
(error "COMPILE-INDENT-RESOLVES: duplicated labels")
)
(dolist (item resolves)
(when (setq resolve (indresolve-code item))
(setq
match
(indresolve-match item)
resolve
(cons
(if (listp match)
(list 'member '*ind-token* `',match :test `#'eq)
(list 'eq '*ind-token* match)
)
resolve
)
resolve-code
(nconc resolve-code (list resolve))
)
)
)
(and resolve-code (cons 'cond resolve-code))
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Create an indentation table
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun compile-indent-table (name &rest lists
&aux main elements switches begins tables symbols
label code token-code check-code reduce-code
(inits (remove-if-not #'indinit-p lists))
(reduces (remove-if-not #'indreduce-p lists))
(resolves (remove-if-not #'indresolve-p lists))
)
(setq
lists (delete-if
#'(lambda (object)
(or
(indinit-p object)
(indreduce-p object)
(indresolve-p object)
)
)
lists)
main (apply #'indtable name lists)
elements (list-indtable-elements main)
switches (remove-if #'null (car elements) :key #'indtoken-switch)
begins (remove-if #'null (car elements) :key #'indtoken-begin)
tables (cons main (cdr elements))
)
;; Check for typos in the keywords, or for not defined indent tables.
(dolist (item (mapcar #'indtoken-switch switches))
(unless
(or (and (integerp item) (minusp item))
(member item tables :key #'indtable-label)
)
(error "COMPILE-INDENT-TABLE: SWITCH ~A cannot be matched" item)
)
)
(dolist (item (mapcar #'indtoken-begin begins))
(unless (member item tables :key #'indtable-label)
(error "COMPILE-INDENT-TABLE: BEGIN ~A cannot be matched" item)
)
)
;; Build augment list.
(compile-indent-augment-list main tables)
(link-indent-augment-list main)
;; Change switch and begin fields to point to the indent table
(dolist (item switches)
(if (keywordp (indtoken-switch item))
(setf
(indtoken-switch item)
(car (member (indtoken-switch item) tables :key #'indtable-label))
)
)
)
(dolist (item begins)
(setf
(indtoken-begin item)
(car (member (indtoken-begin item) tables :key #'indtable-label))
)
)
;; Build initialization list
(dolist (init inits)
(setq symbols (nconc symbols (indinit-variables init)))
)
;; Build token code
(dolist (item (car elements))
(when (setq code (indtoken-code item))
(setf
label
(intern (string (gensym)) 'keyword)
(indtoken-label item)
label
code
(list (list 'eq '*ind-label* label) code)
token-code
(nconc token-code (list code))
)
)
)
(multiple-value-setq
(check-code reduce-code)
(compile-indent-reduces reduces)
)
(make-indent
:tables tables
:inits symbols
:reduces reduces
:resolves resolves
:token-code (and token-code (cons 'cond token-code))
:check-code check-code
:reduce-code reduce-code
:resolve-code (compile-indent-resolves resolves)
)
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Search rule-pattern in match-pattern
;; Returns offset of match, and it's length, if any
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun indent-search-rule (rule-pattern match-pattern
&aux start rule rulep matchp test offset length)
(if (member-if-not #'keywordp rule-pattern)
;; rule has wildcards
(progn
(setq
rulep rule-pattern
matchp match-pattern
start match-pattern
)
(loop
(setq rule (car rulep))
(cond
;; Special pattern
((consp rule)
(if (eq (car rule) 'not)
(progn
(setq
test (cadr rule)
rulep (cdr rulep)
rule (car rulep)
)
(while
(and
;; something to match
matchp
;; NOT match is true
(not (eq (car matchp) test))
;; next match is not true
(not (eq (car matchp) rule))
)
(setq matchp (cdr matchp))
)
(if (eq (car matchp) rule)
;; rule matched
(setq
matchp (cdr matchp)
rulep (cdr rulep)
)
;; failed
(setq
rulep rule-pattern
matchp (cdr start)
start matchp
)
)
)
;; (eq (car rule) 'or)
(progn
(if (member (car matchp) (cdr rule) :test #'eq)
(setq rulep (cdr rulep) matchp (cdr matchp))
;; failed
(progn
;; end of match found!
(and (null matchp) (return))
;; reset search
(setq
rulep rule-pattern
matchp (cdr start)
start matchp
)
)
)
)
)
)
;; Skip until end of match-pattern or rule is found
((null rule)
(setq rulep (cdr rulep))
;; If matches everything
(if (null rulep)
(progn (setq matchp nil) (return))
;; If next token cannot be matched
(unless
(setq
matchp
(member (car rulep) matchp :test #'eq)
)
(setq rulep rule-pattern)
(return)
)
)
(setq rulep (cdr rulep) matchp (cdr matchp))
)
;; Matched
((eq rule t)
;; If there isn't a rule to skip
(and (null matchp) (return))
(setq rulep (cdr rulep) matchp (cdr matchp))
)
;; Matched
((eq rule (car matchp))
(setq rulep (cdr rulep) matchp (cdr matchp))
)
;; No match
(t
;; end of match found!
(and (null matchp) (return))
;; reset search
(setq
rulep rule-pattern
matchp (cdr start)
start matchp
)
)
)
;; if everything matched
(or rulep (return))
)
;; All rules matched
(unless rulep
;; Calculate offset and length of match
(setq offset 0 length 0)
(until (eq match-pattern start)
(setq
offset (1+ offset)
match-pattern (cdr match-pattern)
)
)
(until (eq match-pattern matchp)
(setq
length (1+ length)
match-pattern (cdr match-pattern)
)
)
)
)
;; no wildcards
(and (setq offset (search rule-pattern match-pattern :test #'eq))
(setq length (length rule-pattern))
)
)
(values offset length)
)
(compile 'indent-search-rule)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Indentation parser
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmacro indent-macro (ind-definition ind-offset &optional ind-no-tabs)
`(prog*
(
;; Current indentation table
(ind-table (car (indent-tables ,ind-definition)))
;; The parser rules
(ind-reduces (indent-reduces ,ind-definition))
;; Token list for the table
(ind-tokens (indtable-tokens ind-table))
;; Stack of nested tables/states
ind-stack
;; indentation to be used
(*indent* 0)
;; offset to apply indentation
*offset*
;; Number of lines read
(*ind-lines* 1)
;; Matched token
*ind-token*
;; list of tokens after current match, should not be changed
*ind-token-list*
;; label associated with rule
*ind-label*
;; offset of match
*ind-offset*
;; length of match
*ind-length*
;; insert position
(*ind-point* (point))
(ind-from (scan ,ind-offset :eol :left))
(ind-to ,ind-offset)
(ind-line (read-text ind-from (- ind-to ind-from)))
;; start of current line
(*ind-start* ind-from)
;; State information
ind-state
;; For use with (indent-macro-reject)
ind-prev-state
;; Matches for the current line
ind-matches
;; Matched tokens not yet used
ind-cache
;; Pattern being tested
ind-token
;; Used when searching for a regex
ind-match
;; Table to change
ind-change
;; Length of ind-line
(ind-length (length ind-line))
;; Don't parse after this offset
(ind-end ind-length)
;; Temporary variables used during loops
ind-left
ind-right
ind-tleft
ind-tright
;; Set when start of file is found
ind-startp
;; Flag for regex search
(ind-noteol (< ind-to (scan ind-from :eol :right)))
;; Initialization variables expanded here
,@(indent-inits (eval ind-definition))
)
;; Initial input already read
(go :ind-loop)
;; Just to avoid a warning about unused variable, as this
;; variable is somewhat redundant as code should already
;; know before entering indent parser, but useful inside
;; indent macros.
*ind-point*
;------------------------------------------------------------------------
; Read a text line
:ind-read
(setq
ind-to ind-from
ind-from (scan ind-from :eol :left :count 2)
)
;; If start of file reached
(and (= ind-to ind-from) (setq ind-startp t) (go :ind-process))
(setq
*ind-lines* (1+ *ind-lines*)
ind-to (scan ind-from :eol :right)
ind-line (read-text ind-from (- ind-to ind-from))
ind-length (length ind-line)
ind-end ind-length
ind-noteol nil
ind-cache nil
ind-prev-state ind-state
)
;------------------------------------------------------------------------
; Loop parsing backwards
:ind-loop
(setq ind-matches nil)
(dolist (token ind-tokens)
;; Prepare to loop
(setq
ind-token (indtoken-regex token)
ind-left 0
)
;; While the pattern matches
(loop
(setq ind-right ind-left)
(if
(consp
(setq
ind-match
(re-exec
ind-token
ind-line
:start ind-left
:end ind-end
:notbol (> ind-left 0)
:noteol ind-noteol
)
)
)
;; Remember about match
(setq
ind-match (car ind-match)
ind-left (cdr ind-match)
ind-matches (cons (cons token ind-match) ind-matches)
)
;; No match
(return)
)
;; matched an empty string
(and (= ind-left ind-right) (incf ind-left))
;; matched a single eol or bol
(and (>= ind-left ind-end) (return))
)
)
;; Add new matches to cache
(when ind-matches
(setq
ind-cache
(stable-sort
(nconc (nreverse ind-matches) ind-cache) #'< :key #'cadr
)
)
)
;; If nothing in the cache
(or ind-cache (go :ind-process))
(setq
ind-left (cadar ind-cache)
ind-right (cddar ind-cache)
ind-matches (cdr ind-cache)
)
;; If only one element in the cache
(or ind-matches (go :ind-parse))
(setq
ind-tleft (cadar ind-matches)
ind-tright (cddar ind-matches)
)
;; Remove overlaps
(loop
(if (or (>= ind-tleft ind-right) (<= ind-tright ind-left))
;; No overlap
(progn
(setq
ind-left ind-tleft
ind-right ind-tright
ind-matches (cdr ind-matches)
)
;; If everything checked
(or ind-matches (return))
)
;; Overlap found
(progn
(if (consp (cdr ind-matches))
;; There are yet items to be checked
(progn
(rplaca ind-matches (cadr ind-matches))
(rplacd ind-matches (cddr ind-matches))
)
;; Last item
(progn
(rplacd (last ind-cache 2) nil)
(return)
)
)
)
)
;; Prepare for next check
(setq
ind-tleft (cadar ind-matches)
ind-tright (cddar ind-matches)
)
)
;------------------------------------------------------------------------
; Process the matched tokens
:ind-parse
(setq ind-cache (nreverse ind-cache))
:ind-parse-loop
(or (setq ind-match (car ind-cache)) (go :ind-process))
(setq
ind-cache (cdr ind-cache)
ind-token (car ind-match)
)
(or (member ind-token ind-tokens :test #'eq)
(go :ind-parse-loop)
)
;; If a state should be added
(when (setq ind-change (indtoken-token ind-token))
(setq
ind-left (cadr ind-match)
ind-right (cddr ind-match)
*ind-offset*
(+ ind-from ind-left)
*ind-length*
(- ind-right ind-left)
ind-state
(cons
(cons ind-change (cons *ind-offset* *ind-length*))
ind-state
)
*ind-label*
(indtoken-label ind-token)
)
;; Expand token code
,(indent-token-code (eval ind-definition))
)
;; Check if needs to switch to another table
(when (setq ind-change (indtoken-switch ind-token))
;; Need to switch to a previous table
(if (integerp ind-change)
;; Relative switch
(while (and ind-stack (minusp ind-change))
(setq
ind-table (pop ind-stack)
ind-change (1+ ind-change)
)
)
;; Search table in the stack
(until
(or
(null ind-stack)
(eq
(setq ind-table (pop ind-stack))
ind-change
)
)
)
)
;; If no match or stack became empty
(and (null ind-table)
(setq
ind-table
(car (indent-tables ,ind-definition))
)
)
)
;; Check if needs to start a new table
;; XXX use ind-tleft to reduce number of local variables
(when (setq ind-tleft (indtoken-begin ind-token))
(setq
ind-change ind-tleft
ind-stack (cons ind-table ind-stack)
ind-table ind-change
)
)
;; If current "indent pattern table" changed
(when ind-change
(setq
ind-tokens (indtable-tokens ind-table)
ind-cache (nreverse ind-cache)
ind-end (cadr ind-match)
ind-noteol (> ind-length ind-end)
)
(go :ind-loop)
)
(and ind-cache (go :ind-parse-loop))
;------------------------------------------------------------------------
; Everything checked, process result
:ind-process
;; If stack is not empty, don't apply rules
(and ind-stack (not ind-startp) (go :ind-read))
(block ind-terminate-block
(setq ind-cache nil ind-tleft 0 ind-change (mapcar #'car ind-state))
(dolist (entry ind-reduces)
(setq
*ind-token* (indreduce-token entry)
*ind-label* (indreduce-label entry)
)
(dolist (rule (indreduce-rules entry))
(loop
;; Check if reduction can be applied
(or
(multiple-value-setq
(ind-match ind-length)
(indent-search-rule rule ind-change)
)
(return)
)
(setq
;; First element matched
ind-matches (nthcdr ind-match ind-state)
;; Offset of match
*ind-offset* (cadar ind-matches)
*ind-token-list* (nthcdr ind-match ind-change)
;; Length of match, note that *ind-length*
;; Will be transformed to zero bellow if
;; the rule is deleting entries.
*ind-length*
(if (> ind-length 1)
(progn
(setq
;; XXX using ind-tright, to reduce
;; number of local variables...
ind-tright
(nth (1- ind-length) ind-matches)
ind-right
(+ (cadr ind-tright)
(cddr ind-tright)
)
)
(- ind-right *ind-offset*)
)
(cddar ind-matches)
)
)
;; XXX using ind-tleft as a counter, to reduce
;; number of used variables...
(and (>= (incf ind-tleft) 1000)
;; Should never apply so many reduce rules on
;; every iteration, if needs to, something is
;; wrong in the indentation definition...
(error "~D INDREDUCE iterations, ~
now checking (~A ~A)"
ind-tleft *ind-token* rule
)
)
;; Check if should apply the reduction
(or
;; Expand check code
,(indent-check-code (eval ind-definition))
(return)
)
(if (null *ind-token*)
;; Remove match
(progn
(setq *ind-length* 0)
(if (= ind-match 0)
;; Matched the first entry
(setq
ind-state
(nthcdr ind-length ind-matches)
)
(progn
(setq
ind-matches
(nthcdr (1- ind-match) ind-state)
)
(rplacd
ind-matches
(nthcdr (1+ ind-length) ind-matches)
)
)
)
)
;; Substitute/simplify
(progn
(rplaca (car ind-matches) *ind-token*)
(when (> ind-length 1)
(rplacd (cdar ind-matches) *ind-length*)
(rplacd
ind-matches
(nthcdr ind-length ind-matches)
)
)
)
)
(setq
ind-cache t
ind-change (mapcar #'car ind-state)
)
;; Expand reduce code
,(indent-reduce-code (eval ind-definition))
)
)
)
;; ind-cache will be T if at least one change was done
(and ind-cache (go :ind-process))
;; Start of file reached
(or ind-startp (go :ind-read))
) ;; end of ind-terminate-block
(block ind-terminate-block
(setq *ind-token-list* (mapcar #'car ind-state))
(dolist (item ind-state)
(setq
*ind-token* (car item)
*ind-offset* (cadr item)
*ind-length* (cddr item)
)
;; Expand resolve code
,(indent-resolve-code (eval ind-definition))
(setq *ind-token-list* (cdr *ind-token-list*))
)
)
(and (integerp *indent*)
(integerp *offset*)
(indent-text *indent* *offset* ,ind-no-tabs)
)
)
)