;;; its.el --- Input Translation Systam AKA "ITS(uDekirunDa!)" ;; Copyright (C) 1997 Mule Project, Powered by Electrotechnical ;; Laboratory, JAPAN. ;; Project Leader: Satoru Tomura ;; Author: NIIBE Yutaka ;; Maintainer: NIIBE Yutaka ;; Keywords: mule, multilingual, input method ;; This file will be part of GNU Emacs (in future). ;; GNU Emacs is 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 2, or (at your option) ;; any later version. ;; GNU Emacs 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, Inc., 59 Temple Place - Suite 330, ;; Boston, MA 02111-1307, USA. ;;; Commentary: ;;; Code: ;; Data structure in ITS ;; (1) SYL and CURSOR ;; ;; "SYL" stands for something like a syllable. ;; ;; ::= ( . ( . )) ; Determined: DSYL ;; | ; Intermediate: ISYL ;; | ( . ) ; Verbatim: VSYL ;; | nil ; None ;; ;; ; ::= ;; ; ( . ( . )) ;; ;; ::= "string" of key sequence ;; ::= "string" ;; ;; ::= integer which specifies point ;; ;; ::= nil ; Previous SYL is active (input will go that SYL) ;; | t ; input makes new SYL. DEL deletes previous SYL ;; | its-cursor ; DEL breaks previous SYL, input makes new SYL ;; Data structures in ITS ;; (2) State machine which recognizes SYL ;; ;; ::= ( . ) ;; ;; ::= ; intermediate state ;; | ; terminal state ;; ;; ::= ( . ) ;; ::= ( ... ) ;; ::= ( . ) ;; ::= Positive INTEGER which specifies KEY STROKE ;; | -1 ; means END of key stroke ;; ;; Only applicable for last transition. ;; ::= ( ( . ( . ))... ) ;; ::= something like "[a-z]" which specifies class of key. ;; | NIL; means ANY of key (except END of the key stroke) ;; ;; ;; ::= "string" ;; ;; ::= nil ;; | ;; ;; ::= integer which specifies how many key strokes we go back ;; ;; ::= "string" ;; Data structure in ITS (3) Map ;; ;; ::= ( . ( . ) ) ;; ::= ;; ::= "string" ;; ::= "string" ;; (defsubst its-new-state (output keyseq back) (cons output (cons keyseq back))) (defsubst its-new-map (name indicator) (cons name (cons indicator (its-new-state "" "" nil)))) (defsubst its-get-indicator (map) (car (cdr map))) (defsubst its-set-indicator (map indicator) (setcar (cdr map) indicator)) (defsubst its-get-start-state (map) (cdr (cdr map))) (defsubst its-reset-start-state (map) (setcdr (cdr map) (its-new-state "" "" nil)) map) (defsubst its-get-kst/t (state) (cdr (cdr state))) (defsubst its-set-kst (state kst) (setcdr (cdr state) kst)) (defsubst its-get-keyseq (state) (car (cdr state))) (defsubst its-set-keyseq (state keyseq) (setcar (cdr state) keyseq)) (defun its-get-keyseq-cooked (state) (let ((keyseq (its-get-keyseq state)) (back (its-get-kst/t state))) (if back (substring keyseq 0 back) keyseq))) (defsubst its-kst-p (kst/t) (not (or (numberp kst/t) (null kst/t)))) (defsubst its-get-output (syl/state) (car syl/state)) (defsubst its-set-output (state output) (setcar state output)) (defsubst its-get-keyseq-syl (syl) (let ((l (cdr syl))) (cond ((stringp l) ; DSYL l) ((numberp l) ; VSYL (car syl)) (t (car (cdr syl)))))) (defsubst its-eob-keyexpr (eob) (car (cdr eob))) (defsubst its-eob-back (eob) (cdr (cdr eob))) (defsubst its-make-class+back (class back) (cons class back)) (defsubst its-make-otherwise (output class+back) (cons output class+back)) ;; ;; (defvar its-mode-map (let ((map (make-sparse-keymap)) (i 33)) (define-key map "\C-a" 'its-beginning-of-input-buffer) (define-key map "\C-b" 'its-backward-SYL) (define-key map "\C-d" 'its-delete-SYL) (define-key map "\C-e" 'its-end-of-input-buffer) (define-key map "\C-f" 'its-forward-SYL) (define-key map "\C-]" 'its-cancel-input) (define-key map "\C-h" 'its-mode-help-command) (define-key map "\C-k" 'its-kill-line) ;; (define-key map "\C-l" 'its-exit-mode) (define-key map "\C-m" 'its-exit-mode) ; RET (define-key map [return] 'its-exit-mode) (define-key map "\C-t" 'its-transpose-chars) (define-key map [delete] 'its-delete-backward-SYL) (define-key map [right] 'its-forward-SYL) (define-key map [left] 'its-backward-SYL) (define-key map "\C-\\" 'its-exit-mode-off-input-method) (while (< i 127) (define-key map (vector i) 'its-self-insert-char) (setq i (1+ i))) (define-key map " " 'its-kick-convert-region) (define-key map "\177" 'its-delete-backward-SYL) ;; (define-key map "\C-p" 'its-previous-map) (define-key map "\C-n" 'its-next-map) ; (define-key map "\M-h" 'its-hiragana) ; hiragana-region for input-buffer ; (define-key map "\M-k" 'its-katakana) ; (define-key map "\M-<" 'its-hankaku) ; (define-key map "\M->" 'its-zenkaku) ; (define-key map "\M-\C-h" 'its-select-hiragana) ; (define-key map "\M-\C-k" 'its-select-katakana) ;;; (define-key map "\M-q" 'its-select-downcase) ; ; (define-key map "\M-Q" 'its-select-upcase) ; (define-key map "\M-z" 'its-select-zenkaku-downcase) ; (define-key map "\M-Z" 'its-select-zenkaku-upcase) map) "Keymap for ITS mode.") (defvar its-fence-open "|" "*フェンスの始点を示す文字列 (1 文字)") (defvar its-fence-close "|" "*フェンスの終点を示す文字列 (1 文字)") (defvar its-fence-face nil "*フェンス表示に用いる face または nil") (defun its-put-cursor (cursor) (let ((p (point))) (insert "!") (add-text-properties p (point) (list 'local-map its-mode-map 'invisible t 'intangible 'its-part-2 'its-cursor cursor)) (goto-char p))) ;; ;; +-- START property ;; | --- CURSOR Property ;; | / ;; v v v-- END Property ;; |SYL SYL ^ SYL| ;; ^^^ ^^^ ^^^------ SYL Property ;; <-------><----> ;; intangible intangible ;; 1 2 ;; (defun its-start (key) (let (p cursor) (setq p (point)) (insert its-fence-open) (add-text-properties p (point) (let ((props '(its-start t intangible its-part-1))) (if its-fence-face (append '(invisible t) props) props))) (setq p (point)) (setq cursor (its-input nil key)) (its-put-cursor cursor) (forward-char 1) (setq p (point)) (insert its-fence-close) (add-text-properties p (point) (let ((props '(its-end t intangible its-part-2))) (if its-fence-face (append '(invisible t) props) props))) (forward-char -2) (force-mode-line-update))) (defun its-self-insert-char () (interactive) (let ((key last-command-char) (cursor (get-text-property (point) 'its-cursor)) (syl nil)) (if (null cursor) (setq syl (get-text-property (1- (point)) 'its-syl))) ;; delete cursor (delete-region (point) (1+ (point))) (setq cursor (its-input syl key)) (its-put-cursor cursor))) (defvar its-current-map nil) (make-variable-buffer-local 'its-current-map) (put 'its-current-map 'permanent-local t) (defun its-initial-ISYL () (its-get-start-state its-current-map)) (defun its-make-VSYL (keyseq) (cons keyseq (length keyseq))) ;; Return CURSOR (defun its-input (syl key) (if (null syl) (setq syl (its-initial-ISYL))) (let ((output (car syl)) (k/kk/s (cdr syl))) (if (numberp k/kk/s) ;; k/kk/s is "point in keyseq" (its-input-to-vsyl syl key k/kk/s output) ;; It's ISYL (its-state-machine syl key 'its-buffer-ins/del-SYL)))) (defun its-input-to-vsyl (syl key point output) (if (< key 0) t (let ((len (length output))) (if (= len point) ;; point is at end of VSYL. Don't need to call state machine. (progn (its-buffer-ins/del-SYL (its-make-VSYL (concat output (vector key))) syl) nil) ;; point is at middle of VSYL. (let ((new-keyseq (concat (substring output 0 point) (vector key) (substring output point)))) (its-state-machine-keyseq new-keyseq 'its-buffer-ins/del-SYL)))))) (defvar its-barf-on-invalid-keyseq nil "T means don't allow invalid key sequence in input buffer.") ;;; ;;; ITS State Machine ;;; ;; Return CURSOR (defun its-state-machine (state key emit) (let ((next-state (its-get-next-state state key)) expr-output-back) (if next-state (let ((kst/t (its-get-kst/t next-state))) (funcall emit next-state state) (if (not (its-kst-p kst/t)) ;; Here we arrive to a terminal state. ;; Emit a DSYL, and go ahead. (let ((output (its-get-output next-state)) (keyseq (its-get-keyseq next-state)) (back kst/t)) (if back ;; It's negative integer which specifies how many ;; characters we go backwards (its-state-machine-keyseq (substring keyseq back) emit (< key 0)) 'its-cursor)) ;; Still, it's a intermediate state. nil)) (if (and (>= key 0) (setq expr-output-back (its-get-otherwise state key))) (let ((keyseq (concat (its-get-keyseq state) (char-to-string key)))) (funcall emit expr-output-back state) (its-state-machine-keyseq (substring keyseq (its-eob-back expr-output-back)) emit)) ;; No next state for KEY. It's invalid sequence. (if (< key 0) ; no next state for END of keystroke ;; ISYL --> DSYL XXX (if its-barf-on-invalid-keyseq (error its-barf-on-invalid-keyseq) (funcall emit (cons (car state) (list (its-get-keyseq state))) state) t) (if its-barf-on-invalid-keyseq (error its-barf-on-invalid-keyseq) ;; XXX Should make DSYL (instead of VSYL)? (let ((keyseq (concat (its-get-keyseq state) (vector key)))) (funcall emit (its-make-VSYL keyseq) state) nil))))))) (defvar its-latest-SYL nil "The latest SYL inserted.") (defsubst its-update-latest-SYL (syl) (setq its-latest-SYL syl)) ;; Return CURSOR (defun its-state-machine-keyseq (keyseq emit &optional eol) (let ((i 0) (len (length keyseq)) (its-barf-on-invalid-keyseq nil) ; temporally disable DING (syl (its-initial-ISYL)) cursor) (while (< i len) (let ((key (aref keyseq i))) (setq cursor (if (numberp (cdr syl)) ; VSYL (progn (funcall emit (its-make-VSYL (concat (car syl) (vector key))) syl) nil) (its-state-machine syl key emit))) (setq i (1+ i)) (if cursor (setq syl (its-initial-ISYL)) (setq syl its-latest-SYL)))) (if eol (its-state-machine syl -1 emit) cursor))) (defun its-buffer-ins/del-SYL (newsyl oldsyl) (its-buffer-delete-SYL oldsyl) (its-update-latest-SYL newsyl) (let ((p (point))) (insert (its-get-output newsyl)) (add-text-properties p (point) (list 'its-syl newsyl 'intangible 'its-part-1)) (if its-fence-face (put-text-property p (point) 'face its-fence-face)))) (defun its-buffer-delete-SYL (syl) (let ((len (length (its-get-output syl)))) (delete-region (- (point) len) (point)))) (defun its-get-next-state (state key) (let ((kst/t (its-get-kst/t state))) (cdr (assq key (car kst/t))))) ;; XXX XXX XXX (defun its-otherwise-match (expr key) (or (null expr) ; ::= NIL means "ANY" (let ((case-fold-search nil)) (string-match expr (char-to-string key))))) (defun its-get-otherwise (state key) (let* ((kst/t (its-get-kst/t state)) (ebl (cdr kst/t)) expr-output-back) (while ebl (setq expr-output-back (car ebl)) (let ((expr (its-eob-keyexpr expr-output-back))) (if (its-otherwise-match expr key) (setq ebl nil) (setq ebl (cdr ebl))))) expr-output-back)) ;;; ;;; Name --> map ;;; ;;; ITS name: string (defvar its-map-alist nil) (defun its-get-map (name) (assoc name its-map-alist)) (defun its-register-map (map) (let* ((name (car map)) (place (assoc name its-map-alist))) (if place (setcdr place (cdr map)) (setq its-map-alist (cons map its-map-alist))) map)) (defun its-define-state-machine (name indicator &optional continue) "NAME で指定された State Machine の定義を開始する。 INDICATOR は mode line に表示する indicator を指定する。 CONTINUE が nil の時には State Machine の定義を空にする。its-defrule を参照。" (setq its-current-map (if (null (its-get-map name)) (its-register-map (its-new-map name indicator)) (let ((map (its-get-map name))) (its-set-indicator map indicator) (if continue map (its-reset-start-state map)))))) (defmacro define-its-state-machine (map name indicator doc &rest exprs) `(let ((its-current-map (its-new-map ,name ,indicator))) ,(cons 'progn exprs) (defconst ,map its-current-map ,doc))) ;;(defmacro define-its-state-machine (map name indicator doc &rest exprs) ;; (let ((its-current-map (its-new-map name indicator))) ;; (eval (cons 'progn exprs)) ;; `(defconst ,map ',its-current-map ,doc))) (defmacro define-its-state-machine-append (map &rest exprs) (append `(let ((its-current-map ,map))) exprs (list `(setq ,map its-current-map)))) ;; ;; Construct State Machine ;; (defun its-defrule (input output &optional back enable-overwrite) "入力 INPUT を認識し, OUTPUT を出力するようにステートマシンを構成する。 BACK が(負の)整数の時は, OUTPUT を出力した後, BACK の分 key stroke を 戻って動くものとする。変換規則はもっとも最近に its-define-state-machine された変換表に登録される。 Return last state." (let ((state (its-goto-state (substring input 0 -1) nil t)) (key (aref input (1- (length input))))) (if (and (its-get-next-state state key) (not enable-overwrite)) (error "Duplicated definition (%s)" input) (its-make-next-state state key input output back)))) (defun its-goto-state (input &optional initial-state build-if-none) (let ((len (length input)) (i 0) (state (or initial-state (its-get-start-state its-current-map)))) (while (< i len) (setq state (or (its-get-next-state state (aref input i)) (if build-if-none (let ((keyseq (substring input 0 (1+ i)))) (its-make-next-state state (aref input i) keyseq keyseq)) (error "No such state (%s)" input))) i (1+ i))) state)) (defun its-defoutput (input display) (let ((state (its-goto-state input))) (its-set-output state display))) (defun its-define-otherwise (state otherwise) (let ((kst (its-get-kst/t state))) (if kst (setcdr kst (cons otherwise (cdr kst))) (its-set-kst state (cons nil (cons otherwise nil)))))) (defconst its-otherwise-back-one (its-make-class+back nil -1)) (defun its-defrule-otherwise (state output &optional class back) (let (class+back) (if (null back) (setq class+back its-otherwise-back-one) (setq class+back (its-make-class+back class back))) (its-define-otherwise state (its-make-otherwise output class+back)))) (defun its-defrule* (input output) (let ((state (its-defrule input output))) (its-defrule-otherwise state output))) (defun its-make-next-state (state key keyseq output &optional back) (let ((next-state (its-new-state output keyseq back)) (kst (its-get-kst/t state))) (if kst (setcar kst (cons (cons key next-state) (car kst))) (its-set-kst state (list (list (cons key next-state))))) next-state)) ;;; (defun its-beginning-of-input-buffer () (interactive) (its-input-end) (if (not (get-text-property (1- (point)) 'its-start)) (let ((begpos (previous-single-property-change (point) 'its-start))) ;; Make SYLs have property of "part 2" (put-text-property begpos (point) 'intangible 'its-part-2) (goto-char begpos) (its-put-cursor t)))) (defun its-end-of-input-buffer () (interactive) (its-input-end) (if (not (get-text-property (point) 'its-end)) (let ((endpos (next-single-property-change (point) 'its-end))) ;; Make SYLs have property of "part 1" (put-text-property (point) endpos 'intangible 'its-part-1) (goto-char endpos) (its-put-cursor t)))) ;; TODO: move in VSYL (defun its-backward-SYL (n) (interactive "p") (its-input-end) (let ((syl (get-text-property (1- (point)) 'its-syl)) (p (point)) (old-point (point))) (while (and syl (> n 0)) (setq p (- p (length (its-get-output syl)))) (setq syl (get-text-property (1- p) 'its-syl)) (setq n (1- n))) ;; Make SYLs have property of "part 2" (put-text-property p old-point 'intangible 'its-part-2) (goto-char p) (its-put-cursor t) (if (> n 0) (signal 'beginning-of-buffer nil)))) ;; TODO: move in VSYL (defun its-forward-SYL (n) (interactive "p") (its-input-end) (let ((syl (get-text-property (point) 'its-syl)) (p (point)) (old-point (point))) (while (and syl (> n 0)) (setq p (+ p (length (its-get-output syl)))) (setq syl (get-text-property p 'its-syl)) (setq n (1- n))) ;; Make SYLs have property of "part 1" (put-text-property p old-point'intangible 'its-part-1) (goto-char p) (its-put-cursor t) (if (> n 0) (signal 'end-of-buffer nil)))) ;; TODO: handle VSYL. KILLFLAG (defun its-delete-SYL (n killflag) (interactive "p\nP") (its-input-end) (let ((syl (get-text-property (point) 'its-syl)) (p (point))) (while (and syl (> n 0)) (setq p (+ p (length (its-get-output syl)))) (setq syl (get-text-property p 'its-syl)) (setq n (1- n))) (if (> n 0) (progn (its-put-cursor t) (signal 'args-out-of-range (list p n))) (delete-region (point) p) ;; Check if empty (let ((s (get-text-property (1- (point)) 'its-start)) (e (get-text-property (point) 'its-end))) (if (and s e) (its-exit-mode-internal) (its-put-cursor t)))))) ;; TODO: killflag (defun its-delete-backward-SYL (n killflag) (interactive "p\nP") (let ((syl (get-text-property (1- (point)) 'its-syl)) (cursor (get-text-property (point) 'its-cursor))) (if (null syl) (signal 'beginning-of-buffer nil) (if (eq cursor t) (its-delete-backward-SYL-internal n killflag) (its-delete-backward-within-SYL syl n killflag))))) ;; TODO: killflag (defun its-delete-backward-SYL-internal (n killflag) (let ((syl (get-text-property (1- (point)) 'its-syl)) (p (point))) (while (and syl (> n 0)) (setq p (- p (length (its-get-output syl)))) (setq syl (get-text-property (1- p) 'its-syl)) (setq n (1- n))) (if (> n 0) (signal 'args-out-of-range (list p n)) (delete-region p (1+ (point))) ; also delete cursor ;; Check if empty (let ((s (get-text-property (1- (point)) 'its-start)) (e (get-text-property (point) 'its-end))) (if (and s e) (its-exit-mode-internal) (its-put-cursor t)))))) (defvar its-delete-by-keystroke nil) ;; TODO: killflag (defun its-delete-backward-within-SYL (syl n killflag) (let* ((keyseq (its-get-keyseq-syl syl)) (len (length keyseq)) (p (point))) (if (> n len) (signal 'args-out-of-range (list p n))) ;; Delete CURSOR (delete-region p (1+ p)) (its-buffer-delete-SYL syl) (if (= n len) ;; Check if empty (let ((s (get-text-property (1- (point)) 'its-start)) (e (get-text-property (point) 'its-end))) (if (and s e) (its-exit-mode-internal) (its-put-cursor (not its-delete-by-keystroke)))) (setq keyseq (substring keyseq 0 (- len n))) (let ((r (its-state-machine-keyseq keyseq 'its-buffer-ins/del-SYL))) (its-put-cursor r))))) ;; XXX: NIY (defun its-transpose-chars (n) (interactive) (let ((syl (get-text-property (1- (point)) 'its-syl)) (cursor (get-text-property (point) 'its-cursor))) (if (null syl) (signal 'beginning-of-buffer nil) (if (eq cursor t) (its-delete-backward-SYL-internal n nil) (its-delete-backward-within-SYL syl 2 nil))))) ;; Return VOID (defun its-input-end () (let ((cursor (get-text-property (point) 'its-cursor))) ;; key "END" (if (null cursor) (its-input (get-text-property (1- (point)) 'its-syl) -1)) (delete-region (point) (1+ (point))))) (defun its-exit-mode () "Exit ITS mode." (interactive) (its-input-end) (its-exit-mode-internal)) (defun its-exit-mode-off-input-method () "Exit ITS mode." (interactive) (its-input-end) (its-exit-mode-internal) (inactivate-input-method)) ;; TODO: handle overwrite-mode, insertion-hook, fill... (defun its-exit-mode-internal (&optional proceed-to-conversion) (let (start end) ;; Delete open fence (if (get-text-property (1- (point)) 'its-start) (setq start (1- (point))) (setq start (1- (previous-single-property-change (point) 'its-start)))) (delete-region start (1+ start)) ;; Delete close fence (if (get-text-property (point) 'its-end) (setq end (point)) (setq end (next-single-property-change (point) 'its-end))) (delete-region end (1+ end)) ;; Remove all properties added by ITS (remove-text-properties start end '(its-syl nil face nil intangible nil)) (if proceed-to-conversion (egg-convert-region start end) (egg-do-auto-fill) (run-hooks 'input-method-after-insert-chunk-hook)))) (defun its-kick-convert-region () (interactive) (its-input-end) (its-exit-mode-internal t)) (defvar its-translation-result nil "") (defun its-ins/del-SYL-batch (newsyl oldsyl) (its-update-latest-SYL newsyl) (if (and newsyl (consp (cdr newsyl)) (not (its-kst-p (its-get-kst/t newsyl)))) ;; DSYL (setq its-translation-result (cons (its-get-output newsyl) its-translation-result)))) (defun its-translate-region (start end &optional map) (interactive "r") (setq its-translation-result nil) (goto-char start) (let ((i 0) (syl (its-initial-ISYL)) ;; temporally enable DING (its-barf-on-invalid-keyseq "Invalid Romaji Sequence") cursor) (while (< (point) end) (let ((key (following-char))) (setq cursor (its-state-machine syl key 'its-ins/del-SYL-batch)) (forward-char 1) (if cursor (setq syl (its-initial-ISYL)) (setq syl its-latest-SYL)))) (if (eq syl its-latest-SYL) (its-state-machine syl -1 'its-ins/del-SYL-batch)) (delete-region start end) (apply 'insert (reverse its-translation-result)))) (defvar its-select-map-menu '(menu "Map:" nil)) (defun its-select-map-from-menu () (interactive) (setcar (nthcdr 2 its-select-map-menu) its-map-alist) (setq its-current-map (menudiag-select its-select-map-menu)) (force-mode-line-update)) (defun its-select-hiragana () (interactive) (its-select-map "roma-kana")) (defun its-select-katakana () (interactive) (its-select-map "roma-kata")) (defun its-select-downcase () (interactive) (its-select-map "downcase")) (defun its-select-upcase () (interactive) (its-select-map "upcase")) (defun its-select-zenkaku-downcase () (interactive) (its-select-map "zenkaku-downcase")) (defun its-select-zenkaku-upcase () (interactive) (its-select-map "zenkaku-upcase")) (defun its-select-map (name) (interactive (list (completing-read "ITS map: " its-map-alist))) (if (its-get-map name) (progn (setq its-current-map (its-get-map name)) (force-mode-line-update)) (ding))) ;; Escape character to Zenkaku inputs (defconst its-zenkaku-escape "Z") ;; Escape character to Hankaku inputs (defconst its-hankaku-escape "~") (provide 'its) ;;; its.el ends here.