;;; teco.el --- Teco interpreter for Gnu Emacs, version 1.

(require 'backquote)
;; This code has been tested some, but no doubt contains a zillion bugs.
;; You have been warned.

;; Written by Dale R. Worley based on a C implementation by Matt Fichtenbaum.
;; Please send comments, bug fixes, enhancements, etc. to drw@math.mit.edu.

;; Emacs Lisp version copyright (C) 1991 by Dale R. Worley.
;; Do what you will with it.

;; Since much of this code is translated from the C version by 
;; Matt Fichtenbaum, I include his copyright notice:
;; TECO for Ultrix.   Copyright 1986 Matt Fichtenbaum.
;; This program and its components belong to GenRad Inc, Concord MA 01742.
;; They may be copied if this copyright notice is included.

;; To invoke directly, do:
;; (global-set-key ?\C-z 'teco-command)
;; (autoload teco-command "teco"
;;   "Read and execute a Teco command string."
;;   t nil)

;; Differences from other Tecos:
;; Character positions in the buffer are numbered in the Emacs way:  The first
;; character is numbered 1 (or (point-min) if narrowing is in effect).  The
;; B command returns that number.
;; Ends of lines are represented by a single character (newline), so C and R
;; skip over them, rather than 2C and 2R.
;; All file I/O is left to the underlying Emacs.  Thus, almost all Ex commands
;; are omitted.

;; Command set:
;;	NUL	Not a command.
;;	^A	Output message to terminal (argument ends with ^A)
;;	^C	Exit macro
;;	^C^C	Stop execution
;;	^D	Set radix to decimal
;;	^EA	(match char) Match alphabetics
;;	^EC	(match char) Match symbol constituents
;;	^ED	(match char) Match numerics
;;	^EGq	(match char) Match any char in q-reg
;;	^EL	(match char) Match line terminators
;;	^EQq	(string char) Use contents of q-reg
;;	^ER	(match char) Match alphanumerics
;;	^ES	(match char) Match non-null space/tab
;;	^EV	(match char) Match lower case alphabetic
;;	^EW	(match char) Match upper case alphabetic
;;	^EX	(match char) Match any char
;;	^G^G	(type-in) Kill command string
;;	^G<sp>	(type-in) Retype current command line
;;	^G*	(type-in) Retype current command input
;;	TAB	Insert tab and text
;;	LF	Line terminator; Ignored in commands
;;	VT	Ignored in commands
;;	FF	Ignored in commands
;;	CR	Ignored in commands
;;	^Nx	(match char) Match all but x
;;	^O	Set radix to octal
;;	^P	Find matching parenthesis
;;	^Q	Convert line argument into character argument
;;	^Qx	(string char) Use x literally
;;	n^R	Set radix to n
;;	:^R	Enter recursive edit
;;	^S	-(length of last referenced string)
;;	^S	(match char) match separator char
;;	^T	Ascii value of next character typed
;;	n^T	Output Ascii character with value n
;;	^U	(type-in) Kill command line
;;	^Uq	Put text argument into q-reg
;;	n^Uq	Put Ascii character 'n' into q-reg
;;	:^Uq	Append text argument to q-reg
;;	n:^Uq	Append character 'n' to q-reg
;;	^X	Set/get search mode flag
;;	^X	(match char) Match any character
;;	^Y	Equivalent to '.+^S,.'
;;	^Z	Not a Teco command
;;	ESC	String terminator; absorbs arguments
;;	ESC ESC	(type-in) End command
;;	^\	Not a Teco command
;;	^]	Not a Teco command
;;	^^x	Ascii value of the character x
;;	^_	One's complement (logical NOT)
;;	!	Define label (argument ends with !)
;;	"	Start conditional
;;	n"<	Test for less than zero
;;	n">	Test for greater than zero
;;	n"=	Test for equal to zero
;;	n"A	Test for alphabetic
;;	n"C	Test for symbol constituent
;;	n"D	Test for numeric
;;	n"E	Test for equal to zero
;;	n"F	Test for false
;;	n"G	Test for greater than zero
;;	n"L	Test for less than zero
;;	n"N	Test for not equal to zero
;;	n"R	Test for alphanumeric
;;	n"S	Test for successful
;;	n"T	Test for true
;;	n"U	Test for unsuccessful
;;	n"V	Test for lower case
;;	n"W	Test for upper case
;;	#	Logical OR
;;	$	Not a Teco command
;;	n%q	Add n to q-reg and return result
;;	&	Logical AND
;;	'	End conditional
;;	(	Expression grouping
;;	)	Expression grouping
;;	*	Multiplication
;;	+	Addition
;;	,	Argument separator
;;	-	Subtraction or negation
;;	.	Current pointer position
;;	/	Division
;;	0-9	Digit
;;	n<	Iterate n times
;;	=	Type in decimal
;;	:=	Type in decimal, no newline
;;	=	Type in octal
;;	:=	Type in octal, no newline
;;	=	Type in hexadecimal
;;	:=	Type in hexadecimal, no newline
;;	::	Make next search a compare
;;	>	End iteration
;;	n:A	Get Ascii code of character at relative position n
;;	B	Character position of beginning of buffer
;;	nC	Advance n characters
;;	nD	Delete n characters
;;	n,mD	Delete characters between n and m
;;	Gq	Get string from q-reg into buffer
;;	:Gq	Type out q-reg
;;	H	Equivalent to 'B,Z'
;;	I	Insert text argument
;;	nJ	Move pointer to character n
;;	nK	Kill n lines
;;	n,mK	Kill characters between n and m
;;	nL	Advance n lines
;;	Mq	Execute string in q-reg
;;	O	Goto label
;;	nO	Go to n-th label in list (0-origin)
;;	Qq	Number in q-reg
;;	nQq	Ascii value of n-th character in q-reg
;;	:Qq	Size of text in q-reg
;;	nR	Back up n characters
;;	nS	Search
;;	nT	Type n lines
;;	n,mT	Type chars from n to m
;;	nUq	Put number n into q-reg
;;	nV	Type n lines around pointer
;;	nXq	Put n lines into q-reg
;;	n,mXq	Put characters from n to m into q-reg
;;	n:Xq	Append n lines to q-reg q
;;	n,m:Xq	Append characters from n to m into q-reg
;;	Z 	Pointer position at end of buffer
;;	[q	Put q-reg on stack
;;	\	Value of digit string in buffer
;;	n\	Convert n to digits and insert in buffer
;;	]q	Pop q-reg from stack
;;	:]q	Test whether stack is empty and return value
;;	`	Not a Teco command
;;	a-z	Treated the same as A-Z
;;	{	Not a Teco command
;;	|	Conditional 'else'
;;	}	Not a Teco comand
;;	~	Not a Teco command
;;	DEL	Delete last character typed in


;; set a range of elements of an array to a value
(defun teco-set-elements (array start end value)
  (let ((i start))
    (while (<= i end)
      (aset array i value)
      (setq i (1+ i)))))

;; set a range of elements of an array to their indexes plus an offset
(defun teco-set-elements-index (array start end offset)
  (let ((i start))
    (while (<= i end)
      (aset array i (+ i offset))
      (setq i (1+ i)))))

(defvar teco-command-string ""
  "The current command string being executed.")

(defvar teco-command-pointer nil
  "Pointer into teco-command-string showing next character to be executed.")

(defvar teco-ctrl-r 10
  "Current number radix.")

(defvar teco-digit-switch nil
  "Set if we have just executed a digit.")

(defvar teco-exp-exp nil
  "Expression value preceeding operator.")

(defvar teco-exp-val1 nil
  "Current argument value.")

(defvar teco-exp-val2 nil
  "Argument before comma.")

(defvar teco-exp-flag1 nil
  "t if argument is present.")

(defvar teco-exp-flag2 nil
  "t if argument before comma is present.")

(defvar teco-exp-op nil
  "Pending arithmetic operation on argument.")

(defvar teco-exp-stack nil
  "Stack for parenthesized expressions.")

(defvar teco-macro-stack nil
  "Stack for macro invocations.")

(defvar teco-mapch-l nil
  "Translation table to lower-case letters.")

    (setq teco-mapch-l (make-vector 256 0))
    (teco-set-elements-index teco-mapch-l 0 255 0)
    (teco-set-elements-index teco-mapch-l ?A ?Z (- ?a ?A))

(defvar teco-trace nil
  "t if tracing is on.")

(defvar teco-at-flag nil
  "t if an @ flag is pending.")

(defvar teco-colon-flag nil
  "1 if a : flag is pending, 2 if a :: flag is pending.")

(defvar teco-qspec-valid nil
  "Flags describing whether a character is a vaid q-register name.
3 means yes, 2 means yes but only for file and search operations.")

    (setq teco-qspec-valid (make-vector 256 0))
    (teco-set-elements teco-qspec-valid ?a ?z 3)
    (teco-set-elements teco-qspec-valid ?0 ?9 3)
    (aset teco-qspec-valid ?_ 2)
    (aset teco-qspec-valid ?* 2)
    (aset teco-qspec-valid ?% 2)
    (aset teco-qspec-valid ?# 2)

(defvar teco-exec-flags 0
  "Flags for iteration in process, ei macro, etc.")

(defvar teco-iteration-stack nil
  "Iteration list.")

(defvar teco-cond-stack nil
  "Conditional stack.")

(defvar teco-qreg-text (make-vector 256 "")
  "The text contents of the q-registers.")

(defvar teco-qreg-number (make-vector 256 0)
  "The number contents of the q-registers.")

(defvar teco-qreg-stack nil
  "The stack of saved q-registers.")

(defconst teco-prompt "*"
  "*Prompt to be used when inputting Teco command.")

(defconst teco-exec-1 (make-vector 256 nil)
  "Names of routines handling type 1 characters (characters that are
part of expression processing).")

(defconst teco-exec-2 (make-vector 256 nil)
  "Names of routines handling type 2 characters (characters that are
not part of expression processing).")

(defvar teco-last-search-string ""
  "Last string searched for.")

(defvar teco-last-search-regexp ""
  "Regexp version of teco-last-search-string.")

(defmacro teco-define-type-1 (char &rest body)
  "Define the code to process a type 1 character.
Transforms
	(teco-define-type-1 ?x
	  code ...)
into
        (defun teco-type-1-x ()
	  code ...)
and does
	(aset teco-exec-1 ?x 'teco-type-1-x)"
  (let ((s (intern (concat "teco-type-1-" (char-to-string char)))))
    (` (progn
	 (defun (, s) ()
	   (,@ body))
	 (aset teco-exec-1 (, char) '(, s))))))

(defmacro teco-define-type-2 (char &rest body)
  "Define the code to process a type 2 character.
Transforms
	(teco-define-type-2 ?x
	  code ...)
into
        (defun teco-type-2-x ()
	  code ...)
and does
	(aset teco-exec-2 ?x 'teco-type-2-x)"
  (let ((s (intern (concat "teco-type-2-" (char-to-string char)))))
    (` (progn
	 (defun (, s) ()
	   (,@ body))
	 (aset teco-exec-2 (, char) '(, s))))))

(defconst teco-char-types (make-vector 256 0)
  "Define the characteristics of characters, as tested by \":
	1	alphabetic
	2	alphabetic, $, or .
	4	digit
	8	alphabetic or digit
	16	lower-case alphabetic
	32	upper-case alphabetic")

    (teco-set-elements teco-char-types ?0 ?9 (+ 4 8))
    (teco-set-elements teco-char-types ?A ?Z (+ 1 2 8 32))
    (teco-set-elements teco-char-types ?a ?z (+ 1 2 8 16))
    (aset teco-char-types ?$ 2)
    (aset teco-char-types ?. 2)

(defconst teco-error-texts '(("BNI" . "> not in iteration")
			     ("CPQ" . "Can't pop Q register")
			     ("COF" . "Can't open output file ")
			     ("FNF" . "File not found ")
			     ("IEC" . "Invalid E character")
			     ("IFC" . "Invalid F character")
			     ("IIA" . "Invalid insert arg")
			     ("ILL" . "Invalid command")
			     ("ILN" . "Invalid number")
			     ("IPA" . "Invalid P arg")
			     ("IQC" . "Invalid \" character")
			     ("IQN" . "Invalid Q-reg name")
			     ("IRA" . "Invalid radix arg")
			     ("ISA" . "Invalid search arg")
			     ("ISS" . "Invalid search string")
			     ("IUC" . "Invalid ^ character")
			     ("LNF" . "Label not found")
			     ("MEM" . "Insufficient memory available")
			     ("MRP" . "Missing )")
			     ("NAB" . "No arg before ^_")
			     ("NAC" . "No arg before ,")
			     ("NAE" . "No arg before =")
			     ("NAP" . "No arg before )")
			     ("NAQ" . "No arg before \"")
			     ("NAS" . "No arg before ;")
			     ("NAU" . "No arg before U")
			     ("NFI" . "No file for input")
			     ("NFO" . "No file for output")
			     ("NYA" . "Numeric arg with Y")
			     ("OFO" . "Output file already open")
			     ("PDO" . "Pushdown list overflow")
			     ("POP" . "Pointer off page")
			     ("SNI" . "; not in iteration")
			     ("SRH" . "Search failure ")
			     ("STL" . "String too long")
			     ("UTC" . "Unterminated command")
			     ("UTM" . "Unterminated macro")
			     ("XAB" . "Execution interrupted")
			     ("YCA" . "Y command suppressed")
			     ("IWA" . "Invalid W arg")
			     ("NFR" . "Numeric arg with FR")
			     ("INT" . "Internal error")
			     ("EFI" . "EOF read from std input")
			     ("IAA" . "Invalid A arg")
			     ))

(defconst teco-spec-chars 
  [
   0          1          0          0	; ^@ ^A ^B ^C
   0          64         0          0	; ^D ^E ^F ^G
   0          2          128        128	; ^H ^I ^J ^K
   128        0          64         0	; ^L ^M ^N ^O
   0          64         64         64	; ^P ^Q ^R ^S
   0          34         0          0	; ^T ^U ^V ^W
   64         0          0          0	; ^X ^Y ^Z ^\[
   0          0          1          0	; ^\ ^\] ^^ ^_
   0          1          16         0	;    !  \"  # 
   0          0          0          16	; $  %  &  ' 
   0          0          0          0	; \(  \)  *  + 
   0          0          0          0	; ,  -  .  / 
   0          0          0          0	; 0  1  2  3 
   0          0          0          0	; 4  5  6  7 
   0          0          0          0	; 8  9  :  ; 
   16         0          16         0	; <  =  >  ? 
   1          0          12         0	; @  A  B  C 
   0          1          1          32	; D  E  F  G 
   0          6          0          0	; H  I  J  K 
   0          32         10         2	; L  M  N  O 
   0          32         4          10	; P  Q  R  S 
   0          32         0          4	; T  U  V  W 
   32         0          0          32	; X  Y  Z  \[ 
   0          32         1          6	; \  \]  ^  _ 
   0          0          12         0	; `  a  b  c 
   0          1          1          32	; d  e  f  g 
   0          6          0          0	; h  i  j  k 
   0          32         10         2	; l  m  n  o 
   0          32         4          10	; p  q  r  s 
   0          32         0          4	; t  u  v  w 
   32         0          0          0	; x  y  z  { 
   16         0          0          0	; |  }  ~  DEL
   ]
  "The special properties of characters:
	1	skipto() special character
	2	command with std text argument
	4	E<char> takes a text argument
	8	F<char> takes a text argument
	16	char causes skipto() to exit
	32	command with q-register argument
	64	special char in search string
	128	character is a line separator")


(defun teco-execute-command (string)
  "Execute teco command string."
  ;; Initialize everything
  (let ((teco-command-string string)
	(teco-command-pointer 0)
	(teco-digit-switch nil)
	(teco-exp-exp nil)
	(teco-exp-val1 nil)
	(teco-exp-val2 nil)
	(teco-exp-flag1 nil)
	(teco-exp-flag2 nil)
	(teco-exp-op 'start)
	(teco-trace nil)
	(teco-at-flag nil)
	(teco-colon-flag nil)
	(teco-exec-flags 0)
	(teco-iteration-stack nil)
	(teco-cond-stack nil)
	(teco-exp-stack nil)
	(teco-macro-stack nil)
	(teco-qreg-stack nil))
    ;; initialize output
    (teco-out-init)
    ;; execute commands
    (catch 'teco-exit
      (while t
	;; get next command character
	(let ((cmdc (teco-get-command0 teco-trace)))
	  ;; if it's ^, interpret the next character as a control character
	  (if (eq cmdc ?^)
	      (setq cmdc (logand (teco-get-command teco-trace) 31)))
	  (if (and (<= ?0 cmdc) (<= cmdc ?9))
	      ;; process a number
	      (progn
		(setq cmdc (- cmdc ?0))
		;; check for invalid digit
		(if (>= cmdc teco-ctrl-r)
		    (teco-error "ILN"))
		(if teco-digit-switch
		    ;; later digits
		    (setq teco-exp-val1 (+ (* teco-exp-val1 teco-ctrl-r) cmdc))
		  ;; first digit
		  (setq teco-exp-val1 cmdc)
		  (setq teco-digit-switch t))
		;; indicate a value was read in
		(setq teco-exp-flag1 t))
	    ;; not a digit
	    (setq teco-digit-switch nil)
	    ;; cannonicalize the case
	    (setq cmdc (aref teco-mapch-l cmdc))
	    ;; dispatch on the character, if it is a type 1 character
	    (let ((r (aref teco-exec-1 cmdc)))
	      (if r
		  (funcall r)
		;; if a value has been entered, process any pending operation
		(if teco-exp-flag1
		    (cond ((eq teco-exp-op 'start)
			   nil)
			  ((eq teco-exp-op 'add)
			   (setq teco-exp-val1 (+ teco-exp-exp teco-exp-val1))
			   (setq teco-exp-op 'start))
			  ((eq teco-exp-op 'sub)
			   (setq teco-exp-val1 (- teco-exp-exp teco-exp-val1))
			   (setq teco-exp-op 'start))
			  ((eq teco-exp-op 'mult)
			   (setq teco-exp-val1 (* teco-exp-exp teco-exp-val1))
			   (setq teco-exp-op 'start))
			  ((eq teco-exp-op 'div)
			   (setq teco-exp-val1
				 (if (/= teco-exp-val1 0)
				     (/ teco-exp-exp teco-exp-val1)
				   0))
			   (setq teco-exp-op 'start))
			  ((eq teco-exp-op 'and)
			   (setq teco-exp-val1
				 (logand teco-exp-exp teco-exp-val1))
			   (setq teco-exp-op 'start))
			  ((eq teco-exp-op 'or)
			   (setq teco-exp-val1
				 (logior teco-exp-exp teco-exp-val1))
			   (setq teco-exp-op 'start))))
		;; dispatch on a type 2 character
		(let ((r (aref teco-exec-2 cmdc)))
		  (if r
		      (funcall r)
		    (teco-error "ILL")))))))))))

;; Type 1 commands

(teco-define-type-1
 ?\m					; CR
 nil)

(teco-define-type-1
 ?\n					; LF
 nil)

(teco-define-type-1
 ?\^k					; VT
 nil)

(teco-define-type-1
 ?\^l					; FF
 nil)

(teco-define-type-1
 32					; SPC
 nil)

(teco-define-type-1
 ?\e					; ESC
 (if (teco-peek-command ?\e)
     ;; ESC ESC terminates macro or command
     (teco-pop-macro-stack)
   ;; otherwise, consume argument
   (setq teco-exp-flag1 nil)
   (setq teco-exp-op 'start)))

(teco-define-type-1
 ?!					; !
 (while (/= (teco-get-command teco-trace) ?!)
   nil))

(teco-define-type-1
 ?@					; @
 ;; set at-flag
 (setq teco-at-flag t))

(teco-define-type-1
 ?:					; :
 ;; is it '::'?
 (if (teco-peek-command ?:)
     (progn
       ;; skip second colon
       (teco-get-command teco-trace)
       ;; set flag to show two colons
       (setq teco-colon-flag 2))
   ;; set flag to show one colon
   (setq teco-colon-flag 1)))

(teco-define-type-1
 ??					; ?
 ;; toggle trace
 (setq teco-trace (not teco-trace)))

(teco-define-type-1
 ?.					; .
 ;; value is point
 (setq teco-exp-val1 (point)
       teco-exp-flag1 t))

(teco-define-type-1
 ?z					; z
 ;; value is point-max
 (setq teco-exp-val1 (point-max)
       teco-exp-flag1 t))

(teco-define-type-1
 ?b					; b
 ;; value is point-min
 (setq teco-exp-val1 (point-min)
       teco-exp-flag1 t))

(teco-define-type-1
 ?h					; h
 ;; value is b,z
 (setq teco-exp-val1 (point-max)
       teco-exp-val2 (point-min)
       teco-exp-flag1 t
       teco-exp-flag2 t
       teco-exp-op 'start))

(teco-define-type-1
 ?\^s					; ^s
 ;; value is - length of last insert, etc.
 (setq teco-exp-val1 teco-ctrl-s
       teco-exp-flag1 t))

(teco-define-type-1
 ?\^y					; ^y
 ;; value is .+^S,.
 (setq teco-exp-val1 (+ (point) teco-ctrl-s)
       teco-exp-val2 (point)
       teco-exp-flag1 t
       teco-exp-flag2 t
       teco-exp-op 'start))

(teco-define-type-1
 ?\(					; \(
 ;; push expression stack
 (teco-push-exp-stack)
 (setq teco-exp-flag1 nil
       teco-exp-flag2 nil
       teco-exp-op 'start))

(teco-define-type-1
 ?\^p					; ^p
 (teco-do-ctrl-p))

(teco-define-type-1
 ?\C-^					; ^^
 ;; get next command character
 (setq teco-exp-val1 (teco-get-command teco-trace)
       teco-exp-flag1 t))


;; Type 2 commands
(teco-define-type-2
 ?+					; +
 (setq teco-exp-exp (if teco-exp-flag1 teco-exp-val1 0)
       teco-exp-flag1 nil
       teco-exp-op 'add))

(teco-define-type-2
 ?-					; -
 (setq teco-exp-exp (if teco-exp-flag1 teco-exp-val1 0)
       teco-exp-flag1 nil
       teco-exp-op 'sub))

(teco-define-type-2
 ?*					; *
 (setq teco-exp-exp (if teco-exp-flag1 teco-exp-val1 0)
       teco-exp-flag1 nil
       teco-exp-op 'mult))

(teco-define-type-2
 ?/					; /
 (setq teco-exp-exp (if teco-exp-flag1 teco-exp-val1 0)
       teco-exp-flag1 nil
       teco-exp-op 'div))

(teco-define-type-2
 ?&					; &
 (setq teco-exp-exp (if teco-exp-flag1 teco-exp-val1 0)
       teco-exp-flag1 nil
       teco-exp-op 'and))

(teco-define-type-2
 ?#					; #
 (setq teco-exp-exp (if teco-exp-flag1 teco-exp-val1 0)
       teco-exp-flag1 nil
       teco-exp-op 'or))

(teco-define-type-2
 ?\)					; \)
 (if (or (not teco-exp-flag1) (not teco-exp-stack))
     (teco-error "NAP"))
 (let ((v teco-exp-val1))
   (teco-pop-exp-stack)
   (setq teco-exp-val1 v
	 teco-exp-flag1 t)))

(teco-define-type-2
 ?,					; ,
 (if (not teco-exp-flag1)
     (teco-error "NAC"))
 (setq teco-exp-val2 teco-exp-val1
       teco-exp-flag2 t
       teco-exp-flag1 nil))

(teco-define-type-2
 ?\^_					; ^_
 (if (not teco-exp-flag1)
     (teco-error "NAB")
   (setq teco-exp-val1 (lognot teco-exp-val1))))

(teco-define-type-2
 ?\^d					; ^d
 (setq teco-ctrl-r 10
       teco-exp-flag1 nil
       teco-exp-op 'start))

(teco-define-type-2
 ?\^o					; ^o
 (setq teco-ctrl-r 8
       teco-exp-flag1 nil
       teco-exp-op 'start))

(teco-define-type-2
 ?\^r					; ^r
 (if teco-colon-flag
     (progn
       (recursive-edit)
       (setq teco-colon-flag nil))
   (if teco-exp-flag1
       ;; set radix
       (progn
	 (if (and (/= teco-exp-val1 8)
		  (/= teco-exp-val1 10)
		  (/= teco-exp-val1 16))
	     (teco-error "IRA"))
	 (setq teco-ctrl-r teco-exp-val1
	       teco-exp-flag1 nil
	       teco-exp-op 'start))
     ;; get radix
     (setq teco-exp-val1 teco-ctrl-r
	   teco-exp-flag1 t))))

(teco-define-type-2
 ?\^c					; ^c
 (if (teco-peek-command ?\^c)
     ;; ^C^C stops execution
     (throw 'teco-exit nil)
   (if teco-macro-stack
       ;; ^C inside macro exits macro
       (teco-pop-macro-stack)
     ;; ^C in command stops execution
     (throw 'teco-exit nil))))

(teco-define-type-2
 ?\^x					; ^x
 ;; set/get search mode flag
 (teco-set-var 'teco-ctrl-x))

(teco-define-type-2
 ?m					; m
 (let ((macro-name (teco-get-qspec nil
				   (teco-get-command teco-trace))))
   (teco-push-macro-stack)
   (setq teco-command-string (aref teco-qreg-text macro-name)
	 teco-command-pointer 0)))

(teco-define-type-2
 ?<					; <
 ;; begin iteration
 (if (and teco-exp-flag1 (<= teco-exp-val1 0))
     ;; if this is not to be executed, just skip the
     ;; intervening stuff
     (teco-find-enditer)
   ;; push iteration stack
   (teco-push-iter-stack teco-command-pointer
			 teco-exp-flag1 teco-exp-val1)
   ;; consume the argument
   (setq teco-exp-flag1 nil)))

(teco-define-type-2
 ?>					; >
 ;; end iteration
 (if (not teco-iteration-stack)
     (teco-error "BNI"))
 ;; decrement count and pop conditionally
 (teco-pop-iter-stack nil)
 ;; consume arguments
 (setq teco-exp-flag1 nil
       teco-exp-flag2 nil
       teco-exp-op 'start))

(teco-define-type-2
 59					; ;
 ;; semicolon iteration exit
 (if (not teco-iteration-stack)
     (teco-error "SNI"))
 ;; if exit
 (if (if (>= (if teco-exp-flag1
		 teco-exp-val1
	       teco-search-result) 0)
	 (not teco-colon-flag)
       teco-colon-flag)
     (progn
       (teco-find-enditer)
       (teco-pop-iter-stack t)))
 ;; consume argument and colon
 (setq teco-exp-flag1 nil
       teco-colon-flag nil
       teco-exp-op 'start))

(teco-define-type-2
 ?\"					; \"
 ;; must be an argument
 (if (not teco-exp-flag1)
     (teco-error "NAQ"))
 ;; consume argument
 (setq teco-exp-flag1 nil
       teco-exp-op 'start)
 (let* (;; get the test specification
	(c (aref teco-mapch-l (teco-get-command teco-trace)))
	;; determine whether the test is true
	(test (cond ((eq c ?a)
		     (/= (logand (aref teco-char-types teco-exp-val1)
				 1) 0))
		    ((eq c ?c)
		     (/= (logand (aref teco-char-types teco-exp-val1)
				 2) 0))
		    ((eq c ?d)
		     (/= (logand (aref teco-char-types teco-exp-val1)
				 4) 0))
		    ((or (eq c ?e) (eq c ?f) (eq c ?u) (eq c ?=))
		     (= teco-exp-val1 0))
		    ((or (eq c ?g) (eq c ?>))
		     (> teco-exp-val1 0))
		    ((or (eq c ?l) (eq c ?s) (eq c ?t) (eq c ?<))
		     (< teco-exp-val1 0))
		    ((eq c ?n)
		     (/= teco-exp-val1 0))
		    ((eq c ?r)
		     (/= (logand (aref teco-char-types teco-exp-val1)
				 8) 0))
		    ((eq c ?v)
		     (/= (logand (aref teco-char-types teco-exp-val1)
				 16) 0))
		    ((eq c ?w)
		     (/= (logand (aref teco-char-types teco-exp-val1)
				 32) 0))
		    (t
		     (teco-error "IQC")))))
   (if (not test)
       ;; if the conditional isn't satisfied, read
       ;; to matching | or '
       (let ((ll 1)
	     c)
	 (while (> ll 0)
	   (while (progn (setq c (teco-skipto))
			 (and (/= c ?\")
			      (/= c ?|)
			      (/= c ?\')))
	     (if (= c ?\")
		 (setq ll (1+ ll))
	       (if (= c ?\')
		   (setq ll (1- ll))
		 (if (= ll 1)
		     (break))))))))))

(teco-define-type-2
 ?'					; '
 ;; ignore it if executing
 t)

(teco-define-type-2
 ?|					; |
 (let ((ll 1)
       c)
   (while (> ll 0)
     (while (progn (setq c (teco-skipto))
		   (and (/= c ?\")
			(/= c ?\')))
       nil)
     (if (= c ?\")
	 (setq ll (1+ ll))
       (setq ll (1- ll))))))

(teco-define-type-2
 ?u					; u
 (if (not teco-exp-flag1)
     (teco-error "NAU"))
 (aset teco-qreg-number
       (teco-get-qspec 0 (teco-get-command teco-trace))
       teco-exp-val1)
 (setq teco-exp-flag1 teco-exp-flag2	; command's value is second arg
       teco-exp-val1 teco-exp-val2
       teco-exp-flag2 nil
       teco-exp-op 'start))

(teco-define-type-2
 ?q					; q
 ;; Qn is numeric val, :Qn is # of chars, mQn is mth char
 (let ((mm (teco-get-qspec (or teco-colon-flag teco-exp-flag1)
			   (teco-get-command teco-trace))))
   (if (not teco-exp-flag1)
       (setq teco-exp-val1 (if teco-colon-flag
			       ;; :Qn
			       (length (aref teco-qreg-text mm))
			     ;; Qn
			     (aref teco-qreg-number mm))
	     teco-exp-flag1 t)
     ;; mQn
     (let ((v (aref teco-qreg-text mm)))
       (setq teco-exp-val1 (condition-case nil
			       (aref v teco-exp-val1)
			     (error -1))
	     teco-exp-op 'start)))
   (setq teco-colon-flag nil)))

(teco-define-type-2
 ?%					; %
 (let* ((mm (teco-get-qspec nil (teco-get-command teco-trace)))
	(v (+ (aref teco-qreg-number mm) (teco-get-value 1))))
   (aset teco-qreg-number mm v)
   (setq teco-exp-val1 v
	 teco-exp-flag1 t)))

(teco-define-type-2
 ?c					; c
 (let ((p (+ (point) (teco-get-value 1))))
   (if (or (< p (point-min)) (> p (point-max)))
       (teco-error "POP")
     (goto-char p)
     (setq teco-exp-flag2 nil))))

(teco-define-type-2
 ?r					; r
 (let ((p (- (point) (teco-get-value 1))))
   (if (or (< p (point-min)) (> p (point-max)))
       (teco-error "POP")
     (goto-char p)
     (setq teco-exp-flag2 nil))))

(teco-define-type-2
 ?j					; j
 (let ((p (teco-get-value (point-min))))
   (if (or (< p (point-min)) (> p (point-max)))
       (teco-error "POP")
     (goto-char p)
     (setq teco-exp-flag2 nil))))

(teco-define-type-2
 ?l					; l
 ;; move forward by lines
 (forward-char (teco-lines (teco-get-value 1))))

(teco-define-type-2
 ?\C-q					; ^q
 ;; number of characters until the nth line feed
 (setq teco-exp-val1 (teco-lines (teco-get-value 1))
       teco-exp-flag1 t))

(teco-define-type-2
 ?=					; =
 ;; print numeric value
 (if (not teco-exp-flag1)
     (teco-error "NAE"))
 (teco-output (format
	       (if (teco-peek-command ?=)
		   ;; at least one more =
		   (progn
		     ;; read past it
		     (teco-get-command teco-trace)
		     (if (teco-peek-command ?=)
			 ;; another?
			 (progn
			   ;; read it too
			   (teco-get-command teco-trace)
			   ;; print in hex
			   "%x")
		       ;; print in octal
		       "%o"))
		 ;; print in decimal
		 "%d")
	       teco-exp-val1))
 ;; add newline if no colon
 (if (not teco-colon-flag)
     (teco-output ?\n))
 ;; absorb argument, etc.
 (setq teco-exp-flag1 nil
       teco-exp-flag2 nil
       teco-colon-flag nil
       teco-exp-op 'start))

(teco-define-type-2
 ?\t					; TAB
 (if exp-flag1
     (teco-error "IIA"))
 (let ((text (teco-get-text-arg)))
   (insert ?\t text)
   (setq teco-ctrl-s (1+ (length text))))
 ;; clear arguments
 (setq teco-colon-flag nil
       teco-exp-flag1 nil
       teco-exp-flag2 nil))

(teco-define-type-2
 ?i					; i
 (let ((text (teco-get-text-arg)))
   (if teco-exp-flag1
       ;; if a nI$ command
       (progn
	 ;; text argument must be null
	 (or (string-equal text "") (teco-error "IIA"))
	 ;; insert the character
	 (insert teco-exp-val1)
	 (setq teco-ctrl-s 1)
	 ;; consume argument
	 (setq teco-exp-op 'start))
     ;; otherwise, insert the text
     (insert text)
     (setq teco-ctrl-s (length text)))
   ;; clear arguments
   (setq teco-colon-flag nil
	 teco-exp-flag1 nil
	 teco-exp-flag2 nil)))

(teco-define-type-2
 ?t					; t
 (let ((args (teco-line-args nil)))
   (teco-output (buffer-substring (car args) (cdr args)))))

(teco-define-type-2
 ?v					; v
 (let ((ll (teco-get-value 1)))
   (teco-output (buffer-substring (+ (point) (teco-lines (- 1 ll)))
				  (+ (point) (teco-lines ll))))))

(teco-define-type-2
 ?\C-a					; ^a
 (teco-output (teco-get-text-arg nil ?\C-a))
 (setq teco-at-flag nil
       teco-colon-flag nil
       teco-exp-flag1 nil
       teco-exp-flag2 nil
       teco-exp-op 'start))

(teco-define-type-2
 ?d					; d
 (if (not teco-exp-flag2)
     ;; if only one argument
     (delete-char (teco-get-value 1))
   ;; if two arguments, treat as n,mK
   (let ((ll (teco-line-args 1)))
     (delete-region (car ll) (cdr ll)))))

(teco-define-type-2
 ?k					; k
 (let ((ll (teco-line-args 1)))
   (delete-region (car ll) (cdr ll))))

(teco-define-type-2
 ?\C-u					; ^u
 (let* ((mm (teco-get-qspec nil (teco-get-command teco-trace)))
	(text-arg (teco-get-text-arg))
	(text (if (not teco-exp-flag1)
		  text-arg
		(if (string-equal text-arg "")
		    (char-to-string teco-exp-val1)
		  (teco-error "IIA")))))
   ;; if :, append to the register
   (aset teco-qreg-text mm (if teco-colon-flag
			       (concat (aref teco-qreg-text mm) text)
			     text))
   ;; clear various flags
   (setq teco-exp-flag1 nil
	 teco-at-flag nil
	 teco-colon-flag nil
	 teco-exp-flag1 nil)))

(teco-define-type-2
 ?x					; x
 (let* ((mm (teco-get-qspec nil (teco-get-command teco-trace)))
	(args (teco-line-args 0))
	(text (buffer-substring (car args) (cdr args))))
   ;; if :, append to the register
   (aset teco-qreg-text mm (if teco-colon-flag
			       (concat (aref teco-qreg-text mm) text)
			     text))
   ;; clear various flags
   (setq teco-exp-flag1 nil
	 teco-at-flag nil
	 teco-colon-flag nil
	 teco-exp-flag1 nil)))

(teco-define-type-2
 ?g					; g
 (let ((mm (teco-get-qspec t (teco-get-command teco-trace))))
   (if teco-colon-flag
       (teco-output (aref teco-qreg-text mm))
     (insert (aref teco-qreg-text mm)))
   (setq teco-colon-flag nil)))

(teco-define-type-2
 ?\[					; \[
 (let ((mm (teco-get-qspec t (teco-get-command teco-trace))))
   (setq teco-qreg-stack
	 (cons (cons (aref teco-qreg-text mm)
		     (aref teco-qreg-number mm))
	       teco-qreg-stack))))

(teco-define-type-2
 ?\]					; \]
 (let ((mm (teco-get-qspec t (teco-get-command teco-trace))))
   (if teco-colon-flag
       (setq teco-exp-flag1 t
	     teco-exp-val1 (if teco-qreg-stack -1 0))
     (if teco-qreg-stack
	 (let ((pop (car teco-qreg-stack)))
	   (aset teco-qreg-text mm (car pop))
	   (aset teco-qreg-number mm (cdr pop))
	   (setq teco-qreg-stack (cdr teco-qreg-stack)))
       (teco-error "CPQ")))
   (setq teco-colon-flag nil)))

(teco-define-type-2
 ?\\					; \
 (if (not teco-exp-flag1)
     ;; no argument; read number
     (let ((p (point))
	   (sign +1)
	   (n 0)
	   c)
       (setq c (char-after p))
       (if c
	   (if (= c ?+)
	       (setq p (1+ p))
	     (if (= c ?-)
		 (setq p (1+ p)
		       sign -1))))
       (cond
	((= teco-ctrl-r 8) 
	 (while (progn
		  (setq c (char-after p))
		  (and c (>= c ?0) (<= c ?7)))
	   (setq p (1+ p)
		 n (+ c -48 (* n 8)))))
	((= teco-ctrl-r 10) 
	 (while (progn
		  (setq c (char-after p))
		  (and c (>= c ?0) (<= c ?9)))
	   (setq p (1+ p)
		 n (+ c -48 (* n 10)))))
	(t
	 (while (progn
		  (setq c (char-after p))
		  (and c
		       (or
			(and (>= c ?0) (<= c ?9))
			(and (>= c ?a) (<= c ?f))
			(and (>= c ?A) (<= c ?F)))))
	   (setq p (1+ p)
		 n (+ c (if (> c ?F)
			    ;; convert 'a' to 10
			    -87 
			  (if (> c ?9)
			      ;; convert 'A' to 10
			      -55
			    ;; convert '0' to 0
			    -48))
		      (* n 16))))))
       (setq teco-exp-val1 (* n sign)
	     teco-exp-flag1 t
	     teco-ctrl-s (- (point) p)))
   ;; argument: insert it as a digit string
   (insert (format (cond 
		    ((= teco-ctrl-r 8) "%o")
		    ((= teco-ctrl-r 10) "%d")
		    (t "%x"))
		   teco-exp-val1))
   (setq teco-exp-flag1 nil
	 teco-exp-op 'start)))

(teco-define-type-2
 ?\C-t					; ^t
 (if teco-exp-flag1
     ;; type a character
     (progn
       (teco-output teco-exp-val1)
       (setq teco-exp-flag1 nil))
   ;; input a character
   (let* ((echo-keystrokes 0)
	  (c (read-char)))
     (teco-output c)
     (setq teco-exp-val1 c
	   teco-exp-flag1 t))))

(teco-define-type-2
 ?s					; s
 (let ((arg (teco-get-text-arg))
       (count (if teco-exp-flag1 teco-expr-val1 1))
       regexp)
   (if (not (string-equal arg ""))
       (setq regexp (teco-parse-search-string arg)
	     teco-last-search-string arg
	     teco-last-search-regexp regexp)
     (setq regexp (teco-last-search-regexp)
	   arg teco-last-search-string))
   (let ((p (point))
	 (result (cond
		  ((> count 0)
		   (re-search-forward regexp nil t count))
		  ((< count 0)
		   (re-search-backward regexp nil t count))
		  (t
		   ;; 0s always is successful
		   t))))
     ;; if ::s, restore point
     (if (eq teco-colon-flag 2)
	 (goto-char p))
     ;; if no real or implied colon, error if not found
     (if (and (not result)
	      (not teco-colon-flag)
	      (/= (teco-peekcmdc) 34))
	 (teco-error "SRH"))
     ;; set return results
     (setq teco-exp-flag2 nil
	   teco-colon-flag nil
	   teco-at-flag nil
	   teco-exp-op 'start)
     (if teco-colon-flag
	 (setq teco-exp-flag1 t
	       teco-exp-val1 (if result -1 0))
       (setq teco-exp-flag1 nil)))))

(defun teco-parse-search-string (s)
  (let ((i 0)
	(l (length s))
	(r "")
	c)
    (while (< i l)
      (setq r (concat r (teco-parse-search-string-1))))
    r))

(defun teco-parse-search-string-1 ()
  (if (>= i l)
      (teco-error "ISS"))
  (setq c (aref s i))
  (setq i (1+ i))
  (cond
   ((eq c ?\C-e)			; ^E - special match characters
    (teco-parse-search-string-e))
   ((eq c ?\C-n)			; ^Nx - match all but x
    (teco-parse-search-string-n))
   ((eq c ?\C-q)			; ^Qx - use x literally
    (teco-parse-search-string-q))
   ((eq c ?\C-s)			; ^S - match separator chars
    "[^A-Za-z0-9]")
   ((eq c ?\C-x)			; ^X - match any character
    "[\000-\377]")
   (t					; ordinary character
    (teco-parse-search-string-char c))))

(defun teco-parse-search-string-char (c)
  (regexp-quote (char-to-string c)))

(defun teco-parse-search-string-q ()
  (if (>= i l)
      (teco-error "ISS"))
  (setq c (aref s i))
  (setq i (1+ i))
  (teco-parse-search-string-char c))

(defun teco-parse-search-string-e ()
  (if (>= i l)
      (teco-error "ISS"))
  (setq c (aref s i))
  (setq i (1+ i))
  (cond
   ((or (eq c ?a) (eq c ?A))		; ^EA - match alphabetics
    "[A-Za-z]")
   ((or (eq c ?c) (eq c ?C))		; ^EC - match symbol constituents
    "[A-Za-z.$]")
   ((or (eq c ?d) (eq c ?D))		; ^ED - match numerics
    "[0-9]")
   ((eq c ?g)				; ^EGq - match any char in q-reg
    (teco-parse-search-string-e-g))
   ((or (eq c ?l) (eq c ?L))		; ^EL - match line terminators
    "[\012\013\014]")
   ((eq c ?q)				; ^EQq - use contents of q-reg
    (teco-parse-search-string-e-q))
   ((eq c ?r)				; ^ER - match alphanumerics
    "[A-Za-z0-9]")
   ((eq c ?s)				; ^ES - match non-null space/tab seq
    "[ \t]+")
   ((eq c ?v)				; ^EV - match lower case alphabetic
    "[a-z]")
   ((eq c ?w)				; ^EW - match upper case alphabetic
    "[A-Z]")
   ((eq c ?x)				; ^EX - match any character
    "[\000-\377]")
   (t
    (teco-error "ISS"))))

(defun teco-parse-search-string-e-q ()
  (if (>= i l)
      (teco-error "ISS"))
  (setq c (aref s i))
  (setq i (1+ i))
  (regexp-quote (aref reco:q-reg-text c)))

(defun teco-parse-search-string-e-g ()
  (if (>= i l)
      (teco-error "ISS"))
  (setq c (aref s i))
  (setq i (1+ i))
  (let* ((q (aref teco-qreg-text c))
	 (len (length q))
	 (null (= len 0))
	 (one-char (= len 1))
	 (dash-present (string-match "-" q))
	 (caret-present (string-match "\\^" q))
	 (outbracket-present (string-match "]" q))
	 p)
    (cond
     (null
      "[^\000-\377]")
     (one-char
      (teco-parse-search-string-char c))
     (t
      (while (setq p (string-match "^]\\^"))
	(setq q (concat (substring q 1 p) (substring q (1+ p)))))
      (concat
       "["
       (if outbracket-present "]" "")
       (if dash-present "---" "")
       q
       (if caret-present "^" ""))))))

(defun teco-parse-search-string-n ()
  (let ((p (teco-parse-search-string-1)))
    (cond
     ((= (aref p 0) ?\[)
      (if (= (aref p 1) ?^)
	  ;; complement character set
	  (if (= (length p) 4)
	      ;; complement of one character
	      (teco-parse-search-string-char (aref p 2))
	    ;; complement of more than one character
	    (concat "[" (substring p 2)))
	;; character set - invert it
      (concat "[^" (substring p 1))))
     ((= (aref p 0) ?\\)
      ;; single quoted character
      (concat "[^" (substring p 1) "]"))
     (t
      ;; single character
      (if (string-equal p "-")
	  "[^---]"
	(concat "[^" p "]"))))))

(teco-define-type-2
 ?o					; o
 (let ((label (teco-get-text-arg))
       (index (and teco-exp-flag1 teco-exp-val1)))
   (setq teco-exp-flag1 nil)
   ;; handle computed goto by extracting the proper label
   (if index
       (if (< index 0)
	   ;; argument < 0 is a noop
	   (setq label "")
	 ;; otherwise, find the n-th label (0-origin)
	 (setq label (concat label ","))
	 (let ((p 0))
	   (while (and (> index 0)
		       (setq p (string-match "," label p))
		       (setq p (1+ p)))
	     (setq index (1- index)))
	   (setq q (string-match "," label p))
	   (setq label (substring label p q)))))
   ;; if the label is non-null, find the correct label
   ;; start from beginning of iteration or macro, and look for tag
   (setq teco-command-pointer
	 (if teco-iteration-stack
	     ;; if in iteration, start at beginning of iteration
	     (aref (car teco-iteration-stack) 0)
	   ;; if not in iteration, start at beginning of command or macro
	   0))
   ;; search for tag
   (catch 'label
     (let ((level 0)
	   c p l)
       ;; look for interesting things, including !
       (while t
	 (setq c (teco-skipto t))
	 (cond
	  ((= c ?<)			; start of iteration
	   (setq level (1+ level)))
	  ((= c ?>)			; end of iteration
	   (if (= level 0)
	       (teco-pop-iter-stack t)
	     (setq level (1- level))))
	  ((= c ?!)			; start of tag
	   (setq p (string-match "!" teco-command-string teco-command-pointer))
	   (if (and p
		    (string-equal label (substring teco-command-string
						   teco-command-pointer
						   p)))
	       (progn
		 (setq teco-command-pointer (1+ p))
		 (throw 'label nil))))))))))

(teco-define-type-2
 ?a					; :a
 ;; 'a' must be used as ':a'
 (if (and teco-exp-flag1 teco-colon-flag)
     (let ((char (+ (point) teco-exp-val1)))
       (setq teco-exp-val1
	     (if (and (>= char (point-min))
		      (< char (point-max)))
		 (char-after char)
	       -1)
	     teco-colon-flag nil))
   (teco-error "ILL")))


;; Routines to get next character from command buffer
;; getcmdc0, when reading beyond command string, pops
;; macro stack and continues.
;; getcmdc, in similar circumstances, reports an error.
;; If pushcmdc() has returned any chars, read them first
;; routines type characters as read, if argument != 0.

(defun teco-get-command0 (trace)
  ;; get the next character
  (let (char)
    (while (not (condition-case nil
		    (setq char (aref teco-command-string teco-command-pointer))
		  ;; if we've exhausted the string, pop the macro stack
		  ;; if we exhaust the macro stack, exit
		  (error (teco-pop-macro-stack)
			 nil))))
    ;; bump the command pointer
    (setq teco-command-pointer (1+ teco-command-pointer))
    ;; trace, if requested
    (and trace (teco-trace-type char))
    ;; return the character
    char))

;; 	while (cptr.dot >= cptr.z)		/* if at end of this level, pop macro stack
;; 		{
;; 		if (--msp < &mstack[0])		/* pop stack; if top level
;; 			{
;; 			msp = &mstack[0];		/* restore stack pointer
;; 			cmdc = ESC;				/* return an ESC (ignored)
;; 			exitflag = 1;			/* set to terminate execution
;; 			return(cmdc);			/* exit "while" and return
;; 			}
;; 		}
;; 	cmdc = cptr.p->ch[cptr.c++];		/* get char
;; 	++cptr.dot;							/* increment character count
;; 	if (trace) type_char(cmdc);			/* trace
;; 	if (cptr.c > CELLSIZE-1)			/* and chain if need be
;; 		{
;; 		cptr.p = cptr.p->f;
;; 		cptr.c = 0;
;; 		}
;; 	return(cmdc);
;; 	}


(defun teco-get-command (trace)
  ;; get the next character
  (let ((char (condition-case nil
		  (aref teco-command-string teco-command-pointer)
		;; if we've exhausted the string, give error
		(error
		 (teco-error (if teco-macro-stack "UTM" "UTC"))))))
    ;; bump the command pointer
    (setq teco-command-pointer (1+ teco-command-pointer))
    ;; trace, if requested
    (and trace (teco-trace-type char))
    ;; return the character
    char))

;; char getcmdc(trace)
;; 	{
;; 	if (cptr.dot++ >= cptr.z) ERROR((msp <= &mstack[0]) ? E_UTC : E_UTM);
;; 	else
;; 		{
;; 		cmdc = cptr.p->ch[cptr.c++];	/* get char
;; 		if (trace) type_char(cmdc);		/* trace
;; 		if (cptr.c > CELLSIZE-1)		/* and chain if need be
;; 			{
;; 			cptr.p = cptr.p->f;
;; 			cptr.c = 0;
;; 			}
;; 		}
;; 	return(cmdc);
;; 	}


;; peek at next char in command string, return 1 if it is equal
;; (case independent) to argument

(defun teco-peek-command (arg)
  (condition-case nil
      (eq (aref teco-mapch-l (aref teco-command-string teco-command-pointer))
	  (aref teco-mapch-l arg))
    (error nil)))

;; int peekcmdc(arg)
;; 	char arg;
;; 	{
;; 	return(((cptr.dot < cptr.z) && (mapch_l[cptr.p->ch[cptr.c]] == mapch_l[arg])) ? 1 : 0);
;; 	}

(defun teco-get-text-arg (&optional term-char default-term-char)
  ;; figure out what the terminating character is
  (setq teco-term-char (or term-char
			   (if teco-at-flag
			       (teco-get-command teco-trace)
			     (or default-term-char
				 ?\e)))
	teco-at_flag nil)
  (let ((s "")
	c)
    (while (progn
	     (setq c (teco-get-command teco-trace))
	     (/= c teco-term-char))
      (setq s (concat s (char-to-string c))))
    s))


;; Routines to manipulate the stacks

;; Pop the macro stack.  Throw to 'teco-exit' if the stack is empty.
(defun teco-pop-macro-stack ()
  (if teco-macro-stack
      (let ((frame (car teco-macro-stack)))
	(setq teco-macro-stack (cdr teco-macro-stack)
	      teco-command-string (aref frame 0)
	      teco-command-pointer (aref frame 1)
	      teco-exec-flags (aref frame 2)
	      teco-iteration-stack (aref frame 3)
	      teco-cond-stack (aref frame 4)))
    (throw 'teco-exit nil)))

;; Push the macro stack.
(defun teco-push-macro-stack ()
  (setq teco-macro-stack
	(cons (vector teco-command-string
		      teco-command-pointer
		      teco-exec-flags
		      teco-iteration-stack
		      teco-cond-stack)
	      teco-macro-stack)))

;; Pop the expression stack.
(defun teco-pop-exp-stack ()
  (let ((frame (car teco-exp-stack)))
    (setq teco-exp-stack (cdr teco-exp-stack)
	  teco-exp-val1 (aref frame 0)
	  teco-exp-flag1 (aref frame 1)
	  teco-exp-val2 (aref frame 2)
	  teco-exp-flag2 (aref frame 3)
	  teco-exp-exp (aref frame 4)
	  teco-exp-op (aref frame 5))))

;; Push the expression stack.
(defun teco-push-exp-stack ()
  (setq teco-exp-stack
	(cons (vector teco-exp-val1
		      teco-exp-flag1
		      teco-exp-val2
		      teco-exp-flag2
		      teco-exp-exp
		      teco-exp-op)
	      teco-exp-stack)))

;; Pop the iteration stack
;; if arg t, exit unconditionally
;; else check exit conditions and exit or reiterate
(defun teco-pop-iter-stack (arg)
  (let ((frame (car teco-iteration-stack)))
    (if (or arg
	    (not (aref frame 1))
	    ;; test against 1, since one iteration has already been done
	    (<= (aref frame 2) 1))
	;; exit iteration
	(setq teco-iteration-stack (cdr teco-iteration-stack))
      ;; continue with iteration
      ;; decrement count
      (aset frame 2 (1- (aref frame 2)))
      ;; reset command pointer
      (setq teco-command-pointer (aref frame 0)))))

;; Push the iteration stack
(defun teco-push-iter-stack (pointer flag count)
  (setq teco-iteration-stack
	(cons (vector pointer
		      flag
		      count)
	      teco-iteration-stack)))	      

(defun teco-find-enditer ()
  (let ((icnt 1)
	c)
    (while (> icnt 0)
      (while (progn (setq c (teco-skipto))
		    (and (/= c ?<)
			 (/= c ?>)))
	(if (= c ?<)
	    (setq icnt (1+ icnt))
	  (setq icnt (1- icnt)))))))


;; I/O routines

(defvar teco-output-buffer (get-buffer-create "*Teco Output*")
  "The buffer into which Teco output is written.")

(defun teco-out-init ()
  ;; Recreate the teco output buffer, if necessary
  (setq teco-output-buffer (get-buffer-create "*Teco Output*"))
  (save-excursion
    (set-buffer teco-output-buffer)
    ;; get a fresh line in output buffer
    (goto-char (point-max))
    (insert ?\n)
    ;; remember where to start displaying
    (setq teco-output-start (point))
    ;; clear minibuffer, in case we have to display in it
    (save-window-excursion
      (select-window (minibuffer-window))
      (erase-buffer))
    ;; if output is visible, position it correctly
    (let ((w (get-buffer-window teco-output-buffer)))
      (if w
	  (progn
	    (set-window-start w teco-output-start)
	    (set-window-point w teco-output-start))))))

(defun teco-output (s)
  (let ((w (get-buffer-window teco-output-buffer))
	(b (current-buffer))
	(sw (selected-window)))
    ;; Put the text in the output buffer
    (set-buffer teco-output-buffer)
    (goto-char (point-max))
    (insert s)
    (let ((p (point)))
      (set-buffer b)
      (if w
	  ;; if output is visible, move the window point to the end
	  (set-window-point w p)
	;; Otherwise, we have to figure out how to display the text
	;; Has a newline followed by another character been added to the
	;; output buffer?  If so, we have to make the output buffer visible.
	(if (save-excursion
	      (set-buffer teco-output-buffer)
	      (backward-char 1)
	      (search-backward "\n" teco-output-start t))
	    ;; a newline has been seen, clear the minibuffer and make the
	    ;; output buffer visible
	    (progn
	      (save-window-excursion
		(select-window (minibuffer-window))
		(erase-buffer))
	      (let ((pop-up-windows t))
		(pop-to-buffer teco-output-buffer)
		(goto-char p)
		(set-window-start w teco-output-start)
		(set-window-point w p)
		(select-window sw)))
	  ;; a newline has not been seen, add output to minibuffer
	  (save-window-excursion
	    (select-window (minibuffer-window))
	    (goto-char (point-max))
	    (insert s)))))))

;; Output a character of tracing information
(defun teco-trace-type (c)
  (teco-output (if (= c ?\e)
		?$
	      c)))

;; Report an error
(defun teco-error (code)
  (let ((text (cdr (assoc code teco-error-texts))))
    (teco-output (concat (if (save-excursion (set-buffer teco-output-buffer)
					     (/= (point) teco-output-start))
			     "\n"
			   "")
			 "? " code " " text))
    (beep)
    (if debug-on-error (debug nil code text))
    (throw 'teco-exit nil)))


;; Utility routines

;; copy characters from command string to buffer
(defun teco-moveuntil (string pointer terminate trace)
  (let ((count 0))
    (condition-case nil
	(while (/= (aref string pointer) terminate)
	  (and teco-trace (teco-trace-type (aref string pointer)))
	  (insert (aref string pointer))
	  (setq pointer (1+ pointer))
	  (setq count (1+ count)))
      (error (teco-error (if teco-macro-stack "UTM" "UTC"))))
    count))

;; Convert character to q-register name
;; If file-or-search is t, allow _, *, %, #
(defun teco-get-qspec (file-or-search char)
  ;; lower-case char
  (setq char (aref teco-mapch-l char))
  ;; test that it's valid
  (if (= (logand (aref teco-qspec-valid char) (if file-or-search 2 1)) 0)
      (teco-error "IQN"))
  char)

;; Set or get value of a variable
(defun teco-set-var (var)
  (if teco-exp-flag1
      (progn
	(if teco-exp-flag2
	    ;; if two arguments, they they are <clear bits>, <set bits>
	    (set var (logior (logand (symbol-value var) (lognot teco-exp-val2))
			     teco-exp-val1))
	  ;; if one argument, it is the new value
	  (set var teco-exp-val1))
	;; consume argument(s)
	(setq teco-exp-flag2 nil
	      teco-exp-flag1 nil))
    ;; if no arguments, fetch the value
    (setq teco-exp-val1 (symbol-value var)
	  teco-exp-flag1 t)))

;; Get numeric argument
(defun teco-get-value (default)
  (prog1
      (if teco-exp-flag1
	  teco-exp-val1
	(if (eq teco-exp-op 'sub)
	    (- default)
	  default))
    ;; consume argument
    (setq teco-exp-flag1 nil
	  teco-exp-op 'start)))

;; Get argument measuring in lines
(defun teco-lines (r)
  (- (save-excursion
       (if (> r 0)
	   (if (search-forward "\n" nil t r)
	       (point)
	     (point-max))
	 (if (search-backward "\n" nil t (- 1 r))
	     (1+ (point))
	   (point-min))))
     (point)))

;; routine to handle args for K, T, X, etc.
;; if two args, 'char x' to 'char y'
;; if just one arg, then n lines (default 1)
(defun teco-line-args (arg)
  (if teco-exp-flag2
      (cons teco-exp-val1 teco-exp-val2)
    (cons (point) (+ (point) (teco-lines (if teco-exp-flag1
					     teco-exp-val1
					   1))))))

;; routine to skip to next ", ', |, <, or >
;; skips over these chars embedded in text strings
;; stops in ! if argument is t
;; returns character found
(defun teco-skipto (&optional arg)
  (catch 'teco-skip
    (let (;; "at" prefix
	  (atsw nil)
	  ;; temp attributes
	  ta
	  ;; terminator
	  term
	  skipc)
      (while t				; forever
	(while (progn
		 (setq skipc (teco-get-command nil)
		       ta (aref teco-spec-chars skipc))
		 ;; if char is ^, treat next char as control
		 (if (eq skipc ?^)
		     (setq skipc (logand 31 (teco-get-command nil))
			   ta (aref teco-spec-chars skipc)))
		 (= (logand ta 51) 0))	; read until something interesting
					; found
	  nil)
	(if (/= (logand ta 32) 0)
	    (teco-get-command nil))	; if command takes a Q spec,
					; skip the spec
	(if (/= (logand ta 16) 0)	; sought char found: quit 
	    (progn
	      (if (= skipc ?\")		; quote must skip next char
		  (teco-get-command nil))
	      (throw 'teco-skip skipc)))
	(if (/= (logand ta 1) 0)	; other special char
	    (cond
	     ((eq skipc ?@)		; use alternative text terminator
	      (setq atsw t))
	     ((eq skipc ?\C-^)		; ^^ is value of next char
					; skip that char
	      (teco-get-command nil))
	     ((eq skipc ?\C-a)		; type text
	      (setq term (if atsw (teco-get-command nil) ?\C-a)
		    atsw nil)
	      (while (/= (teco-get-command nil) term)
		nil))			; skip text
	     ((eq skipc ?!)		; tag 
	      (if arg
		  (throw 'teco-skip skipc))
	      (while (/= (teco-get-command nil) ?!)
		nil))			; skip until next !
	     ((or (eq skipc ?e)
		  (eq skipc ?f))	; first char of two-letter E or F
					; command
	      nil)))			; not implemented
	(if (/= (logand ta 2) 0)	; command with a text
					; argument
	    (progn
	      (setq term (if atsw (teco-get-command nil) ?\e)
		    atsw nil)
	      (while (/= (teco-get-command nil) term)
		nil)			; skip text
	      ))))))


(defvar teco-command-keymap
  ;; This is what used to be (make-vector 128 'teco-command-self-insert)
  ;; Oh well
  (let ((map (make-keymap)) (n 127))
    (while (>= n 0)
      (define-key map (if (< n 32) (list 'control (+ n 32)) n)
	'teco-command-self-insert)
      (setq n (1- n)))
    map)
  "Keymap used while reading teco commands.")

(define-key teco-command-keymap "\^g" 'teco-command-ctrl-g)
(define-key teco-command-keymap "\^m" 'teco-command-return)
(define-key teco-command-keymap "\^u" 'teco-command-ctrl-u)
(define-key teco-command-keymap "\e" 'teco-command-escape)
(define-key teco-command-keymap "\^?" 'teco-command-delete)

(defvar teco-command-escapes nil
  "Records where ESCs are, since they are represented in the command buffer
by $.")

;;;###autoload
(defun teco-command ()
  "Read and execute a Teco command string."
  (interactive)
  (let* ((teco-command-escapes nil)
	 (command (catch 'teco-command-quit
		    (read-from-minibuffer teco-prompt nil
					  teco-command-keymap))))
    (if command
	(progn
	  (while teco-command-escapes
	    (aset command (car teco-command-escapes) ?\e)
	    (setq teco-command-escapes (cdr teco-command-escapes)))
	  (setq teco-output-buffer (get-buffer-create "*Teco Output*"))
	  (save-excursion
	    (set-buffer teco-output-buffer)
	    (goto-char (point-max))
	    (insert teco-prompt command))
	  (teco-execute-command command)))))

(defun teco-read-command ()
  "Read a teco command string from the user."
  (let ((command (catch 'teco-command-quit
		   (read-from-minibuffer teco-prompt nil
					 teco-command-keymap)))
	teco-command-escapes)
    (if command
	(while teco-command-escapes
	  (aset command (car teco-command-escapes ?\e))
	  (setq teco-command-escapes (cdr teco-command-escapes))))
    command))

(defun teco-command-self-insert ()
  (interactive)
  (insert last-command-char)
  (if (not (pos-visible-in-window-p))
      (enlarge-window 1)))

(defun teco-command-ctrl-g ()
  (interactive)
  (beep)
  (throw 'teco-command-quit nil))

(defun teco-command-return ()
  (interactive)
  (setq last-command-char ?\n)
  (teco-command-self-insert))

(defun teco-command-escape ()
  (interactive)
  ;; Two ESCs in a row terminate the command string
  (if (eq last-command 'teco-command-escape)
      (throw 'teco-command-quit (buffer-string)))
  (setq teco-command-escapes (cons (1- (point)) teco-command-escapes))
  (setq last-command-char ?$)
  (teco-command-self-insert))

(defun teco-command-ctrl-u ()
  (interactive)
  ;; delete the characters
  (kill-line 0)
  ;; forget that they were ESCs
  (while (and teco-command-escapes (<= (point) (car teco-command-escapes)))
      (setq teco-command-escapes (cdr teco-command-escapes)))
  ;; decide whether to shrink the window
  (while (let ((a (insert ?\n))
	       (b (pos-visible-in-window-p))
	       (c (backward-delete-char 1)))
	   b)
    (shrink-window 1)))

(defun teco-command-delete ()
  (interactive)
  ;; delete the character
  (backward-delete-char 1)
  ;; forget that it was an ESC
  (if (and teco-command-escapes (= (point) (car teco-command-escapes)))
      (setq teco-command-escapes (cdr teco-command-escapes)))
  ;; decide whether to shrink the window
  (insert ?\n)
  (if (prog1 (pos-visible-in-window-p)
	(backward-delete-char 1))
      (shrink-window 1)))

(provide 'teco)

;;; teco.el ends here
