;;;; -*- Mode: Lisp; lexical-binding: t; -*- ;;;; web68.el --- major mode for editing Web 68 source ;;;; This file contains Web 68 mode. ;;;; It is used to colourise Web 68 and Algol 68 files. ;;;; Copyright (C) 2014 Sian Mountbatten ;;;; Author: Sian Mountbatten ;;;; Keywords: languages (defgroup web68 nil "Major mode for editing Web 68/Algol 68 source in Emacs." :link '(custom-group-link :tag "Font Lock Faces group" font-lock-faces) :group 'languages) (defvar web68-mode-abbrev-table nil "Abbrev table in use in Web68 mode buffers.") (define-abbrev-table 'web68-mode-abbrev-table ()) (defvar web68-mode-map (let ((map (make-sparse-keymap))) map) "Keymap used in Web68 mode.") (defvar web68-imenu-generic-expression '((nil "^[ \t]*\\(PROC\\|OP\\)[ \t\n]+\\([a-zA-Z0-9_.:]+\\)" 2)) "Imenu expression for Web68 mode. See `imenu-generic-expression'.") (defvar web68-mode-syntax-table (let ((st (make-syntax-table))) (modify-syntax-entry ?\( "()" st) (modify-syntax-entry ?\) ")(" st) (modify-syntax-entry ?# "$" st) (modify-syntax-entry ?{ "<" st) (modify-syntax-entry ?} ">" st) (modify-syntax-entry ?$ "$" st) (modify-syntax-entry ?_ "_" st) st) "Syntax table in use in Web68-mode buffers.") ;;; Define the font lock colours (defface *web68-grey* '((((class color) (min-colors 88) (background dark)) ;. :background "black" :foreground "#808080")) "Used for Web 68 text." :group 'web68) (defface *web68-red* '((((class color) (min-colors 88) (background dark)) ;. :background "black" :foreground "red")) "Used for numbers." :group 'web68) (defface *web68-darkcyan* '((((class color) (min-colors 88) (background dark)) ;. :background "black" :foreground "darkCyan")) "Used for punctuation." :group 'web68) (defface *web68-yellow* '((((class color) (min-colors 88) (background dark)) ;. :background "black" :foreground "yellow")) "Used for strings." :group 'web68) (defface *web68-brown* '((((class color) (min-colors 88) (background dark)) ;. :background "black" :foreground "brown")) "Used for comments." :group 'web68) (defface *web68-magenta* '((((class color) (min-colors 88) (background dark)) ;. :background "black" :foreground "magenta")) "Used for operators." :group 'web68) (defface *web68-green* '((((class color) (min-colors 88) (background dark)) ;. :background "black" :foreground "green")) "Used for dates/delimited text." :group 'web68) (defface *web68-cyan* '((((class color) (min-colors 88) (background dark)) ;. :background "black" :foreground "cyan")) "Used for Algol 68 modes and Web 68 commands." :group 'web68) (defface *web68-blue* '((((class color) (min-colors 88) (background dark)) ;. :background "black" :foreground "skyblue")) "Used for identifiers." :group 'web68) (defface *web68-white* '((((class color) (min-colors 88) (background dark)) ;. :background "black" :foreground "white")) "Used for bold tags." :group 'web68) ;;;; The next data structure is used to identify a bold tag. ;;;; The length of the tag determines which sorted list is used. ;;;; The `chop' function then finds the actual position of the ;;;; bold tag and, consequently, its face property. ;;;; ;;;; Define which face is used for each bold tag (defconst *web68-bold-tags* '(nil (("I" *web68-magenta*)) ; length 1 (("AT" *web68-white*) ; length 2 ("BY" *web68-white*) ("CO" *web68-white*) ("DO" *web68-white*) ("EQ" *web68-magenta*) ("FI" *web68-white*) ("GE" *web68-magenta*) ("GT" *web68-magenta*) ("IF" *web68-white*) ("IM" *web68-magenta*) ("IN" *web68-white*) ("IS" *web68-white*) ("LE" *web68-magenta*) ("LT" *web68-magenta*) ("NE" *web68-magenta*) ("OD" *web68-white*) ("OF" *web68-white*) ("OP" *web68-white*) ("OR" *web68-magenta*) ("RE" *web68-magenta*) ("TO" *web68-white*) ("UP" *web68-magenta*)) (("ABS" *web68-magenta*) ; length 3 ("AND" *web68-magenta*) ("BIN" *web68-magenta*) ("DIV" *web68-magenta*) ("END" *web68-white*) ("FOR" *web68-white*) ("INT" *web68-cyan*) ("LOC" *web68-cyan*) ("LWB" *web68-magenta*) ("MOD" *web68-magenta*) ("NIL" *web68-red*) ("NOT" *web68-magenta*) ("OUT" *web68-white*) ("REF" *web68-cyan*) ("SHL" *web68-magenta*) ("SHR" *web68-magenta*) ("UPB" *web68-magenta*) ("USE" *web68-white*)) (("BITS" *web68-cyan*) ; length 4 ("BOOL" *web68-cyan*) ("CASE" *web68-white*) ("CHAR" *web68-cyan*) ("CONJ" *web68-magenta*) ("DECS" *web68-white*) ("DOWN" *web68-magenta*) ("ELEM" *web68-magenta*) ("ELIF" *web68-white*) ("ELSE" *web68-white*) ("ESAC" *web68-white*) ("EXIT" *web68-white*) ("FILE" *web68-cyan*) ("FLEX" *web68-cyan*) ("FROM" *web68-white*) ("GOTO" *web68-white*) ("HEAP" *web68-cyan*) ("ISNT" *web68-white*) ("KEEP" *web68-white*) ("LONG" *web68-cyan*) ("MODE" *web68-white*) ("OUSE" *web68-white*) ("OVER" *web68-magenta*) ("PRIO" *web68-white*) ("PROC" *web68-cyan*) ("REAL" *web68-cyan*) ("REPR" *web68-magenta*) ("SKIP" *web68-white*) ("THEN" *web68-white*) ("TRUE" *web68-red*) ("VOID" *web68-cyan*)) (("BEGIN" *web68-white*) ; length 5 ("COMPL" *web68-cyan*) ("DIVAB" *web68-magenta*) ("EMPTY" *web68-red*) ("FALSE" *web68-red*) ("MODAB" *web68-magenta*) ("SHORT" *web68-cyan*) ("UNION" *web68-cyan*) ("WHILE" *web68-white*)) (("ENTIER" *web68-magenta*) ; length 6 ("FINISH" *web68-white*) ("OVERAB" *web68-magenta*) ("PLUSAB" *web68-magenta*) ("PLUSTO" *web68-magenta*) ("STRING" *web68-cyan*) ("STRUCT" *web68-cyan*)) (("CHANNEL" *web68-cyan*) ; length 7 ("CONTEXT" *web68-white*) ("MINUSAB" *web68-magenta*) ("PROGRAM" *web68-white*) ("TIMESAB" *web68-magenta*)))) (setq *web68-font-lock-keywords* (let ((ops "\\<\\(A\\(?:BS\\|ND\\)\\|BIN\\|CONJ\\|D\\(?:IV\\(?:AB\\)?\\|OWN\\)\\|E\\(?:LEM\\|NTIER\\|Q\\)\\|G[ET]\\|IM?\\|L\\(?:WB\\|[ET]\\)\\|M\\(?:INUSAB\\|OD\\(?:AB\\)?\\)\\|N\\(?:E\\|OT\\)\\|O\\(?:R\\|VER\\(?:AB\\)?\\)\\|PLUS\\(?:AB\\|TO\\)\\|RE\\(?:PR\\)?\\|SH[LR]\\|TIMESAB\\|UPB?\\)\\>") (bold-tags "\\<\\(AT\\|B\\(?:EGIN\\|Y\\)\\|C\\(?:ASE\\|ONTEXT\\)\\|D\\(?:ECS\\|O\\)\\|E\\(?:L\\(?:IF\\|SE\\)\\|ND\\|SAC\\|XIT\\)\\|F\\(?:I\\(?:NISH\\)?\\|OR\\|ROM\\)\\|GOTO\\|I\\(?:SNT\\|[FNS]\\)\\|KEEP\\|MODE\\|O\\(?:U\\(?:SE\\|T\\)\\|[DFP]\\)\\|PR\\(?:IO\\|OGRAM\\)\\|SKIP\\|T\\(?:HEN\\|O\\)\\|\\(?:US\\|WHIL\\)E\\)\\>") (modes "\\<\\(B\\(?:ITS\\|OOL\\)\\|C\\(?:HA\\(?:NNEL\\|R\\)\\|OMPL\\)\\|F\\(?:ILE\\|LEX\\)\\|HEAP\\|INT\\|LO\\(?:C\\|NG\\)\\|PROC\\|RE\\(?:AL\\|F\\)\\|S\\(?:HORT\\|TR\\(?:ING\\|UCT\\)\\)\\|UNION\\|VOID\\)\\>") (number-tags "\\<\\(EMPTY\\|FALSE\\|NIL\\|TRUE\\)\\>")) '((list (ops . *web68-magenta*)) (list (bold-tags . *web68-white*)) (list (modes . *web68-cyan*)) (list (number-tags . *web68-red*))))) ; Keywords for Web 68 mode. Font-locking is performed by `web68-finite-state-machine'." (defun web68-chop (object sequence &optional key comp order) "Finds OBJECT in a sorted SEQUENCE. KEY -- function to extract the key from an element of SEQUENCE (default #'identity). COMP -- function to compare OBJECTs (default #'string=). ORDER -- function to order OBJECTs (default #'string<). CHOP returns a list containing two values: if (cadr return) = nil, OBJECT is not in SEQUENCE else (car return) gives its index in SEQUENCE." (let ((fkey (if key key 'identity)) (fcomp (if comp comp 'string=)) (forder (if order order 'string<)) (l 0) (u (1- (length sequence))) m item) (while (if (<= l u) (progn (setf m (/ (+ l u) 2)) (setf item (elt sequence m)) (not (funcall fcomp object (funcall fkey item)))) nil) (if (funcall forder object (funcall fkey item)) (setf u (1- m)) (setf l (1+ m)))) (list (if (> l u) u m) (if (> l u) nil m)))) (defvar *web68-tag* nil) ; set to tag by web68-finite-state-machine (defun web68-set-text-colour (leng face) "Set the colour of the chars at (POINT):(POINT+leng) to FACE." (put-text-property (point) (+ (point) leng) 'face face)) (defun web68-find-bold-tag () "Identify the tag in *web68-tag* and colourise it accordingly. If the tag is not identified, colourise it as a mode indicant." (let* ((lst (nth (length *web68-tag*) *web68-bold-tags*)) (ret (web68-chop *web68-tag* lst))) (if (cadr ret) (web68-set-text-colour (length *web68-tag*) (cadr (nth (car ret) lst))) (web68-set-text-colour (length *web68-tag*) *web68-cyan*)))) (defvar *states* '(;; ('face ;; (flags/nil matcher face/nil 'function/next-state)) (*web68-darkcyan* ; default: punctuation [0] Algol 68 (nil "COMMENT" *web68-white* 1) ; start of COMMENT comment (nil "#" *web68-white* 3) ; start of # comment (nil "{" *web68-white* 4) ; start of brace comment (nil "!" *web68-darkcyan* 9) ; end of snippet (?- "@[ 123adhim<^.!=\/,]" nil 9) ; Web 68 command (nil "@@" *web68-magenta* 0) ; AT operator (?- "[A-Z]" nil 5) ; start of a bold tag (?- "[a-z]" nil 6) ; start of an identifier (?- "[0-9]" nil 7) ; start of a number (?- "[.]" nil 7) ; start of a REAL (?- "[-<=>+*/%^]" nil 8) ; start of an operator (nil ":=:" *web68-magenta* 0) (nil ":/=:" *web68-magenta* 0) (nil ":=" *web68-darkcyan* 0)) (*web68-brown* ; default: comment [1] COMMENT state (nil "COMMENT" *web68-white* 0)) (*web68-brown* ; default: comment [2] CO state (nil "CO[^A-Z]" '*web68-white* 0)) (*web68-brown* ; default: comment [3] # state (nil "#" *web68-white* 0)) ; end of # comment (*web68-brown* ; default: comment [4] { state (nil "}" *web68-white* 0)) (*web68-white* ; default: bold tag [5] (?f "[A-Z][0-9A-Z]*" nil 'web68-find-bold-tag)) (*web68-blue* ; default: identifier [6] (nil "[a-z][0-9a-z ]" *web68-blue* 0)) (*web68-red* ; default: number [7] (nil "2r[01 ]+" *web68-red* 0) ; binary denotation (nil "4r[0-3 ]+" *web68-red* 0) ; base-4 " (nil "8r[0-7 ]+" *web68-red* 0) ; octal " (nil "16r[0-9a-f ]+" *web68-red* 0) ; hex " ;; reals with exponent: -1e+10 .1e+10 +1.2e+10 (nil "[-+]?[0-9 ]+e[-+]?[0-9]+" *web68-red* 0) (nil "[-+]?\.[0-9 ]+e[-+]?[0-9]+" *web68-red* 0) (nil "[-+]?[0-9 ]+\.[0-9 ]+e[-+]?[0-9]+" *web68-red* 0) ;; reals without exponent: -1 .1 +1.2 (nil "[-+]?[0-9 ]+" *web68-red* 0) (nil "[-+]?\.[0-9 ]+" *web68-red* 0) (nil "[-+]?[0-9 ]+\.[0-9 ]+" *web68-red* 0)) (*web68-magenta* ; default: operator [8] (nil "[-+*/%]:=" *web68-magenta* 0) (nil "\\(%*:=\\|+=:\\|\*\*\\|/=\\)" *web68-magenta* 0) (nil "\\(<=\\|>=\\)" *web68-magenta* 0) (nil "[-+*/%^<>=]" *web68-magenta* 0)) ; simple operator (*web68-grey* ; default: Web 68 text [9] (nil "!" *web68-white* 0) ; start of snippet (nil "<" *web68-magenta* 11) ; start of HTML tag (nil "@[123 ]" *web68-cyan* 9) ; section command (nil "@[adm]" *web68-cyan* 0) ; to Algol 68 state (nil "@h" *web68-cyan* 12) ; HTML state (nil "@[i<.^]" *web68-cyan* 10) ; start of delimited text (nil "@[!\/,=]" '*web68-cyan* 0)) (*web68-green* ; default: delimited text [10] (nil "@>=?" *web68-cyan* 0)) ; end of delimited text (*web68-magenta* ; default: HTML tag [11] (nil ">" *web68-magenta* 9)) ; end of HTML tag (*web68-grey* ; default: HTML state [12] (nil "@>" *web68-cyan* 9)))) ; end of HTML state (defmacro inc (var) (list 'setq var (list '1+ var))) (defun web68-finite-state-machine (start end &optional verbose) "Executes the transitions defined in STATES starting at START upto END. The elements of each transition have the following meanings: Element 0: flags: nil -- no flags - -- (point) should not be moved after the match f -- the 3rd element is a function instead of a face Element 1: matcher Element 2: face/function Element 3: next state." (let ((current-state-index 0) state default index transition flags regexp transition-setup) (fset 'transition-setup (lambda (inx stat) (setq transition (nth inx stat) flags (car transition) regexp (cadr transition)))) (goto-char start) (while (< (point) end) (setq state (nth current-state-index *states*) default (car state) index 1) (while (< index (length state)) ((symbol-function 'transition-setup) index state) ;; we need a function which computes transition, flags and regexp each ;; time index is incremented (while (not (looking-at regexp)) ((symbol-function 'transition-setup) (inc index) state)) (if (= index (length state)) ;; no match (progn (web68-set-text-colour 1 default-face) (forward-char 1)) ;; match (let ((matched (match-string 0))) (cond ((zerop (length flags)) ; no flags (web68-set-text-colour (length matched) (nth 2 transition)) (forward-char (length matched))) ((char-equal ?f flags) ; Element 2 is a function (setq *web68-tag* (buffer-substring-no-properties (point) (+ (point) (length matched)))) (funcall (nth 2 transition)) ; call the function (forward-char (length matched))) ((char-equal ?- flags) ; Don't move point (web68-set-text-colour (length matched) (nth 2 transition)))) (setq current-state-index (nth 3 transition)))))))) (defun web68-find-font-lock-region () "Finds the region over which font-locking will occur. (1) Looks backward for a Web 68 sectioning command or the buffer beginning. (2) Looks forward for a Web 68 sectioning command or the buffer end. Due account is taken of the visible part of the text." (goto-char (window-start)) (if (re-search-backward "@[123 ]" (point-min) t) (push-mark (point)) (push-mark (point-min))) (goto-char (window-end)) (if (re-search-forward "@[123 ]" (point-max) t) (beginning-of-line) (goto-char (point-max)))) (defun web68-find-font-lock-buffer () "Sets POINT and MARK to POINT-MIN and POINT-MAX respectively." (push-mark (point-max)) (goto-char (point-min))) ;;;###autoload (define-derived-mode web68-mode prog-mode "Web 68" "Major mode for editing Web 68 code as well as displaying Algol 68 code." (setq-local case-fold-search nil) (setq-local blink-matching-paren-dont-ignore-comments t) ;; Font lock support (setq-local font-lock-defaults (list *web68-font-lock-keywords* t nil nil nil '(font-lock-fontify-region-function . web68-finite-state-machine) '(font-lock-fontify-buffer-function . web68-find-font-lock-buffer))) (add-hook 'web68-mode-hook 'web68-find-font-lock-region)) (provide 'web68) ;;;; web68.el ends here