(* 3-LISP mode for EMACS. *)


(GLOBALVARS EMACS.LAST.STREAM.FLASHED
            EMACS.LAST.CORRESPONDING.OPEN.PTR 
            EMACS.LAST.CLOSE.PTR
            EMACS.LAST.CLOSE.CHARACTER)

(CONSTANTS EMACS.WORD)
(SETQ EMACS.WORD 16)

(DEFINEQ (EMACS.ENTER.3LISP.MODE (LAMBDA ()
  (SETQ EMACS.LAST.STREAM.FLASHED NIL)
  (SETQ EMACS.LAST.CORRESPONDING.OPEN.PTR NIL) 
  (SETQ EMACS.LAST.CLOSE.PTR NIL) 
  (SETQ EMACS.LAST.CLOSE.CHARACTER NIL)
  (* Define EMACS.WORD for 3-LISP. *)
  (FOR I FROM 0 TO 255 DO
      (SETA EMACS.ARRAY I
             (LOGOR (ELT EMACS.ARRAY I) EMACS.WORD)))
  (FOR I IN (CONSTANT (LIST (CHARCODE "(") (CHARCODE ")") 
          (CHARCODE "[") (CHARCODE "]") (CHARCODE CR)  
          (CHARCODE LF) (CHARCODE TAB) (CHARCODE SP)  
          (CHARCODE "'") (CHARCODE "↑") (CHARCODE "\")  
          (CHARCODE ",") (CHARCODE "%""))) DO
     (SETA EMACS.ARRAY I
          (LOGAND (ELT EMACS.ARRAY I)
               (LOGNOT EMACS.WORD)))))
  NIL)))


(* Backward "skip-read" for 3-lisp; i.e., returns no result, just 
   positions the stream pointer to before the first
   character of the sexpression to the left of the
   initial position of the cursor.  Error return if
   the input cannot be parsed, with the stream pointer
   left just before the character at which things
   seemed screwy. *)

(DEFINEQ (EMACS.SEXPR.BACK (LAMBDA (STREAM)
  (PROG (CHAR CLOSE.PTR)
     (EMACS.SKIP.SYNTAX.BACK STREAM EMACS.WS)
     (IF (EMACS.BOFP STREAM)  THEN         
         (ERROR!))
     (SETQ CHAR (\BACKBIN STREAM))
     (IF (EQ CHAR (CHARCODE ")")) THEN
         (EMACS.BALANCED.SEXPR.BACK STREAM (CHARCODE "(") (CHARCODE ")"))
         (SKIP.PREFIX.CHARACTERS.BACK STREAM)
         (RETURN NIL))
 
     (IF (EQ CHAR (CHARCODE "]")) THEN
         (EMACS.BALANCED.SEXPR.BACK STREAM (CHARCODE "[") (CHARCODE "]"))
         (SKIP.PREFIX.CHARACTERS.BACK STREAM)
         (RETURN NIL))

     (IF (EQ CHAR (CHARCODE "%"" )) THEN
         (PROG ()
            AGAIN
              (WHILE (AND (NOT (EMACS.BOFP STREAM))
                        (NEQ (SETQ CHAR (\BACKBIN STREAM)) 
                             (CHARCODE %" ))) DO
                   (* Keep reading. *))
              (IF (EQ (\BACKPEEKBIN STREAM) (CHARCODE %%)) THEN
                  (\BACKBIN STREAM)
                  (GO AGAIN)))
          (SKIP.PREFIX.CHARACTERS.BACK STREAM)
          (RETURN NIL))

     (IF (EQ CHAR (CHARCODE "(")) THEN
         (ERROR!))

     (IF (EQ CHAR (CHARCODE "[")) THEN
         (ERROR!))

     (EMACS.SKIP.SYNTAX.BACK STREAM EMACS.WORD)
     (SKIP.PREFIX.CHARACTERS.BACK STREAM)
     (RETURN)))))
           
               
(DEFINEQ (SKIP.PREFIX.CHARACTERS.BACK (LAMBDA (STREAM)
  (PROG (CHAR)
    (EMACS.SKIP.SYNTAX.BACK STREAM EMACS.WS)
    (WHILE (AND (NOT (EMACS.BOFP STREAM))
                (MEMB (SETQ CHAR (\BACKPEEKBIN STREAM))
                  (CONSTANT 
                    (LIST (CHARCODE %,) (CHARCODE %↑)
                          (CHARCODE %\) (CHARCODE %'))))) DO
       (\BACKBIN STREAM)
       (EMACS.SKIP.SYNTAX.BACK STREAM EMACS.WS))
    (EMACS.SKIP.SYNTAX.FWD STREAM EMACS.WS)
    (RETURN)))))


(* EMACS.BALANCED.SEXPR.BACK: Back-skips the rest of an s-expr 
   that is bounded by single-character delimiters. OPENER is
   (the character code of) the opening delimiter; CLOSER is
   that of the matching closing delimiter.  Assumes that
   the stream is positioned to the left of the closing delimiter.
   Leaves the stream positioned to the left of the corresponding
   opening delimiter.  A simple one-entry cache is used. *)     

(DEFINEQ (EMACS.BALANCED.SEXPR.BACK (LAMBDA (STREAM OPENER CLOSER)
  (IF (NULL (NLSETQ
    (PROG (CHAR CLOSE.PTR)
      (SETQ CLOSE.PTR (EMACS.GETFILEPTR STREAM))
      (IF (AND (EQUAL EMACS.LAST.CLOSE.PTR CLOSE.PTR)
                (EQ EMACS.LAST.STREAM.FLASHED STREAM)
                (EQ CLOSER EMACS.LAST.CLOSE.CHARACTER)) THEN
        (EMACS.SETCARETPTR STREAM EMACS.LAST.CORRESPONDING.OPEN.PTR)
       ELSE  
        (WHILE (NEQ (SETQ CHAR (\BACKPEEKBIN STREAM)) OPENER) DO
            (EMACS.SEXPR.BACK STREAM)) 
        (\BACKBIN STREAM)
        (SETQ EMACS.LAST.CLOSE.PTR CLOSE.PTR) 
        (SETQ EMACS.LAST.STREAM.FLASHED STREAM) 
        (SETQ EMACS.LAST.CLOSE.CHARACTER CLOSER) 
        (SETQ EMACS.LAST.CORRESPONDING.OPEN.PTR
                (EMACS.GETFILEPTR STREAM))))
      T)) THEN
          (SETQ EMACS.LAST.STREAM.FLASHED NIL)
          (ERROR!)))))



(* Parenthesis balancing. *)

(DEFINEQ (EMACS.FLASH.SEXPR.BACK (LAMBDA (STREAM)
   (PROG (INITIAL.PTR MATCHED FINAL.PTR)
      (SETQ INITIAL.PTR (EMACS.GETFILEPTR STREAM))
      (SETQ MATCHED
          (NLSETQ
              (EMACS.SEXPR.BACK STREAM)
              T))
      (SETQ FINAL.PTR (EMACS.GETFILEPTR STREAM))
      (IF (NOT MATCHED) THEN
          (RINGBELLS))
      (IF (NOT (NULL (EMACS.GETFILEPTR STREAM))) THEN
          (TEDIT.SHOWSEL STREAM NIL)
          (TEDIT.SETSEL STREAM (ADD1 FINAL.PTR) 1 'RIGHT T)
          (DISMISS (IF MATCHED THEN 200 ELSE 1000)))
      (EMACS.SETCARETPTR STREAM INITIAL.PTR) 
      (RETURN))))) 

(DEFINEQ (\\COM.FLASH.PAREN (LAMBDA (STREAM)
   (\BOUT STREAM (CHARCODE ")"))
   (EMACS.FLASH.SEXPR.BACK STREAM))))

(EMACS.MAKE.COMMAND (CHARCODE ")") '\\COM.FLASH.PAREN) 

(DEFINEQ (\\COM.FLASH.BRACKET (LAMBDA (STREAM)
   (\BOUT STREAM (CHARCODE "]"))
   (EMACS.FLASH.SEXPR.BACK STREAM))))

(EMACS.MAKE.COMMAND (CHARCODE "]") '\\COM.FLASH.BRACKET)
        

(* New versions of commands that detect bad s-exprs. *)

(DEFINEQ (\\COM.BACKWARDS.SEXPR (LAMBDA (STREAM)
  (IF (NOT (NLSETQ (EMACS.SEXPR.BACK STREAM) T)) THEN
      (RINGBELLS)))))

(DEFINEQ (\\COM.FORWARD.SEXPR (LAMBDA (STREAM)
  (IF (NOT (NLSETQ (EMACS.SEXPR.FWD STREAM) T)) THEN
      (RINGBELLS)))))