File: tlc.el

package info (click to toggle)
matlab-mode 6.3-1
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 900 kB
  • sloc: lisp: 10,932; sh: 5; makefile: 5
file content (507 lines) | stat: -rw-r--r-- 20,382 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
;;; tlc --- Major mode for editing tlc files -*- lexical-binding: t -*-

;; Copyright (C) 2024 Free Software Foundation, Inc.

;; Author: Eric M. Ludlam <eludlam@mathworks.com>
;; Keywords: tlc
;; X-Abstract: Major mode for editing tlc files

(defvar tlc-version "1.3"
  "The current version of TLC mode.")

;; This program is derived from free software; you can redistribute it
;; and/or modify it under the terms of the GNU General Public License
;; as published by the Free Software Foundation; either version 3, or
;; (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING.  If not, write to
;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
;;
;;; Commentary:
;;
;;  This is a major mode for editing Target Language Compiler, TLC, programs.
;;  TLC programs are an advanced template system which are used to create
;;  source code for compilers, e.g. to produce C code or C++ code.
;;
;;  TLC programs consist of :
;;     - Keywords of the form %function, %if, etc.
;;     - Built-in functions of form EXISTS().
;;     - Statement continuation using "...", e.g.
;;          %assign a = b + ...
;;              c
;;     - Single line comments are of the from: %% line
;;     - "%%{", "%%{{", "%%}", "%%}}", etc. 'indent comment shift operators' adjust indentation
;;       by 4, 8, -4, -8, etc.
;;     - "%%{N}" 'indent comment shift operators' adjust what follows to indent to column N
;;     - Block comments are of form: /% ... %/
;;     - All other content is sent to the output to create target language source code, e.g. C.
;;
;;  TLC programs therefore mix TLC semantics with target language semantics, e.g. C code:
;;
;;     %function Foo(mode) output
;;         %if mode.loop
;;             int i;
;;             for (i = 0; i < %<mode.loopLimit>; i++) {
;;                 %<Out(mode)>[i] = %<In(mode)>[i];
;;             }
;;         %else
;;             %<Out(mode)> = %<In(mode)>;
;;         %endif
;;     %endfunction
;;
;;  The mix of TLC code and C code can result in challenges for semantic indentation.  In this case
;;  you can use the special "%%{", "%%}", etc. comments (called indent comment shift operators) to
;;  adjust indentation as in:
;;
;;      %if mode == "standard"
;;          if (a > 1) {
;;              if (b > 0) {
;;                  %<v> = %<v> + b;
;;      %else
;;          if (x > 0) {
;;              if (y > 0) {
;;                  %<v> = %<v> + y;
;;      %endif
;;      %%{{{                                      ;; this adjusts indentation by 12
;;                  %<w> = %<v>
;;              }
;;          }
;;      %%}                                        ;; this adjusts indentation by -4
;;
;;  Another use of the indent comment shift operators is with %openfile to write
;;  target language output comments, e.g.
;;
;;      %function GetHeader() void
;;          %openfile header
;;      %%}
;;      Unindented C
;;      comment text
;;      %%{
;;          %closefile header
;;          %return header
;;      %endfunction
;;
;;  Using %openfile/%closefile is helpful when creating messages and this often requires that the
;;  content starts at a specific column, e.g. column 1 (zero based), such that when the lines of the
;;  message are combined into a single line the words at the line boundaries are joined by spaces,
;;  e.g.
;;
;;   %function GetMessage() void
;;       %openfile message
;;   %%{1}
;;    This is a message
;;    that spans multiple lines
;;   %%{4}
;;       %closefile
;;   %endfunction
;;
;; Issues:
;;   1. Font's for continuations multiline statements are not correct, i.e.
;;        %assign a = 1 + ...
;;            2 + ...
;;            3
;;      will have the "2 + ..." and "3" line shown using tlc-font-lock-output-code.
;;      To fix, we probably need to use Multiline Font Lock constructs.

;;; History:
;;
;;  10Sep1998 by Eric M.  Ludlam <eludlam@mathworks.com>
;;    Posted First revision onto the FTP site.
;;
;;  06Oct2005 Peter S galbraith <psg@debian.org>
;;    Minor changes for:
;;    - support customization.
;;    - added autoload cookies.
;;    - CVS storage elsewhere without changing the version number.
;;
;;  Recent history is in the ChangeLog and the matlab-emacs repository.

;;; Code:
(defun tlc-version ()
  "Display the current version of TLC mode."
  (interactive)
  (message tlc-version))

(defgroup tlc nil
  "Major mode for editing tlc files."
  :group 'languages)

(defcustom tlc-mode-hook nil
  "*List of functions to call on entry to TLC mode."
  :group 'tlc
  :type 'hook)

(defvar tlc-syntax-table nil
  "Syntax table used in an TLC file.")

(unless tlc-syntax-table
  (setq tlc-syntax-table (make-syntax-table (standard-syntax-table)))
  ;; Multiline comments:   /% ... %/
  ;; Single line comments: %% ...
  (modify-syntax-entry ?/  ". 14c" tlc-syntax-table)
  (modify-syntax-entry ?%  ". 123" tlc-syntax-table)
  (modify-syntax-entry ?\n ">" tlc-syntax-table)
  ;; Strings
  (modify-syntax-entry ?\" "\"" tlc-syntax-table)
  ;; %<variable> support:
  (modify-syntax-entry ?< "(>" tlc-syntax-table)
  (modify-syntax-entry ?> ")>" tlc-syntax-table))

(defvar tlc-mode-map
  (let ((km  (make-sparse-keymap)))
    (define-key km "\C-m" 'tlc-return)
    (define-key km [return] 'tlc-return)
    (define-key km "\C-i" 'tlc-indent)
    km)
  "Keymap for `tlc-mode'.")

(defvar tlc-font-lock-output-code 'tlc-font-lock-output-code
  "Face for output code.")

(defface tlc-font-lock-output-code
  '((((class grayscale) (background light))
     (:foreground "DimGray" :underline t))
    (((class grayscale) (background dark))
     (:foreground "LightGray" :underline t))
    (((class color) (background light)) (:foreground "DarkGreen"))
    (((class color) (background dark))  (:foreground "chartreuse"))
    (t (:underline t)))
  "Font Lock mode face used to highlight tlc keywords."
  :group 'tlc)

(defcustom tlc-keywords
  '("CAST" "EXISTS" "FEVAL" "FILE_EXISTS" "FORMAT"
    "FIELDNAMES" "GETFIELD" "GENERATE"
    "GENERATE_FILENAME" "GENERATE_FORMATTED_VALUE"
    "GENERATE_FUNCTION_EXISTS" "GENERATE_TYPE"
    "GENERATE_TYPE_FUNCTION_EXISTS" "GET_COMMAND_SWITCH"
    "IDNUM" "IMAG"
    "INT8MAX" "INT8MIN"
    "INT16MAX" "INT16MIN"
    "INT32MAX" "INT32MIN"
    "ISEQUAL" "ISFIELD" "ISINF" "ISNAN" "ISFINITE"
    "NULL_FILE" "NUMTLCFILES"
    "OUTPUT_LINES" "SIZE" "STDOUT" "STRING" "STRINGOF"
    "SYSNAME" "TLCFILES" "TLC_TIME"
    "TLC_FALSE" "TLC_TRUE"
    "TLC_VERSION" "TYPE"
    "UINT8MAX" "UINT16MAX" "UINT32MAX"
    "UINTWHITE_SPACE" "WILL_ROLL")
  "Built-in function keywords to highlight in TLC."
  :type '(repeat (string :tag "keyword"))
  :group 'tlc)

(defvar tlc-font-lock-keywords
  (list
   ;; %function keyword
   '("^%function\\s-+\\(\\sw+\\)\\s-*(" 1 font-lock-function-name-face)
   '("^%function\\s-+\\(\\sw+\\)\\s-*("
     ("\\s-*\\(\\sw+\\)\\s-*[,)]" nil nil
      (1 font-lock-variable-name-face)))
   ;; Single line comments: %% text
   ;; Special "%%{", "%%}", etc. comments
   '("\\(?:%%\\)\\({+\\|{+[0-9]+}\\)\\s-*$" 1 'bold prepend)
   '("\\(?:%%\\)\\(}+\\)\\s-*$" 1 'bold prepend)
   ;; Target language output code
   '("\\(^[ \t]*\\([^ \n\t%]\\|%<\\)[^\n]*\\)$" 1 tlc-font-lock-output-code append)
   ;; Keywords, e.g., %if
   '("\\(^\\|\\s-\\)\\(%[^% \t(\n>]+\\)\\>" 2 font-lock-keyword-face)
   ;; %assign keyword
   '("%assign\\s-+:*\\([_a-zA-Z0-9.]+\\)\\s-*\\($\\|=\\)" 1 font-lock-variable-name-face)
   ;; %exit, %warning, %error, %trace keywords
   '("%\\(exit\\|warning\\|error\\|trace\\) \\([^\n]+\\)$" 2 font-lock-string-face prepend)
   ;; %<var> expansions
   '("\\(%<[^%\n>]+>\\)" 1 font-lock-constant-face prepend)
   ;; Built-in functions, e.g. EXISTS
   (list (concat "\\<\\(" (regexp-opt tlc-keywords) "\\)\\>")
         1 'font-lock-type-face)
   '("[^.]\\(\\.\\.\\.\\)$" 1 'underline prepend)
   )
  "List of keywords for nicely coloring X defaults.")

;;;###autoload
(define-derived-mode tlc-mode prog-mode "TLC" ()
  "Major mode for editing Tlc files, or files found in tlc directories."
  (kill-all-local-variables)
  (setq major-mode 'tlc-mode)
  (setq mode-name "TLC")
  (use-local-map tlc-mode-map)
  (set-syntax-table tlc-syntax-table)
  (make-local-variable 'comment-start)
  (make-local-variable 'comment-end)
  (make-local-variable 'comment-start-skip)
  (setq comment-start "%% "
        comment-end   "")
  (setq comment-start-skip "%%\\|/%")
  (make-local-variable 'indent-line-function)
  (setq indent-line-function 'tlc-indent)
  (make-local-variable 'font-lock-defaults)
  (setq font-lock-defaults '((tlc-font-lock-keywords)
                             nil ; do not do string/comment highlighting
                             nil ; keywords are case sensitive.
                             ;; This puts _ as a word constituent,
                             ;; simplifying our keywords significantly
                             ((?_ . "w"))))
  (tlc-version)
  (save-excursion
    (goto-char (point-min))
    (run-hooks 'tlc-mode-hook)))

(defun tlc-return ()
  "Handle carriage return in `tlc-mode'."
  (interactive)
  (delete-horizontal-space)
  (newline)
  (tlc-indent))

(defun tlc-indent ()
  "Indent the current line to the indentation of the previous line."
  (interactive)
  (let (curr-indent
        new-indent)
    (save-excursion
      (save-restriction
        (setq curr-indent (current-indentation))
        (setq new-indent (tlc--calc-indentation))))
    (if (= curr-indent new-indent)
        (when (< (current-column) curr-indent)
          (goto-char (+ (line-beginning-position) curr-indent)))
      ;; else indent
      (save-restriction
        (let ((curr-column (current-column)))
          (beginning-of-line)
          (delete-horizontal-space)
          (indent-to new-indent) ;; now current-column is new-indent
          (when (> curr-column curr-indent)
            ;; Suppose prior to tlc-indent, we had the following with cursor at column 17:
            ;;     0123456789012345678901234567890
            ;;        %assign var = MyFcn(p1)
            ;;                      ^
            ;; after indentation, we move the %assign to column 8 (because it is within another
            ;; context which requires we have indentation of 8):
            ;;     0123456789012345678901234567890
            ;;             %assign var = MyFcn(p1)
            ;;                           ^
            ;; we want the cursor to remain at 'M' and that's what the goto-char does.
            ;;
            ;; If the cursor was anywhere before the %assign, we want the cursor to be at the
            ;; '%' and that's what the indent-to function does, which is why we guard the goto-char
            ;; with (> curr-column curr-indent).
            (goto-char (+ (line-beginning-position) new-indent (- curr-column curr-indent)))))))))

(defvar tlc--indent-because-of-continuation nil)

(defun tlc--calc-indentation ()
  "Calculate the indentation of this line."
  (beginning-of-line)

  (if (and (looking-at "\\s-*%%{[0-9]+}\\s-*$")
           (not (tlc--in-multiline-comment)))
      ;; %%{N} means the place %%{N} at column 0 and the following line goes to column N
      0
    ;; Else calculate indentation based on current line PLUS the context of the prior line
    (let ((i-col (cond
                  ((and (looking-at
                         "\\s-*\\(?:\
\\(?:\\(?:%end\\(switch\\|roll\\|with\\|for\\|foreach\\|while\\|function\\)\\)\\>\\)\
\\|}\\)")
                        (not (tlc--in-multiline-comment)))
                   -4)
                  ((and (looking-at "\\s-*\\(%case\\|%default\\)\\>")
                        (not (tlc--in-multiline-comment)))
                   -2)
                  ;; %%} means shift by -4, %%}} means shift by -8, etc.
                  ((and (looking-at "\\s-*%%\\(}+\\)\\s-*$")
                        (not (tlc--in-multiline-comment)))
                   (* -4 (- (match-end 1) (match-beginning 1))))
                  ;;
                  (t 0)))
          (is-tlc-if-part (and (looking-at "\\s-*%\\(?:else\\|elseif\\|endif\\)") ;; part of a %if?
                               (not (tlc--in-multiline-comment))))
          (percent-in-multiline-comment (and (looking-at "\\s-*%") (tlc--in-multiline-comment))))

      ;; Walk up to calculate the indent based on the construct we are within and then add it to
      ;; i-col for the current construct.
      (setq tlc--indent-because-of-continuation nil)
      (if (bobp)
          (current-indentation)
        (save-excursion
          (tlc--indent-move-up is-tlc-if-part)
          (cond ((bobp)
                 (setq i-col (+ i-col (tlc--calc-next-indentation))))
                ;; '%' line following a "/%" line, if so add 1
                ((and percent-in-multiline-comment (looking-at "\\s-*/%"))
                 (setq i-col (1+ (current-indentation))))
                ;; Align %elsif, %else, %endif with corresponding %if?
                (is-tlc-if-part
                 (setq i-col (current-indentation)))
                (t
                 (setq i-col (+ (current-indentation)
                                (if (and tlc--indent-because-of-continuation
                                         (or (> 0 i-col)
                                             is-tlc-if-part))
                                    i-col
                                  (+ i-col (tlc--calc-next-indentation)))))
                 (if (< i-col 0) (setq i-col 0))))
          i-col)))))

(defun tlc--indent-move-up (is-tlc-if-part)
  "Move up for indent.
Move to first prior non-blank line or matching %if,  %else, %endif
when IS-TLC-IF-PART is t. Specify IS-TLC-IF-PART as t, if current
  line is %else, %elsif, %endif to align the %if statements."
  (let ((n-if-statements-to-skip 0)  ;; num %if statements to skip over when is-tlc-if-part is t
        done)
    (while (not done)
      ;;
      ;; Move up to first non-blank line
      ;;
      (forward-line -1)
      (beginning-of-line)
      (while (and (not (bobp))
                  (looking-at "^\\s-*$")) ;; skip blank lines
        (forward-line -1))
      ;;
      ;; Align %elseif, %else, %endif with the prior TLC statement
      ;;
      (if (and (not (bobp)) is-tlc-if-part)
          ;; If within a non-TLC statement, look up until we find the matching if "part"
          (if (looking-at "\\s-*%endif\\>")
              (setq n-if-statements-to-skip (1+ n-if-statements-to-skip))
            (if (> n-if-statements-to-skip 0)
                (if (looking-at "\\s-*%if\\>")
                    (setq n-if-statements-to-skip (1- n-if-statements-to-skip)))
              (setq  done (looking-at "\\s-*%\\(?:if\\|elseif\\|else\\)\\>"))))
        (setq done t)))))

(defun tlc--calc-next-indentation ()
  "Calculate indentation for the next line based on the current line."
  (if (and (looking-at "\\s-*%%")
           (not (tlc--in-multiline-comment)))
      (cond ((looking-at "\\s-*%%\\({+\\)\\s-*$")
             ;; %%{ means shift by 4, %%{{ means shift by 8, etc.
             (* 4 (- (match-end 1) (match-beginning 1))))
            ((looking-at "\\s-*%%{\\([0-9]+\\)}\\s-*$")
             ;; %%{N} means the place %%{N} at column 0 and the following line goes to column N
             (string-to-number (buffer-substring (match-beginning 1) (match-end 1))))
            (t 0))
    ;; Else compute indent based on language element
    (+
     ;; Include offset based on language element, e.g. %function means to indent by 4
     (cond ((save-excursion
              (and (not (tlc--assignment-continuation-p))
                   (tlc--beginning-of-statement))
              (or
               (and (looking-at "\\s-*%\\(?:switch\\|roll\\|with\\|if\\|for\\|\
foreach\\|while\\|else\\|elseif\\|function\\)\\>")
                    ;; Do not indent "tlc guards", i.e.
                    ;;    %if EXISTS(::_FILE_NAME_) == 0
                    ;;    %assign _FILE_NAME_ = 1
                    ;;    <tlc file body starts at first column, 0>
                    ;;    %endif
                    (not (looking-at "\\s-*%if\\s-+\\(?:\
EXISTS(\"?\\(?:::\\)?_[A-Z_0-9]+_\"?)\\s-*==\\s-*\\(?:0\\|TLC_FALSE\\)\\|\
!EXISTS(\"?\\(?:::\\)?_[A-Z_0-9]+_\"?)\\)"))
                    (not (tlc--in-multiline-comment)))
               (and (looking-at "\\s-*/%")
                    (tlc--in-multiline-comment))))
            4)
           ((and (save-excursion
                   (and (not (tlc--assignment-continuation-p))
                        (tlc--beginning-of-statement))
                   (looking-at "\\s-*%\\(?:case\\|default\\)\\>"))
                 (not (tlc--in-multiline-comment)))
            2)
           ;; End of multiline comment?
           ((and (looking-at "\\s-*%/")
                 (tlc--in-multiline-comment))
            (if (= (% (current-indentation) 2) 1)
                ;; When current-indentation is odd, we've shifted the "%/" by 1, so need to shift it
                ;; back
                -1
              0))
           ;; Continuation?
           ((and (tlc--assignment-continuation-p)
                 (save-excursion (forward-line -1)
                                 (not (tlc--assignment-continuation-p)))
                 (not (tlc--in-multiline-comment)))
            (setq tlc--indent-because-of-continuation t)
            4)
           ;; Open bracket target language line, e.g.  "void foo() {", "struct {\", etc.
           ((and (save-excursion
                   (end-of-line)
                   (re-search-backward "[^[:space:]]" (line-beginning-position) t)
                   (when (and (> (current-column) 0) ;; Do we have line ending in \, if so remove it
                              (looking-at "\\\\"))
                     (backward-char)
                     (when (looking-at "\\s-")
                       (re-search-backward "[^[:space:]]" (line-beginning-position) t)))
                   (looking-at "{"))
                 (not (tlc--in-multiline-comment)))
            4)
           (t 0))
     ;; Don't include continuation if not in a continuation block
     (if (and (not (tlc--line-special))
              (not (tlc--assignment-continuation-p))
              (save-excursion (forward-line -1)
                              (tlc--assignment-continuation-p))
              (not (tlc--in-multiline-comment)))
         -4
       0))))

(defun tlc--beginning-of-statement ()
  "Goto the beginning of a statement, skipping over continuation lines."
  (beginning-of-line)
  (if (not (save-excursion (forward-line -1) (tlc--assignment-continuation-p)))
      nil
    (forward-line -1)
    (while (tlc--assignment-continuation-p)
      (forward-line -1))
    (forward-line 1)
    (beginning-of-line)))

(defun tlc--line-special ()
  "Return t if the current line is a special language line."
  (save-excursion
    (save-match-data
      (beginning-of-line)
      (looking-at "\\s-*\\(?:%[^<]\\|}\\)"))))

(defun tlc--assignment-continuation-p ()
  "See if continuation lines should be indented."
  (save-excursion
    (end-of-line)
    (when (> (current-column) 2)
      (forward-char -3)
      (looking-at "\\.\\.\\."))))

(defun tlc--in-multiline-comment ()
  "Return t we are in a multiline comment."
  ;; One way to do this is to use
  ;;   (save-excursion (and (re-search-backward "/%\\|%/" nil t) (looking-at "/%")))
  ;; however this fails on something like the following where /% is in a string
  ;;    %assign foo = "dir/%<file>.h"
  ;;    %if a
  ;;    ^------- point
  ;; From
  ;; https://emacs.stackexchange.com/questions/14269/how-to-detect-if-the-point-is-within-a-comment-area
  ;; we can use syntax-ppss
  (nth 4 (syntax-ppss)))

;;; Add to mode list
;;;###autoload
(add-to-list 'auto-mode-alist '("\\.tlc\\'" . tlc-mode))

(provide 'tlc)

;;; tlc.el ends here

;; LocalWords:  Ludlam eludlam galbraith psg debian defun defcustom setq keymap defface grayscale sw
;; LocalWords:  IDNUM NUMTLCFILES STRINGOF SYSNAME TLCFILES UINTWHITE repeat:nil prog calc endswitch
;; LocalWords:  bobp nexti progn alist el stackexchange ppss openfile closefile curr elsif