pspp-mode.el: Make indentation closer to what lisp people like.
[pspp] / pspp-mode.el
1 ;;; pspp-mode.el --- Major mode for editing PSPP files
2
3 ;; Copyright (C) 2005,2018,2020 Free Software Foundation
4 ;; Author: Scott Andrew Borton <scott@pp.htv.fi>
5 ;; Created: 05 March 2005
6 ;; Version: 1.1
7 ;; Keywords: PSPP major-mode
8 ;; This file is not part of GNU Emacs.
9
10 ;;; Commentary:
11 ;; Based on the example wpdl-mode.el by Scott Borton
12
13 ;; Copyright (C) 2000, 2003 Scott Andrew Borton <scott@pp.htv.fi>
14
15 ;; This program is free software: you can redistribute it and/or modify
16 ;; it under the terms of the GNU General Public License as published by
17 ;; the Free Software Foundation, either version 3 of the License, or
18 ;; (at your option) any later version.
19 ;;
20 ;; This program is distributed in the hope that it will be useful,
21 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
22 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23 ;; GNU General Public License for more details.
24 ;;
25 ;; You should have received a copy of the GNU General Public License
26 ;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
27
28 ;;; Code:
29 (defvar pspp-mode-hook nil)
30
31
32 (defvar pspp-mode-map
33   (let ((pspp-mode-map (make-keymap)))
34     (define-key pspp-mode-map "\C-j" 'newline-and-indent)
35     pspp-mode-map)
36   "Keymap for PSPP major mode")
37
38
39 ;;;###autoload
40 (add-to-list 'auto-mode-alist '("\\.sps\\'" . pspp-mode))
41
42
43 (defun pspp-data-block-p ()
44   "Returns t if current line is inside a data block."
45   (save-excursion
46     (let ((pspp-end-of-block-found nil)
47           (pspp-start-of-block-found nil))
48       (beginning-of-line)
49       (while (not (or
50                    (or (bobp) pspp-end-of-block-found)
51                    pspp-start-of-block-found))
52         (set 'pspp-end-of-block-found (looking-at "^[ \t]*END[\t ]+DATA\."))
53         (set 'pspp-start-of-block-found (looking-at "^[ \t]*BEGIN[\t ]+DATA"))
54         (forward-line -1))
55
56       (and pspp-start-of-block-found (not pspp-end-of-block-found)))))
57
58
59 (defconst pspp-indent-width
60   2
61   "size of indent")
62
63
64 (defconst pspp-indenters
65   (concat "^[\t ]*"
66           (regexp-opt '("DO"
67                         "BEGIN"
68                         "LOOP"
69                         "INPUT") t)
70           "[\t ]+")
71   "constructs which cause indentation")
72
73
74 (defconst pspp-unindenters
75   (concat "^[\t ]*END[\t ]+"
76           (regexp-opt '("IF"
77                         "DATA"
78                         "LOOP"
79                         "REPEAT"
80                         "INPUT") t)
81           "[\t ]*")
82   ;; Note that "END CASE" and "END FILE" do not unindent.
83   "constructs which cause end of indentation")
84
85
86 (defun pspp-indent-line ()
87   "Indent current line as PSPP code."
88   (beginning-of-line)
89   (let ((verbatim nil)
90         (the-indent 0)    ; Default indent to column 0
91         (case-fold-search t))
92     (if (bobp)
93         (setq the-indent 0))  ; First line is always non-indented
94     (let ((within-command nil) (blank-line t))
95       ;; If the most recent non blank line ended with a `.' then
96       ;; we're at the start of a new command.
97       (save-excursion
98         (while (and blank-line (not (bobp)))
99           (forward-line -1)
100
101           (if (and (not (pspp-data-block-p)) (not (looking-at "^[ \t]*$")))
102               (progn
103                 (setq blank-line nil)
104
105                 (if (not (looking-at ".*\\.[ \t]*$"))
106                     (setq within-command t))))))
107       ;; If we're not at the start of a new command, then add an indent.
108       (if within-command
109           (set 'the-indent (+ 1 the-indent))))
110     ;; Set the indentation according to the DO - END blocks
111     (save-excursion
112       (beginning-of-line)
113       (while (not (bobp))
114         (beginning-of-line)
115         (if (not (pspp-comment-p))
116             (cond ((save-excursion
117                      (forward-line -1)
118                      (looking-at pspp-indenters))
119                    (set 'the-indent (+ the-indent 1)))
120
121                   ((looking-at pspp-unindenters)
122                    (set 'the-indent (- the-indent 1)))))
123         (forward-line -1)))
124
125     (save-excursion
126       (beginning-of-line)
127       (if (looking-at "^[\t ]*ELSE")
128           (set 'the-indent (- the-indent 1))))
129
130     ;; Stuff in the data-blocks should be untouched
131     (if (not (pspp-data-block-p)) (indent-line-to (* pspp-indent-width the-indent)))))
132
133
134 (defun pspp-comment-start-line-p ()
135   "Returns t if the current line is the first line of a comment, nil otherwise"
136   (beginning-of-line)
137   (or (looking-at "^\*")
138       (looking-at "^[\t ]*COMMENT[\t ]")))
139
140
141 (defun pspp-comment-end-line-p ()
142   "Returns t if the current line is the candidate for the last line of a comment, nil otherwise"
143   (beginning-of-line)
144   (looking-at ".*\\.[\t ]*$"))
145
146
147 (defun pspp-comment-p ()
148   "Returns t if point is in a comment.  Nil otherwise."
149   (if (pspp-data-block-p)
150       nil
151     (let ((pspp-comment-start-found nil)
152           (pspp-comment-end-found nil)
153           (pspp-single-line-comment nil)
154           (lines 1))
155       (save-excursion
156         (end-of-line)
157         (while (and (>= lines 0)
158                     (not pspp-comment-start-found)
159                     (not pspp-comment-end-found))
160           (beginning-of-line)
161           (if (pspp-comment-start-line-p) (set 'pspp-comment-start-found t))
162           (if (bobp)
163               (set 'pspp-comment-end-found nil)
164             (save-excursion
165               (forward-line -1)
166               (if (pspp-comment-end-line-p) (set 'pspp-comment-end-found t))))
167           (set 'lines (forward-line -1))))
168
169       (save-excursion
170         (set 'pspp-single-line-comment (and
171                                         (pspp-comment-start-line-p)
172                                         (pspp-comment-end-line-p))))
173
174       (or pspp-single-line-comment
175           (and pspp-comment-start-found (not pspp-comment-end-found))))))
176
177
178 (defvar pspp-mode-syntax-table
179   (let ((x-pspp-mode-syntax-table (make-syntax-table)))
180     
181     ;; Special chars allowed in variables
182     (modify-syntax-entry ?#  "w" x-pspp-mode-syntax-table)
183     (modify-syntax-entry ?@  "w" x-pspp-mode-syntax-table)
184     (modify-syntax-entry ?$  "w" x-pspp-mode-syntax-table)
185
186     ;; Comment syntax
187     ;;  This is incomplete, because:
188     ;;  a) Comments can also be given by COMMENT
189     ;;  b) The sequence .\n* is interpreted incorrectly.
190
191     (modify-syntax-entry ?*  ". 2" x-pspp-mode-syntax-table)
192     (modify-syntax-entry ?.  ". 3" x-pspp-mode-syntax-table)
193     (modify-syntax-entry ?\n  "- 41" x-pspp-mode-syntax-table)
194
195     ;; String delimiters
196     (modify-syntax-entry ?'  "\"" x-pspp-mode-syntax-table)
197     (modify-syntax-entry ?\"  "\"" x-pspp-mode-syntax-table)
198
199     x-pspp-mode-syntax-table)
200
201   "Syntax table for pspp-mode")
202
203
204 (defconst pspp-font-lock-keywords
205   (list (cons
206          (concat "\\<"
207                  (regexp-opt '(
208                                "END DATA"
209                                "ACF"
210                                "ADD FILES"
211                                "ADD VALUE LABELS"
212                                "AGGREGATE"
213                                "ANOVA"
214                                "APPLY DICTIONARY"
215                                "AREG"
216                                "ARIMA"
217                                "AUTORECODE"
218                                "BEGIN DATA"
219                                "BREAK"
220                                "CASEPLOT"
221                                "CASESTOVARS"
222                                "CCF"
223                                "CLEAR TRANSFORMATIONS"
224                                "CLUSTER"
225                                "COMPUTE"
226                                "CONJOINT"
227                                "CORRELATIONS"
228                                "COXREG"
229                                "COUNT"
230                                "CREATE"
231                                "CROSSTABS"
232                                "CURVEFIT"
233                                "DATA LIST"
234                                "DATE"
235                                "DEBUG CASEFILE"
236                                "DEBUG EVALUATE"
237                                "DEBUG MOMENTS"
238                                "DEBUG POOL"
239                                "DELETE VARIABLES"
240                                "DESCRIPTIVES"
241                                "DISCRIMINANT"
242                                "DISPLAY"
243                                "DOCUMENT"
244                                "DO IF"
245                                "DO REPEAT"
246                                "DROP DOCUMENTS"
247                                "ECHO"
248                                "EDIT"
249                                "ELSE"
250                                "ELSE IF"
251                                "END CASE"
252                                "END FILE"
253                                "END FILE TYPE"
254                                "END IF"
255                                "END INPUT PROGRAM"
256                                "END LOOP"
257                                "END REPEAT"
258                                "ERASE"
259                                "EXAMINE"
260                                "EXECUTE"
261                                "EXIT"
262                                "EXPORT"
263                                "FACTOR"
264                                "FILE HANDLE"
265                                "FILE LABEL"
266                                "FILE TYPE"
267                                "FILTER"
268                                "FINISH"
269                                "FIT"
270                                "FLIP"
271                                "FORMATS"
272                                "FREQUENCIES"
273                                "GENLOG"
274                                "GET"
275                                "GET TRANSLATE"
276                                "GLM"
277                                "GRAPH"
278                                "HILOGLINEAR"
279                                "HOST"
280                                "IF"
281                                "IGRAPH"
282                                "IMPORT"
283                                "INCLUDE"
284                                "INFO"
285                                "INPUT MATRIX"
286                                "INPUT PROGRAM"
287                                "KEYED DATA LIST"
288                                "LEAVE"
289                                "LIST"
290                                "LOGLINEAR"
291                                "LOGISITIC REGRESSION"
292                                "LOOP"
293                                "MATCH FILES"
294                                "MATRIX DATA"
295                                "MCONVERT"
296                                "MEANS"
297                                "MISSING VALUES"
298                                "MODIFY VARS"
299                                "MULT RESPONSE"
300                                "MVA"
301                                "NEW FILE"
302                                "N"
303                                "N OF CASES"
304                                "NLR"
305                                "NONPAR CORR"
306                                "NPAR TESTS"
307                                "NUMBERED"
308                                "NUMERIC"
309                                "OLAP CUBES"
310                                "OMS"
311                                "ONEWAY"
312                                "ORTHOPLAN"
313                                "PACF"
314                                "PARTIAL CORR"
315                                "PEARSON CORRELATIONS"
316                                "PERMISSIONS"
317                                "PLOT"
318                                "POINT"
319                                "PPLOT"
320                                "PREDICT"
321                                "PRESERVE"
322                                "PRINT EJECT"
323                                "PRINT"
324                                "PRINT FORMATS"
325                                "PRINT SPACE"
326                                "PROCEDURE OUTPUT"
327                                "PROXIMITIES"
328                                "Q"
329                                "QUICK CLUSTER"
330                                "QUIT"
331                                "RANK"
332                                "RECODE"
333                                "RECORD TYPE"
334                                "REFORMAT"
335                                "REGRESSION"
336                                "RENAME VARIABLES"
337                                "REPEATING DATA"
338                                "REPORT"
339                                "REREAD"
340                                "RESTORE"
341                                "RMV"
342                                "SAMPLE"
343                                "SAVE"
344                                "SAVE TRANSLATE"
345                                "SCRIPT"
346                                "SELECT IF"
347                                "SET"
348                                "SHOW"
349                                "SORT CASES"
350                                "SORT"
351                                "SPCHART"
352                                "SPLIT FILE"
353                                "STRING"
354                                "SUBTITLE"
355                                "SUMMARIZE"
356                                "SURVIVAL"
357                                "SYSFILE INFO"
358                                "TEMPORARY"
359                                "TITLE"
360                                "TSET"
361                                "TSHOW"
362                                "TSPLOT"
363                                "T-TEST"
364                                "UNIANOVA"
365                                "UNNUMBERED"
366                                "UPDATE"
367                                "USE"
368                                "VALUE LABELS"
369                                "VARIABLE ALIGNMENT"
370                                "VARIABLE LABELS"
371                                "VARIABLE LEVEL"
372                                "VARIABLE WIDTH"
373                                "VARSTOCASES"
374                                "VECTOR"
375                                "VERIFY"
376                                "WEIGHT"
377                                "WRITE"
378                                "WRITE FORMATS"
379                                "XSAVE") t) "\\>")
380          'font-lock-builtin-face)
381
382         (cons
383          (concat "\\<" (regexp-opt
384                         '("ALL" "AND" "BY" "EQ" "GE" "GT" "LE" "LT" "NE" "NOT" "OR" "TO" "WITH")
385                         t) "\\>")
386          'font-lock-keyword-face)
387
388         (cons
389          (concat "\\<"
390                  (regexp-opt '(
391                                "ABS"
392                                "ACOS"
393                                "ANY"
394                                "ANY"
395                                "ARCOS"
396                                "ARSIN"
397                                "ARTAN"
398                                "ASIN"
399                                "ATAN"
400                                "CDF.BERNOULLI"
401                                "CDF.BETA"
402                                "CDF.BINOM"
403                                "CDF.BVNOR"
404                                "CDF.CAUCHY"
405                                "CDF.CHISQ"
406                                "CDF.EXP"
407                                "CDF.F"
408                                "CDF.GAMMA"
409                                "CDF.GEOM"
410                                "CDF.HALFNRM"
411                                "CDF.HYPER"
412                                "CDF.IGAUSS"
413                                "CDF.LAPLACE"
414                                "CDF.LNORMAL"
415                                "CDF.LOGISTIC"
416                                "CDF.NEGBIN"
417                                "CDF.NORMAL"
418                                "CDF.PARETO"
419                                "CDF.POISSON"
420                                "CDF.RAYLEIGH"
421                                "CDF.SMOD"
422                                "CDF.SRANGE"
423                                "CDF.T"
424                                "CDF.T1G"
425                                "CDF.T2G"
426                                "CDF.UNIFORM"
427                                "CDF.WEIBULL"
428                                "CDFNORM"
429                                "CFVAR"
430                                "CONCAT"
431                                "COS"
432                                "CTIME.DAYS"
433                                "CTIME.HOURS"
434                                "CTIME.MINUTES"
435                                "CTIME.SECONDS"
436                                "DATE.DMY"
437                                "DATE.MDY"
438                                "DATE.MOYR"
439                                "DATE.QYR"
440                                "DATE.WKYR"
441                                "DATE.YRDAY"
442                                "EXP"
443                                "IDF.BETA"
444                                "IDF.CAUCHY"
445                                "IDF.CHISQ"
446                                "IDF.EXP"
447                                "IDF.F"
448                                "IDF.GAMMA"
449                                "IDF.HALFNRM"
450                                "IDF.IGAUSS"
451                                "IDF.LAPLACE"
452                                "IDF.LNORMAL"
453                                "IDF.LOGISTIC"
454                                "IDF.NORMAL"
455                                "IDF.PARETO"
456                                "IDF.RAYLEIGH"
457                                "IDF.SMOD"
458                                "IDF.SRANGE"
459                                "IDF.T"
460                                "IDF.T1G"
461                                "IDF.T2G"
462                                "IDF.UNIFORM"
463                                "IDF.WEIBULL"
464                                "INDEX"
465                                "INDEX"
466                                "LAG"
467                                "LAG"
468                                "LAG"
469                                "LAG"
470                                "LENGTH"
471                                "LG10"
472                                "LN"
473                                "LNGAMMA"
474                                "LOWER"
475                                "LPAD"
476                                "LPAD"
477                                "LTRIM"
478                                "LTRIM"
479                                "MAX"
480                                "MAX"
481                                "MBLEN.BYTE"
482                                "MEAN"
483                                "MIN"
484                                "MIN"
485                                "MISSING"
486                                "MOD"
487                                "MOD10"
488                                "NCDF.BETA"
489                                "NCDF.CHISQ"
490                                "NCDF.F"
491                                "NCDF.T"
492                                "NMISS"
493                                "NORMAL"
494                                "NPDF.BETA"
495                                "NPDF.CHISQ"
496                                "NPDF.F"
497                                "NPDF.T"
498                                "NUMBER"
499                                "NVALID"
500                                "PDF.BERNOULLI"
501                                "PDF.BETA"
502                                "PDF.BINOM"
503                                "PDF.BVNOR"
504                                "PDF.CAUCHY"
505                                "PDF.CHISQ"
506                                "PDF.EXP"
507                                "PDF.F"
508                                "PDF.GAMMA"
509                                "PDF.GEOM"
510                                "PDF.HALFNRM"
511                                "PDF.HYPER"
512                                "PDF.IGAUSS"
513                                "PDF.LANDAU"
514                                "PDF.LAPLACE"
515                                "PDF.LNORMAL"
516                                "PDF.LOG"
517                                "PDF.LOGISTIC"
518                                "PDF.NEGBIN"
519                                "PDF.NORMAL"
520                                "PDF.NTAIL"
521                                "PDF.PARETO"
522                                "PDF.POISSON"
523                                "PDF.RAYLEIGH"
524                                "PDF.RTAIL"
525                                "PDF.T"
526                                "PDF.T1G"
527                                "PDF.T2G"
528                                "PDF.UNIFORM"
529                                "PDF.WEIBULL"
530                                "PDF.XPOWER"
531                                "PROBIT"
532                                "RANGE"
533                                "RANGE"
534                                "RINDEX"
535                                "RINDEX"
536                                "RND"
537                                "RPAD"
538                                "RPAD"
539                                "RTRIM"
540                                "RTRIM"
541                                "RV.BERNOULLI"
542                                "RV.BETA"
543                                "RV.BINOM"
544                                "RV.CAUCHY"
545                                "RV.CHISQ"
546                                "RV.EXP"
547                                "RV.F"
548                                "RV.GAMMA"
549                                "RV.GEOM"
550                                "RV.HALFNRM"
551                                "RV.HYPER"
552                                "RV.IGAUSS"
553                                "RV.LANDAU"
554                                "RV.LAPLACE"
555                                "RV.LEVY"
556                                "RV.LNORMAL"
557                                "RV.LOG"
558                                "RV.LOGISTIC"
559                                "RV.LVSKEW"
560                                "RV.NEGBIN"
561                                "RV.NORMAL"
562                                "RV.NTAIL"
563                                "RV.PARETO"
564                                "RV.POISSON"
565                                "RV.RAYLEIGH"
566                                "RV.RTAIL"
567                                "RV.T"
568                                "RV.T1G"
569                                "RV.T2G"
570                                "RV.UNIFORM"
571                                "RV.WEIBULL"
572                                "RV.XPOWER"
573                                "SD"
574                                "SIG.CHISQ"
575                                "SIG.F"
576                                "SIN"
577                                "SQRT"
578                                "STRING"
579                                "SUBSTR"
580                                "SUBSTR"
581                                "SUM"
582                                "SYSMIS"
583                                "SYSMIS"
584                                "TAN"
585                                "TIME.DAYS"
586                                "TIME.HMS"
587                                "TRUNC"
588                                "UNIFORM"
589                                "UPCASE"
590                                "VALUE"
591                                "VARIANCE"
592                                "XDATE.DATE"
593                                "XDATE.HOUR"
594                                "XDATE.JDAY"
595                                "XDATE.MDAY"
596                                "XDATE.MINUTE"
597                                "XDATE.MONTH"
598                                "XDATE.QUARTER"
599                                "XDATE.SECOND"
600                                "XDATE.TDAY"
601                                "XDATE.TIME"
602                                "XDATE.WEEK"
603                                "XDATE.WKDAY"
604                                "XDATE.YEAR"
605                                "YRMODA")
606                              t) "\\>")  'font-lock-function-name-face)
607
608         '( "\\<[#$@a-zA-Z][a-zA-Z0-9_]*\\>" . font-lock-variable-name-face))
609   "Highlighting expressions for PSPP mode.")
610
611
612 ;;;###autoload
613 (defun pspp-mode ()
614   (interactive)
615   (kill-all-local-variables)
616   (use-local-map pspp-mode-map)
617   (set-syntax-table pspp-mode-syntax-table)
618
619   (set (make-local-variable 'font-lock-keywords-case-fold-search) t)
620   (set (make-local-variable 'font-lock-defaults) '(pspp-font-lock-keywords))
621
622   (set (make-local-variable 'indent-line-function) 'pspp-indent-line)
623   (set (make-local-variable 'comment-start) "* ")
624   (set (make-local-variable 'compile-command)
625        (concat "pspp "
626                buffer-file-name))
627
628   (setq major-mode 'pspp-mode)
629   (setq mode-name "PSPP")
630   (run-hooks 'pspp-mode-hook))
631
632 (provide 'pspp-mode)
633
634 ;;; pspp-mode.el ends here