Initial git import. - sam - An updated version of the sam text editor.
 (HTM) git clone git://vernunftzentrum.de/sam.git
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) LICENSE
       ---
 (DIR) commit f8d68c3f2e8aa987821894c9b82aa63bb411e714
 (HTM) Author: Rob King <jking@deadpixi.com>
       Date:   Fri, 31 Jul 2015 23:02:18 -0500
       
       Initial git import.
       
       Diffstat:
         LICENSE                             |      18 ++++++++++++++++++
         Makefile                            |      40 +++++++++++++++++++++++++++++++
         README                              |     118 +++++++++++++++++++++++++++++++
         config.mk                           |      23 +++++++++++++++++++++++
         doc/B                               |      43 ++++++++++++++++++++++++++++++
         doc/Makefile                        |      18 ++++++++++++++++++
         doc/keyboard                        |     206 +++++++++++++++++++++++++++++++
         doc/sam.1                           |    1027 +++++++++++++++++++++++++++++++
         doc/sam.1.pdf                       |       0 
         doc/sam.ps                          |    8261 +++++++++++++++++++++++++++++++
         doc/sam.tut.ms                      |    1776 ++++++++++++++++++++++++++++++
         doc/se.ps                           |    1487 ++++++++++++++++++++++++++++++
         include/frame.h                     |      72 +++++++++++++++++++++++++++++++
         include/libc.h                      |      54 +++++++++++++++++++++++++++++++
         include/libg.h                      |     225 +++++++++++++++++++++++++++++++
         include/regexp.h                    |      65 +++++++++++++++++++++++++++++++
         include/u.h                         |     117 +++++++++++++++++++++++++++++++
         libXg/Gwin.h                        |      43 ++++++++++++++++++++++++++++++
         libXg/GwinP.h                       |      45 +++++++++++++++++++++++++++++++
         libXg/Makefile                      |      58 ++++++++++++++++++++++++++++++
         libXg/arc.c                         |      45 +++++++++++++++++++++++++++++++
         libXg/arith.c                       |     191 +++++++++++++++++++++++++++++++
         libXg/balloc.c                      |      60 +++++++++++++++++++++++++++++++
         libXg/bitblt.c                      |      59 +++++++++++++++++++++++++++++++
         libXg/bitbltclip.c                  |      68 +++++++++++++++++++++++++++++++
         libXg/border.c                      |      28 ++++++++++++++++++++++++++++
         libXg/bscreenrect.c                 |      18 ++++++++++++++++++
         libXg/circle.c                      |      23 +++++++++++++++++++++++
         libXg/clipline.c                    |     225 +++++++++++++++++++++++++++++++
         libXg/clipr.c                       |      21 +++++++++++++++++++++
         libXg/copymasked.c                  |      49 +++++++++++++++++++++++++++++++
         libXg/cursorset.c                   |      16 ++++++++++++++++
         libXg/cursorswitch.c                |      66 +++++++++++++++++++++++++++++++
         libXg/disc.c                        |      23 +++++++++++++++++++++++
         libXg/ellipse.c                     |      23 +++++++++++++++++++++++
         libXg/font.c                        |      18 ++++++++++++++++++
         libXg/gcs.c                         |     401 +++++++++++++++++++++++++++++++
         libXg/getrect.c                     |      73 +++++++++++++++++++++++++++++++
         libXg/gwin.c                        |     466 +++++++++++++++++++++++++++++++
         libXg/latin1.c                      |     313 +++++++++++++++++++++++++++++++
         libXg/ldconvert.c                   |      55 +++++++++++++++++++++++++++++++
         libXg/libgint.h                     |      93 +++++++++++++++++++++++++++++++
         libXg/menuhit.c                     |     236 +++++++++++++++++++++++++++++++
         libXg/point.c                       |      21 +++++++++++++++++++++
         libXg/polysegment.c                 |      27 +++++++++++++++++++++++++++
         libXg/rdbitmap.c                    |      63 +++++++++++++++++++++++++++++++
         libXg/rdbitmapfile.c                |      65 +++++++++++++++++++++++++++++++
         libXg/rectclip.c                    |      25 +++++++++++++++++++++++++
         libXg/rune.c                        |     213 +++++++++++++++++++++++++++++++
         libXg/segment.c                     |      25 +++++++++++++++++++++++++
         libXg/string.c                      |      38 +++++++++++++++++++++++++++++++
         libXg/strwidth.c                    |      23 +++++++++++++++++++++++
         libXg/test.c                        |     237 +++++++++++++++++++++++++++++++
         libXg/texture.c                     |      42 +++++++++++++++++++++++++++++++
         libXg/wrbitmap.c                    |      55 +++++++++++++++++++++++++++++++
         libXg/wrbitmapfile.c                |      50 +++++++++++++++++++++++++++++++
         libXg/xtbinit.c                     |     830 ++++++++++++++++++++++++++++++
         libframe/Makefile                   |      47 +++++++++++++++++++++++++++++++
         libframe/frbox.c                    |     156 +++++++++++++++++++++++++++++++
         libframe/frdelete.c                 |     104 +++++++++++++++++++++++++++++++
         libframe/frdraw.c                   |      59 +++++++++++++++++++++++++++++++
         libframe/frinit.c                   |      42 +++++++++++++++++++++++++++++++
         libframe/frinsert.c                 |     247 +++++++++++++++++++++++++++++++
         libframe/frptofchar.c               |     116 ++++++++++++++++++++++++++++++
         libframe/frselect.c                 |      94 +++++++++++++++++++++++++++++++
         libframe/frstr.c                    |      41 +++++++++++++++++++++++++++++++
         libframe/frutil.c                   |     107 +++++++++++++++++++++++++++++++
         libframe/misc.c                     |      93 +++++++++++++++++++++++++++++++
         rsam/Makefile                       |      18 ++++++++++++++++++
         rsam/rsam.c                         |     147 +++++++++++++++++++++++++++++++
         sam/B.rc                            |      51 +++++++++++++++++++++++++++++++
         sam/B.sh                            |      56 +++++++++++++++++++++++++++++++
         sam/Makefile                        |      89 +++++++++++++++++++++++++++++++
         sam/address.c                       |     241 +++++++++++++++++++++++++++++++
         sam/buffer.c                        |     179 +++++++++++++++++++++++++++++++
         sam/cmd.c                           |     591 +++++++++++++++++++++++++++++++
         sam/disc.c                          |     335 +++++++++++++++++++++++++++++++
         sam/error.c                         |     132 +++++++++++++++++++++++++++++++
         sam/errors.h                        |      63 +++++++++++++++++++++++++++++++
         sam/file.c                          |     465 +++++++++++++++++++++++++++++++
         sam/io.c                            |     257 +++++++++++++++++++++++++++++++
         sam/list.c                          |      48 +++++++++++++++++++++++++++++++
         sam/mesg.c                          |     762 +++++++++++++++++++++++++++++++
         sam/mesg.h                          |     102 +++++++++++++++++++++++++++++++
         sam/moveto.c                        |     168 +++++++++++++++++++++++++++++++
         sam/multi.c                         |      91 +++++++++++++++++++++++++++++++
         sam/parse.h                         |      69 ++++++++++++++++++++++++++++++
         sam/plan9.c                         |     174 +++++++++++++++++++++++++++++++
         sam/rasp.c                          |     276 ++++++++++++++++++++++++++++++
         sam/regexp.c                        |     820 ++++++++++++++++++++++++++++++
         sam/sam.c                           |     682 +++++++++++++++++++++++++++++++
         sam/sam.h                           |     408 +++++++++++++++++++++++++++++++
         sam/samsave                         |      14 ++++++++++++++
         sam/shell.c                         |     155 +++++++++++++++++++++++++++++++
         sam/string.c                        |     179 +++++++++++++++++++++++++++++++
         sam/sys.c                           |      61 +++++++++++++++++++++++++++++++
         sam/unix.c                          |     220 +++++++++++++++++++++++++++++++
         sam/xec.c                           |     492 +++++++++++++++++++++++++++++++
         samterm/Makefile                    |      49 +++++++++++++++++++++++++++++++
         samterm/flayer.c                    |     436 +++++++++++++++++++++++++++++++
         samterm/flayer.h                    |      48 +++++++++++++++++++++++++++++++
         samterm/icons.c                     |      54 +++++++++++++++++++++++++++++++
         samterm/io.c                        |     204 +++++++++++++++++++++++++++++++
         samterm/main.c                      |     592 +++++++++++++++++++++++++++++++
         samterm/menu.c                      |     380 ++++++++++++++++++++++++++++++
         samterm/mesg.c                      |     794 +++++++++++++++++++++++++++++++
         samterm/plan9.c                     |     113 +++++++++++++++++++++++++++++++
         samterm/rasp.c                      |     263 +++++++++++++++++++++++++++++++
         samterm/samterm.h                   |     158 +++++++++++++++++++++++++++++++
         samterm/scroll.c                    |     145 +++++++++++++++++++++++++++++++
         samterm/unix.c                      |     159 +++++++++++++++++++++++++++++++
       
       111 files changed, 29826 insertions(+), 0 deletions(-)
       ---
 (DIR) diff --git a/LICENSE b/LICENSE
       @@ -0,0 +1,18 @@
       +
       +/*
       + * The authors of this software are Rob Pike and Howard Trickey.
       + * Copyright (c) 1998 by Lucent Technologies.
       + *
       + * Rob King made some changes.
       + * Copyright (c) 2015 by Rob King.
       + *
       + * Permission to use, copy, modify, and distribute this software for any
       + * purpose without fee is hereby granted, provided that this entire notice
       + * is included in all copies of any software which is or includes a copy
       + * or modification of this software and in all copies of the supporting
       + * documentation for such software.
       + * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
       + * WARRANTY.  IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY
       + * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
       + * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
       + */
 (DIR) diff --git a/Makefile b/Makefile
       @@ -0,0 +1,40 @@
       +#        Copyright (c) 1998 Lucent Technologies - All rights reserved.
       +#        Changes Copyright (c) 2014-2015 Rob King
       +#
       +#        master makefile for sam.  configure sub-makefiles first.
       +#
       +
       +all:        lXg lframe rsamdir samdir samtermdir docdir
       +
       +lXg:
       +        cd libXg; $(MAKE)
       +lframe:
       +        cd libframe; $(MAKE)
       +
       +docdir:
       +        cd doc; $(MAKE)
       +
       +rsamdir:
       +        cd rsam; $(MAKE)
       +
       +samdir:
       +        cd sam; $(MAKE)
       +
       +samtermdir:
       +        cd samterm; $(MAKE)
       +
       +install:
       +        cd libXg; $(MAKE) install
       +        cd libframe; $(MAKE) install
       +        cd sam; $(MAKE) install
       +        cd samterm; $(MAKE) install
       +        cd doc; $(MAKE) install
       +        cd rsam; $(MAKE) install
       +
       +clean:
       +        cd libXg; $(MAKE) clean
       +        cd libframe; $(MAKE) clean
       +        cd sam; $(MAKE) clean
       +        cd samterm; $(MAKE) clean
       +        cd rsam; $(MAKE) clean
       +
 (DIR) diff --git a/README b/README
       @@ -0,0 +1,118 @@
       +
       + * The authors of this software are Rob Pike and Howard Trickey.
       + *                Copyright (c) 1998 by Lucent Technologies.
       + *
       + * Rob King made some changes.
       + *                Those changes, Copyright (c) 2014-2015 by Rob King.
       + *
       + * Permission to use, copy, modify, and distribute this software for any
       + * purpose without fee is hereby granted, provided that this entire notice
       + * is included in all copies of any software which is or includes a copy
       + * or modification of this software and in all copies of the supporting
       + * documentation for such software.
       + * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
       + * WARRANTY.  IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY
       + * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
       + * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
       +
       +This is an X11 version of Rob Pike's editor, sam.  Documentation describing
       +its use and construction are provided in subdirectory doc.  The file
       +doc/sam.1 contains the manual page; doc/sam.1.pdf is a PDF version of that
       +page. doc/sam.tut.ms is a tutorial that can be formatted with troff -ms.
       +It substitutes Bold and Italics for the fonts named CW and CS; if your system
       +has these fonts, remove the CW and CS macros at the beginning of the file. The
       +files doc/sam.ps and doc/se.ps are postscript versions of published papers
       +describing sam and structural regular expressions.  These papers reflect
       +sam's capabilities at the time of publication several years ago; while the
       +general description remains accurate, some functions may have changed or been
       +removed. The file doc/keyboard is an example of a Unicode input configuration
       +file; it serves as a good starting point for a customized version (see the
       +manual page for more information).
       +
       +Sam is composed of three programs: sam itself, which does the command
       +processing and file manipulation; and samterm, which controls the display
       +and interacts with the user.  You can run sam on one machine and samterm on
       +another connected via remote execution. A third program, rsam, is automatically
       +interpolated between the terminal and the remote instance of sam by default
       +to allow an additional control channel on the remote machine.
       +
       +This version of sam is based on the Plan 9 implementation.  Its
       +design and expression reflect the functionality of the Plan 9 environment;
       +most notably, characters are represented internally by 16-bit values called
       +Runes.  Header files include/u.h and include/libc.h and source files
       +libframe/misc.c, libXg/rune.c, sam/unix.c and samterm/unix.c contain
       +code that insinuates sam into the Unix world.  Two other files,
       +sam/plan9.c and samterm/plan9.c, contain Plan 9-specific code; they
       +are not used in the Unix version of sam and are provided as examples
       +of the Plan 9 interface.
       +
       +The typedefs for uchar, ushort, and ulong defined in include/u.h, may conflict
       +with exisiting definitions in include file <sys/types.h> on some systems.
       +If this occurs, remove the offending definitions from include/u.h.
       +
       +The distribution consists of several directories:
       +
       +        sam - The source for sam.  It loads against libXg to pick
       +                up some utility routines.
       +
       +        samterm - The source for samterm.  It loads against libframe,
       +                libXg, and your local X11 libraries.
       +
       +        rsam - The source for rsam.
       +
       +        libframe - The source for the frame library.  This library is used
       +                by samterm so it must be made first.
       +        
       +        libXg - The source code of the graphics library and some general
       +                utility modules.  Header file u.h provides much of the
       +                interface between sam and the local environment.  It is
       +                included in every source file.  Sam's graphics
       +                operations are implemented by Plan 9 libg functions.  This
       +                library emulates those functions using X11 operations.
       +
       +        include - header files.
       +
       +        doc - The documentation for sam.
       +
       +Each source directory contains a makefile.
       +The master makefile in the top directory builds the subdirectories in
       +proper order.
       +
       +Most customization effort is confined to configuring the makefiles.
       +They are configured by editing config.mk; see that file for details.
       +
       +After configuring the makefiles, change to the top-level directory and
       +type "make". Typing "make install" installs B, sam, samterm and samsave in
       +their permanent homes as well as their on-line documentation.
       +
       +During testing, the path of samterm may be specified using the -t command line
       +option to sam.  Similarly, the path of sam itself may be specified using the
       +-s command line option; this is handy for testing the remote execution feature.
       +
       +The script doc/B is a Bourne shell-compatible script that sends a 'B' command
       +to a running instance of sam.
       +
       +The original protocol between sam and samterm assumed that memory addresses
       +and longs were 32 bits.  Dave Hanson removed this dependency from the
       +protocol allowing sam to run on 64-bit processors.  However, other
       +dependencies on structure alignment remain. The USE64BITS configuration
       +variable in config.mk can be set to cope with 64-bit systems.
       +
       +Note that sam seems to have some issues when running under a compositing window manager.
       +
       +Rob Pike designed and implemented the original Unix version of sam and
       +the current version ported from Plan 9.  Howard Trickey provided the X
       +version of the graphics library, libXg.  Matty Farrow and his colleagues
       +at the University of Sydney extended libXg to support Runes.  Boyd Roberts
       +supplied the external command interface and the shell scripts for the 'B'
       +command. Doug Gwyn contributed many useful ideas to the X implementation of
       +sam. James Clark provided the simulations of the V10 Unix Man macros at the
       +beginning of the original manual pages. Rob King modified sam to support
       +scalable fonts, environment variable-based configration, the ability to
       +control remote sam processes via the B script on remote machines, dynamic
       +key substitutions from a configuration file, handle 64-bit architectures
       +better, and to compile cleanly on modern Linux systems.
       +
       +Please send bug fixes and comments to:
       +
       +Rob King, jking@deadpixi.com
 (DIR) diff --git a/config.mk b/config.mk
       @@ -0,0 +1,23 @@
       +# config.mk - makefile configuration for sam
       +# copyright 2015 Rob King <jking@deadpixi.com>
       +
       +# DESTDIR is the root of the installation tree
       +DESTDIR=/usr/local
       +
       +# BINDIR is the directory where binaries go
       +BINDIR=$(DESTDIR)/bin
       +
       +# MANDIR is where manual pages go
       +MANDIR=$(DESTDIR)/share/man/man1
       +
       +# USE64BITS should be 1 for little-endian architectures with 64-bit pointers,
       +# 2 for big-endian architectures with 64-bit pointers, and 0 otherwisew.
       +# x86_64 systems would generally use 1 here, while DEC Alpha systems would
       +# generally use 2.
       +USE64BITS=1
       +
       +# FREETYPEINC should name the directory of your freetype2 includes.
       +FREETYPEINC=/usr/include/freetype2
       +
       +# TMPDIR should be set to a directory for temporary files with lots of room
       +TMPDIR=/tmp
 (DIR) diff --git a/doc/B b/doc/B
       @@ -0,0 +1,43 @@
       +#!/bin/sh
       +
       +if [ $# = 0 ] ; then
       +        echo "usage: B [-r machine] files..." 1>&2
       +        exit 1
       +fi
       +
       +machine=localhost
       +if [ $1 = "-r" ] ; then
       +        shift
       +        machine=$1
       +        shift
       +
       +        if [ "$machine" = "" ] ; then
       +                echo "usage: B [-r machine] files..." 1>&2
       +                exit 1
       +        fi
       +        echo "machine = $machine"
       +fi
       +
       +pipe="${HOME}/.sam.${machine}"
       +dir=`/bin/pwd`
       +files=
       +for i in $*
       +do
       +        case "$i" in
       +                /*)        files="$files $i"
       +                        ;;
       +                *)        files="$files $dir/$i"
       +                        ;;
       +        esac
       +done
       +
       +if [ ! -w "$pipe" ] ; then
       +  pipe="${HOME}/.sam.fifo" # created by rsam
       +fi
       +
       +if [ ! -w "$pipe" ]; then
       +    sam $files & # start sam if it's not already running
       +else
       +    echo "B $files" >> $pipe
       +fi
       +
 (DIR) diff --git a/doc/Makefile b/doc/Makefile
       @@ -0,0 +1,18 @@
       +# Copyright 2014-2014 Rob King <jking@deadpixi.com>
       +
       +include ../config.mk
       +
       +all: sam.1.pdf
       +
       +sam.1.pdf: sam.1
       +        tbl $< | groff -mdoc -Tpdf > $@
       +
       +install: sam.1 B
       +        cp sam.1 "$(MANDIR)"
       +        cp B "$(BINDIR)"
       +        ln -sf "$(MANDIR)/sam.1" "$(MANDIR)/B.1"
       +        ln -sf "$(MANDIR)/sam.1" "$(MANDIR)/samterm.1"
       +        ln -sf "$(MANDIR)/sam.1" "$(MANDIR)/rsam.1"
       +        ln -sf "$(MANDIR)/sam.1" "$(MANDIR)/sam.save.1"
       +        ln -sf "$(MANDIR)/sam.1" "$(MANDIR)/samsave.1"
       +
 (DIR) diff --git a/doc/keyboard b/doc/keyboard
       @@ -0,0 +1,206 @@
       +!! 0xa1
       +c$ 0xa2
       +l$ 0xa3
       +g$ 0xa4
       +y$ 0xa5
       +|| 0xa6
       +SS 0xa7
       +"" 0xa8
       +cO 0xa9
       +sa 0xaa
       +<< 0xab
       +no 0xac
       +-- 0xad
       +rO 0xae
       +__ 0xaf
       +de 0xb0
       ++- 0xb1
       +s2 0xb2
       +s3 0xb3
       +'' 0xb4
       +mi 0xb5
       +pg 0xb6
       +.. 0xb7
       +,, 0xb8
       +s1 0xb9
       +so 0xba
       +>> 0xbb
       +14 0xbc
       +12 0xbd
       +34 0xbe
       +?? 0xbf
       +`A 0xc0
       +'A 0xc1
       +^A 0xc2
       +~A 0xc3
       +"A 0xc4
       +oA 0xc5
       +AE 0xc6
       +,C 0xc7
       +`E 0xc8
       +'E 0xc9
       +^E 0xca
       +"E 0xcb
       +`I 0xcc
       +'I 0xcd
       +^I 0xce
       +"I 0xcf
       +D- 0xd0
       +~N 0xd1
       +`O 0xd2
       +'O 0xd3
       +^O 0xd4
       +~O 0xd5
       +"O 0xd6
       +mu 0xd7
       +/O 0xd8
       +`U 0xd9
       +'U 0xda
       +^U 0xdb
       +"U 0xdc
       +'Y 0xdd
       +|P 0xde
       +ss 0xdf
       +`a 0xe0
       +'a 0xe1
       +^a 0xe2
       +~a 0xe3
       +"a 0xe4
       +oa 0xe5
       +ae 0xe6
       +,c 0xe7
       +`e 0xe8
       +'e 0xe9
       +^e 0xea
       +"e 0xeb
       +`i 0xec
       +'i 0xed
       +^i 0xee
       +"i 0xef
       +d- 0xf0
       +~n 0xf1
       +`o 0xf2
       +'o 0xf3
       +^o 0xf4
       +~o 0xf5
       +"o 0xf6
       +-: 0xf7
       +/o 0xf8
       +`u 0xf9
       +'u 0xfa
       +^u 0xfb
       +"u 0xfc
       +'y 0xfd
       +|p 0xfe
       +"y 0xff
       +wk 0x2654
       +wq 0x2655
       +wr 0x2656
       +wb 0x2657
       +wn 0x2658
       +wp 0x2659
       +bk 0x265a
       +bq 0x265b
       +br 0x265c
       +bb 0x265d
       +bn 0x265e
       +bp 0x265f
       +*a 0x3b1        Greek letter alpha
       +*b 0x3b2
       +*g 0x3b3
       +*d 0x3b4
       +*e 0x3b5
       +*z 0x3b6
       +*y 0x3b7
       +*h 0x3b8
       +*i 0x3b9
       +*k 0x3ba
       +*l 0x3bb
       +*m 0x3bc
       +*n 0x3bd
       +*c 0x3be
       +*o 0x3bf
       +*p 0x3c0
       +*r 0x3c1
       +ts 0x3c2
       +*s 0x3c3
       +*t 0x3c4
       +*u 0x3c5
       +*f 0x3c6
       +*x 0x3c7
       +*q 0x3c8
       +*w 0x3c9
       +*A 0x391
       +*B 0x392
       +*G 0x393
       +*D 0x394
       +*E 0x395
       +*Z 0x396
       +*Y 0x397
       +*H 0x398
       +*I 0x399
       +*K 0x39a
       +*L 0x39b
       +*M 0x39c
       +*N 0x39d
       +*C 0x39e
       +*O 0x39f
       +*P 0x3a0
       +*R 0x3a1
       +*S 0x3a3
       +*T 0x3a4
       +*U 0x3a5
       +*F 0x3a6
       +*X 0x3a7
       +*Q 0x3a8
       +*W 0x3a9
       +<- 0x2190
       +ua 0x2191
       +-> 0x2192
       +da 0x2193
       +ab 0x2194
       +V= 0x21d0
       +=V 0x21d2
       +fa 0x2200
       +te 0x2203
       +pd 0x2202
       +es 0x2205
       +De 0x2206
       +gr 0x2207
       +mo 0x2208
       +!m 0x2209
       +st 0x220d
       +** 0x2217
       +bu 0x2219
       +sr 0x221a
       +pt 0x221d
       +if 0x221e
       +an 0x2220
       +l& 0x2227
       +l| 0x2228
       +ca 0x2229
       +cu 0x222a
       +is 0x222b
       +tf 0x2234
       +~= 0x2243
       +cg 0x2245
       +~~ 0x2248
       +!= 0x2260
       +== 0x2261
       +<= 0x2266
       +>= 0x2267
       +sb 0x2282
       +sp 0x2283
       +!b 0x2284
       +ib 0x2286
       +ip 0x2287
       +O+ 0x2295
       +O- 0x2296
       +Ox 0x2297
       +tu 0x22a2
       +Tu 0x22a8
       +lz 0x22c4        Lozenge
       +el 0x22ef        Elipsis
       +:( 0x2639        Frowny
       +:) 0x263a        Smiley
       +;) 0x263b        Dark smiley
 (DIR) diff --git a/doc/sam.1 b/doc/sam.1
       @@ -0,0 +1,1026 @@
       +.Dd $Mdocdate$
       +.Dt SAM 1
       +.Os
       +.Sh NAME
       +.Nm sam
       +.Nd screen editor with structural regular expressions
       +.Sh SYNOPSIS
       +.Nm
       +.Op Fl d
       +.Op Fl t Ar terminal
       +.Ar
       +.Nm
       +.Fl r Ar machine
       +.Op Fl s Ar file
       +.Op Fl t Ar terminal
       +.Ar
       +.Nm sam.save
       +.Nm B
       +.Op Fl r Ar machine
       +.Ar
       +.Sh DESCRIPTION
       +.Nm sam
       +is a multi-file editor.
       +It modifies a local copy of an external file.
       +The copy is here called a
       +.Em file "."
       +The files are listed in a menu available through mouse button 3 or the
       +.Li n
       +command.
       +Each file has an associated name, usually the name of the external file from which it was read, and a
       +.Dq modified
       +bit that indicates whether the editor's file agrees with the external file.
       +The external file is not read into the editor's file until it first becomes the current file \[en] that file to which editing commands apply \[en] whereupon its menu entry is printed.
       +The options are
       +.Bl -tag -width Ds
       +.It Fl d
       +Do not download the terminal part of
       +.Nm "."
       +Editing will be done with the command language only, as in
       +.Xr ed 1 "."
       +.It Fl r Ar machine
       +Run the host part remotely on the specified machine, the terminal part locally, or cause commands to be sent to an instance of
       +.Nm
       +associated with
       +.Ar machine "."
       +.It Fl s Ar file
       +Start the host part from the indicated file on the remote host.
       +By default, this is
       +.Pa rsam ","
       +which is a program that interposes itself between the host and terminal parts of
       +.Nm
       +and allows
       +.Nm
       +to be controlled via commands on the remote system.
       +.It Fl t Ar file
       +Start the terminal part from the indicated file.
       +Useful for debugging.
       +.El
       +.Ss Regular expressions
       +Regular expressions are more-or-less as they are in
       +.Xr regex 7 ","
       +with the addition of
       +.Li \[rs]n
       +to represent newlines.
       +A regular expression may never contain a literal newline character.
       +The elements of regular expressions are:
       +.Bl -tag -width Ds
       +.It Li "."
       +Match any character except newline.
       +.It Li \[rs]n
       +Match newline.
       +.It Li \[rs]x
       +For any character except
       +.Li n
       +match the character
       +.Po
       +here
       +.Sy x
       +.Pc "."
       +.It Li "[abc]"
       +Match any character in the square brackets.
       +.Li \[rs]n
       +may be mentioned.
       +.It Li "[^abc]"
       +Match any character not in the square brackets, but never a newline.
       +Both this and the positive form above accept a range of ASCII characters indicated by a dash, as in
       +.Li "a-z" "."
       +.It Li "^"
       +Match the null string immediately after a newline.
       +.It Li "$"
       +Match the null string immediately before a newline.
       +.El
       +.Pp
       +Any other character except newline matches itself.
       +.Pp
       +In the following,
       +.Sy r1
       +and
       +.Sy r2
       +are regular expressions.
       +.Bl -tag -width Ds
       +.It Pq Sy r1
       +Match what
       +.Sy r1
       +matches.
       +.It Sy r1|r2
       +Match what
       +.Sy r1
       +or
       +.Sy r2
       +matches.
       +.It Sy r1*
       +Match zero or more adjacent matches of
       +.Sy r1 "."
       +.It Sy r1+
       +Match one or more adjacent matches of
       +.Sy r1 "."
       +.It Sy "r1?"
       +Match zero or one matches of
       +.Sy r1 "."
       +.El
       +.Pp
       +The operators
       +.Li "*" ","
       +.Li "+" ","
       +and
       +.Li "?"
       +are highest precedence, then catenation, then
       +.Li "|"
       +is lowest.
       +The empty regular expression stands for the last complete expression encountered.
       +A regular expression in
       +.Nm
       +matches the longest leftmost substring formally matched by the expression.
       +Searching in the reverse direction is equivalent to search backwards with the catenation operations reversed in the expression.
       +.Ss Addresses
       +An address identifies a substring in a file.
       +In the following
       +.Do
       +character
       +.Sy n
       +.Dc
       +means the null string after the
       +.Sy n\fR-th
       +character in the file, with 1 the first character in the file.
       +.Do
       +Line
       +.Sy n
       +.Dc
       +means the
       +.Sy n\fR-th
       +match, starting at the beginning of the file, of the regular expression
       +.Li ".*\[rs]n?" "."
       +.Po
       +The peculiar properties of a last line without a newline are temporarily undefined.
       +.Pc
       +All files always have a current substring, called
       +.Sy dot ","
       +that is the default address.
       +.Ss Simple addresses
       +.Bl -tag -width Ds
       +.It Li # Sy n
       +The empty string after character
       +.Sy n ";"
       +.Li #0
       +is the beginning of the file.
       +.It Sy n
       +Line
       +.Sy n "."
       +.It Li / Sy regexp Li /
       +.It Li ? Sy regexp Li ?
       +The substring that matches the regular expression, found by looking toward the end
       +.Pq Li /
       +or beginning
       +.Pq Li ?
       +of the file, and if necessary continuing the search from the other end to the starting point of the search.
       +The matched substring may straddle the starting point.
       +When entering a pattern containing a literal question mark for a backward search, the question mark should be specified as a member of a class.
       +.It Li 0
       +The string before the first full line.
       +This is not necessarily the null string; see
       +.Li +
       +and
       +.Li -
       +below.
       +.It Li $
       +The null string at the end of the file.
       +.It Li "."
       +Dot.
       +.It Li "'"
       +The mark in the file
       +.Po
       +see the
       +.Sy k
       +command below
       +.Pc "."
       +.It Sy "regexp"
       +.Do
       +A regular expression in double quotes.
       +.Dc
       +Preceding a simple address
       +.Do
       +default
       +.Li "."
       +.Dc ","
       +refers to the address evaluated in the unique file whose menu line matches the regular expression.
       +.El
       +.Ss Compound addresses
       +In the following,
       +.Sy a1
       +and
       +.Sy a2
       +are addresses.
       +.Bl -tag -width Ds
       +.It Sy a1+a2
       +The address
       +.Sy a2
       +evaulated starting at the end of
       +.Sy a1 "."
       +.It Sy a1-a2
       +The address
       +.Sy a2
       +evaluated looking the reverse direction starting at the beginning of
       +.Sy a1 "."
       +.It Sy "a1,a2"
       +The substring from the beinning of
       +.Sy a1
       +to the end of
       +.Sy a2 "."
       +If
       +.Sy a1
       +is missing,
       +.Li 0
       +is substituted.
       +If
       +.Sy a2
       +is missing,
       +.Li $
       +is substituted.
       +.It Sy a1;a2
       +Like
       +.Dq Sy a1,a2
       +but with
       +.Sy a2
       +evaluated at the end of, and dot set to,
       +.Sy a1 "."
       +.El
       +.Pp
       +The operators
       +.Li +
       +and
       +.Li -
       +are high precedence, while
       +.Li ,
       +and
       +.Li ;
       +are low precedence.
       +.Pp
       +In both
       +.Li +
       +and
       +.Li -
       +forms, if
       +.Sy a2
       +is a line or character address with a missing number, the number defaults to 1.
       +If
       +.Sy a1
       +is missing,
       +.Li "."
       +is subtituted.
       +If both
       +.Sy a1
       +and
       +.Sy a2
       +are present and distinguishable,
       +.Li +
       +may be elided.
       +.Sy a2
       +may be a regular expression; if it is delimited by
       +.Li "?"
       +characters, the effect of the
       +.Li +
       +or
       +.Li -
       +is reversed.
       +.Pp
       +It is an error for a compound address to represent a malformed substring.
       +.Pp
       +Some useful idioms:
       +.Bl -tag -width Ds
       +.It Sy a1+- Po Sy a1-+ Pc
       +selects the line containing the end
       +.Dq beginning
       +of
       +.Sy a1 "."
       +.It Sy 0/regexp/
       +locates the first match of the expression in the file.
       +.Do
       +The form
       +.Li 0;//
       +sets dot unnecessarily.
       +.Dc
       +.It Sy "./regexp///"
       +find the second following occurence of the expression, and
       +.Sy ".,/regexp/"
       +extends dot.
       +.El
       +.Ss Commands
       +In the following, text demarcated by slashes represnets text delimited by any printable ASCII character except alphanumerics.
       +Any number of trailing delimiters may be elided, with multiple elisions then representing null strings, but the first delimiter must always be present.
       +In any delimited text, newline may not appear literally;
       +.Li \[rs]n
       +may be typed for newline; and
       +.Li \[rs]/
       +quotes the delimiter, here
       +.Li / "."
       +Backslash is other interpreted literally, except in
       +.Sy s
       +commands.
       +.Pp
       +Most commands may be prefixed with an address to indicate their range of operation.
       +Those that may not are marked with a
       +.Sy "*"
       +below.
       +If a command takes an address and none is supplised, dot is used.
       +The sole exception is the
       +.Sy w
       +command, which defaults to
       +.Li "0,$" "."
       +In the description,
       +.Dq range
       +is used to represent whatever address is supplied.
       +Many commands set the value of dot as a side effect.
       +If so, it is always to the
       +.Dq result
       +of the change: the empty string for a deletion, the new text for an insertion, etc.
       +.Po
       +but see the
       +.Sy s
       +and
       +.Sy e
       +commands
       +.Pc "."
       +.Ss Text commands
       +.Bl -tag -width Ds
       +.It Sy a/text/
       +Insert the text into the file after the range.
       +Set dot.
       +.Pp
       +May also be written as
       +.Bd -literal -offset indent
       + a
       + lines
       + of
       + text
       + .
       +.Ed
       +.It Sy c \fR or Sy i
       +Same as
       +.Sy a ","
       +but
       +.Sy c
       +replaces the text, while
       +.Sy i
       +inserts
       +.Em before
       +the range.
       +.It Sy d
       +Delete the text in range.
       +Set dot.
       +.It Sy s/regexp/text/
       +Substitute
       +.Sy text
       +for the first match to the regular expression in the range.
       +Set dot to the modified range.
       +In
       +.Sy text
       +the character
       +.Li "&"
       +stands for the string that matched the expression.
       +Backslash behaves as usual unless followed by a digit:
       +.Sy \[rs]d
       +stands for the string that matched the subexpression begun by the
       +.Sy d\fR-th
       +left parenthesis.
       +If
       +.Sy s
       +is followed immediately by a number
       +.Sy n ","
       +as in
       +.Li "s2/x/y/" ","
       +the
       +.Sy n\fR-th
       +match in the range is substituted.
       +If the command is followed by
       +.Sy g ","
       +as in
       +.Li "s/x/y/g" ","
       +all matches in the range are substituted.
       +.It Sy "m a1"
       +Move the range to after
       +.Sy a1 "."
       +Set dot.
       +.It Sy "t a1"
       +Copy the range to after
       +.Sy a1 "."
       +Set dot.
       +.El
       +.Ss Display commands
       +.Bl -tag -width Ds
       +.It Sy p
       +Print the text in the range.
       +Set dot.
       +.It Sy =
       +Print the line address and character address of the range.
       +.It Sy =#
       +Print just the character address of the range.
       +.El
       +.Ss File commands.
       +.Bl -tag -width Ds
       +.It * Sy "b file-list"
       +Set the current file to the first file named in the list that
       +.Nm
       +also has in its menu.
       +The list may be expressed
       +.Sy "<shell-command"
       +in which case the file names are taken as words
       +.Pq "in the shell sense"
       +generated by the shell command.
       +.It * Sy "B file-list"
       +Same as
       +.Sy b ","
       +except that filenames not in the menu are entered there, and all file names in the list are examined.
       +.It * Sy n
       +Print a menu of files.
       +The format is:
       +.Bl -tag -width Ds
       +.It "' or blank"
       +indicating the file is modified or clean,
       +.It "- or +"
       +indicating the file is unread or has been read
       +.Po
       +in the terminal,
       +.Li "*"
       +means more than one window is open
       +.Pc ","
       +.It ". or blank"
       +indicating the current file,
       +.El
       +a blank,
       +and the filename.
       +.It "*" Sy "D file-list"
       +Delete the named files from the menu.
       +If no files are named, the current file is deleted.
       +It is an error to delete a modified file, but a subsequent
       +.Sy D
       +will delete such a file.
       +.El
       +.Ss I/O commands
       +.Bl -tag -width Ds
       +.It "*" Sy "e filename"
       +Replace the file by the contents of the named external file.
       +Set dot to the beginning of the file.
       +.It Sy "r filename"
       +Replace the text in the range by the contents of the named external file.
       +Set dot.
       +.It Sy "w filename"
       +Write the range
       +.Po
       +default
       +.Li 0,$
       +.Pc
       +to the named external file.
       +.It "*" Sy "f filename"
       +Set the file name and print the resulting menu entry.
       +.El
       +.Pp
       +If the file name argument is absent from any of these, the current file name is used.
       +.Sy e
       +always sets the file name,
       +.Sy r
       +and
       +.Sy w
       +will do so if the file has no name.
       +.Bl -tag -width Ds
       +.It Sy "< shell-command"
       +Replace the range by the standard output of the shell command.
       +.It Sy "> shell-command"
       +Sends the range to the standard input of the shell command.
       +.It Sy "| shell-command"
       +Send the range to the standard input, and replace it by the standard output, of the shell command.
       +.It "*" Sy "! shell-command"
       +Run the shell command.
       +.It "*" Sy "cd directory"
       +Change working directory.
       +If no directory is specified,
       +.Ev "$HOME"
       +is used.
       +.El
       +.Pp
       +In any of
       +.Sy "<" ","
       +.Sy ">" ","
       +.Sy "|" ", or"
       +.Sy "!" ","
       +if the shell command is omitted, the last shell command
       +.Pq "of any type"
       +is substituted.
       +If
       +.Nm
       +is downloaded,
       +.Sy "!"
       +sets standard input to
       +.Pa "/dev/null" ","
       +and otherwise unassigned output
       +.Po
       +.Pa stdout
       +for
       +.Sy "!"
       +and
       +.Sy ">" ","
       +.Pa stderr
       +for all
       +.Pc
       +is placed in
       +.Pa "${HOME}/sam.err"
       +and the first few lines are printed.
       +.Ss Loops and conditionals
       +.Bl -tag -width Ds
       +.It Sy "x/regexp/ command"
       +For each match of the regular expression in the range, run the command with dot set to the match.
       +Set dot to the last match.
       +If the regular expression and its slashes are omitted,
       +.Li "/.*\[rs]n/"
       +is assumed.
       +Null string matches potentially occur before every character of the range and at the end of the range.
       +.It Sy "y/regexp/ command"
       +Like
       +.Sy x ","
       +but run the command for each substring that lies before, between, or after the matches that would be generated by
       +.Sy x "."
       +There is no default behavior.
       +Null substrings potentially occur before every character in the range.
       +.It "*" Sy "X/regexp/ command"
       +For each file whose menu entry matches the regular expression, make that the current file and run the command.
       +If the expression is omitted, the command is run in every file.
       +.It "*" Sy "Y/regexp/ command"
       +Same as
       +.Sy X ","
       +but for files that do not match the regular expression, and the expression is required.
       +.It Sy "g/regexp/ command"
       +.It Sy "v/regexp/ command"
       +If the range contains
       +.Po
       +.Sy g
       +.Pc
       +or does not contain
       +.Po
       +.Sy v
       +.Pc
       +a match for the expression, set dot to the range and run the command.
       +.El
       +.Pp
       +These may be nested arbitrarily deeply, but only one instance of either
       +.Sy X
       +or
       +.Sy Y
       +may appear in a single command.
       +An empty command in an
       +.Sy x
       +or
       +.Sy y
       +defaults to
       +.Sy p ";"
       +an empty command in
       +.Sy X
       +or
       +.Sy Y
       +defaults to
       +.Sy f "."
       +.Sy g
       +and
       +.Sy v
       +do not have defaults.
       +.Ss Miscellany
       +.Bl -tag -width Ds
       +.It Sy k
       +Set the current file's mark to the range.
       +Does not set dot.
       +.It "*" Sy q
       +Quit.
       +It is an error to quit with modified files, but a second
       +.Sy q
       +will succeed.
       +.It "*" Sy "u n"
       +Undo the last
       +.Sy n
       +.Pq "default 1"
       +top-level commands that changed the contents or name of the current file, and any other file whose most recent change was simultaneous with the current file's change.
       +Successive
       +.Sy u
       +commands move further back in time.
       +The only commands for which
       +.Sy u
       +is ineffective are
       +.Sy cd ","
       +.Sy u ","
       +.Sy q ","
       +.Sy w ","
       +and
       +.Sy D "."
       +.It Sy empty
       +If the range is explicit, set dot to the range.
       +If
       +.Nm
       +is downloaded, the resulting dot is selected on the screen; otherwise it is printed.
       +If no address is specified
       +.Pq "the command is a newline"
       +dot is extended in either direction to the line boundaries and printed.
       +If dot is thereby unchanges, i is set to
       +.Li ".+1"
       +and printed.
       +.El
       +.Ss Grouping and multiple changes
       +Commands may be groups by enclosing them in curly braces.
       +Commands within the braces must appear on separate lines
       +.Pq "no backslashes are required between commands" "."
       +Semantically, the opening brance is like a command: it takes an
       +.Pq optional
       +address and sets dot for each sub-command.
       +Commands within the braces are executed sequentially, but changes made by one command are not visible to other commands
       +.Pq "see the next paragraph" "."
       +Braces may be nested arbitrarily.
       +.Pp
       +When a command makes a number of changes to a file, as in
       +.Li "x/re/c/text/" ","
       +the addresses of all changes to the file are computed in the original file.
       +If the changes are in sequence, they are applied to the file.
       +Successive insertions at the same address are catenated into a single insertion composed of the several insertions in the order applied.
       +.Ss The terminal
       +What follows refers to the behavior of
       +.Nm
       +when downloaded, that is, when operating as a display editor on a bitmap display.
       +This is the default behavior; invoking
       +.Nm
       +with the
       +.Fl d
       +.Pq "no download"
       +option provides access to the command language only.
       +.Pp
       +Each file may have zero or more windows open.
       +Each window is equivalent and is updated simultaneously with changes in other windows on the same file.
       +Each window has an independent value of dot, indicated by a highlighted substring on the display.
       +Dot may be in a region not within the window.
       +There is usually a
       +.Dq "current window" ","
       +marked with a dark border, to which typed text and editing commands apply.
       +The escape key selects
       +.Pq "sets dot to"
       +text typed since the last mouse button hit.
       +.Pp
       +The button 3 menu controls window operations.
       +The top of the menu provides the following operators, each of which uses one or more cursors to prompt for selection of a window or sweeping of a rectangle.
       +.Bl -tag -width Ds
       +.It Sy new
       +Create a new empty file:
       +Depress button 3 where one corner of the new rectangle should appear
       +.Pq "box cursor" ","
       +and move the mouse while holding down button 3 to the diagonally opposite corner.
       +.Dq Sweeping
       +a null rectangle gets a large window disjoint from the command window or the whole
       +.Nm
       +window, depending on where the null rectangle is.
       +.It Sy zerox
       +Create a copy of an existing window.
       +After selecting the window to copy with button 1,
       +sweep out the window for the copy.
       +.It Sy reshape
       +Change the size and location of a window.
       +First click button 3 in the window to be changed
       +.Pq "gunsight cursor" "."
       +Then sweep out a window as for the
       +.Sy new
       +menu selection.
       +.It Sy close
       +Delete the window.
       +In the last window of a file,
       +.Sy close
       +is equivalent to a
       +.Sy D
       +for the file.
       +.It Sy write
       +Equivalent to a
       +.Sy w
       +for the file.
       +.El
       +.Pp
       +Below these operators is a list of available files, starting with
       +.Sy "~~sam~~" ","
       +the command window.
       +Selecting a file from the list makes the most recently used window on that file current, unless it is already current, in which case selections cycle through the open windows.
       +If no windows are open on the file, the user is prompted to open one.
       +Files other than
       +.Sy "~~sam~~"
       +are marked with one of the characters
       +.Li "-+*"
       +according as zero, one, or more windows are open on the file.
       +A further mark,
       +.Li "." ","
       +appears on the file in the current window and a single quote,
       +.Li "'" ","
       +on a file modified since last write.
       +.Pp
       +The command window, created automatically when
       +.Nm
       +starts, is an ordinary window except that text typed to it is interpreted as commands for the editor rather than passive text, and text printed by editor commands appears in it.
       +There is an
       +.Dq "output point"
       +that separates commands being typed from previous output.
       +Commands typed in the command window apply to the current open file\[en]the file in the most recently current window.
       +.Ss Manipulating text
       +Typed characters replace the current selection
       +.Pq dot
       +in the current window.
       +Backspace deletes the previous character.
       +Escape selects
       +.Pq "sets dot to"
       +everything typed since the last mouse hit.
       +.Pp
       +Button 1 changes selection.
       +Pointing to a non-current window with button 1 makes it current; within the current window, button 1 selects text, thus setting dot.
       +Double-clicking selects text to the boundaries of words, lines, quoted strings, or bracketed strings, depending on the text at the click.
       +.Pp
       +Button 2 provides a menu of editing commands:
       +.Bl -tag -width Ds
       +.It Sy cut
       +Delete dot and save the delted text in the snarf buffer.
       +.It Sy paste
       +Replace the text in dot by the contents of the snarf buffer.
       +.It Sy snarf
       +Save the text in dot in the snarf buffer.
       +.It Sy look
       +Search forward for the next occurence of the literal text in dot.
       +If dot is the null string, the text in the snarf buffer is used.
       +The snarf buffer is unaffected.
       +.It Sy <exch>
       +Exchange the snarf buffer with the current system-wide text selection.
       +The exchange of a large amount of selected text is truncated to the size of the editor's internal snarf buffer
       +.Pq "currently 4K"
       +without warning.
       +.It Sy "/regexp"
       +Search forward for the next match of the last regular expression typed in a command.
       +.Pq "Not in command window."
       +.It Sy send
       +Send the text in dot, or the snarf buffer if dot is the null string, as if it were typed to the command window.
       +Saves the sent text in the snarf buffer.
       +.Pq "Command window only."
       +.El
       +.Ss Abnormal termination
       +If
       +.Nm
       +terminates other than by a
       +.Sy q
       +command
       +.Pq "by hangup, deleting its window, etc." ","
       +modified files are saved in an executable file,
       +.Pq "${HOME}/sam.save" "."
       +This program, when executed, asks whether to write each file back to an external file.
       +The answer
       +.Sy y
       +causes writing; anything else skips the file.
       +If a machine crash prevents the creation of a
       +.P "sam.save"
       +file, all changes are lost.
       +If an editing session is difficult to replicate, writing changed files often is recommended.
       +.Ss Remote execution
       +.Nm sam
       +allows the host and terminal parts of the editor to run on diffrent machines, in a process called
       +.Dq downloading "."
       +This process can be suppressed with the
       +.Fl d
       +option, which then runs only the host part in the manner of
       +.Xr ed 1 "."
       +.Pp
       +Running the host part on another machine is accomplished using the
       +.Fl r
       +option, which is used to specify a remote machine name suitable for passing to the remote shell command specified in the
       +.Ev RSH
       +environment variable.
       +.Pp
       +By default,
       +.Nm sam
       +will run a command called
       +.Nm rsam
       +as the host-part on the remote machine.
       +.Nm rsam
       +opens up an additional control channel on the remote machine, allowing
       +.Nm sam
       +to be controlled via the
       +.Nm B
       +command on the remote machine as well.
       +.Pp
       +The only components of
       +.Nm sam
       +that need to be on the remote machine are
       +.Nm rsam
       +and
       +.Nm sam ","
       +and any command specified as the argument to the
       +.Fl s
       +option.
       +Users may also like to have the
       +.Nm B
       +command present on the remote system; invoking this command on the remote system will
       +.Po
       +if
       +.Nm sam
       +was invoked with its default remote host command, i.e.
       +.Nm rsam
       +.Pc
       +open files in the local terminal.
       +This allows users to run the terminal part of
       +.Nm sam
       +locally while controlling it via a remote shell connection.
       +.Ss Controlling running instances of Nm
       +.Nm B
       +is a shell command that causes a downloaded instance of
       +.Nm sam
       +to load the named files.
       +The
       +.Fl r
       +option causes the instance of
       +.Nm sam
       +connected to
       +.Ar machine
       +to load the named files; the default is the most-recently started local instance.
       +.Pp
       +.Nm B
       +may also be called on a remote machine, causing the downloaded instance of sam connected to that machine to load the named files.
       +.Ss Unicode Text Input
       +.Nm sam
       +allows the input of arbitrary Unicode characters from the Basic Multilingual Plane
       +.Pq BMP
       +via five-character and two-character sequences.
       +These sequences are entered while holding down the system compose key
       +.Po
       +on most keyboards, this key is labeled
       +.Sy Alt
       +.Pc "."
       +.Pp
       +The first method allows the entry of any code point in the BMP.
       +While holding down the compose key, an uppercase
       +.Li X
       +character is typed, followed by exactly four lowercase hexadecimal digits naming the codepoint.
       +.Pp
       +Commonly used codepoints can be entered with an abbreviated two-character sequence.
       +These sequence definitions are read from a file called
       +.Pa ".keyboard"
       +in the user's home directory.
       +Each line in this file consists of two characters, followed by any number of spaces, and a hexadecimal number.
       +Holding down the compose key and typing the two listed characters will insert the codepoint indicated by the number.
       +For example, given the
       +.Pa ".keyboard"
       +line:
       +.Bd -literal -offset indent
       +12 0x00BD
       +.Ed
       +.Pp
       +then typing the characters 1 and 2 while holding down the compose key will insert Unicode code point 0x00BD
       +.Pq \[u00BD]
       +into the file.
       +.Pp
       +After the hexadecimal codepoint specification, the rest of the line is ignored and is therefore usable as a comment field.
       +.Pp
       +If no
       +.Pa ".keyboard"
       +file is present, the following key sequences are defined by default:
       +.Pp
       +.TS
       +box;
       +c | c | c | c | c | c | c | c
       +- | - | - | - | - | - | - | -
       +c | c | c | c | c | c | c | c.
       +Keys        Codepoint        Keys        Codepoint        Keys        Codepoint        Keys        Codepoint
       +!!        \[u00A1]        c$        \[u00A2]        l$        \[u00A3]        g$        \[u00A4]        
       +y$        \[u00A5]        ||        \[u00A6]        SS        \[u00A7]        ""        \[u00A8]
       +cO        \[u00A9]        sa        \[u00AA]        <<        \[u00AB]        no        \[u00AC]        
       +--        \[u00AD]        rO        \[u00AE]        __        \[u00AF]        de        \[u00B0]
       ++-        \[u00B1]        s2        \[u00B2]        s3        \[u00B3]        ''        \[u00B4]        
       +mi        \[u00B5]        pg        \[u00B6]        ..        \[u00B7]        ,,        \[u00B8]
       +s1        \[u00B9]        so        \[u00BA]        >>        \[u00BB]        14        \[u00BC]        
       +12        \[u00BD]        34        \[u00BE]        ??        \[u00BF]        `A        \[u00C0]
       +'A        \[u00C1]        ^A        \[u00C2]        ~A        \[u00C3]        "A        \[u00C4]        
       +oA        \[u00C5]        AE        \[u00C6]        ,C        \[u00C7]        `E        \[u00C8]
       +'E        \[u00C9]        ^E        \[u00CA]        "E        \[u00CB]        `I        \[u00CC]        
       +'I        \[u00CD]        ^I        \[u00CE]        "I        \[u00CF]        D-        \[u00D0]
       +~N        \[u00D1]        `O        \[u00D2]        'O        \[u00D3]        ^O        \[u00D4]        
       +~O        \[u00D5]        "O        \[u00D6]        mu        \[u00D7]        /O        \[u00D8]
       +`U        \[u00D9]        'U        \[u00DA]        ^U        \[u00DB]        "U        \[u00DC]        
       +'Y        \[u00DD]        |P        \[u00DE]        ss        \[u00DF]        `a        \[u00E0]
       +'a        \[u00E1]        ^a        \[u00E2]        ~a        \[u00E3]        "a        \[u00E4]        
       +oa        \[u00E5]        ae        \[u00E6]        ,c        \[u00E7]        `e        \[u00E8]
       +'e        \[u00E9]        ^e        \[u00EA]        "e        \[u00EB]        `i        \[u00EC]        
       +'i        \[u00ED]        ^i        \[u00EE]        "i        \[u00EF]        d-        \[u00F0]
       +~n        \[u00F1]        `o        \[u00F2]        'o        \[u00F3]        ^o        \[u00F4]        
       +~o        \[u00F5]        "o        \[u00F6]        -:        \[u00F7]        /o        \[u00F8]
       +`u        \[u00F9]        'u        \[u00FA]        ^u        \[u00FB]        "u        \[u00FC]        
       +'y        \[u00FD]        |p        \[u00FE]        "y        \[u00FF]        wk        \[u2654]
       +.TE
       +.TS
       +box;
       +c | c | c | c | c | c | c | c
       +- | - | - | - | - | - | - | -
       +c | c | c | c | c | c | c | c.
       +Keys        Codepoint        Keys        Codepoint        Keys        Codepoint        Keys        Codepoint
       +wq        \[u2655]        wr        \[u2656]        wb        \[u2657]        wn        \[u2658]        
       +wp        \[u2659]        bk        \[u265A]        bq        \[u265B]        br        \[u265C]
       +bb        \[u265D]        bn        \[u265E]        bp        \[u265F]        *a        \[u03B1]        
       +*b        \[u03B2]        *g        \[u03B3]        *d        \[u03B4]        *e        \[u03B5]
       +*z        \[u03B6]        *y        \[u03B7]        *h        \[u03B8]        *i        \[u03B9]        
       +*k        \[u03BA]        *l        \[u03BB]        *m        \[u03BC]        *n        \[u03BD]
       +*c        \[u03BE]        *o        \[u03BF]        *p        \[u03C0]        *r        \[u03C1]        
       +ts        \[u03C2]        *s        \[u03C3]        *t        \[u03C4]        *u        \[u03C5]
       +*f        \[u03C6]        *x        \[u03C7]        *q        \[u03C8]        *w        \[u03C9]        
       +*A        \[u0391]        *B        \[u0392]        *G        \[u0393]        *D        \[u0394]
       +*E        \[u0395]        *Z        \[u0396]        *Y        \[u0397]        *H        \[u0398]        
       +*I        \[u0399]        *K        \[u039A]        *L        \[u039B]        *M        \[u039C]
       +*N        \[u039D]        *C        \[u039E]        *O        \[u039F]        *P        \[u03A0]        
       +*R        \[u03A1]        *S        \[u03A3]        *T        \[u03A4]        *U        \[u03A5]
       +*F        \[u03A6]        *X        \[u03A7]        *Q        \[u03A8]        *W        \[u03A9]        
       +<-        \[u2190]        ua        \[u2191]        ->        \[u2192]        da        \[u2193]
       +ab        \[u2194]        V=        \[u21D0]        =V        \[u21D2]        fa        \[u2200]        
       +te        \[u2203]        pd        \[u2202]        es        \[u2205]        De        \[u2206]
       +gr        \[u2207]        mo        \[u2208]        !m        \[u2209]        st        \[u220D]        
       +**        \[u2217]        bu        \[u2219]        sr        \[u221A]        pt        \[u221D]
       +if        \[u221E]        an        \[u2220]        l&        \[u2227]        l|        \[u2228]        
       +ca        \[u2229]        cu        \[u222A]        is        \[u222B]        tf        \[u2234]
       +~=        \[u2243]        cg        \[u2245]        ~~        \[u2248]        !=        \[u2260]        
       +==        \[u2261]        <=        \[u2266]        >=        \[u2267]        sb        \[u2282]
       +sp        \[u2283]        !b        \[u2284]        ib        \[u2286]        ip        \[u2287]        
       +O+        \[u2295]        O-        \[u2296]        Ox        \[u2297]        tu        \[u22A2]
       +Tu        \[u22A8]        lz        \[u22C4]        el        \[u22EF]        :(        \[u2639]        
       +:)        \[u263A]        ;)        \[u263B]
       +.TE
       +.Sh ENVIRONMENT
       +The following environment variables affect the operation of
       +.Nm sam ":"
       +.Bl -tag -width Ds
       +.It Ev FOREGROUND
       +Sets the foreground color used by
       +.Nm
       +to draw its terminal.
       +Common English color names can be used
       +.Po
       +see
       +.Xr rgb 5
       +.Pc ","
       +or exact colors can be specified as
       +.Sy "#rrggbb" ","
       +where
       +.Sy "rr" ","
       +.Sy "gg" ","
       +and
       +.Sy "bb"
       +are hexadecimal digits describing the red, green, and blue components of the color, respectively.
       +By default, this is the string
       +.Dq black "."
       +.It Ev BACKGROUND
       +As
       +.Ev FOREGROUND ","
       +but describing the background color used to draw the terminal.
       +By default, this is the string
       +.Dq white "."
       +.It Ev FONT
       +A string representing a
       +.Xr fc-match 1
       +compatible font pattern.
       +The font described by this pattern will be used to render text in the terminal.
       +By default, this is the string
       +.Dq "monospace" "."
       +.It Ev RSH
       +The name of a command to be used to connect to a remote machine when
       +.Nm
       +is invoked with the
       +.Fl r
       +option.
       +It will be passed at least two arguments: the name of the machine to connect to and the name of the remote command to execute
       +.Po
       +e.g.
       +.Nm rsam
       +.Pc "."
       +Any additional arguments should be passed to the command on the remote machine.
       +By default, this is the string
       +.Dq "ssh" "."
       +.El
       +.Sh FILES
       +.Bl -tag -width Ds
       +.It Pa "${HOME}/.keyboard"
       +Provides a mapping of two-character sequences to Unicode code points.
       +Note that the code points must be in the Basic Multilingual Plane.
       +.It Pa "${HOME}/sam.save"
       +Created if
       +.Nm
       +terminates abnormally.
       +Executing this file will prompt the user to restore the files that were being edited at the time of termination.
       +.It Pa "${HOME}/sam.err"
       +Stores output of shell commands executed by
       +.Nm "."
       +.El
       +.Sh SEE ALSO
       +.Xr ed 1
       +.Sh BUGS
       +When a
       +.Nm sam
       +window is resized, the command window may have the wrong size.
       +.Pp
       +Under some window managers, resizing the window may cause a panic.
       +.Pp
       +.Nm
       +has issues with compositing window managers like compiz, resulting in some rendering glitches.
       +.Pp
       +The only human language in which colors may be specified is English.
       +.Pp
       +The only human language in which output is generated is English.
       +.Pp
       +There is no support for right-to-left text, ligatures, composed characters, or any other complex text rendering.
       +\ No newline at end of file
 (DIR) diff --git a/doc/sam.1.pdf b/doc/sam.1.pdf
       Binary files differ.
 (DIR) diff --git a/doc/sam.ps b/doc/sam.ps
       @@ -0,0 +1,8261 @@
       +%!PS
       +%%Version: 3.3.2
       +%%DocumentFonts: (atend)
       +%%Pages: (atend)
       +%%EndComments
       +%
       +% Version 3.3.2 prologue for troff files.
       +%
       +
       +/#copies 1 store
       +/aspectratio 1 def
       +/formsperpage 1 def
       +/landscape false def
       +/linewidth .3 def
       +/magnification 1 def
       +/margin 0 def
       +/orientation 0 def
       +/resolution 720 def
       +/rotation 1 def
       +/xoffset 0 def
       +/yoffset 0 def
       +
       +/roundpage true def
       +/useclippath true def
       +/pagebbox [0 0 612 792] def
       +
       +/R  /Times-Roman def
       +/I  /Times-Italic def
       +/B  /Times-Bold def
       +/BI /Times-BoldItalic def
       +/H  /Helvetica def
       +/HI /Helvetica-Oblique def
       +/HB /Helvetica-Bold def
       +/HX /Helvetica-BoldOblique def
       +/CW /Courier def
       +/CO /Courier def
       +/CI /Courier-Oblique def
       +/CB /Courier-Bold def
       +/CX /Courier-BoldOblique def
       +/PA /Palatino-Roman def
       +/PI /Palatino-Italic def
       +/PB /Palatino-Bold def
       +/PX /Palatino-BoldItalic def
       +/Hr /Helvetica-Narrow def
       +/Hi /Helvetica-Narrow-Oblique def
       +/Hb /Helvetica-Narrow-Bold def
       +/Hx /Helvetica-Narrow-BoldOblique def
       +/KR /Bookman-Light def
       +/KI /Bookman-LightItalic def
       +/KB /Bookman-Demi def
       +/KX /Bookman-DemiItalic def
       +/AR /AvantGarde-Book def
       +/AI /AvantGarde-BookOblique def
       +/AB /AvantGarde-Demi def
       +/AX /AvantGarde-DemiOblique def
       +/NR /NewCenturySchlbk-Roman def
       +/NI /NewCenturySchlbk-Italic def
       +/NB /NewCenturySchlbk-Bold def
       +/NX /NewCenturySchlbk-BoldItalic def
       +/ZD /ZapfDingbats def
       +/ZI /ZapfChancery-MediumItalic def
       +/S  /S def
       +/S1 /S1 def
       +/GR /Symbol def
       +
       +/inch {72 mul} bind def
       +/min {2 copy gt {exch} if pop} bind def
       +
       +/show {show} bind def                % so later references don't bind
       +/widthshow {widthshow} bind def
       +/stringwidth {stringwidth} bind def
       +
       +/setup {
       +        counttomark 2 idiv {def} repeat pop
       +
       +        landscape {/orientation 90 orientation add def} if
       +        /scaling 72 resolution div def
       +        linewidth setlinewidth
       +        1 setlinecap
       +
       +        pagedimensions
       +        xcenter ycenter translate
       +        orientation rotation mul rotate
       +        width 2 div neg height 2 div translate
       +        xoffset inch yoffset inch neg translate
       +        margin 2 div dup neg translate
       +        magnification dup aspectratio mul scale
       +        scaling scaling scale
       +
       +        addmetrics
       +        0 0 moveto
       +} def
       +
       +/pagedimensions {
       +        useclippath userdict /gotpagebbox known not and {
       +                /pagebbox [clippath pathbbox newpath] def
       +                roundpage currentdict /roundpagebbox known and {roundpagebbox} if
       +        } if
       +        pagebbox aload pop
       +        4 -1 roll exch 4 1 roll 4 copy
       +        landscape {4 2 roll} if
       +        sub /width exch def
       +        sub /height exch def
       +        add 2 div /xcenter exch def
       +        add 2 div /ycenter exch def
       +        userdict /gotpagebbox true put
       +} def
       +
       +/addmetrics {
       +        /Symbol /S null Sdefs cf
       +        /Times-Roman /S1 StandardEncoding dup length array copy S1defs cf
       +} def
       +
       +/pagesetup {
       +        /page exch def
       +        currentdict /pagedict known currentdict page known and {
       +                page load pagedict exch get cvx exec
       +        } if
       +} def
       +
       +/decodingdefs [
       +        {counttomark 2 idiv {y moveto show} repeat}
       +        {neg /y exch def counttomark 2 idiv {y moveto show} repeat}
       +        {neg moveto {2 index stringwidth pop sub exch div 0 32 4 -1 roll widthshow} repeat}
       +        {neg moveto {spacewidth sub 0.0 32 4 -1 roll widthshow} repeat}
       +        {counttomark 2 idiv {y moveto show} repeat}
       +        {neg setfunnytext}
       +] def
       +
       +/setdecoding {/t decodingdefs 3 -1 roll get bind def} bind def
       +
       +/w {neg moveto show} bind def
       +/m {neg dup /y exch def moveto} bind def
       +/done {/lastpage where {pop lastpage} if} def
       +
       +/f {
       +        dup /font exch def findfont exch
       +        dup /ptsize exch def scaling div dup /size exch def scalefont setfont
       +        linewidth ptsize mul scaling 10 mul div setlinewidth
       +        /spacewidth ( ) stringwidth pop def
       +} bind def
       +
       +/changefont {
       +        /fontheight exch def
       +        /fontslant exch def
       +        currentfont [
       +                1 0
       +                fontheight ptsize div fontslant sin mul fontslant cos div
       +                fontheight ptsize div
       +                0 0
       +        ] makefont setfont
       +} bind def
       +
       +/sf {f} bind def
       +
       +/cf {
       +        dup length 2 idiv
       +        /entries exch def
       +        /chtab exch def
       +        /newencoding exch def
       +        /newfont exch def
       +
       +        findfont dup length 1 add dict
       +        /newdict exch def
       +        {1 index /FID ne {newdict 3 1 roll put}{pop pop} ifelse} forall
       +
       +        newencoding type /arraytype eq {newdict /Encoding newencoding put} if
       +
       +        newdict /Metrics entries dict put
       +        newdict /Metrics get
       +        begin
       +                chtab aload pop
       +                1 1 entries {pop def} for
       +                newfont newdict definefont pop
       +        end
       +} bind def
       +
       +%
       +% A few arrays used to adjust reference points and character widths in some
       +% of the printer resident fonts. If square roots are too high try changing
       +% the lines describing /radical and /radicalex to,
       +%
       +%        /radical        [0 -75 550 0]
       +%        /radicalex        [-50 -75 500 0]
       +%
       +% Move braceleftbt a bit - default PostScript character is off a bit.
       +%
       +
       +/Sdefs [
       +        /bracketlefttp                [201 500]
       +        /bracketleftbt                [201 500]
       +        /bracketrighttp                [-81 380]
       +        /bracketrightbt                [-83 380]
       +        /braceleftbt                [203 490]
       +        /bracketrightex                [220 -125 500 0]
       +        /radical                [0 0 550 0]
       +        /radicalex                [-50 0 500 0]
       +        /parenleftex                [-20 -170 0 0]
       +        /integral                [100 -50 500 0]
       +        /infinity                [10 -75 730 0]
       +] def
       +
       +/S1defs [
       +        /underscore                [0 80 500 0]
       +        /endash                        [7 90 650 0]
       +] def
       +%
       +% Tries to round clipping path dimensions, as stored in array pagebbox, so they
       +% match one of the known sizes in the papersizes array. Lower left coordinates
       +% are always set to 0.
       +%
       +
       +/roundpagebbox {
       +    7 dict begin
       +        /papersizes [8.5 inch 11 inch 14 inch 17 inch] def
       +
       +        /mappapersize {
       +                /val exch def
       +                /slop .5 inch def
       +                /diff slop def
       +                /j 0 def
       +                0 1 papersizes length 1 sub {
       +                        /i exch def
       +                        papersizes i get val sub abs
       +                        dup diff le {/diff exch def /j i def} {pop} ifelse
       +                } for
       +                diff slop lt {papersizes j get} {val} ifelse
       +        } def
       +
       +        pagebbox 0 0 put
       +        pagebbox 1 0 put
       +        pagebbox dup 2 get mappapersize 2 exch put
       +        pagebbox dup 3 get mappapersize 3 exch put
       +    end
       +} bind def
       +
       +%%EndProlog
       +%%BeginSetup
       +mark
       +/linewidth 0.5 def
       +/#copies 1 store
       +/landscape false def
       +/resolution 720 def
       +setup
       +2 setdecoding
       +%%EndSetup
       +%%Page: 1 1
       +/saveobj save def
       +mark
       +1 pagesetup
       +12 B f
       +(The Text Editor)2 827 1 2343 1230 t
       +12 CW f
       +(sam)3200 1230 w
       +10 I f
       +(ROB PIKE)1 441 1 2659 1470 t
       +10 R f
       +(AT&T Bell Laboratories)2 993 1 2383 1650 t
       +(Murray Hill, New Jersey 07974)4 1267 1 2246 1770 t
       +10 I f
       +(ABSTRACT)2643 2150 w
       +10 CW f
       +(Sam)1080 2446 w
       +10 R f
       +( textual com-)2 541( A)1 129( text editor intended for bitmap displays.)6 1666(is an interactive multi-file)3 1053 4 1291 2446 t
       +( the mouse-driven, cut-and-paste interface to make complex)7 2450(mand language supplements)2 1150 2 1080 2566 t
       +( language is characterized by the composi-)6 1719( The)1 208( editing tasks easy to specify.)5 1186(or repetitive)1 487 4 1080 2686 t
       +( treat-)1 237( The)1 207( regular expressions to describe the structure of the text being modified.)11 2889(tion of)1 267 4 1080 2806 t
       +(ment of files as a database, with changes logged as atomic transactions, guides the imple-)14 3600 1 1080 2926 t
       +(mentation and makes a general `undo' mechanism straightforward.)7 2672 1 1080 3046 t
       +10 CW f
       +(Sam)1330 3202 w
       +10 R f
       +( a low-bandwidth stream, one)4 1224(is implemented as two processes connected by)6 1912 2 1544 3202 t
       +( it can run)3 435( Therefore)1 453( the other the editing algorithms.)5 1360(process handling the display and)4 1352 4 1080 3322 t
       +( with both pro-)3 600(with the display process in a bitmap terminal and the editor on a local host,)14 3000 2 1080 3442 t
       +( process in the terminal and the edi-)7 1442(cesses on a bitmap-equipped host, or with the display)8 2158 2 1080 3562 t
       +( can even run without a bitmap)6 1243( suppressing the display process, it)5 1388( By)1 167(tor in a remote host.)4 802 4 1080 3682 t
       +(terminal.)1080 3802 w
       +( 17, number)2 502(This paper is reprinted from Software\320Practice and Experience, Vol)8 2848 2 1330 3958 t
       +(11, pp. 813-845.)2 658 1 1080 4078 t
       +6 R f
       +(KEY WORDS)1 354 1 1080 4318 t
       +8 R f
       +( Undo)1 258( expressions)1 391( Caches Regular)2 642(Text editors)1 382 4 1494 4318 t
       +10 B f
       +(Introduction)720 4438 w
       +10 CW f
       +(Sam)720 4594 w
       +10 R f
       +( that combines cut-and-paste interactive editing with an unusual command)9 3025(is an interactive text editor)4 1085 2 930 4594 t
       +( is written as two programs: one, the `host)8 1775( It)1 123( composition of regular expressions.)4 1499(language based on the)3 923 4 720 4714 t
       +( the command language and provides file access; the other,)9 2428(part,' runs on a Unix* system and implements)7 1892 2 720 4834 t
       +( bitmap display and supports the)5 1337(the `terminal part,' runs asynchronously on a machine with a mouse and)11 2983 2 720 4954 t
       +( host part may be even run in isolation on an ordinary terminal to edit)14 2859( The)1 211(display and interactive editing.)3 1250 3 720 5074 t
       +( command language, much like a traditional line editor, without assistance from a mouse or)14 3763(text using the)2 557 2 720 5194 t
       +( runs on a Blit)4 583( often, the terminal part)4 952(display. Most)1 573 3 720 5314 t
       +6 R f
       +(1)2828 5264 w
       +10 R f
       +(terminal \(actually on a Teletype DMD 5620, the pro-)8 2153 1 2887 5314 t
       +( Sun com-)2 423(duction version of the Blit\), whose host connection is an ordinary 9600 bps RS232 link; on the)16 3897 2 720 5434 t
       +(puter the host and display processes run on a single machine, connected by a pipe.)14 3283 1 720 5554 t
       +10 CW f
       +(Sam)970 5710 w
       +10 R f
       +( unlike)1 282( has no facilities for multiple fonts, graphics or tables,)9 2210( It)1 117(edits uninterpreted ASCII text.)3 1250 4 1181 5710 t
       +(MacWrite,)720 5830 w
       +6 R f
       +(2)1149 5780 w
       +10 R f
       +(Bravo,)1211 5830 w
       +6 R f
       +(3)1480 5780 w
       +10 R f
       +(Tioga)1542 5830 w
       +6 R f
       +(4)1775 5780 w
       +10 R f
       +(or Lara.)1 322 1 1837 5830 t
       +6 R f
       +(5)2159 5780 w
       +10 R f
       +( this)1 176( \(Throughout)1 561( has a rich command language.)5 1266(Also unlike them, it)3 816 4 2221 5830 t
       +(paper, the phrase)2 708 1 720 5950 t
       +10 I f
       +(command language)1 800 1 1468 5950 t
       +10 R f
       +( commands activated from the mouse)5 1575(refers to textual commands;)3 1157 2 2308 5950 t
       +(form the)1 344 1 720 6070 t
       +10 I f
       +(mouse language.)1 679 1 1092 6070 t
       +10 R f
       +(\))1771 6070 w
       +10 CW f
       +(Sam)1856 6070 w
       +10 R f
       +(developed as an editor for use by programmers, and tries to join the styles)13 2977 1 2063 6070 t
       +(of the Unix text editor)4 968 1 720 6190 t
       +10 CW f
       +(ed)1733 6190 w
       +6 R f
       +(6,7)1853 6140 w
       +10 R f
       +( cut-and-paste editors by providing a comfortable)6 2100(with that of interactive)3 967 2 1973 6190 t
       +( The)1 214( language driven by regular expressions.)5 1658(mouse-driven interface to a program with a solid command)8 2448 3 720 6310 t
       +( language, and acquired a notation for describing the)8 2186(command language developed more than the mouse)6 2134 2 720 6430 t
       +( a dataflow-like syntax for specifying)5 1594(structure of files more richly than as a sequence of lines, using)11 2726 2 720 6550 t
       +(changes.)720 6670 w
       +(The interactive style was influenced by)5 1576 1 970 6826 t
       +10 CW f
       +(jim)2573 6826 w
       +10 R f
       +(,)2753 6826 w
       +6 R f
       +(1)2778 6776 w
       +10 R f
       +( for the Blit, and by)5 798(an early cut-and-paste editor)3 1144 2 2835 6826 t
       +10 CW f
       +(mux)4805 6826 w
       +10 R f
       +(,)4985 6826 w
       +6 R f
       +(8)5010 6776 w
       +10 R f
       +(the Blit window system.)3 1000 1 720 6946 t
       +10 CW f
       +(Mux)1779 6946 w
       +10 R f
       +(merges the original Blit window system,)5 1662 1 1993 6946 t
       +10 CW f
       +(mpx)3688 6946 w
       +10 R f
       +(,)3868 6946 w
       +6 R f
       +(1)3893 6896 w
       +10 R f
       +(with cut-and-paste editing,)2 1084 1 3956 6946 t
       +8 S1 f
       +(__________________)720 7046 w
       +8 R f
       +(* Unix is a registered trademark of AT&T.)7 1365 1 720 7146 t
       +cleartomark
       +showpage
       +saveobj restore
       +%%EndPage: 1 1
       +%%Page: 2 2
       +/saveobj save def
       +mark
       +2 pagesetup
       +10 R f
       +(- 2 -)2 166 1 2797 480 t
       +( like a multiplexed version of)5 1199(forming something)1 768 2 720 840 t
       +10 CW f
       +(jim)2717 840 w
       +10 R f
       +(that edits the output of \(and input to\) command ses-)9 2113 1 2927 840 t
       +(sions rather than files.)3 882 1 720 960 t
       +( paper describes the command language, then the mouse language, and explains)11 3251(The first part of this)4 819 2 970 1116 t
       +( first of the host part, then of the)8 1290( is followed by a description of the implementation,)8 2076( That)1 234(how they interact.)2 720 4 720 1236 t
       +( influenced the design of)4 991( principle that)2 555( A)1 122(terminal part.)1 538 4 720 1356 t
       +10 CW f
       +(sam)2952 1356 w
       +10 R f
       +(is that it should have no explicit limits, such as)9 1882 1 3158 1356 t
       +( honor these two)3 663( To)1 161( secondary consideration is that it be efficient.)7 1841( A)1 122( length.)1 300(upper limits on file size or line)6 1233 6 720 1476 t
       +( breaking them into)3 783(goals together requires a method for efficiently manipulating huge strings \(files\) without)11 3537 2 720 1596 t
       +( control of the command language.)5 1407(lines, perhaps while making thousands of changes under)7 2280 2 720 1716 t
       +10 CW f
       +(Sam)4460 1716 w
       +10 R f
       +('s method)1 400 1 4640 1716 t
       +( updates may)2 541( These)1 295( as a transaction database, implementing changes as atomic updates.)9 2783(is to treat the file)4 701 4 720 1836 t
       +( is achieved through a collection of caches that minimizes)9 2328( Efficiency)1 467( easily to `undo' changes.)4 1031(be unwound)1 494 4 720 1956 t
       +(disc traffic and data motion, both within the two parts of the program and between them.)15 3542 1 720 2076 t
       +(The terminal part of)3 804 1 970 2232 t
       +10 CW f
       +(sam)1800 2232 w
       +10 R f
       +( interesting is how the two halves of the edi-)9 1791( More)1 268(is fairly straightforward.)2 975 3 2006 2232 t
       +( data structure that)3 746( is achieved through a)4 888( This)1 231(tor stay synchronized when either half may initiate a change.)9 2455 4 720 2352 t
       +(organizes the communications and is maintained in parallel by both halves.)10 3005 1 720 2472 t
       +(The last part of the paper chronicles the writing of)9 2067 1 970 2628 t
       +10 CW f
       +(sam)3069 2628 w
       +10 R f
       +( lessons that were learned)4 1057(and discusses the)2 702 2 3281 2628 t
       +(through its development and use.)4 1324 1 720 2748 t
       +( is composed largely of two papers of reasonable length: a description of the)13 3164(The paper is long, but)4 906 2 970 2904 t
       +(user interface of)2 653 1 720 3024 t
       +10 CW f
       +(sam)1401 3024 w
       +10 R f
       +( are combined because the implementa-)5 1597( They)1 258(and a discussion of its implementation.)5 1576 3 1609 3024 t
       +(tion is strongly influenced by the user interface, and vice versa.)10 2528 1 720 3144 t
       +10 B f
       +(The Interface)1 579 1 720 3384 t
       +10 CW f
       +(Sam)720 3540 w
       +10 R f
       +( names may be provided when it is invoked:)8 1765( File)1 206(is a text editor for multiple files.)6 1291 3 925 3540 t
       +9 CW f
       +(sam file1 file2 ...)3 1026 1 1008 3710 t
       +10 R f
       +( are not read until necessary to)6 1255( Files)1 251( files and discard unneeded ones.)5 1349(and there are commands to add new)6 1465 4 720 3890 t
       +( file is read; the)4 673( operations apply to an internal copy made when the)9 2214( Editing)1 359(complete some command.)2 1074 4 720 4010 t
       +( simplify the discussion, the)4 1124( To)1 162( is changed only by an explicit command.)7 1672(Unix file associated with the copy)5 1362 4 720 4130 t
       +(internal copy is here called a)5 1144 1 720 4250 t
       +10 I f
       +(file)1889 4250 w
       +10 R f
       +(, while the disc-resident original is called a)7 1714 1 2017 4250 t
       +10 I f
       +(disc file.)1 339 1 3756 4250 t
       +10 CW f
       +(Sam)970 4406 w
       +10 R f
       +( connected to a bitmap display that presents a cut-and-paste editor driven by the)13 3446(is usually)1 400 2 1194 4406 t
       +( special window, called the)4 1116( this mode, the command language is still available: text typed in a)12 2776(mouse. In)1 428 3 720 4526 t
       +10 CW f
       +(sam)720 4646 w
       +10 I f
       +(window,)930 4646 w
       +10 R f
       +( editing may be)3 637( Cut-and-paste)1 616( as commands to be executed in the current file.)9 1961(is interpreted)1 529 4 1297 4646 t
       +(used in any window \320 even in the)7 1467 1 720 4766 t
       +10 CW f
       +(sam)2223 4766 w
       +10 R f
       +( mode of operation,)3 812( other)1 241( The)1 216(window to construct commands.)3 1332 4 2439 4766 t
       +(invoked by starting)2 774 1 720 4886 t
       +10 CW f
       +(sam)1520 4886 w
       +10 R f
       +(with the option)2 608 1 1726 4886 t
       +10 CW f
       +(-d)2360 4886 w
       +10 R f
       +( the mouse or bitmap display,)5 1193(\(for `no download'\), does not use)5 1341 2 2506 4886 t
       +( even on an ordinary terminal, interactively or)7 1849(but still permits editing using the textual command language,)8 2471 2 720 5006 t
       +(from a script.)2 535 1 720 5126 t
       +(The following sections describe first the command language \(under)8 2783 1 970 5282 t
       +10 CW f
       +(sam -d)1 360 1 3789 5282 t
       +10 R f
       +(and in the)2 418 1 4185 5282 t
       +10 CW f
       +(sam)4640 5282 w
       +10 R f
       +(win-)4857 5282 w
       +( two languages are nearly independent, but connect through the)9 2573( These)1 293( the mouse interface.)3 846(dow\), and then)2 608 4 720 5402 t
       +10 I f
       +(current text,)1 488 1 720 5522 t
       +10 R f
       +(described below.)1 676 1 1233 5522 t
       +10 B f
       +(The Command Language)2 1090 1 720 5762 t
       +10 R f
       +( array of characters \(that is, a string\); the)8 1644(A file consists of its contents, which are an)8 1733 2 720 5918 t
       +10 I f
       +(name)4125 5918 w
       +10 R f
       +(of the associated)2 671 1 4369 5918 t
       +(disc file; the)2 498 1 720 6038 t
       +10 I f
       +(modified bit)1 483 1 1245 6038 t
       +10 R f
       +( those of the disc file; and a substring of the)10 1758(that states whether the contents match)5 1527 2 1755 6038 t
       +(contents, called the)2 786 1 720 6158 t
       +10 I f
       +(current text)1 472 1 1540 6158 t
       +10 R f
       +(or)2046 6158 w
       +10 I f
       +(dot)2163 6158 w
       +10 R f
       +( the current text is a null string, dot falls)9 1689( If)1 126( and 2\).)2 322(\(see Figures 1)2 578 4 2325 6158 t
       +( The)1 211(between characters.)1 791 2 720 6278 t
       +10 I f
       +(value)1753 6278 w
       +10 R f
       +( the location of the current text; the)7 1441(of dot is)2 340 2 2000 6278 t
       +10 I f
       +(contents)3811 6278 w
       +10 R f
       +(of dot are the charac-)4 866 1 4174 6278 t
       +(ters it contains.)2 610 1 720 6398 t
       +10 CW f
       +(Sam)1381 6398 w
       +10 R f
       +(imparts to the text no two-dimensional interpretation such as columns or fields; text is)13 3453 1 1587 6398 t
       +( the idea of a `line' of text as understood by most Unix programs \320 a)15 3037( Even)1 272(always one-dimensional.)1 1011 3 720 6518 t
       +(sequence of characters terminated by a newline character \320 is only weakly supported.)12 3454 1 720 6638 t
       +(The)970 6794 w
       +10 I f
       +(current file)1 453 1 1156 6794 t
       +10 R f
       +( in the)2 264( current text is therefore dot)5 1141( The)1 211(is the file to which editing commands refer.)7 1784 4 1640 6794 t
       +( explicitly name a particular file or piece of text, the command is)12 2827( a command doesn't)3 870( If)1 137(current file.)1 486 4 720 6914 t
       +( presence of multiple files and consider)6 1629( the moment, ignore the)4 988( For)1 198(assumed to apply to the current text.)6 1505 4 720 7034 t
       +(editing a single file.)3 794 1 720 7154 t
       +( for non-editing commands such as writing the file to disc,)10 2354( Except)1 329(Commands have one-letter names.)3 1387 3 970 7310 t
       +cleartomark
       +showpage
       +saveobj restore
       +%%EndPage: 2 2
       +%%Page: 3 3
       +/saveobj save def
       +mark
       +3 pagesetup
       +10 R f
       +(- 3 -)2 166 1 2797 480 t
       +cleartomark
       +saveobj restore
       +%ps_include: begin
       +save
       +/ed {exch def} def
       +{} /showpage ed
       +{} /copypage ed
       +{} /erasepage ed
       +{} /letter ed
       +currentdict /findfont known systemdict /findfont known and {
       +        /findfont systemdict /findfont get def
       +} if
       +36 dict dup /PS-include-dict-dw ed begin
       +/context ed
       +count array astore /o-stack ed
       +%ps_include: variables begin
       +/llx 24 def
       +/lly 241 def
       +/urx 587.76 def
       +/ury 550.6 def
       +/w 0 def
       +/o 0 def
       +/s 0 def
       +/cx 2880 def
       +/cy -2220 def
       +/sx 4320 def
       +/sy 2520 def
       +/ax 0.5 def
       +/ay 0.5 def
       +/rot 0 def
       +%ps_include: variables end
       +{llx lly urx ury} /bbox ed
       +{newpath 2 index exch 2 index exch dup 6 index exch
       + moveto 3 {lineto} repeat closepath} /boxpath ed
       +{dup mul exch dup mul add sqrt} /len ed
       +{2 copy gt {exch} if pop} /min ed
       +{2 copy lt {exch} if pop} /max ed
       +{transform round exch round exch A itransform} /nice ed
       +{6 array} /n ed
       +n defaultmatrix n currentmatrix n invertmatrix n concatmatrix /A ed
       +urx llx sub 0 A dtransform len /Sx ed
       +0 ury lly sub A dtransform len /Sy ed
       +llx urx add 2 div lly ury add 2 div A transform /Cy ed /Cx ed
       +rot dup sin abs /S ed cos abs /C ed
       +Sx S mul Sy C mul add /H ed
       +Sx C mul Sy S mul add /W ed
       +sy H div /Scaley ed
       +sx W div /Scalex ed
       +s 0 eq {Scalex Scaley min dup /Scalex ed /Scaley ed} if
       +sx Scalex W mul sub 0 max ax 0.5 sub mul cx add /cx ed
       +sy Scaley H mul sub 0 max ay 0.5 sub mul cy add /cy ed
       +urx llx sub 0 A dtransform exch atan rot exch sub /rot ed
       +n currentmatrix initgraphics setmatrix
       +cx cy translate
       +Scalex Scaley scale
       +rot rotate
       +Cx neg Cy neg translate
       +A concat
       +bbox boxpath clip newpath
       +w 0 ne {gsave bbox boxpath 1 setgray fill grestore} if
       +end
       +gsave
       +%ps_include: inclusion begin
       +/picstr 98 string def
       +24 241 translate
       +563.76 309.60 scale
       +
       +783 430 1 [783 0 0 -430 0 430]
       +{currentfile picstr readhexstring pop} image
       +
       +0000000000000000000000000000000000000000000000000000000000000000
       +0000000000000000000000000000000000000000000000000000000000000000
       +0000000000000000000000000000000000000000000000000000000000000000
       +0001
       +0000000000000000000000000000000000000000000000000000000000000000
       +0000000000000000000000000000000000000000000000000000000000000000
       +0000000000000000000000000000000000000000000000000000000000000000
       +0001
       +0000000000000000000000000000000000000000000000000000000000000000
       +0000000000000000000000000000000000000000000000000000000000000000
       +0000000000000000000000000000000000000000000000000000000000000000
       +0001
       +0000000000000000000000000000000000000000000000000000000000000000
       +0000000000000000000000000000000000000000000000000000000000000000
       +0000000000000000000000000000000000000000000000000000000000000000
       +0001
       +0000000000000000000000000000000000000000000000000000000000000000
       +0000000000000000000000000000000000000000000000000000000000000000
       +0000000000000000000000000000000000000000000000000000000000000000
       +0001
       +0000000000000000000000000000000000000000000000000000000000000000
       +0000000000000000000000000000000000000000000000000000000000000000
       +0000000000000000000000000000000000000000000000000000000000000000
       +0001
       +02001fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02ffdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02ffdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02ffdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02ffdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02ffdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02ffdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02ffdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02ffdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02ffdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02ffdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02ffdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02ffdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02ffdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02ffdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02ffdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02ffdfffffffe7fffffffe227ff0feffffffffffffff3ff87ff11ffcffffffff
       +fffffffe7fe7ffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02ffde01ffffe7fffffffe227fe7f9f833900fffffef3ff3e0711ffcffdffff1
       +e0fffffe7fe7ffe7f3e0ffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02ffdfcfffffe7fffffffe233fe7f3f3b39e7fffffcf9ff3e7311ffcff9fffe9
       +ce7fffffffe7ffc7e3ce7fffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02ffdfcfe3ffe4f879cfff773f80f3e7f3de7f0e73019fc0673bbffc8e03ffe9
       +ce7ff3907f07ffa7d3ce7fffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02ffdfcfe3ffe27339cfff779fe7e7e7f25e7e6673cfcff3e73bbffc479fffd9
       +ce7ff3de7e67ff67b3fe7fffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02ffdfcfe3ffe7279cdfff779fe7e7e7f25e7ce737cfcff3e73bbffce79fffd9
       +ce7ff25e7ce7ffe7f3fe7fffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02ffdfcfffffe7279c3fffffcfe7e7e7f25e7ce70fcfe7f3e67ffffce79fffb9
       +ce7ff25e7ce7ffe7f3fcffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221fcfffffe7279f7fffffcfe7e7e7f93e7c07dfcfe7f3e0fffffce79fff80
       +ce7ff25e7ce7ffe7f3f9ffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889fcfffffe7279e1fffffe7e7e7e7f93e7cff87cff3f3e7fffffce79fff80
       +ce7ff93e7ce7ffe7f3f3ffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221fcfe3ffe7279d9fffffe7e7e7e7f93e7cff67cff3f3e7fffffce79ffff9
       +ce7ff93e7ce7ffe7f3e7ffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889fcfe3ffe67339cffffff3e7e7f3b93e7e6673cff9f3e7fffffce79ffff9
       +ce7ff93e7e47ffe7f3c07fffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221fcfe3ffe0f879cffffff381f3f8393e7f0e73e1f9c0e7fffffce7c3ffe0
       +e0fff9300f27ff81c0c07fffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889ffffffffffffffffffff9fff3fffffffffffffffcffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221ffffffffffffffffffff9fff9fffffffffffffffcffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889ffffffffffffffffffffffffeffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889fffffffe7fffffffe227ff0feffffffffffffff3ff87ff11ffcffffffff
       +fffffffe7fe7fffffffffffcfffcffffffffffffffffffefffffffffffffffff
       +fffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221e07ffffe7fffffffe227fe7f9f833903fffffff3ff3e0711ffcffdffff1
       +e0fffffe7fe7ff83c1fffffcff7cfffffffffffffeffff9c03ffffffffffff3e
       +0fffff00ffffffffff3fffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889e73ffffe7fffffffe233fe7f3f3b3939fffffff9ff3e7311ffcff9fffe9
       +ce7fffffffe7ff399cfffffffe7cfffffffffffffcffff3f9ffffffffffffe3c
       +e7ffffe7ffffffffff9fffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221e73e3ffe4f879cfff773f80f3e7f3d39e0f07279fc0673bbffc8e03ffe9
       +ce7ff3907f07ff399cffe720f80c8fffff9cfff0701fff3f9fffe1ffe73cfd3c
       +ffffffe7fff87ff9cf9fffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889e73e3ffe27339cfff779fe7e7e7f2539ce67313cff3e73bbffc479fffd9
       +ce7ff3de7e67ff399cffe7bcfe7c47ffff9effe73cfffe7f9fffccffe73cfb3c
       +ffffffe7fff33ff9cfcfffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221e77e3ffe7279cdfff779fe7e7e7f253bfe67f39cff3e73bbffce79fffd9
       +ce7ff25e7ce7ff399cffe4bcfe7ce7ffff92ffff3cfffe7f9fff9cfff37cff3c
       +ffffffe7ffe73ff9efcfffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889e0fffffe7279c3fffffcfe7e7e7f2507fe63f39e7f3e67ffffce79fffb9
       +ce7ff25e7ce7ff819cffe4bcfe7ce7ffff92ffff3cfffe7f9fff9cfff0e01f3c
       +0fffffe7ffe73ffccfcfffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221e67ffffe7279f7fffffcfe7e7e7f9333e070739e7f3e0fffffce79fff80
       +ce7ff25e7ce7fff99cffe4bcfe7ce7ffff92fff03cfffe7f9fff80fffde01f3c
       +e7ffffe7ffe03ffcdfcfffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889e67ffffe7279e1fffffe7e7e7e7f9333ce7e339f3f3e7fffffce79fff80
       +ce7ff93e7ce7fff99cfff27cfe7ce7ffffc9ffe73cfffe7f9fff9ffff87cff3c
       +e7ffffe7ffe7fffcdfcfffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221e73e3ffe7279d9fffffe7e7e7e7f9339ce7f339f3f3e7fffffce79ffff9
       +ce7ff93e7ce7fff99cfff27cfe7ce7ffc7c9ffe73cfffe7f9fc79ff1f67cff3c
       +e78fffe7f1e7fc7e3fcfffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889e73e3ffe67339cffffff3e7e7f3b9339cc67333f9f3e7fffffce79ffff9
       +ce7ff93e7e47ff399cfff27cfe7ce7ffc7c9ffe63cfffe7f9fc7ccf1e73cff3c
       +e78fffe7f1f33c7e3fcfffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221e71e3ffe0f879cffffff381f3f839338e270707f9c0e7fffffce7c3ffe0
       +e0fff9300f27ff83c1fff2601f0ce7ffc7c9fff13e1fff3f9fc7e1f1e73ffc0e
       +0f8fffe7f1f87c7f3f9fffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889ffffffffffffffffffff9fff3ffffffffffff3ffcffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffff3fffffffffffffffff
       +ffcfffffffffffff7f9fffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221ffffffffffffffffffff9fff9ffffffffffff3ffcffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffff9fffffffffffffffff
       +ffcffffffffffffe7f3fffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889ffffffffffffffffffffffffeffffffffffff3fffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffefffffffffffffffff
       +ff9ffffffffffffc7effffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889e0fe7ffffffff0fffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221fcfe7fffffffe7fffffffff807fffffffbfffff03ffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889fcffffffffffe7ffffffffff3ffffffff3fffff39ffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221fcf07c8f87ff80c670e5bfff3fffc3ffc070fff39ffe73fffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889fcfe7c4733ffe7e066601fff3fff99fff3e67ff39ffe7bfffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221fcfe7ce673ffe7e64f249fff3fff39fff3cf3ff3bffe4bfffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889fcfe7ce673ffe7e7cf249fff3fff39fff3cf3ff07ffe4bfffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221fcfe7ce603ffe7e7cf249fff3fff01fff3cf3ff33ffe4bfffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889fcfe7ce67fffe7e7cf249fff3fff3ffff3cf3ff33fff27fffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221fcfe7ce67fffe7e7cf249fff3f8f3ffff3cf3ff39f1f27fffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889fcfe7ce733ffe7e7e6649fff3f8f99fff3e67ff39f1f27fffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221e0100ce787ff81c1f0e49fff3f8fc3fff870fff38f1f27fffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffff80000000800000ffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221c00000000001fffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff3f80000001800000ffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889c00000000001fff87ffffffffffffffffffffcfffcfffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff0f80078c67f00000ffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221c00000000001fff3fffffffffc0ffffffffffcfffcffdfffc1f8fffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff1f800ccc61800000ffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889c00000000001fff3fffffffffce7fffffffffffffcff9fff9cf4fffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff3f8018cc61800000ffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221c7c73398f0c7ffc0633872dffce7ffe1ffe320fe0c8e03ff9cf4fffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7d80180c61800000ffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889cc63f1f998c3fff3f033300ffce7ffccfff03cfccc479ffffcecfffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffff980180c61800000ffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221c063319b0cdbfff3f327924ffcefff9cfff33cf9cce79ffffcecfffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffef080180c61800000ffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889c06301830cdbfff3f3e7924ffc1fff9cfff3fcf9cce79ffff9dcfffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffce080180c61800000ffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221c7e301830cdbfff3f3e7924ffccfff80fff3fcf9cce79ffff3c07ffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fff8c0000ccee1800000ffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889cc6301830c6dfff3f3e7924ffccfff9ffff3fcf9cce79fffe7c07ffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fff08000078760f00000ffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221cc6301830c6dfff3f3e7924ffce7c79ffff3fcf9cce79fffcffcfffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe00000000000000000ffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889cce30181986dfff3f3f3324ffce7c7ccfff3fcfc8ce79fff80fcfffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffc10000000000000000ffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221c767c3e0f06dffc0e0f8724ffce3c7e1ffe0e01e4ce7c3ff80f07ffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ff837ffffffffffffffeffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889c00000000001ffffffffffffffffffffffffffffcffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffc77ffffffffffffffeffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221c00000000001fffffffffffffffffffffffffff9cffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffef7ffffffffffffffeffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889c00000000001fffffffffffffffffffffffffffc1ffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7fffffffff7ffffeffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7ffffffffe7ffffeffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889f3ffffffffcffffff9fffffc47ffffffcffffffffc47ffffe7ffe7fffff
       +fffffffffffffffffcfffffe1ff83fffffffffffffffffffffffffffffffffff
       +ffff7fc9f078380f0ffeffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221f3ffffffffcffffff9fffffc47dfffffcfffffff7c47ffffe7fbe7fffff
       +ffffffff7ffffffffcfffffcffff3ffffff7ffffffffffffffffffffffffffff
       +ffff7fc4e7339e7e67feffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889f3fffffffffffffffffffffc479fffffcffffffe7c47fffffff3e7fffff
       +fffffffe7ffffffffcfffffcffff3fffffe7ffffffffffffffffffffffffffff
       +ffff7fce7f33fe7ce7feffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221f27c3ce7fe0f91ce41f83ffeee0387ffc8f0f8380eefff3907c0647ffff
       +ce7ff8380fff8723e0fff0f01fff3f078380fff07198ce1e73ffffffffffffff
       +ffff7fce7f31fe7ce7feffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889f1399ce7ffcf88ce79f39ffeef9f33ffc466739e7eefff3de7f3e23ffff
       +cf7ff39e7fff3311ccffe67cffff3e7339e7ffe7381c0cce7bffffffffffffff
       +ffff7fce70383e7c07feffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221f393ce6fffcf9ccf79f3fffeef9e79ffce4f33fe7eefff25e7f3e73ffff
       +c97fff9e7ffe73399cffcf3cffff3ff33fe7ffff399cc9e64bffffffffffffff
       +ffff7fce673f1e7cfffeffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889f393ce1fffcf9ce679f1ffffff9e79ffce4f31fe7fffff25e7f3e73ffff
       +c97fff9e7ffe73399cffcf3cffff3ff31fe7ffff39fcf9e64bffffffffffffff
       +ffff7fce673f9e7cfffeffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221f393cfbfffcf9ce6f9f83fffff9e79ffce4f383e7fffff25e7f3e73ffff
       +c97ff81e7ffe03399cffcf3cffff3f0383e7fff039fcf9e64bffffffffffffff
       +ffff7fcce6339e7e67feffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889f393cf0fffcf9ce6f9ff1fffff9e79ffce4f3f1e7fffff93e7f3e73ffff
       +e4fff39e7ffe7f399cffcf3cffff3e73f1e7ffe739fcf9e727ffffffffffffff
       +ffff7fc1f1383f0f0ffeffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221f393cecfffcf9cf1f9ff9fffff9e79ffce4f3f9e7fffff93e7f3e73ffe3
       +e4fff39e7ffe7f399cffcf3cffff3e73f9e7ffe739fcf9e727ffffffffffffff
       +ffff7fcffffffffffffeffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889f3399ce7ffcf9cf1f9f39fffff9f33ffce66739e7fffff93e7f3e73ffe3
       +e4fff31e7fff3339c8ffe67cffff3e6339e7ffe639fcfccf27ffffffffffffff
       +ffff7fcffffffffffffeffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221f07c3ce7fe019cf1c0383fffffc387ffce70f83f0fffff9300f8673ffe3
       +e4fff89f0fff8739e4fff0f03ff8071383f0fff130783e1f27ffffffffffffff
       +ffff7fcffffffffffffeffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7ffffffffffffffeffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7fffffffffff87feffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7fffffffffff3ffeffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7fffffffffff3ffeffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7fe0e47838cc07feffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7fce62339c0f3ffeffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7fcfe73f9ccf3ffeffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7fc7e73f9cff3ffeffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7fe0e7381cff3ffeffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02221fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7ffc67339cff3ffeffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02889fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7ffe67339cff3ffeffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +02001fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7fce67331cff3ffeffffffffffffffffffffffffffffffffffffffffffff
       +ff81
       +0000000000000000000000000000000000000000000000000000000000000000
       +0000000000000000000000000000000000000000000000000000000000000000
       +00007fe0e738983c0ffe00000000000000000000000000000000000000000000
       +0001
       +0000000000000000000000000000000000000000000000000000000000000000
       +0000000000000000000000000000000000000000000000000000000000000000
       +00007ffffffffffffffe00000000000000000000000000000000000000000000
       +0001
       +0000000000000000000000000000000000000000000000000000000000000000
       +0000000000000000000000000000000000000000000000000000000000000000
       +00007ffffffffffffffe00000000000000000000000000000000000000000000
       +0001
       +0000000000000000000000000000000000000000000000000000000000000000
       +0000000000000000000000000000000000000000000000000000000000000000
       +00007ffffffffffffffe00000000000000000000000000000000000000000000
       +0001
       +0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7ffffffffffffffeffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7ffc1fffffcffffeffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e001fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7fff9fffffcffffeffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221ffffffffffe1e7ffffffffff3ffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7fff9fffffcffffeffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889ffffffffffcfe7fff03fffff3ffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7fff9f87c3ce7ffeffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221ffffffffffcffffff03ffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7fff9f3399cefffeffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889ffffffffff0107f077fff9383f87fffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7fff9e793ccdfffeffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221ffffffffffcfe7e677fff89f3f33fffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7fff9e793ccbfffeffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889ffffffffffcfe7ce71fff9cf3e73fffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7fff9e793cc3fffeffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221fff80fffffcfe7ce7c7ff9cf3e7ffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7fff9e793cc9fffeffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889fff80fffffcfe7ce7f3ff9cf3e7ffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7fff9e793cccfffeffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221ffffffffffcfe7ce7f3ff9cf3e7ffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7fff9f3399ce7ffeffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889ffffff1fffcfe7ce7f3c79cf3e7ffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7ffc0387c3ce7ffeffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221ffffff1fffcfe7e4673c799f3f33fffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7ffffffffffffffeffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889ffffff1fff0300f2707c78380787fffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7ffffffffffffffeffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221fffffffffffffffe7ffff9fffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7ffffffffffffffeffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889ffffffffffffffce7ffff9fffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7ffffffffffffffeffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221ffffffffffffffe0fffff9fffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7ffffffffffffffeffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7ffefffffffefffeffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221ff9ffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7ffcfffffffe7ffeffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889ff9ffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7ff9cb7399cf3ffeffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221ff3ffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7ff3c03399cf9ffeffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889ff3c1c6633879cfffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7fe7c9339cdfcffeffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221fe79ce0703339efffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7fc7c9339c3fc7feffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889fe7fce67327992fffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7fe7c9339f7fcffeffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221fcffce7f3e7992fffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7ff3c9339e1f9ffeffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889fcfc0e7f3e7992fffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7ff9c9339d9f3ffeffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221f9f9ce7f3e79c9fffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7ffcc93119ce7ffeffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889f9f9ce7f3e79c9fffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7ffec93899cefffeffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221f3f98e7f3f33c9fffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7ffffffffffffffeffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889f3fc4c1e0f87c9fffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7ffffffffffffffeffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7ffffffffffffffeffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7ffffffffffffffeffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7fe7fffffffffffeffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889dffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7fe7fffffffffffeffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221dffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7fcffffffffffffeffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889dffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7fcf07198ce1e73effffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221dffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7f9e7381c0cce7beffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889dffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7f9ff399cc9e64beffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221dffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7f3ff39fcf9e64beffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889dffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7f3f039fcf9e64beffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221dffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7e7e739fcf9e727effffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889dffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7e7e739fcf9e727effffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221dffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7cfe639fcfccf27effffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889dffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff7cff130783e1f27effffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221dffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff79fffffffffffffeffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889dffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff79fffffffffffffeffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221dffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889dffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221dffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0effdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0effdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0effdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0effdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0effdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0effdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0effdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0effdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0effdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0effdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0effdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0effdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0effdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0effdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0effdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0effdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0effdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0effdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e001fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0000000000000000000000000000000000000000000000000000000000000000
       +0000000000000000000000000000000000000000000000000000000000000000
       +0000000000000000000000000000000000000000000000000000000000000000
       +0001
       +00000000000000000000000000000000000000000000077fef81fffff3ffffff
       +fceec00c0303e633f060300060060fc77ffe7feffff8f07fffff3ff3fff3f9f0
       +7fffff3fdf3fffffffffffffffdfff80e07ff01c1f8ffffffffffffffe000000
       +0001
       +0ffffffffffffffffffffffffffffffffffffffffffff77fef9ffffff3ffffff
       +fcee600c060626330000300030060c677ffe7fcffff4e73ffffffff3ffe3f1e7
       +3fffffff9f3fffffffffffffff9fff80e07ff019cf4ffffffffffffffeffffff
       +ffe1
       +0ffffffffffffffffffffffffffffffffffffffffffff77fef9ff1fff27c3ce7
       +fc44603f860c061303e0301e301fcc622ffe4701fff4e73ff9c83f83ffd3e9e7
       +3ff9c83e0323ffffe4739ffe0e03fffeefffffd9cf4ffffffffffffffeffffff
       +ffe1
       +0e001ffffffffffffffffffffffffffffffffffffffff77fef9ff1fff1399ce7
       +fc44300c0c0c06d30060303318060c622ffe23cfffece73ff9ef3f33ffb3d9ff
       +3ff9ef3f9f11ffffe233dffce79ffffcefffff99cecffffffffffffffeffffff
       +ffe1
       +0e889ffffffffffffffffffffffffffffcffffe7fffff77fef9ff1fff393ce6f
       +fc44300c0c0c06d30060306318060c622ffe73cfffece73ff92f3e73fff3f9ff
       +3ff92f3f9f39ffffe7325fffe79ffff9e3ffff39cecffffffffffffffeffffff
       +ffe1
       +0e221fffffffffffffffbffffffffffffcffffe7fffff77fef83fffff393ce1f
       +fc00180c0c0c06d3e06030630c060cc00ffe73cfffdce73ff92f3e73fff3f9fe
       +7ff92f3f9f39ffffe7325fffe79ffff3f8fffe79cdcffffffffffffffeffffff
       +ffe1
       +0e889fffffffffffffff3fffffffffffffffffe7fffff77fef9ffffff393cfbf
       +fc00180c0c0c03630060307f0c060f800ffe73cfffc0673ff92f3e73fff3f9fc
       +fff92f3f9f39ffffe7325ffe079fffe7fe7ffcf9cc07fffffffffffffeffffff
       +ffe1
       +0e221f19e1f0673c3c1c0707ffc1ffe720f91f070f39f77fef9ffffff393cf0f
       +fc000c0c0c0c03630060306006060c000ffe73cfffc0673ffc9f3e73fff3f9f9
       +fffc9f3f9f39ffffe7393ffce79fffe7fe7ffcf9cc07fffffffffffffeffffff
       +ffe1
       +0e889f81cce6673999cf3e73ff9cffe7bcf88e66673df77fef9ff1fff393cecf
       +fc000c0c0c0c03630060306006060c000ffe73cffffce73ffc9f3e73fff3f9f3
       +fffc9f3f9f39fff1e7393ffce79fffcffe78f9f9cfcffffffffffffffeffffff
       +ffe1
       +0e221f999cce673399ff3e7ffffcffe4bcf9cce4f325f77fef9ff1fff3399ce7
       +fc00060c0c0623630060303303060c000ffe73cffffce73ffc9f3f23fff3f9e0
       +3ffc9f3f9f39fff1e7393ffcc79fffcfce78f9f9cfcffffffffffffffeffffff
       +ffe1
       +0e889f9f9cce673398ff3e3ffffcffe4bcf9cce4f325f77fef9ff1fff07c3ce7
       +fc00063f0603e36303fdfe1e031f8c000ffe73e1fff0707ffc980793ffc0e060
       +3ffc9807c339fff1e7393ffe27c3ffcfe0f8f9fc1f07fffffffffffffeffffff
       +ffe1
       +0e221f9f80ce67301c1f3f07ffc0ffe4bcf9cce4f325f77fefffffffffffffff
       +fc0003000600000000000000018000000fffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffffffffffffcfffffffffffffffffffffeffffff
       +ffe1
       +0e889f9f9fce6733ff8f3fe3ff9cfff27cf9cce4f393f77fefffffffffffffff
       +fc0003000300000000000000018000000fffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffffffffffffcfffffffffffffffffffffeffffff
       +ffe1
       +0e221f9f9fce6733ffcf3ff3ff9cfff27cf9cce4f393f77fefffffffffffffff
       +fc0000000080000000000000000000000fffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffffffffffff9fffffffffffffffffffffeffffff
       +ffe1
       +0e889f9fcce4623999cf3e73ff98fff27cf9ce466793f77fefffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e221f07e1f2713c3c1f8707ffc4fff26019cf270f93f77fefffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e889ffffffe7ffffffffffffffffffffffffffffffff77fefffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e221ffffffe7ffffffffffffffffffffffffffffffff77fefffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e889ffffffe7ffffffffffffffffffffffffffffffff7088fffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e221ffffffffffffffffffffffffffffffffffffffff7222fffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e889ffffffffffffffffffffffffffffffffffffffff7088fffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e221fffe0ce7ffffffffffffffffffffffffffffffff7222fffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e889fffcece7ffffffffffffffffffffffffffffffff7088fffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e221fff9fcf7ff23e1ce7fffffffffffffffffffffff7222fffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e889fff9fc97ff11cccf7fffffffffffffffffffffff7088fffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e221fff9fc97ff399cc97fffffffffffffffffffffff7222fffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e889fff9fc97ff399cc97fffffffffffffffffffffff7088fffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e221fff9fe4fff3980c97fffffffffffffffffffffff7222fffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e889fff9fe4fff399fe4ffffffffffffffffffffffff7088fffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e221fc79fe4fff399fe4fffc7fffffffffffffffffff7222fffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e889fc7cee4fff39cce4fffc7fffffffffffffffffff7088fffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e221fc7e0e4fff39e1e4fffc7fffffffffffffffffff7222f07f3ffffffff87
       +fffffffffffffffffffffffffffff3ffff87c3ffffffffffff0f87ffffffffff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e889fffffffffffffffffffe7fffffffffffffffffff7088fe7f3ffffffff3f
       +ffffffffe0f83fffffffefffffe733ffff3f9fffffffe07ffe7f3fffffffffff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e221fffffffffffffffffffe7fffffffffffffffffff7222fe7ffffffffff3f
       +ffffffffe6739fffffffcfffffe733ffff3f9fffffffe73ffe7f3fffffffffff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e889fffffffffffffffffffcffffffffffffffffffff7088fe783e47c3ffc06
       +33872dffe7339ffc8fff01c3ffe23279cc0603c38cffe733980c078719ffe0ff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e221ffffffffffffffffffffffffffffffffffffffff7222fe7f3e2399fff3f
       +033300ffe73f9ffc47ffcf99ffe23139cf3f9f99c0ffe7339e7f3f3381ffce7f
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e889ffffffffffffffffffffffffffffffffffffffff7088fe7f3e7339fff3f
       +327924ffe73f9ffce7ffcf3cffe93399cf3f9f39ccffe7739e7f3e7399ffcfff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e221fffe0ce7ffffffffffffffffffffffffffffffff7222fe7f3e7339fff3f
       +3e7924ffe73f3ffce7ffcf3cffe93399cf3f9f39cfffe0f39e7f3e739fffc7ff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e889fffcece7ffffffffffffffffffffffffffffffff7088fe7f3e7301fff3f
       +3e7924ffe73e7ffce7ffcf3cffe93399cf3f9f01cfffe7739e7f3e039fffe0ff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e221fff9fcf7ff39e1c670f39fffffffffffffffffff7222fe7f3e733ffff3f
       +3e7924ffe73cfffce7ffcf3cffe93399cf3f9f3fcfffe7339e7f3e7f9ffffc7f
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e889fff9fc97ff39cce066739fffffffffffffffffff7088fe7f3e733ffff3f
       +3e7924ffe739fe3ce7ffcf3cffef3399cf3f9f3fcff1e7339e7f3e7f9fe3fe7f
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e221fff9fc97ff9b9ce64f39bfffffffffffffffffff7222fe7f3e7399fff3f
       +3f3324ffe6701e3ce7ffcf99ffef33388f3f9f99cff1e7311e7f3f339fe3ce7f
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e889fff9fc97ff879ce7cf387fffffffffffffffffff7088f0080673c3ffc0e
       +0f8724ffe0f01e3ce7ffe1c3ffef307c4c0e07c383f1e078981c0f8707e3e0ff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e221fff9fe4fffef80e7cf3effffffffffffffffffff7222fffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e889fff9fe4fffc39fe7cf3c3fffffffffffffffffff7088fffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e221fc79fe4fffb39fe7cf3b3fffffffffffffffffff7222fffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e889fc7cee4fff39cce7e6739fffffffffffffffffff7088fffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e221fc7e0e4fff39e1c1f0f39fffffffffffffffffff7222fffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e889ffffffffffffffffffffffffffffffffffffffff7088fffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e221ffffffffffffffffffffffffffffffffffffffff7222fffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e889ffffffffffffffffffffffffffffffffffffffff7088fffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e221ffffffffffffffffffffffffffffffffffffffff7222fffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e889ffffffffffffffffffffffffffffffffffffffff7088fffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e221ffffffffffffffffffffffffffffffffffffffff7222fffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e889ffffffffffffffffffffffffffffffffffffffff7088fffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e221f878cfffffffffffffffffffffffffffffffffff7222fffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e889f33c0fffffffffffffffffffffffffffffffffff7088fffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e221e79ccfffffffffffffffffffffffffffffffffff7222fffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e889e79cffffffffffffffffffffffffffffffffffff7088fffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e221e79cffffffffffffffffffffffffffffffffffff7000fffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e889e79cffffffffffffffffffffffffffffffffffff7ffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e221e79cffffffffffffffffffffffffffffffffffff7ffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffff
       +ffe1
       +0e889f33cffffffffffffffffffffffffffffffffffff0000000000000000000
       +0000000000000000000000000000000000000000000000000000000000000000
       +0000000000000000000000000000000000000000000000000000000000ffffff
       +ffe1
       +0e221f8783ffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889ffffffffffffffffe7fffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221fffe0ce7ffffffffe7fffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889fffcece7ffffffffe7fffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221fff9fcf7ff19e1e0e478393f0ffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889fff9fc97ff81ccce6233989e67fffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0effdfff9fc97ff999ccfe73f99cce7fffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0effdfff9fc97ff9f9cc7e73f99cce7fffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221fff9fe4fff9f80e0e73819cc07fffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889fff9fe4fff9f9ffc673399ccfffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221fc79fe4fff9f9ffe673399ccffffc7fffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889fc7cee4fff9fccce6733199e67ffc7fffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221fc7e0e4fff07e1e0e738983f0fffc7fffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889fffffffffffffffffffff9ffffffe7fffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221fffffffffffffffffffff9ffffffe7fffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889fffffffffffffffffffff9ffffffcffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889fcffffffff3fffffffffffffffff3ffffffffffffffffffffffffffffff
       +fffffe1fffffffffffcfffffffffffffff07fffffffffffffffffffffffffffc
       +fffffffffff9fff9ffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221fcff7fffff3fffffffffffffffdf3ffffffffffffffffffffffffffffff
       +fffffcfffffffffff7cfffffffffffffffe7fffffffffffffffff7fffffffffc
       +fffffffffff9fef9ffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889fffe7fffff3fffffffffffffff9f3ffffffffffffffffffffffffffffff
       +fffffcffffffffffe7cfffffffffffffffe7ffffffffffffffffe7fffffffffc
       +fffffffffffffcf9ffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221e0f80fff8723c1c8f83c3c1ffe0323e1ffcb7879ce0f87ffe1ce63383c3
       +c67ff018ce1cb7ff80c8f87ff9ce0e7383e7fff07198ce1e73ff80e1fff83ffc
       +9f0f39ffce41f0191fffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889fcfe7fff33119cc4733999cfff9f11ccffc03339cce733ffccce7033999
       +e07ffcfc0ccc03ffe7c4733ff9cce67339e7ffe7381c0cce7bffe7ccfff39ffc
       +4e6739ffcf79fcf88fffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221fcfe7ffe7339fcce673399ffff9f399cffc92799ccfe73ff9cce7333f3c
       +e67ffcfcc9e493ffe7ce673ff9ccfe73f9e7ffff399cc9e64bffe79e7fff9ffc
       +e4f39bffc979fcf9cfffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889fcfe7ffe7f39fcce673398ffff9f399cffc92799cc7e73ff9fce73f1f3c
       +e7fffcfcf9e493ffe7ce673ff9cc7e73f9e7ffff39fcf9e64bffe79e7fff9ffc
       +e4f387ffc979fcf9cfffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221fcfe7ffe7f39c0ce67301c1fff9f3980ffc92799ce0e03ff9fce73f833c
       +e7fffcfcf9e493ffe7ce603ff9ce0e7381e7fff039fcf9e64bffe79e7ff81ffc
       +e4f3efffc979fcf9cfffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889fcfe7ffe7f399cce6733ff8fff9f399fffc92799cfc67fff9fce73ff13c
       +e7fffcfcf9e493ffe7ce67fff9cfc67339e7ffe739fcf9e727ffe79e7ff39ffc
       +e4f3c3ffe4f9fcf9cfffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221fcfe7ffe7f399cce6733ffcfff9f399fffc92799cfe67fff9fce73ff93c
       +e7fffcfcf9e493ffe7ce67fff9cfe67339e7ffe739fcf9e727ffe79e7ff39ffc
       +e4f3b3ffe4f9fcf9cfffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889fcfe7fff333998ce723999cfff9f39ccffc933388ce733ffccc473f3999
       +e7fffcfcfccc93ffe7ce733ff88ce62331e7ffe639fcfccf27ffe7ccfff31ffc
       +ce6739ffe4f9fcf9cfffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221e01f0fff8739c4ce793c3c1fffc339e1ffc9387c4e0f87ffe1e260f83c3
       +c1fff0383e1c93fff0ce787ffc4e0f138900fff130783e1f27fff0e1fff89ffc
       +1f0f39ffe4c03e19cfffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889ffffffffffffffffff3ffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221ffffffffffffffffe73ffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889fffffffffffffffff07ffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889ffffffffffff0783fffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221ffffffffffffe7f3fffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889ffffffffffffe7f3fffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221f83ffe0cb783e7f3fff838cc670f39fffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889f39ffce40339e7f3fff39c0e06673dfffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221ff9ffcfc93f9e7f3ffff9cce64f325fffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889ff9ffc7c93f9e7f3ffff9cfe7cf325fffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221f81ffe0c9381e7f3fff81cfe7cf325fffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889f39fffc49339e7f3fff39cfe7cf393fffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221f39fffe49339e7f3fff39cfe7cf393e3fffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889f31ffce49331e7f3fff31cfe7e6793e3fffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221f89ffe0c938900807ff8983c1f0f93e3fffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc00000
       +000000000000001fffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889ffffffffff3fe7fffffffffffffffffffffff3fffffffffffffffffffff
       +fffffff9ffffffffffffff3fffffffffcffffe79ffffffffffffffffffc00003
       +006000000000001ffffffffffffffffff07fffff3fffffffffffffffffffffff
       +ffe1
       +0e221f03fffffdf3fe7ffffffff7fffdffffffffdf3fffffffffffffffffffff
       +fffffff9ffffffffffffff3fff7fffffcffffe79ffffffbfffffffffffc00003
       +006010000000001ffffffffff7fffffffe7fffff3fffffffffffffffffffffff
       +ffe1
       +0e889fcffffff9f3ffffffffffe7fff9ffffffff9f3fffffffffffffffffffff
       +fffffff9ffffffffffffff3ffe7ffffffffffe7fffffff3fffffffffffc00003
       +000030000000001fffffffffe7fffffffe7fffff3fffffffffffffffffffffff
       +ffe1
       +0e221fcf91ffe032307e0fff8380e0e03c3ffffe0323e1ffcb7879ce0f87ff2d
       +e0e73ff93f0fff39c1f0f83ff80e1ffe0f91f041fc3c1c0787ffc1c8ffc7c733
       +63e0fe731f1ccc7ffc6787c380e0e47c1e7f0fff279cffffffffffffffffffff
       +ffe1
       +0e889fcf88fff9f11e7ce7ff39e7ce79f99fffff9f11ccffc03339cce733ff00
       +ce673ff89e67ff399ce6733ffe7ccfffcf88e679f999cf3f33ff9cc47fcc63f3
       +b060303f318fcc7ffe073399e7ce62399e7e67ff139cffffffffffffffffffff
       +ffe1
       +0e221fcf9cfff9f39e7cffff3fe7fe79f39fffff9f399cffc92799ccfe73ff24
       +fe67bff9cce7ff399fce673ffe79e7ffcf9cce79f39fcf3e73fffcce7fc06333
       +18603033018ccc3ffe667339e7fe67339e7ce7ff399effffffffffffffffffff
       +ffe1
       +0e889fcf9cfff9f39e7c7fff1fe7fe79f39fffff9f399cffc92799cc7e73ff24
       +fe733ff9cce7ff398fce673ffe79e7ffcf9cce79f3ffcf3e73fffcce7fc06303
       +18603030018c067ffe7e733fe7fe67339e7ce7ff39ccffffffffffffffffffff
       +ffe1
       +0e221fcf9cfff9f39e7e0fff83e7e079f01fffff9f3980ffc92799ce0e03ff24
       +e0737ff9cc07ff39c1c0673ffe79e7ffcf9cce79f3fc0f3e03ffc0ce7fc7e303
       +186030301f8c065ffe7e033fe7e067339e7c07ff39cdffffffffffffffffffff
       +ffe1
       +0e889fcf9cfff9f39e7fc7fff1e7ce79f3ffffff9f399fffc92799cfc67fff24
       +ce737ff9ccffff39f8cfe73ffe79e7ffcf9cce79f3f9cf3e7fff9cce7fcc6303
       +18603030318c065ffe7e7f3fe7ce67339e7cffff39cdffffffffffffffffffff
       +ffe1
       +0e221fcf9cfff9f39e7fe7fff9e7ce79f3fe3fff9f399fffc92799cfe67fff24
       +ce78fff9ccffff39fccfe73ffe79e7ffcf9cce79f3f9cf3e7fff9cce7fcc6303
       +18603030318c039ffe7e7f3fe7ce67339e7cffff39e3ffffffffffffffffffff
       +ffe1
       +0e889fcf9cfff9f39e7ce7ff39e7cc79f99e3fff9f39ccffc933388ce733ff24
       +cc78fff99e67ff119ce6723ffe7ccfffcf9ce479f9998f3f33ff98ce7fcce303
       +30603030338c039ffe7f3399e7cc67391e7e67ff33e3ffffffffffffffffffff
       +ffe1
       +0e221f039cfffc33900e0fff83f0e27c3c3e3fffc339e1ffc9387c4e0f87ff24
       +e27cfff83f0fff89c1f0f93fff0e1ffe019cf2403c3c4f8787ffc4ce7fc767c3
       +e3fc1e7c1d9f019ffc1f87c3f0e2673c900f0fff07f3ffffffffffffffffffff
       +ffe1
       +0e889fffffffffffffffffffffffffffffff3fffffffffffffffffffffffffff
       +fffdffffffffffffffffffffffffffffffffffffffffffffffffffffffc00000
       +000000000000011fffffffffffffffff9ffffffffff7ffffffffffffffffffff
       +ffe1
       +0e221fffffffffffffffffffffffffffffff3fffffffffffffffffffffffffff
       +fff9ffffffffffffffffffffffffffffffffffffffffffffffffffffffc00000
       +000000000000031ffffffffffffffff39fffffffffe7ffffffffffffffffffff
       +ffe1
       +0e889ffffffffffffffffffffffffffffffe7fffffffffffffffffffffffffff
       +fff1ffffffffffffffffffffffffffffffffffffffffffffffffffffffc00000
       +000000000000071ffffffffffffffff83fffffffffc7ffffffffffffffffffff
       +ffe1
       +0e221fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889ffffffffffffe7fffffff9fffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffe7ffffff83ffffffff3ffffffff3ffffffffffffff9ff
       +fffffffffffffffe7fffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221ffffffffffffe7fffffff9ffffdfeffffffff83fffffdffffffffffffff
       +fffffffffffffffffffe7fffffff3ffffffff3ffffffff3fdffffffbfffef9ff
       +fffffffffffffffe7fbfffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889fffffffffffffffffffff9ffff9fcffffffff39fffff9ffffffffffffff
       +fffffffffffffffffffe7fffffff3fffffffffffffffffff9ffffff3fffcf9ff
       +ffffffffffffffffff3fffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221f278cf0f078307c8f83ff93ce60301c3c8fff39ffe0e03ffc3c8f87ffe1
       +e1e3323e1c67ff8391f07ff19e1f3f8783c183e47c1ff83e03ffc1c07ff0191f
       +0fff8793c9f0f8307c0787ffe1e1e3323e1c67ffffffffffffffffffffffffff
       +ffe1
       +0e889f13c0e667339e7c4733ff89ce79fcf99c47fff9ffce79fff99c4733ffcc
       +ccf0311cce07ff3988e67ff81ccf3f33399cf3e2399fff3f9fff9cf3fffcf88e
       +67ff3389c4e6739e7f3f33ffccccf0311cce07ffffffffffffffffffffffffff
       +ffe1
       +0e221f39ccce67f3fe7ce673ff9cce79fcf3cce7fff9fffe79fff3cce673ff9c
       +9e733399ce67fff99cce7ff999cf3e73f99ff3e7339fff3f9ffffcf3fffcf9cc
       +e7fe799cce4f33fe7f3e73ff9c9e733399ce67ffffffffffffffffffffffffff
       +ffe1
       +0e889f39cfce63f1fe7ce673ff9cce79fcf3cce7ffc7fffe79fff3cce673ff9f
       +9e73f399ce7ffff99cce7ff9f9cf3e73f98ff3e7339fff3f9ffffcf3fffcf9cc
       +e7fe799cce4f31fe7f3e73ff9f9e73f399ce7fffffffffffffffffffffffffff
       +ffe1
       +0e221f39cfc070783e7ce673ff9cce79fcf3cce7fff9ffe079fff3cce603ff9f
       +9e73f3980e7fff819cce7ff9f80f3e0381c1f3e7339fff3f9fffc0f3fffcf9cc
       +07fe799cce4f383e7f3e03ff9f9e73f3980e7fffffffffffffffffffffffffff
       +ffe1
       +0e889f39cfcffe3f1e7ce673ff9cce79fcf3cce7fff9ffce79fff3cce67fff9f
       +9e73f399fe7fff399cce7ff9f9ff3e7f39f8f3e7339fff3f9fff9cf3fffcf9cc
       +fffe799cce4f3f1e7f3e7fff9f9e73f399fe7fffffffffffffffffffffffffff
       +ffe1
       +0e221f39cfcfff3f9e7ce673ff9cce79fcf3cce7fff9ffce79fff3cce67fff9f
       +9e73f399fe7fff399cce7ff9f9ff3e7f39fcf3e7339fff3f9fff9cf3fffcf9cc
       +fffe799cce4f3f9e7f3e7fff9f9e73f399fe7f8fffffffffffffffffffffffff
       +ffe1
       +0e889f33cfe667339e7ce723ff99c479fcf99ce7ff39ffcc79fff99ce733ffcc
       +ccf3f39cce7fff319ce47ff9fccf3f33319cf3e7391fff3f9fff98f3fffcf9ce
       +67ff3399cce6739e7f3f33ffccccf3f39cce7f8fffffffffffffffffffffffff
       +ffe1
       +0e221f0783f0f078300ce793ff83e27c3e1c3ce7ff83ffe27c3ffc3ce787ffe1
       +e1e0f39e1c1fff899cf27ff07e18078789c180673c9ff807c3ffc4f87ffe19cf
       +0fff8783c1f0f8300f8787ffe1e1e0f39e1c1f8fffffffffffffffffffffffff
       +ffe1
       +0e889f3ffffffffffffffff3ffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffff9fffffffffffffffffffff
       +ffffff9fcfffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221f3ffffffffffffffe73ffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffffffffffffffffff39fffffffffffffffffffff
       +ffffff9fcfffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889f3fffffffffffffff07ffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffffffffffffffffff83fffffffffffffffffffff
       +ffffff9fcfffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889ffffffffffffffffffffffffff9fffffffc1fffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221f39fffffffffffffffffffffff9ffffffbf9fffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889f39ffffffffffffffffffffffffffffff3f9fffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221f11c3c6787ffe1e1e4739e1c8c1fc391c079f39ffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889f1199e0733ffcccce2339ccc479f9988f3f9f39ffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221f493ce6673ff9c9e6733d9cce79f399cf3f9f3dffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889f493ce7e73ff9f9e673999cce79f399cf3f9f99ffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221f493ce7e03ff9f9e6739b80ce79f019cf3f9f9bffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889f493ce7e7fff9f9e6739b9fce79f3f9cf3f9f9bffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221f793ce7e7fff9f9e673c79fce79f3f9cf3f9fc7e3ffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889f7999e7f33ffcccce73c7ccce79f999cf3f9fc7e3ffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221f79c3c1f87ffe1e1e73c7e1ce403c39cf8403e7e3ffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889fffffffffffffffffffffffffffffffffffffeff3ffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221fffffffffffffffffffffffffffffffffffffcff3ffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889fffffffffffffffffffffffffffffffffffff8fe7ffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889f3fffffffffffffffffffffffffffffff3fffff07fffff3fffffffe0fe7
       +ffe7ffffcfffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221f3ffffbfdffffffff07ffffffffffffff3fffffe7fffff3ffffffffcfe7
       +ffe7ffffcfffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889f3ffff3f9fffffffe73ffffffffffffffffffffe7fffff3ffffffffcfff
       +ffe7ffffcfffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221f279cc06038791ffe73ff2de0e73ffc183cb727e7ce7ff27e1fff87cf07
       +f0e73c3e0fffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889f139cf3f9f3388ffff3ff00ce673ff9cf3c0313e7ce7ff13ccfff33cfe7
       +e667799ccfffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221f399cf3f9e799cffff3ff24fe67bff9ff3c9339e7cf7ff399cffe73cfe7
       +ce66f399cfffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889f399cf3f9e799cfff8fff24fe733ff8ff3c9339e7e67ff399cffe7fcfe7
       +cfe5f399cfffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221f399cf3f9e799cffff3ff24e0737ffc1f3c9339e7e6fff3980ffe7fcfe7
       +cfe1f019cfffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889f399cf3f9e799cffff3ff24ce737fff8f3c9339e7e6fff399fffe7fcfe7
       +cfe4f3f9cfffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221f399cf3f9e799cffff3ff24ce78ffffcf3c9339e7f1fff399fffe7fcfe7
       +cfe673f9cf1fffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889f3388f3f9f339cffe73ff24cc78fff9cf3c9333e7f1fff33ccfff33cfe7
       +e667399c8f1fffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221f07c4f87c3879cfff07ff24e27cfffc1804930700f9fff07e1fff860100
       +f0e73c3e4f1fffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889ffffffffffffffffffffffffffdffffffffff3ffffbffffffffffffffff
       +ffffffffff9fffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221ffffffffffffffffffffffffff9ffffffffff3ffff3ffffffffffffffff
       +ffffffffff9fffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889ffffffffffffffffffffffffff1ffffffffff3fffe3ffffffffffffffff
       +ffffffffff3fffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889fff9fffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221fff9fffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889fff9fffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221f3991f0e33c39cc9f0f23ffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889f3d88e6703999cc4e6711ffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221f259cce733399cce4f339ffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889f259cce73f399cce4f339ffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221f259cc073f019cce4f339ffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889f939ccff3f3f9cce4f339ffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221f939ccff3f3f9cce4f339ffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889f939ce673f9988cce6739ffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221f939cf0e0fc3c4c1f0f39ffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889ffffffffffffffcffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221ffffffffffffffcffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889ffffffffffffffcffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e221fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e889fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0e001fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffe1
       +0000000000000000000000000000000000000000000000000000000000000000
       +0000000000000000000000000000000000000000000000000000000000000000
       +0000000000000000000000000000000000000000000000000000000000000000
       +0001
       +0000000000000000000000000000000000000000000000000000000000000000
       +0000000000000000000000000000000000000000000000000000000000000000
       +0000000000000000000000000000000000000000000000000000000000000000
       +0001
       +0000000000000000000000000000000000000000000000000000000000000000
       +0000000000000000000000000000000000000000000000000000000000000000
       +0000000000000000000000000000000000000000000000000000000000000000
       +0001
       +0000000000000000000000000000000000000000000000000000000000000000
       +0000000000000000000000000000000000000000000000000000000000000000
       +0000000000000000000000000000000000000000000000000000000000000000
       +0001
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffff
       +showpage
       +%ps_include: inclusion end
       +grestore
       +PS-include-dict-dw begin
       +o 0 ne {gsave A defaultmatrix /A ed llx lly nice urx ury nice
       +        initgraphics 0.1 setlinewidth boxpath stroke grestore} if
       +clear o-stack aload pop
       +context end restore
       +%ps_include: end
       +/saveobj save def
       +mark
       +8 I f
       +(Figure 1. A typical)3 623 1 720 3580 t
       +8 CW f
       +(sam)1370 3580 w
       +8 I f
       +( The)1 167(screen, with the editing menu presented.)5 1320 2 1541 3580 t
       +8 CW f
       +(sam)3055 3580 w
       +8 I f
       +(\(command language\) window is in the middle, with file)8 1814 1 3226 3580 t
       +( partially obscured window is a)5 1029( The)1 164( user interface makes it easy to create these abutting windows.\))10 2059( \(The)1 191(windows above and below.)3 877 5 720 3680 t
       +( Each)1 206( typing and mouse operations apply, as indicated by its heavy border.)11 2242( uppermost window is that to which)6 1143( The)1 161(third file window.)2 568 5 720 3780 t
       +( The)1 162(window has its current text highlighted in reverse video.)8 1810 2 720 3880 t
       +8 CW f
       +(sam)2714 3880 w
       +8 I f
       +( string on the last visible line, indi-)7 1121(window's current text is the null)5 1039 2 2880 3880 t
       +( also Figure 2.)3 470( See)1 150(cated by a vertical bar.)4 740 3 720 3980 t
       +10 R f
       +( and leave dot set to the text resulting from the change.)11 2202(most commands make some change to the text in dot)9 2118 2 720 4220 t
       +(For example, the delete command,)4 1389 1 720 4340 t
       +10 CW f
       +(d)2135 4340 w
       +10 R f
       +(, deletes the text in dot, replacing it by the null string and setting dot to)15 2845 1 2195 4340 t
       +( change command,)2 767( The)1 213(the result.)1 402 3 720 4460 t
       +10 CW f
       +(c)2135 4460 w
       +10 R f
       +(, replaces dot by text delimited by an arbitrary punctuation character,)10 2845 1 2195 4460 t
       +( Thus,)1 275(conventionally a slash.)2 913 2 720 4580 t
       +9 CW f
       +(c/Peter/)1008 4750 w
       +10 R f
       +(replaces the text in dot by the string)7 1429 1 720 4930 t
       +10 CW f
       +(Peter)2174 4930 w
       +10 R f
       +(. Similarly,)1 473 1 2474 4930 t
       +9 CW f
       +(a/Peter/)1008 5100 w
       +10 R f
       +(\(append\) adds the string after dot, and)6 1516 1 720 5280 t
       +9 CW f
       +(i/Peter/)1008 5450 w
       +10 R f
       +( three leave dot set to the new text,)8 1389( All)1 178(\(insert\) inserts before dot.)3 1031 3 720 5630 t
       +10 CW f
       +(Peter)3343 5630 w
       +10 R f
       +(.)3643 5630 w
       +( lexically terminates a command.)4 1335(Newlines are part of the syntax of commands: the newline character)10 2735 2 970 5786 t
       +( it is often convenient to insert)6 1282( since)1 242( But)1 207(Within the inserted text, however, newlines are never implicit.)8 2589 4 720 5906 t
       +(multiple lines of text,)3 856 1 720 6026 t
       +10 CW f
       +(sam)1601 6026 w
       +10 R f
       +(has a special syntax for that case:)6 1330 1 1806 6026 t
       +9 CW f
       +(a)1008 6196 w
       +(some lines of text)3 972 1 1008 6306 t
       +(to be inserted in the file,)5 1458 1 1008 6416 t
       +(terminated by a period)3 1188 1 1008 6526 t
       +(on a line by itself)4 1026 1 1008 6636 t
       +(.)1008 6746 w
       +10 R f
       +(In the one-line syntax, a newline character may be specified by a C-like escape, so)14 3291 1 720 6926 t
       +9 CW f
       +(c/\\n/)1008 7096 w
       +10 R f
       +(replaces dot by a single newline character.)6 1692 1 720 7276 t
       +cleartomark
       +showpage
       +saveobj restore
       +%%EndPage: 3 3
       +%%Page: 4 4
       +/saveobj save def
       +mark
       +4 pagesetup
       +10 R f
       +(- 4 -)2 166 1 2797 480 t
       +10 CW f
       +(Sam)970 840 w
       +10 R f
       +(also has a substitute command,)4 1241 1 1175 840 t
       +10 CW f
       +(s)2441 840 w
       +10 R f
       +(:)2501 840 w
       +9 CW f
       +(s/)1008 1010 w
       +9 I f
       +(expression)1116 1010 w
       +9 CW f
       +(/)1501 1010 w
       +9 I f
       +(replacement)1555 1010 w
       +9 CW f
       +(/)2000 1010 w
       +10 R f
       +( if dot is the)4 530( Thus,)1 288( expression.)1 490(substitutes the replacement text for the first match, in dot, of the regular)12 3012 4 720 1190 t
       +(string)720 1310 w
       +10 CW f
       +(Peter)973 1310 w
       +10 R f
       +(, the command)2 591 1 1273 1310 t
       +9 CW f
       +(s/t/st/)1008 1480 w
       +10 R f
       +(changes it to)2 517 1 720 1660 t
       +10 CW f
       +(Pester)1268 1660 w
       +10 R f
       +( general,)1 349(. In)1 164 2 1628 1660 t
       +10 CW f
       +(s)2172 1660 w
       +10 R f
       +(is unnecessary, but it was inherited from)6 1653 1 2263 1660 t
       +10 CW f
       +(ed)3947 1660 w
       +10 R f
       +( has some conve-)3 711(and it)1 231 2 4098 1660 t
       +( instance, the replacement text may include the matched text, specified by)11 2947( For)1 189(nient variations.)1 644 3 720 1780 t
       +10 CW f
       +(&)4525 1780 w
       +10 R f
       +(:)4585 1780 w
       +9 CW f
       +(s/Peter/Oh, &, &, &, &!/)4 1296 1 1008 1950 t
       +10 R f
       +(There are also three commands that apply programs to text:)9 2376 1 970 2166 t
       +9 CW f
       +(<)1008 2336 w
       +9 I f
       +(Unix program)1 513 1 1116 2336 t
       +10 R f
       +( the)1 149( Similarly,)1 450(replaces dot by the output of the Unix program.)8 1908 3 720 2516 t
       +10 CW f
       +(>)3254 2516 w
       +10 R f
       +(command runs the program with dot as its)7 1699 1 3341 2516 t
       +(standard input, and)2 763 1 720 2636 t
       +10 CW f
       +(|)1508 2636 w
       +10 R f
       +( example,)1 388( For)1 189(does both.)1 411 3 1593 2636 t
       +9 CW f
       +(| sort)1 324 1 1008 2806 t
       +10 R f
       +( special sig-)2 483( newlines have no)3 724( Again,)1 321(replaces dot by the result of applying the standard sorting utility to it.)12 2792 4 720 2986 t
       +( these)1 235(nificance for)1 512 2 720 3106 t
       +10 CW f
       +(sam)1497 3106 w
       +10 R f
       +( text acted upon and resulting from these commands is not neces-)11 2665(commands. The)1 668 2 1707 3106 t
       +(sarily bounded by newlines, although for connection with Unix programs, newlines may be necessary to)14 4320 1 720 3226 t
       +(obey conventions.)1 727 1 720 3346 t
       +(One more command:)2 843 1 970 3502 t
       +10 CW f
       +(p)1838 3502 w
       +10 R f
       +( I summarizes)2 560( Table)1 277(prints the contents of dot.)4 1019 3 1923 3502 t
       +10 CW f
       +(sam)3804 3502 w
       +10 R f
       +('s commands.)1 555 1 3984 3502 t
       +(The value of dot may be changed by specifying an)9 2015 1 970 3658 t
       +10 I f
       +(address)3010 3658 w
       +10 R f
       +( simplest address is)3 778( The)1 206(for the command.)2 709 3 3347 3658 t
       +(a line number:)2 577 1 720 3778 t
       +9 CW f
       +(3)1008 3948 w
       +10 R f
       +(refers to the third line of the file, so)8 1417 1 720 4128 t
       +9 CW f
       +(3d)1008 4298 w
       +10 R f
       +( 3.)1 111(deletes the third line of the file, and implicitly renumbers the lines so the old line 4 is now numbered)19 4209 2 720 4478 t
       +(\(This is one of the few places where)7 1485 1 720 4598 t
       +10 CW f
       +(sam)2236 4598 w
       +10 R f
       +( Line)1 239(deals with lines directly.\))3 1028 2 2447 4598 t
       +10 CW f
       +(0)3745 4598 w
       +10 R f
       +( string at the begin-)4 797(is the null)2 407 2 3836 4598 t
       +( a command consists of only an address, a)8 1846( If)1 137(ning of the file.)3 679 3 720 4718 t
       +10 CW f
       +(p)3429 4718 w
       +10 R f
       +(command is assumed, so typing an)5 1504 1 3536 4718 t
       +(unadorned)720 4838 w
       +10 CW f
       +(3)1173 4838 w
       +10 R f
       +( are a couple of other basic addresses: a period addresses)10 2325( There)1 288(prints line 3 on the terminal.)5 1163 3 1264 4838 t
       +(dot itself; and a dollar sign \()6 1127 1 720 4958 t
       +10 CW f
       +($)1847 4958 w
       +10 R f
       +(\) addresses the null string at the end of the file.)10 1872 1 1907 4958 t
       +( the address)2 477( Thus,)1 278( single substring of the file.)5 1109(An address is always a)4 917 4 970 5114 t
       +10 CW f
       +(3)3779 5114 w
       +10 R f
       +(addresses the characters after)3 1173 1 3867 5114 t
       +( A)1 125( file.)1 186(the second newline of the file through the third newline of the)11 2515 3 720 5234 t
       +10 I f
       +(compound address)1 755 1 3574 5234 t
       +10 R f
       +(is constructed by)2 683 1 4357 5234 t
       +(the comma operator)2 798 1 720 5354 t
       +9 I f
       +(address1)1008 5524 w
       +9 CW f
       +(,)1333 5524 w
       +9 I f
       +(address2)1387 5524 w
       +10 R f
       +( the substring of the file from the beginning of)9 1860(and addresses)1 551 2 720 5704 t
       +10 I f
       +(address1)3157 5704 w
       +10 R f
       +(to the end of)3 505 1 3544 5704 t
       +10 I f
       +(address2)4075 5704 w
       +10 R f
       +( example,)1 389(. For)1 215 2 4436 5704 t
       +(the command)1 543 1 720 5824 t
       +10 CW f
       +(3,5p)1290 5824 w
       +10 R f
       +(prints the third through fifth lines of the file and)9 1936 1 1557 5824 t
       +10 CW f
       +(.,$d)3520 5824 w
       +10 R f
       +( the begin-)2 429(deletes the text from)3 824 2 3787 5824 t
       +(ning of dot to the end of the file.)8 1296 1 720 5944 t
       +(These addresses are all absolute positions in the file, but)9 2247 1 970 6100 t
       +10 CW f
       +(sam)3242 6100 w
       +10 R f
       +( indicated by)2 518(also has relative addresses,)3 1075 2 3447 6100 t
       +10 CW f
       +(+)720 6220 w
       +10 R f
       +(or)805 6220 w
       +10 CW f
       +(-)913 6220 w
       +10 R f
       +( example,)1 388(. For)1 214 2 973 6220 t
       +9 CW f
       +($-3)1008 6390 w
       +10 R f
       +(is the third line before the end of the file and)10 1780 1 720 6570 t
       +9 CW f
       +(.+1)1008 6740 w
       +10 R f
       +( no address appears to the left of the)8 1457( If)1 118(is the line after dot.)4 782 3 720 6920 t
       +10 CW f
       +(+)3104 6920 w
       +10 R f
       +(or)3191 6920 w
       +10 CW f
       +(-)3301 6920 w
       +10 R f
       +(, dot is assumed; if nothing appears to the)8 1679 1 3361 6920 t
       +(right,)720 7040 w
       +10 CW f
       +(1)959 7040 w
       +10 R f
       +( Therefore,)1 467(is assumed.)1 461 2 1044 7040 t
       +10 CW f
       +(.+1)1997 7040 w
       +10 R f
       +(may be abbreviated to just a plus sign.)7 1532 1 2202 7040 t
       +(The)970 7196 w
       +10 CW f
       +(+)1156 7196 w
       +10 R f
       +(operator acts relative to the end of its first argument, while the)11 2556 1 1247 7196 t
       +10 CW f
       +(-)3834 7196 w
       +10 R f
       +(operator acts relative to the)4 1114 1 3926 7196 t
       +(beginning. Thus)1 679 1 720 7316 t
       +10 CW f
       +(.+1)1428 7316 w
       +10 R f
       +( first line after dot,)4 758(addresses the)1 533 2 1637 7316 t
       +10 CW f
       +(.-)2956 7316 w
       +10 R f
       +(addresses the first line before dot, and)6 1534 1 3104 7316 t
       +10 CW f
       +(+-)4666 7316 w
       +10 R f
       +(refers)4814 7316 w
       +cleartomark
       +showpage
       +saveobj restore
       +%%EndPage: 4 4
       +%%Page: 5 5
       +/saveobj save def
       +mark
       +5 pagesetup
       +10 R f
       +(- 5 -)2 166 1 2797 480 t
       +(Table I.)1 310 1 2393 900 t
       +10 CW f
       +(Sam)2728 900 w
       +10 R f
       +(commands)2933 900 w
       +10 S f
       +(_ ________________________________________________________________________)1 3605 1 1077 968 t
       +10 R f
       +(Text commands)1 641 1 1077 1136 t
       +10 S f
       +(_ ________________________________________________________________________)1 3605 1 1077 1204 t
       +10 CW f
       +(a/)1077 1372 w
       +10 I f
       +(text)1197 1372 w
       +10 CW f
       +(/)1341 1372 w
       +10 R f
       +(Append text after dot)3 851 1 2160 1372 t
       +10 CW f
       +(c/)1077 1492 w
       +10 I f
       +(text)1197 1492 w
       +10 CW f
       +(/)1341 1492 w
       +10 R f
       +(Change text in dot)3 736 1 2160 1492 t
       +10 CW f
       +(i/)1077 1612 w
       +10 I f
       +(text)1197 1612 w
       +10 CW f
       +(/)1341 1612 w
       +10 R f
       +(Insert text before dot)3 834 1 2160 1612 t
       +10 CW f
       +(d)1077 1732 w
       +10 R f
       +(Delete text in dot)3 691 1 2160 1732 t
       +10 CW f
       +(s/)1077 1852 w
       +10 I f
       +(regexp)1197 1852 w
       +10 CW f
       +(/)1468 1852 w
       +10 I f
       +(text)1528 1852 w
       +10 CW f
       +(/)1672 1852 w
       +10 R f
       +(Substitute text for match of regular expression in dot)8 2109 1 2160 1852 t
       +10 CW f
       +(m)1077 1972 w
       +10 I f
       +(address)1197 1972 w
       +10 R f
       +(Move text in dot after address)5 1195 1 2160 1972 t
       +10 CW f
       +(t)1077 2092 w
       +10 I f
       +(address)1197 2092 w
       +10 R f
       +(Copy text in dot after address)5 1179 1 2160 2092 t
       +10 S f
       +(_ ________________________________________________________________________)1 3605 1 1077 2160 t
       +10 R f
       +(Display commands)1 769 1 1077 2328 t
       +10 S f
       +(_ ________________________________________________________________________)1 3605 1 1077 2396 t
       +10 CW f
       +(p)1077 2540 w
       +10 R f
       +(Print contents of dot)3 814 1 2160 2540 t
       +10 CW f
       +(=)1077 2660 w
       +10 R f
       +(Print value \(line numbers and character numbers\) of dot)8 2234 1 2160 2660 t
       +10 S f
       +(_ ________________________________________________________________________)1 3605 1 1077 2728 t
       +10 R f
       +(File commands)1 614 1 1077 2896 t
       +10 S f
       +(_ ________________________________________________________________________)1 3605 1 1077 2964 t
       +10 CW f
       +(b)1077 3108 w
       +10 I f
       +(file-list)1197 3108 w
       +10 R f
       +(Set current file to first file in list that)8 1466 1 2160 3108 t
       +10 CW f
       +(sam)3651 3108 w
       +10 R f
       +(has in menu)2 483 1 3856 3108 t
       +10 CW f
       +(B)1077 3228 w
       +10 I f
       +(file-list)1197 3228 w
       +10 R f
       +(Same as)1 330 1 2160 3228 t
       +10 CW f
       +(b)2515 3228 w
       +10 R f
       +(, but load new files)4 763 1 2575 3228 t
       +10 CW f
       +(n)1077 3348 w
       +10 R f
       +(Print menu lines of all files)5 1086 1 2160 3348 t
       +10 CW f
       +(D)1077 3468 w
       +10 I f
       +(file-list)1197 3468 w
       +10 R f
       +(Delete named files from)3 967 1 2160 3468 t
       +10 CW f
       +(sam)3152 3468 w
       +10 S f
       +(_ ________________________________________________________________________)1 3605 1 1077 3536 t
       +10 R f
       +(I/O commands)1 591 1 1077 3704 t
       +10 S f
       +(_ ________________________________________________________________________)1 3605 1 1077 3772 t
       +10 CW f
       +(e)1077 3916 w
       +10 I f
       +(filename)1197 3916 w
       +10 R f
       +(Replace file with named disc file)5 1317 1 2160 3916 t
       +10 CW f
       +(r)1077 4036 w
       +10 I f
       +(filename)1197 4036 w
       +10 R f
       +(Replace dot by contents of named disc file)7 1700 1 2160 4036 t
       +10 CW f
       +(w)1077 4156 w
       +10 I f
       +(filename)1197 4156 w
       +10 R f
       +(Write file to named disc file)5 1123 1 2160 4156 t
       +10 CW f
       +(f)1077 4276 w
       +10 I f
       +(filename)1197 4276 w
       +10 R f
       +(Set file name and print new menu line)7 1523 1 2160 4276 t
       +10 CW f
       +(<)1077 4396 w
       +10 I f
       +(Unix-command)1197 4396 w
       +10 R f
       +(Replace dot by standard output of command)6 1770 1 2160 4396 t
       +10 CW f
       +(>)1077 4516 w
       +10 I f
       +(Unix-command)1197 4516 w
       +10 R f
       +(Send dot to standard input of command)6 1577 1 2160 4516 t
       +10 CW f
       +(|)1077 4636 w
       +10 I f
       +(Unix-command)1197 4636 w
       +10 R f
       +(Replace dot by result of command applied to dot)8 1948 1 2160 4636 t
       +10 CW f
       +(!)1077 4756 w
       +10 I f
       +(Unix-command)1197 4756 w
       +10 R f
       +(Run the command)2 733 1 2160 4756 t
       +10 S f
       +(_ ________________________________________________________________________)1 3605 1 1077 4824 t
       +10 R f
       +(Loops and conditionals)2 933 1 1077 4992 t
       +10 S f
       +(_ ________________________________________________________________________)1 3605 1 1077 5060 t
       +10 CW f
       +(x/)1077 5204 w
       +10 I f
       +(regexp)1197 5204 w
       +10 CW f
       +(/)1468 5204 w
       +10 I f
       +(command)1588 5204 w
       +10 R f
       +(For each match of regexp, set dot and run command)9 2079 1 2160 5204 t
       +10 CW f
       +(y/)1077 5324 w
       +10 I f
       +(regexp)1197 5324 w
       +10 CW f
       +(/)1468 5324 w
       +10 I f
       +(command)1588 5324 w
       +10 R f
       +(Between adjacent matches of regexp, set dot and run command)9 2522 1 2160 5324 t
       +10 CW f
       +(X/)1077 5444 w
       +10 I f
       +(regexp)1197 5444 w
       +10 CW f
       +(/)1468 5444 w
       +10 I f
       +(command)1588 5444 w
       +10 R f
       +(Run command in each file whose menu line matches regexp)9 2404 1 2160 5444 t
       +10 CW f
       +(Y/)1077 5564 w
       +10 I f
       +(regexp)1197 5564 w
       +10 CW f
       +(/)1468 5564 w
       +10 I f
       +(command)1588 5564 w
       +10 R f
       +(Run command in each file whose menu line does not match)10 2386 1 2160 5564 t
       +10 CW f
       +(g/)1077 5684 w
       +10 I f
       +(regexp)1197 5684 w
       +10 CW f
       +(/)1468 5684 w
       +10 I f
       +(command)1588 5684 w
       +10 R f
       +(If dot contains a match of regexp, run command)8 1921 1 2160 5684 t
       +10 CW f
       +(v/)1077 5804 w
       +10 I f
       +(regexp)1197 5804 w
       +10 CW f
       +(/)1468 5804 w
       +10 I f
       +(command)1588 5804 w
       +10 R f
       +(If dot does not contain a match of regexp, run command)10 2243 1 2160 5804 t
       +10 S f
       +(_ ________________________________________________________________________)1 3605 1 1077 5872 t
       +10 R f
       +(Miscellany)1077 6040 w
       +10 S f
       +(_ ________________________________________________________________________)1 3605 1 1077 6108 t
       +10 CW f
       +(k)1077 6252 w
       +10 R f
       +(Set address mark to value of dot)6 1287 1 2160 6252 t
       +10 CW f
       +(q)1077 6372 w
       +10 R f
       +(Quit)2160 6372 w
       +10 CW f
       +(u)1077 6492 w
       +10 I f
       +(n)1197 6492 w
       +10 R f
       +(Undo last)1 386 1 2160 6492 t
       +10 I f
       +(n)2571 6492 w
       +10 R f
       +(\(default 1\) changes)2 764 1 2646 6492 t
       +10 CW f
       +({ })1 180 1 1077 6612 t
       +10 R f
       +(Braces group commands)2 987 1 2160 6612 t
       +10 S f
       +(_ ________________________________________________________________________)1 3605 1 1077 6668 t
       +cleartomark
       +showpage
       +saveobj restore
       +%%EndPage: 5 5
       +%%Page: 6 6
       +/saveobj save def
       +mark
       +6 pagesetup
       +10 R f
       +(- 6 -)2 166 1 2797 480 t
       +( multiple lines, and)3 788( may span)2 417( \(Dot)1 239(to the line containing the end of dot.)7 1491 4 720 840 t
       +10 CW f
       +(+)3687 840 w
       +10 R f
       +(selects the line after the end of)6 1261 1 3779 840 t
       +(dot, then)1 350 1 720 960 t
       +10 CW f
       +(-)1095 960 w
       +10 R f
       +(backs up one line.\))3 754 1 1180 960 t
       +( addresses the text matched by the expression.)7 1855(The final type of address is a regular expression, which)9 2215 2 970 1116 t
       +(The expression is enclosed in slashes, as in)7 1720 1 720 1236 t
       +9 CW f
       +(/)1008 1406 w
       +9 I f
       +(expression)1062 1406 w
       +9 CW f
       +(/)1447 1406 w
       +10 R f
       +( the same as those in the Unix program)8 1647(The expressions are)2 812 2 720 1586 t
       +10 CW f
       +(egrep)3215 1586 w
       +10 R f
       +(,)3515 1586 w
       +6 R f
       +(6,7)3540 1536 w
       +10 R f
       +(and include closures, alternations,)3 1389 1 3651 1586 t
       +( find the)2 335( They)1 256(and so on.)2 410 3 720 1706 t
       +10 I f
       +(leftmost longest)1 632 1 1747 1706 t
       +10 R f
       +( match after the)3 623(string that matches the expression, that is, the first)8 2012 2 2405 1706 t
       +( the search is started, and if more than one match begins at the same spot, the longest such)18 3834(point where)1 486 2 720 1826 t
       +( assume familiarity with the syntax for regular expressions in Unix programs.)11 3096(match. \(I)1 385 2 720 1946 t
       +6 R f
       +(9)4201 1896 w
       +10 R f
       +(\) For example,)2 585 1 4231 1946 t
       +9 CW f
       +(/x/)1008 2116 w
       +10 R f
       +(matches the next)2 671 1 720 2296 t
       +10 CW f
       +(x)1416 2296 w
       +10 R f
       +(character in the file,)3 797 1 1501 2296 t
       +9 CW f
       +(/xx*/)1008 2466 w
       +10 R f
       +(matches the next run of one or more)7 1444 1 720 2646 t
       +10 CW f
       +(x)2189 2646 w
       +10 R f
       +('s, and)1 266 1 2249 2646 t
       +9 CW f
       +(/x|Peter/)1008 2816 w
       +10 R f
       +(matches the next)2 677 1 720 2996 t
       +10 CW f
       +(x)1425 2996 w
       +10 R f
       +(or)1513 2996 w
       +10 CW f
       +(Peter)1624 2996 w
       +10 R f
       +( character' operator, a)3 885( compatibility with other Unix programs, the `any)7 2014(. For)1 217 3 1924 2996 t
       +(period, does not match a newline, so)6 1459 1 720 3116 t
       +9 CW f
       +(/.*/)1008 3286 w
       +10 R f
       +( from dot to the end of the line, but excludes the newline and so will not match across the)19 3663(matches the text)2 657 2 720 3466 t
       +(line boundary.)1 577 1 720 3586 t
       +( is forwards by default, so)5 1222( direction)1 416( The)1 241(Regular expressions are always relative addresses.)5 2191 4 970 3742 t
       +10 CW f
       +(/Peter/)720 3862 w
       +10 R f
       +(is really an abbreviation for)4 1103 1 1165 3862 t
       +10 CW f
       +(+/Peter/)2293 3862 w
       +10 R f
       +( search can be reversed with a minus sign, so)9 1796(. The)1 230 2 2773 3862 t
       +9 CW f
       +(-/Peter/)1008 4032 w
       +10 R f
       +(finds the first)2 595 1 720 4212 t
       +10 CW f
       +(Peter)1371 4212 w
       +10 R f
       +( with other address forms, so)5 1314( expressions may be used)4 1139( Regular)1 397(before dot.)1 463 4 1727 4212 t
       +10 CW f
       +(0+/Peter/)720 4332 w
       +10 R f
       +(finds the first)2 539 1 1288 4332 t
       +10 CW f
       +(Peter)1855 4332 w
       +10 R f
       +(in the file and)3 561 1 2183 4332 t
       +10 CW f
       +($-/Peter/)2772 4332 w
       +10 R f
       +( II summarizes)2 599( Table)1 280(finds the last.)2 542 3 3340 4332 t
       +10 CW f
       +(sam)4788 4332 w
       +10 R f
       +('s)4968 4332 w
       +(addresses.)720 4452 w
       +( who use Unix text editors such as)7 1389(The language discussed so far will not seem novel to people)10 2422 2 970 4608 t
       +10 CW f
       +(ed)4809 4608 w
       +10 R f
       +(or)4957 4608 w
       +10 CW f
       +(vi)720 4728 w
       +10 R f
       +(.)840 4728 w
       +6 R f
       +(9)865 4678 w
       +10 R f
       +( operations these commands allow, with the exception of regular expres-)10 2897(Moreover, the kinds of editing)4 1222 2 921 4728 t
       +( Indeed,)1 351( a mouse-based interface.)3 1028(sions and line numbers, are clearly more conveniently handled by)9 2659 3 720 4848 t
       +10 CW f
       +(sam)4788 4848 w
       +10 R f
       +('s)4968 4848 w
       +( For)1 194( usually made.)2 590(mouse language \(discussed at length below\) is the means by which simple changes are)13 3536 3 720 4968 t
       +(large or repetitive changes, however, a textual language outperforms a manual interface.)11 3523 1 720 5088 t
       +(Imagine that, instead of deleting just one occurrence of the string)10 2708 1 970 5244 t
       +10 CW f
       +(Peter)3714 5244 w
       +10 R f
       +( wanted to eliminate)3 849(, we)1 177 2 4014 5244 t
       +(every)720 5364 w
       +10 CW f
       +(Peter)970 5364 w
       +10 R f
       +( some text.)2 442( needed is an iterator that runs a command for each occurrence of)12 2656(. What's)1 367 3 1270 5364 t
       +10 CW f
       +(Sam)4788 5364 w
       +10 R f
       +('s)4968 5364 w
       +(iterator is called)2 643 1 720 5484 t
       +10 CW f
       +(x)1388 5484 w
       +10 R f
       +(, for extract:)2 490 1 1448 5484 t
       +9 CW f
       +(x/)1008 5654 w
       +9 I f
       +(expression)1116 5654 w
       +9 CW f
       +(/)1501 5654 w
       +9 I f
       +(command)1609 5654 w
       +10 R f
       +( text matched)2 554(finds all matches in dot of the specified expression, and for each such match, sets dot to the)17 3766 2 720 5834 t
       +( to delete all the)4 638( So)1 156(and runs the command.)3 932 3 720 5954 t
       +10 CW f
       +(Peters:)2471 5954 w
       +9 CW f
       +(0,$ x/Peter/ d)2 756 1 1008 6124 t
       +10 R f
       +( are to improve readability;)4 1163(\(Blanks in these examples)3 1100 2 720 6304 t
       +10 CW f
       +(sam)3027 6304 w
       +10 R f
       +( This)1 247(neither requires nor interprets them.\))4 1542 2 3251 6304 t
       +(searches the entire file \()4 964 1 720 6424 t
       +10 CW f
       +(0,$)1684 6424 w
       +10 R f
       +( of the string)3 514(\) for occurrences)2 680 2 1864 6424 t
       +10 CW f
       +(Peter)3085 6424 w
       +10 R f
       +(, and runs the)3 544 1 3385 6424 t
       +10 CW f
       +(d)3956 6424 w
       +10 R f
       +(command with dot set to)4 997 1 4043 6424 t
       +( contrast, the comparable)3 1003( \(By)1 200(each such occurrence.)2 876 3 720 6544 t
       +10 CW f
       +(ed)2824 6544 w
       +10 R f
       +(command would delete all)3 1057 1 2969 6544 t
       +10 I f
       +(lines)4051 6544 w
       +10 R f
       +(containing)4265 6544 w
       +10 CW f
       +(Peter)4712 6544 w
       +10 R f
       +(;)5012 6544 w
       +10 CW f
       +(sam)720 6664 w
       +10 R f
       +(deletes only the)2 653 1 938 6664 t
       +10 CW f
       +(Peters)1629 6664 w
       +10 R f
       +( address)1 337(.\) The)1 276 2 1989 6664 t
       +10 CW f
       +(0,$)2640 6664 w
       +10 R f
       +( be abbreviated to just a)5 1011(is commonly used, and may)4 1171 2 2858 6664 t
       +( another example,)2 712(comma. As)1 480 2 720 6784 t
       +9 CW f
       +(, x/Peter/ p)2 648 1 1008 6954 t
       +10 R f
       +(prints a list of)3 556 1 720 7134 t
       +10 CW f
       +(Peters,)1303 7134 w
       +10 R f
       +(one for each appearance in the file, with no intervening text \(not even newlines to)14 3290 1 1750 7134 t
       +(separate the instances\).)2 922 1 720 7254 t
       +cleartomark
       +showpage
       +saveobj restore
       +%%EndPage: 6 6
       +%%Page: 7 7
       +/saveobj save def
       +mark
       +7 pagesetup
       +10 R f
       +(- 7 -)2 166 1 2797 480 t
       +(Table II.)1 343 1 2402 900 t
       +10 CW f
       +(Sam)2770 900 w
       +10 R f
       +(addresses)2975 900 w
       +10 S f
       +(_ _________________________________________________________________)1 3264 1 1248 968 t
       +10 R f
       +(Simple addresses)1 691 1 1248 1136 t
       +10 S f
       +(_ _________________________________________________________________)1 3264 1 1248 1204 t
       +10 CW f
       +(#)1248 1348 w
       +10 I f
       +(n)1308 1348 w
       +10 R f
       +(The empty string after character)4 1279 1 2250 1348 t
       +10 I f
       +(n)3554 1348 w
       +(n)1248 1468 w
       +10 R f
       +(Line)2250 1468 w
       +10 I f
       +(n)2458 1468 w
       +10 R f
       +(.)2508 1468 w
       +10 CW f
       +(/)1248 1588 w
       +10 I f
       +(regexp)1308 1588 w
       +10 CW f
       +(/)1579 1588 w
       +10 R f
       +(The first following match of the regular expression)7 2038 1 2250 1588 t
       +10 CW f
       +(-/)1248 1708 w
       +10 I f
       +(regexp)1368 1708 w
       +10 CW f
       +(/)1639 1708 w
       +10 R f
       +(The first previous match of the regular expression)7 1993 1 2250 1708 t
       +10 CW f
       +($)1248 1828 w
       +10 R f
       +(The null string at the end of the file)8 1415 1 2250 1828 t
       +10 CW f
       +(.)1248 1948 w
       +10 R f
       +(Dot)2250 1948 w
       +10 CW f
       +(')1248 2068 w
       +10 R f
       +(The address mark, set by)4 995 1 2250 2068 t
       +10 CW f
       +(k)3270 2068 w
       +10 R f
       +(command)3355 2068 w
       +10 CW f
       +(")1248 2188 w
       +10 I f
       +(regexp)1308 2188 w
       +10 CW f
       +(")1579 2188 w
       +10 R f
       +(Dot in the file whose menu line matches regexp)8 1908 1 2250 2188 t
       +10 S f
       +(_ _________________________________________________________________)1 3264 1 1248 2256 t
       +10 R f
       +(Compound addresses)1 852 1 1248 2424 t
       +10 S f
       +(_ _________________________________________________________________)1 3264 1 1248 2492 t
       +10 I f
       +(a1)1248 2636 w
       +10 CW f
       +(+)1348 2636 w
       +10 I f
       +(a2)1408 2636 w
       +10 R f
       +(The address)1 479 1 2250 2636 t
       +10 I f
       +(a2)2754 2636 w
       +10 R f
       +(evaluated starting at right of)4 1126 1 2879 2636 t
       +10 I f
       +(a1)4030 2636 w
       +(a1)1248 2756 w
       +10 CW f
       +(-)1348 2756 w
       +10 I f
       +(a2 a2)1 942 1 1408 2756 t
       +10 R f
       +(evaluated in the reverse direction starting at left of)8 2012 1 2375 2756 t
       +10 I f
       +(a1)4412 2756 w
       +(a1)1248 2876 w
       +10 CW f
       +(,)1348 2876 w
       +10 I f
       +(a2)1408 2876 w
       +10 R f
       +(From the left of)3 630 1 2250 2876 t
       +10 I f
       +(a1)2905 2876 w
       +10 R f
       +(to the right of)3 547 1 3030 2876 t
       +10 I f
       +(a2)3602 2876 w
       +10 R f
       +(\(default)3727 2876 w
       +10 CW f
       +(0,$)4062 2876 w
       +10 R f
       +(\))4242 2876 w
       +10 I f
       +(a1)1248 2996 w
       +10 CW f
       +(;)1348 2996 w
       +10 I f
       +(a2)1408 2996 w
       +10 R f
       +(Like)2250 2996 w
       +10 CW f
       +(,)2458 2996 w
       +10 R f
       +(but sets dot after evaluating)4 1104 1 2543 2996 t
       +10 I f
       +(a1)3672 2996 w
       +10 S f
       +(_ _________________________________________________________________)1 3264 1 1248 3064 t
       +10 R f
       +(The operators)1 569 1 1440 3232 t
       +10 CW f
       +(+)2052 3232 w
       +10 R f
       +(and)2155 3232 w
       +10 CW f
       +(-)2342 3232 w
       +10 R f
       +(are high precedence, while)3 1122 1 2445 3232 t
       +10 CW f
       +(,)3610 3232 w
       +10 R f
       +(and)3713 3232 w
       +10 CW f
       +(;)3901 3232 w
       +10 R f
       +(are low)1 315 1 4005 3232 t
       +( both)1 208(precedence. In)1 610 2 1440 3352 t
       +10 CW f
       +(+)2288 3352 w
       +10 R f
       +(and)2378 3352 w
       +10 CW f
       +(-)2552 3352 w
       +10 R f
       +(forms,)2642 3352 w
       +10 I f
       +(a2)2930 3352 w
       +10 R f
       +(defaults to 1 and)3 678 1 3060 3352 t
       +10 I f
       +(a1)3768 3352 w
       +10 R f
       +(defaults to)1 423 1 3897 3352 t
       +( both)1 203(dot. If)1 269 2 1440 3472 t
       +10 I f
       +(a1)1937 3472 w
       +10 R f
       +(and)2062 3472 w
       +10 I f
       +(a2)2231 3472 w
       +10 R f
       +(are present,)1 459 1 2356 3472 t
       +10 CW f
       +(+)2840 3472 w
       +10 R f
       +(may be elided.)2 585 1 2925 3472 t
       +10 S f
       +(_ _________________________________________________________________)1 3264 1 1248 3552 t
       +10 R f
       +(Of course, the text extracted by)5 1267 1 970 3852 t
       +10 CW f
       +(x)2265 3852 w
       +10 R f
       +(may be selected by a regular expression, which complicates decid-)9 2687 1 2353 3852 t
       +( is resolved by generating the matches)6 1597( This)1 240( matches is chosen \320 matches may overlap.)7 1853(ing what set of)3 630 4 720 3972 t
       +( starting)1 338(starting from the beginning of dot using the leftmost-longest rule, and searching for each match)14 3982 2 720 4092 t
       +( adja-)1 228( expressions may also match null strings, but a null match)10 2363( Regular)1 371(from the end of the previous one.)6 1358 4 720 4212 t
       +( example,)1 388( For)1 189(cent to a non-null match is never selected; at least one character must intervene.)13 3187 3 720 4332 t
       +9 CW f
       +(, c/AAA/)1 432 1 1008 4502 t
       +(x/B*/ c/-/)1 540 1 1008 4612 t
       +(, p)1 162 1 1008 4722 t
       +10 R f
       +(produces as output)2 749 1 720 4902 t
       +9 CW f
       +(-A-A-A-)1008 5072 w
       +10 R f
       +(because the pattern)2 764 1 720 5252 t
       +10 CW f
       +(B*)1509 5252 w
       +10 R f
       +(matches the null strings separating the)5 1529 1 1654 5252 t
       +10 CW f
       +(A)3208 5252 w
       +10 R f
       +('s.)3268 5252 w
       +(The)970 5408 w
       +10 CW f
       +(x)1150 5408 w
       +10 R f
       +(command has a complement,)3 1165 1 1235 5408 t
       +10 CW f
       +(y)2425 5408 w
       +10 R f
       +( syntax, that executes the command with dot set to)9 2024(, with similar)2 531 2 2485 5408 t
       +(the text)1 297 1 720 5528 t
       +10 I f
       +(between)1042 5528 w
       +10 R f
       +( example,)1 388( For)1 189(the matches of the expression.)4 1206 3 1394 5528 t
       +9 CW f
       +(, c/AAA/)1 432 1 1008 5698 t
       +(y/A/ c/-/)1 486 1 1008 5808 t
       +(, p)1 162 1 1008 5918 t
       +10 R f
       +(produces the same result as the example above.)7 1890 1 720 6098 t
       +(The)970 6254 w
       +10 CW f
       +(x)1158 6254 w
       +10 R f
       +(and)1251 6254 w
       +10 CW f
       +(y)1428 6254 w
       +10 R f
       +(commands are looping constructs, and)4 1566 1 1521 6254 t
       +10 CW f
       +(sam)3120 6254 w
       +10 R f
       +(has a pair of conditional commands to go)7 1707 1 3333 6254 t
       +( have similar syntax:)3 830( They)1 255(with them.)1 428 3 720 6374 t
       +9 CW f
       +(g/)1008 6544 w
       +9 I f
       +(expression)1116 6544 w
       +9 CW f
       +(/)1501 6544 w
       +9 I f
       +(command)1609 6544 w
       +10 R f
       +( is different from)3 688( This)1 231(\(guard\) runs the command exactly once if dot contains a match of the expression.)13 3288 3 720 6724 t
       +10 CW f
       +(x)4955 6724 w
       +10 R f
       +(,)5015 6724 w
       +(which runs the command for)4 1148 1 720 6844 t
       +10 I f
       +(each)1893 6844 w
       +10 R f
       +(match:)2106 6844 w
       +10 CW f
       +(x)2403 6844 w
       +10 R f
       +(loops;)2488 6844 w
       +10 CW f
       +(g)2758 6844 w
       +10 R f
       +( Thus,)1 275(merely tests, without changing the value of dot.)7 1901 2 2843 6844 t
       +9 CW f
       +(, x/Peter/ d)2 648 1 1008 7014 t
       +10 R f
       +(deletes all occurrences of)3 1010 1 720 7194 t
       +10 CW f
       +(Peter)1755 7194 w
       +10 R f
       +(, but)1 178 1 2055 7194 t
       +cleartomark
       +showpage
       +saveobj restore
       +%%EndPage: 7 7
       +%%Page: 8 8
       +/saveobj save def
       +mark
       +8 pagesetup
       +10 R f
       +(- 8 -)2 166 1 2797 480 t
       +9 CW f
       +(, g/Peter/ d)2 648 1 1008 830 t
       +10 R f
       +( string\) if)2 384(deletes the whole file \(reduces it to a null)8 1687 2 720 1010 t
       +10 CW f
       +(Peter)2822 1010 w
       +10 R f
       +( complemen-)1 530( The)1 211(occurs anywhere in the text.)4 1146 3 3153 1010 t
       +(tary conditional is)2 722 1 720 1130 t
       +10 CW f
       +(v)1467 1130 w
       +10 R f
       +(, which runs the command if there is)7 1459 1 1527 1130 t
       +10 I f
       +(no)3011 1130 w
       +10 R f
       +(match of the expression.)3 976 1 3136 1130 t
       +( For)1 190(These control-structure-like commands may be composed to construct more involved operations.)10 3880 2 970 1286 t
       +(example, to print those lines of text that contain the string)10 2307 1 720 1406 t
       +10 CW f
       +(Peter)3052 1406 w
       +10 R f
       +(:)3352 1406 w
       +9 CW f
       +(, x/.*\\n/ g/Peter/ p)3 1080 1 1008 1576 t
       +10 R f
       +(The)720 1756 w
       +10 CW f
       +(x)908 1756 w
       +10 R f
       +( file into lines, the)4 761(breaks the)1 415 2 1001 1756 t
       +10 CW f
       +(g)2211 1756 w
       +10 R f
       +(selects those lines containing)3 1190 1 2305 1756 t
       +10 CW f
       +(Peter)3529 1756 w
       +10 R f
       +(, and the)2 359 1 3829 1756 t
       +10 CW f
       +(p)4222 1756 w
       +10 R f
       +( This)1 237(prints them.)1 487 2 4316 1756 t
       +(command gives an address for the)5 1416 1 720 1876 t
       +10 CW f
       +(x)2172 1876 w
       +10 R f
       +(command \(the whole file\), but because)5 1607 1 2268 1876 t
       +10 CW f
       +(g)3911 1876 w
       +10 R f
       +(does not have an explicit)4 1033 1 4007 1876 t
       +( the value of dot produced by the)7 1359(address, it applies to)3 831 2 720 1996 t
       +10 CW f
       +(x)2941 1996 w
       +10 R f
       +( commands in)2 573( All)1 184(command, that is, to each line.)5 1251 3 3032 1996 t
       +10 CW f
       +(sam)720 2116 w
       +10 R f
       +(except for the command to write a file to disc use dot for the default address.)15 3066 1 925 2116 t
       +(Composition may be continued indefinitely.)4 1764 1 970 2272 t
       +9 CW f
       +(, x/.*\\n/ g/Peter/ v/SaltPeter/ p)4 1782 1 1008 2442 t
       +10 R f
       +(prints those lines containing)3 1125 1 720 2622 t
       +10 CW f
       +(Peter)1870 2622 w
       +10 R f
       +(but)2195 2622 w
       +10 I f
       +(not)2348 2622 w
       +10 R f
       +(those containing)1 658 1 2501 2622 t
       +10 CW f
       +(SaltPeter)3184 2622 w
       +10 R f
       +(.)3724 2622 w
       +10 B f
       +(Structural Regular Expressions)2 1350 1 720 2862 t
       +10 R f
       +( non-interactive ones such as)4 1177(Unlike other Unix text editors, including the)6 1804 2 720 3018 t
       +10 CW f
       +(sed)3732 3018 w
       +10 R f
       +(and)3943 3018 w
       +10 CW f
       +(awk)4118 3018 w
       +10 R f
       +(,)4298 3018 w
       +6 R f
       +(7)4323 2968 w
       +10 CW f
       +(sam)4384 3018 w
       +10 R f
       +(is good for)2 445 1 4595 3018 t
       +( on-line phone book composed of records,)6 1737( example is an)3 604( An)1 182(manipulating files with multi-line `records.')4 1797 4 720 3138 t
       +(separated by blank lines, of the form)6 1461 1 720 3258 t
       +9 CW f
       +(Herbert Tic)1 594 1 1008 3428 t
       +(44 Turnip Ave., Endive, NJ)4 1404 1 1008 3538 t
       +(201-5555642)1008 3648 w
       +(Norbert Twinge)1 756 1 1008 3868 t
       +(16 Potato St., Cabbagetown, NJ)4 1620 1 1008 3978 t
       +(201-5553145)1008 4088 w
       +(...)1008 4308 w
       +10 R f
       +(The format may be encoded as a regular expression:)8 2083 1 720 4488 t
       +9 CW f
       +(\(.+\\n\)+)1008 4658 w
       +10 R f
       +( command to print Mr. Tic's entire record is then)9 1958( The)1 205(that is, a sequence of one or more non-blank lines.)9 2010 3 720 4838 t
       +9 CW f
       +(, x/\(.+\\n\)+/ g/\303Herbert Tic$/ p)4 1674 1 1008 5008 t
       +10 R f
       +(and that to extract just the phone number is)8 1726 1 720 5188 t
       +9 CW f
       +(, x/\(.+\\n\)+/ g/\303Herbert Tic$/ x/\303[0-9]*-[0-9]*\\n/ p)5 2754 1 1008 5358 t
       +10 R f
       +( Tic's record, extracts the phone number from)7 1862(The latter command breaks the file into records, chooses Mr.)9 2458 2 720 5538 t
       +(the record, and finally prints the number.)6 1636 1 720 5658 t
       +(A more involved problem is that of renaming a particular variable, say)11 2927 1 970 5814 t
       +10 CW f
       +(n)3932 5814 w
       +10 R f
       +(, to)1 138 1 3992 5814 t
       +10 CW f
       +(num)4165 5814 w
       +10 R f
       +(in a C program.)3 660 1 4380 5814 t
       +(The obvious first attempt,)3 1033 1 720 5934 t
       +9 CW f
       +(, x/n/ c/num/)2 702 1 1008 6104 t
       +10 R f
       +( flawed: it changes not only the variable)7 1628(is badly)1 317 2 720 6284 t
       +10 CW f
       +(n)2694 6284 w
       +10 R f
       +(but any letter)2 535 1 2783 6284 t
       +10 CW f
       +(n)3347 6284 w
       +10 R f
       +( need to extract all the)5 904( We)1 192(that appears.)1 508 3 3436 6284 t
       +(variables, and select those that match)5 1486 1 720 6404 t
       +10 CW f
       +(n)2231 6404 w
       +10 R f
       +(and only)1 347 1 2316 6404 t
       +10 CW f
       +(n)2688 6404 w
       +10 R f
       +(:)2748 6404 w
       +9 CW f
       +(, x/[A-Za-z_][A-Za-z_0-9]*/ g/n/ v/../ c/num/)4 2430 1 1008 6574 t
       +10 R f
       +(The pattern)1 458 1 720 6754 t
       +10 CW f
       +([A-Za-z_][A-Za-z_0-9]*)1204 6754 w
       +10 R f
       +( Next)1 246(matches C identifiers.)2 876 2 2550 6754 t
       +10 CW f
       +(g/n/)3699 6754 w
       +10 R f
       +(selects those containing an)3 1074 1 3966 6754 t
       +10 CW f
       +(n)720 6874 w
       +10 R f
       +(. Then)1 303 1 780 6874 t
       +10 CW f
       +(v/../)1131 6874 w
       +10 R f
       +(rejects those containing two \(or more\) characters, and finally)8 2614 1 1479 6874 t
       +10 CW f
       +(c/num/)4141 6874 w
       +10 R f
       +(changes the)1 491 1 4549 6874 t
       +(remainder \(identifiers)1 871 1 720 6994 t
       +10 CW f
       +(n)1620 6994 w
       +10 R f
       +(\) to)1 140 1 1680 6994 t
       +10 CW f
       +(num)1849 6994 w
       +10 R f
       +( version clearly works much better, but there may still be problems.)11 2754(. This)1 257 2 2029 6994 t
       +(For example, in C character and string constants, the sequence)9 2510 1 720 7114 t
       +10 CW f
       +(\\n)3257 7114 w
       +10 R f
       +( and)1 170(is interpreted as a newline character,)5 1466 2 3404 7114 t
       +(we don't want to change it to)6 1165 1 720 7234 t
       +10 CW f
       +(\\num.)1910 7234 w
       +10 R f
       +(This problem can be forestalled with a)6 1536 1 2235 7234 t
       +10 CW f
       +(y)3796 7234 w
       +10 R f
       +(command:)3881 7234 w
       +cleartomark
       +showpage
       +saveobj restore
       +%%EndPage: 8 8
       +%%Page: 9 9
       +/saveobj save def
       +mark
       +9 pagesetup
       +10 R f
       +(- 9 -)2 166 1 2797 480 t
       +9 CW f
       +(, y/\\\\n/ x/[A-Za-z_][A-Za-z_0-9]*/ g/n/ v/../ c/num/)5 2808 1 1008 830 t
       +10 R f
       +(\(the second)1 464 1 720 1010 t
       +10 CW f
       +(\\)1216 1010 w
       +10 R f
       +( reject)1 254(is necessary because of lexical conventions in regular expressions\), or we could even)12 3478 2 1308 1010 t
       +(character constants and strings outright:)4 1592 1 720 1130 t
       +9 CW f
       +(, y/'[\303']*'/ y/"[\303"]*"/ x/[A-Za-z_][A-Za-z_0-9]*/ g/n/ v/../ c/num/)6 3618 1 1008 1300 t
       +10 R f
       +(The)720 1480 w
       +10 CW f
       +(y)907 1480 w
       +10 R f
       +( only)1 211( The)1 213( strings.)1 325(commands in this version exclude from consideration all character constants and)10 3292 4 999 1480 t
       +(remaining problem is to deal with the possible occurrence of)9 2439 1 720 1600 t
       +10 CW f
       +(\\')3186 1600 w
       +10 R f
       +(or)3332 1600 w
       +10 CW f
       +(\\")3441 1600 w
       +10 R f
       +(within these sequences, but it's easy)5 1453 1 3587 1600 t
       +(to see how to resolve this difficulty.)6 1435 1 720 1720 t
       +( of the command)3 683( simple version)2 615( A)1 124(The point of these composed commands is successive refinement.)8 2648 4 970 1876 t
       +( can be undone;)3 644( \(Mistakes)1 449( it can be honed by adding a clause or two.)10 1755(is tried, and if it's not good enough,)7 1472 4 720 1996 t
       +( result-)1 283( The)1 208( the mouse language makes it unnecessary to retype the command each time.\))12 3138( Also,)1 267(see below.)1 424 5 720 2116 t
       +(ing chains of commands are somewhat reminiscent of shell pipelines.)9 2783 1 720 2236 t
       +6 R f
       +(7)3503 2186 w
       +10 R f
       +(Unlike pipelines, though, which pass)4 1481 1 3559 2236 t
       +(along modified)1 612 1 720 2356 t
       +10 I f
       +(data)1361 2356 w
       +10 R f
       +(,)1539 2356 w
       +10 CW f
       +(sam)1593 2356 w
       +10 R f
       +(commands pass a)2 707 1 1802 2356 t
       +10 I f
       +(view)2538 2356 w
       +10 R f
       +( text at each step of the command is the)9 1623( The)1 210(of the data.)2 456 3 2751 2356 t
       +( by step until the correct piece is available to the final)11 2237(same, but which pieces are selected is refined step)8 2083 2 720 2476 t
       +(step of the command line, which ultimately makes the change.)9 2494 1 720 2596 t
       +(In other Unix programs, regular expressions are used only for selection, as in the)13 3315 1 970 2752 t
       +10 CW f
       +(sam g)1 272 1 4317 2752 t
       +10 R f
       +(command,)4621 2752 w
       +(never for extraction as in the)5 1159 1 720 2872 t
       +10 CW f
       +(x)1907 2872 w
       +10 R f
       +(or)1995 2872 w
       +10 CW f
       +(y)2106 2872 w
       +10 R f
       +( patterns in)2 448( example,)1 391(command. For)1 611 3 2194 2872 t
       +10 CW f
       +(awk)3671 2872 w
       +6 R f
       +(7)3851 2822 w
       +10 R f
       +(are used to select lines to be)6 1132 1 3908 2872 t
       +( The)1 207( but cannot be used to describe the format of the input text, or to handle newline-free text.)17 3619(operated on,)1 494 3 720 2992 t
       +( the structure of a piece of text rather than its contents, as in the)14 2671(use of regular expressions to describe)5 1554 2 720 3112 t
       +10 CW f
       +(x)4980 3112 w
       +10 R f
       +(command, has been given a name:)5 1455 1 720 3232 t
       +10 I f
       +(structural regular expressions.)2 1268 1 2216 3232 t
       +10 R f
       +(When they are composed, as in the)6 1490 1 3550 3232 t
       +( use is discussed at greater length elsewhere.)7 1779( Their)1 266(above example, they are pleasantly expressive.)5 1870 3 720 3352 t
       +6 R f
       +(10)4635 3302 w
       +10 B f
       +(Multiple files)1 564 1 720 3628 t
       +10 CW f
       +(Sam)720 3784 w
       +10 R f
       +(has a few other commands, mostly relating to input and output.)10 2526 1 925 3784 t
       +9 CW f
       +(e discfilename)1 756 1 1008 3954 t
       +10 R f
       +(replaces the contents and name of the current file with those of the named disc file;)15 3318 1 720 4134 t
       +9 CW f
       +(w discfilename)1 756 1 1008 4304 t
       +10 R f
       +(writes the contents to the named disc file; and)8 1831 1 720 4484 t
       +9 CW f
       +(r discfilename)1 756 1 1008 4654 t
       +10 R f
       +( file's name if)3 590( these commands use the current)5 1350( All)1 188(replaces dot with the contents of the named disc file.)9 2192 4 720 4834 t
       +( Finally,)1 359(none is specified.)2 696 2 720 4954 t
       +9 CW f
       +(f discfilename)1 756 1 1008 5124 t
       +10 R f
       +(changes the name associated with the file and displays the result:)10 2596 1 720 5304 t
       +9 CW f
       +('-. discfilename)1 864 1 1008 5474 t
       +10 R f
       +(This output is called the file's)5 1226 1 720 5654 t
       +10 I f
       +(menu line,)1 423 1 1978 5654 t
       +10 R f
       +( contents of the file's line in the button 3 menu)10 1951(because it is the)3 656 2 2433 5654 t
       +( The)1 205( first three characters are a concise notation for the state of the file.)13 2677( The)1 206(\(described in the next section\).)4 1232 4 720 5774 t
       +( sign indicates the number of windows open on the)9 2052( minus)1 271( The)1 206(apostrophe signifies that the file is modified.)6 1791 4 720 5894 t
       +(file \(see the next section\):)4 1071 1 720 6014 t
       +10 CW f
       +(-)1826 6014 w
       +10 R f
       +(means none,)1 509 1 1921 6014 t
       +10 CW f
       +(+)2465 6014 w
       +10 R f
       +(means one, and)2 636 1 2560 6014 t
       +10 CW f
       +(*)3230 6014 w
       +10 R f
       +( the period)2 445( Finally,)1 368(means more than one.)3 903 3 3324 6014 t
       +( are useful for controlling the)5 1192( characters)1 432( These)1 292(indicates that this is the current file.)6 1453 4 720 6134 t
       +10 CW f
       +(X)4119 6134 w
       +10 R f
       +(command, described)1 831 1 4209 6134 t
       +(shortly.)720 6254 w
       +10 CW f
       +(Sam)970 6410 w
       +10 R f
       +( \(such as all the source for a program\) by invoking it with a)13 2384(may be started with a set of disc files)8 1481 2 1175 6410 t
       +(list of file names as arguments, and more may be added or deleted on demand.)14 3133 1 720 6530 t
       +9 CW f
       +(B discfile1 discfile2 ...)3 1350 1 1008 6700 t
       +10 R f
       +(adds the named files to)4 921 1 720 6880 t
       +10 CW f
       +(sam)1666 6880 w
       +10 R f
       +('s list, and)2 414 1 1846 6880 t
       +9 CW f
       +(D discfile1 discfile2 ...)3 1350 1 1008 7050 t
       +10 R f
       +(removes them from)2 790 1 720 7230 t
       +10 CW f
       +(sam)1539 7230 w
       +10 R f
       +( these commands have a)4 990( Both)1 250('s memory \(without effect on associated disc files\).)7 2081 3 1719 7230 t
       +cleartomark
       +showpage
       +saveobj restore
       +%%EndPage: 9 9
       +%%Page: 10 10
       +/saveobj save def
       +mark
       +10 pagesetup
       +10 R f
       +(- 10 -)2 216 1 2772 480 t
       +(syntax for using the shell)4 1005 1 720 840 t
       +6 R f
       +(7)1725 790 w
       +10 R f
       +(\(the Unix command interpreter\) to generate the lists:)7 2099 1 1780 840 t
       +9 CW f
       +(B <echo *.c)2 594 1 1008 1010 t
       +10 R f
       +(will add all C source files, and)6 1218 1 720 1190 t
       +9 CW f
       +(B <grep -l variable *.c)4 1242 1 1008 1360 t
       +10 R f
       +( files referencing a particular variable \(the Unix command)8 2377(will add all C source)4 851 2 720 1540 t
       +10 CW f
       +(grep -l)1 420 1 3980 1540 t
       +10 R f
       +(lists all files in)3 608 1 4432 1540 t
       +( Finally,)1 379( the specified regular expression\).)4 1429(its arguments that contain matches of)5 1595 3 720 1660 t
       +10 CW f
       +(D)4168 1660 w
       +10 R f
       +(without arguments)1 767 1 4273 1660 t
       +(deletes the current file.)3 914 1 720 1780 t
       +(There are two ways to change which file is current:)9 2047 1 970 1936 t
       +9 CW f
       +(b filename)1 540 1 1008 2106 t
       +10 R f
       +( The)1 213(makes the named file current.)4 1215 2 720 2286 t
       +10 CW f
       +(B)2181 2286 w
       +10 R f
       +( but also adds any new files to)7 1270(command does the same,)3 1028 2 2274 2286 t
       +10 CW f
       +(sam)4606 2286 w
       +10 R f
       +('s list.)1 254 1 4786 2286 t
       +( The)1 208( mouse actions, not by textual commands.\))6 1728(\(In practice, of course, the current file is usually chosen by)10 2384 3 720 2406 t
       +(other way is to use a form of address that refers to files:)12 2223 1 720 2526 t
       +9 CW f
       +(")1008 2696 w
       +9 I f
       +(expression)1062 2696 w
       +9 CW f
       +(")1447 2696 w
       +9 I f
       +(address)1555 2696 w
       +10 R f
       +( matches the expression \(there must be exactly)7 1923(refers to the address evaluated in the file whose menu line)10 2397 2 720 2876 t
       +( example,)1 388( For)1 189(one match\).)1 471 3 720 2996 t
       +9 CW f
       +("peter.c" 3)1 594 1 1008 3166 t
       +10 R f
       +( whose name matches)3 885(refers to the third line of the file)7 1299 2 720 3346 t
       +10 CW f
       +(peter.c)2933 3346 w
       +10 R f
       +( is most useful in the move \()7 1164(. This)1 257 2 3353 3346 t
       +10 CW f
       +(m)4774 3346 w
       +10 R f
       +(\) and)1 206 1 4834 3346 t
       +(copy \()1 252 1 720 3466 t
       +10 CW f
       +(t)972 3466 w
       +10 R f
       +(\) commands:)1 519 1 1032 3466 t
       +9 CW f
       +(0,$ t "peter.c" 0)3 918 1 1008 3636 t
       +10 R f
       +(makes a copy of the current file at the beginning of)10 2040 1 720 3816 t
       +10 CW f
       +(peter.c)2785 3816 w
       +10 R f
       +(.)3205 3816 w
       +(The)970 3972 w
       +10 CW f
       +(X)1150 3972 w
       +10 R f
       +(command is a looping construct, like)5 1477 1 1235 3972 t
       +10 CW f
       +(x)2737 3972 w
       +10 R f
       +(, that refers to files instead of strings:)7 1487 1 2797 3972 t
       +9 CW f
       +(X/)1008 4142 w
       +9 I f
       +(expression)1116 4142 w
       +9 CW f
       +(/)1501 4142 w
       +9 I f
       +(command)1609 4142 w
       +10 R f
       +( best example is)3 641( The)1 205(runs the command in all files whose menu lines match the expression.)11 2797 3 720 4322 t
       +9 CW f
       +(X/'/ w)1 324 1 1008 4492 t
       +10 R f
       +(which writes to disc all modified files.)6 1571 1 720 4672 t
       +10 CW f
       +(Y)2347 4672 w
       +10 R f
       +(is the complement of)3 859 1 2438 4672 t
       +10 CW f
       +(X)3328 4672 w
       +10 R f
       +( command on all files whose)5 1181(: it runs the)3 471 2 3388 4672 t
       +(menu lines don't match the expression:)5 1568 1 720 4792 t
       +9 CW f
       +(Y/\\.c/ D)1 432 1 1008 4962 t
       +10 R f
       +(deletes all files that don't have)5 1223 1 720 5142 t
       +10 CW f
       +(.c)1968 5142 w
       +10 R f
       +(in their names, that is, it keeps all C source files and deletes the rest.)14 2727 1 2113 5142 t
       +(Braces allow commands to be grouped, so)6 1689 1 970 5298 t
       +9 CW f
       +({)1008 5468 w
       +9 I f
       +(command1)1440 5578 w
       +(command2)1440 5688 w
       +9 CW f
       +(})1008 5798 w
       +10 R f
       +( Thus,)1 275(is syntactically a single command that runs two commands.)8 2379 2 720 5978 t
       +9 CW f
       +(X/\\.c/ ,g/variable/ {)2 1134 1 1008 6148 t
       +(f)1440 6258 w
       +(, x/.*\\n/ g/variable/ p)3 1242 1 1440 6368 t
       +(})1008 6478 w
       +10 R f
       +(finds all occurrences of)3 936 1 720 6658 t
       +10 CW f
       +(variable)1682 6658 w
       +10 R f
       +( out the file names and lines of each match.)9 1748(in C source files, and prints)5 1104 2 2188 6658 t
       +(The precise semantics of compound operations is discussed in the implementation sections below.)12 3921 1 720 6778 t
       +(Finally, the undo command,)3 1152 1 970 6934 t
       +10 CW f
       +(u)2156 6934 w
       +10 R f
       +( files were affected.)3 815(, undoes the last command, no matter how many)8 2009 2 2216 6934 t
       +(Multiple undo operations move further back in time, so)8 2212 1 720 7054 t
       +cleartomark
       +showpage
       +saveobj restore
       +%%EndPage: 10 10
       +%%Page: 11 11
       +/saveobj save def
       +mark
       +11 pagesetup
       +10 R f
       +(- 11 -)2 216 1 2772 480 t
       +9 CW f
       +(u)1008 830 w
       +(u)1008 940 w
       +10 R f
       +(\(which may be abbreviated)3 1086 1 720 1120 t
       +10 CW f
       +(u2)1832 1120 w
       +10 R f
       +( be undone, however, nor)4 1022( undo may not)3 578( An)1 173(\) undoes the last two commands.)5 1315 4 1952 1120 t
       +( though, including for example)4 1243( else is undoable,)3 697( Everything)1 497(may any command that adds or deletes files.)7 1796 4 720 1240 t
       +10 CW f
       +(e)4980 1240 w
       +10 R f
       +(commands:)720 1360 w
       +9 CW f
       +(e filename)1 540 1 1008 1530 t
       +(u)1008 1640 w
       +10 R f
       +( of the undo,)3 538( Because)1 393( file completely, including its name, dot, and modified bit.)9 2429(restores the state of the)4 960 4 720 1820 t
       +( Only)1 259(potentially dangerous commands are not guarded by confirmations.)7 2759 2 720 1940 t
       +10 CW f
       +(D)3772 1940 w
       +10 R f
       +( informa-)1 382(, which destroys the)3 826 2 3832 1940 t
       +( a modified file, but a second)6 1210( will not delete)3 618( It)1 118(tion necessary to restore itself, is protected.)6 1772 4 720 2060 t
       +10 CW f
       +(D)4471 2060 w
       +10 R f
       +(of the same)2 476 1 4564 2060 t
       +( The)1 205(file will succeed regardless.)3 1108 2 720 2180 t
       +10 CW f
       +(q)2058 2180 w
       +10 R f
       +(command, which exits)2 902 1 2143 2180 t
       +10 CW f
       +(sam)3070 2180 w
       +10 R f
       +(, is similarly guarded.)3 869 1 3250 2180 t
       +10 B f
       +(Mouse Interface)1 695 1 720 2420 t
       +10 CW f
       +(Sam)720 2576 w
       +10 R f
       +( dif-)1 173( only)1 206( The)1 208(is most commonly run connected to a bitmap display and mouse for interactive editing.)13 3525 4 928 2576 t
       +(ference in the command language between regular, mouse-driven)7 2637 1 720 2696 t
       +10 CW f
       +(sam)3386 2696 w
       +10 R f
       +(and)3595 2696 w
       +10 CW f
       +(sam -d)1 360 1 3768 2696 t
       +10 R f
       +(is that if an address is)5 883 1 4157 2696 t
       +(provided without a command,)3 1217 1 720 2816 t
       +10 CW f
       +(sam -d)1 360 1 1968 2816 t
       +10 R f
       +(will print the text referenced by the address, but regular)9 2280 1 2360 2816 t
       +10 CW f
       +(sam)4672 2816 w
       +10 R f
       +(will)4884 2816 w
       +(highlight it on the screen \320 in fact, dot is always highlighted \(see Figure 2\).)14 3053 1 720 2936 t
       +cleartomark
       +saveobj restore
       +%ps_include: begin
       +save
       +/ed {exch def} def
       +{} /showpage ed
       +{} /copypage ed
       +{} /erasepage ed
       +{} /letter ed
       +currentdict /findfont known systemdict /findfont known and {
       +        /findfont systemdict /findfont get def
       +} if
       +36 dict dup /PS-include-dict-dw ed begin
       +/context ed
       +count array astore /o-stack ed
       +%ps_include: variables begin
       +/llx 80 def
       +/lly 322 def
       +/urx 531.44 def
       +/ury 468.88 def
       +/w 0 def
       +/o 0 def
       +/s 0 def
       +/cx 2880 def
       +/cy -3910 def
       +/sx 4320 def
       +/sy 1468 def
       +/ax 0.5 def
       +/ay 0.5 def
       +/rot 0 def
       +%ps_include: variables end
       +{llx lly urx ury} /bbox ed
       +{newpath 2 index exch 2 index exch dup 6 index exch
       + moveto 3 {lineto} repeat closepath} /boxpath ed
       +{dup mul exch dup mul add sqrt} /len ed
       +{2 copy gt {exch} if pop} /min ed
       +{2 copy lt {exch} if pop} /max ed
       +{transform round exch round exch A itransform} /nice ed
       +{6 array} /n ed
       +n defaultmatrix n currentmatrix n invertmatrix n concatmatrix /A ed
       +urx llx sub 0 A dtransform len /Sx ed
       +0 ury lly sub A dtransform len /Sy ed
       +llx urx add 2 div lly ury add 2 div A transform /Cy ed /Cx ed
       +rot dup sin abs /S ed cos abs /C ed
       +Sx S mul Sy C mul add /H ed
       +Sx C mul Sy S mul add /W ed
       +sy H div /Scaley ed
       +sx W div /Scalex ed
       +s 0 eq {Scalex Scaley min dup /Scalex ed /Scaley ed} if
       +sx Scalex W mul sub 0 max ax 0.5 sub mul cx add /cx ed
       +sy Scaley H mul sub 0 max ay 0.5 sub mul cy add /cy ed
       +urx llx sub 0 A dtransform exch atan rot exch sub /rot ed
       +n currentmatrix initgraphics setmatrix
       +cx cy translate
       +Scalex Scaley scale
       +rot rotate
       +Cx neg Cy neg translate
       +A concat
       +bbox boxpath clip newpath
       +w 0 ne {gsave bbox boxpath 1 setgray fill grestore} if
       +end
       +gsave
       +%ps_include: inclusion begin
       +/picstr 79 string def
       +80 322 translate
       +451.44 146.88 scale
       +
       +627 204 1 [627 0 0 -204 0 204]
       +{currentfile picstr readhexstring pop} image
       +
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffff
       +fe00000000000000000000000000000000000000000000000000000000000000
       +0000000000000000000000000000000000000000000000000000000000000000
       +000000000000000000000000003fff
       +fe00000000000000000000000000000000000000000000000000000000000000
       +0000000000000000000000000000000000000000000000000000000000000000
       +000000000000000000000000003fff
       +fe00000000000000000000000000000000000000000000000000000000000000
       +0000000000000000000000000000000000000000000000000000000000000000
       +000000000000000000000000003fff
       +fe2001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889fffffffffffffffffff3fffffff3cffffffffffffffffffffdfe7f3bff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221fffffffffffffffffff3fffefff3cffffffff07ffffffffff3fe7f3cff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889fffffffffffffffffff3fffe7ff3fffffffff33fffffffffe7fe7f3e7f
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221fffffffffffffffffff27fff3f820fc1f0fff39c3c9f8723e7f0783e7e
       +3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889fffffffffffffffffff13fff9f33cf9ce66033999c4f3311cfe6733f3e
       +3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221fffffffffffffffffff39fffce73cf9fce603393cce67339cfce673f3e
       +3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889fffffffffffffffffff3980fc673cf8fcffff393cce67339cfce673f3f
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2ffdfffffffffffffffffff3980fce73cfc1cffff393cce60339cfce673f3f
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2ffdfffffffffffffffffff39fff9e73cff8cfe03393cce67f39cfce673f3f
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2ffdfffffffffffffffffff39fff3e73cffccfe03393cce67f39cfce673f3e
       +3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2ffdfffffffffffffffffff33ffe7f23cf9ce67ff3399ccf3339cfe4723f3e
       +3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2ffdfffffffffffffffffff07ffeff9201c1f0fff07c3c1f8739e7f2793e7e
       +3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2ffdfffffffffffffffffffffffffffffffffffffffffcffffffe7fffffe7f
       +3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2ffdfffffffffffffffffffffffffffffffffffffffffcfffffff3fffffcff
       +3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2ffdfffffffffffffffffffffffffffffffffffffffffcfffffffdfffffbfe
       +7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2ffdffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2ffdffffffffffffffffffffffffff9fffe7ffff7ff9ffffffffffffe7fffd
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221ffffffffffffffffffffff7fff9fffe7fbfcf0f9ffff7fffffffe7fffe
       +7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889fffffffffffffffffffffe7ffffffffff3f9e679ffff3fffffffe7ffff
       +3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221fffffffffffffffffff8380c641f2307c079e6793fff9fc3c1f0e47c3f
       +3f1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889fffffffffffffffffff39e7e079f11e7f3f3e6f89fffcf999ce662399f
       +9f1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221fffffffffffffffffff3fe7e679f39e7f3f3f1f9cfffe739fcce67339f
       +9f1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889fffffffffffffffffff1fe7e7f9f39e7f3f3f499cc07e33ffccfe7339f
       +9fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221fffffffffffffffffff83e7e7f9f39e7f3f3e4b9cc07e73fc0cfe7301f
       +9fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889ffffffffffffffffffff1e7e7f9f39e7f3f3e679cfffcf3f9ccfe733ff
       +9fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221ffffffffffffffffffff9e7e7f9f39e7f3f3e679cfff9f3f9ccfe733ff
       +9f1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889fffffffffffffffffff39e7e7f9f39e7f3f3e2399fff3f9998e667399f
       +9f1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221fffffffffffffffffff83f0c1c033900f879f1183fff7fc3c4f0e73c3f
       +3f1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889fffffffffffffffffffffffffffffffffff9ffffffffffffffffffffff
       +3f9fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221fffffffffffffffffffffffffffffffffffcfffffffffffffffffffffe
       +7f9fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889ffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffd
       +ff3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889ffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221ffffffffffffffffffffffffbfffffffffe7ffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889ffffffffffffffffffffffff3fffffffffe7ffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221fffffffffffffffffff19e1c06731991ffe4fc7fffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889fffffffffffffffffff81ccf3e738188ffe27c7fffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221fffffffffffffffffff999cf3e73999cffe73c7fffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889fffffffffffffffffff9f9cf3e739f9cffe73fffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221fffffffffffffffffff9f80f3e739f9cffe73fffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889fffffffffffffffffff9f9ff3e739f9cffe73fffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221fffffffffffffffffff9f9ff3e739f9cffe73c7fffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889fffffffffffffffffff9fccf3e239f9cffe67c7fffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221fffffffffffffffffff07e1f8713079cffe0fc7fffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889fffffffffffffffffffffffffffffffffffffe7fffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221fffffffffffffffffffffffffffffffffffffe7fffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889fffffffffffffffffffffffffffffffffffffcffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889f8ffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221fc7fffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221fcffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889fcffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221fe3fffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889fcffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221fcffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889f8ffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889fffff83ffffffffee7fbffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221f03fff3ffffffff9e7fcffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889f39fff3ffffffff3e7fe7fffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221f39e1f3f0f83e1f3e4fe7fffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889f39ccf3e6739cce7e27f3fffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221f3b9cf3cf33f9ce7e73f3fffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889f079ff3cf31f9ce7e73f3fffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221f3b9ff3cf38380e7e73f3fffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889f399ff3cf3f19fe7e73f3fffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221f399ff3cf3f99fe7e73f3fffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889f39ccf3e6739cce7e67f3fffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221f03e18070f83e1f3e0fe7fffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889fffffffffffffff3fffe7fffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221fffffffffffffff9fffcffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889fffffffffffffffefffbffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889ffffffffffffffffffffffffff9fffffffffffffffffc3e1fffffffff9
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221ffffffffffffffffffffffffff9ffff7fffffff81fff9fcffffffffff9
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889ffffffffffffffffffffffffffffffe7fffffff9cfff9fcffffffffff9
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221fffffffffffffffffff19e1f041f8380f0e33ff9cce60301e1c67ffef9
       +3f1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889fffffffffffffffffff81cce679f39e7e6703ff9cce79fcfcce07ffab8
       +9f1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221fffffffffffffffffff999cce79f3fe7ce733ff9dce79fcf9ce67ffc79
       +cf1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889fffffffffffffffffff9f9cce79f1fe7ce73fff83ce79fcf9ce7fff119
       +cfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221fffffffffffffffffff9f80ce79f83e7c073fff9dce79fcf80e7fffc79
       +cfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889fffffffffffffffffff9f9fce79ff1e7cff3fff9cce79fcf9fe7fffab9
       +cfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221fffffffffffffffffff9f9fce79ff9e7cff3fff9cce79fcf9fe7fffef9
       +cf1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889fffffffffffffffffff9fcce479f39e7e673fff9cc479fcfcce7fffff9
       +9f1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221fffffffffffffffffff07e1f240383f0f0e0fff81e260703e1c1fffff8
       +3f1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889ffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffff
       +ff9fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221fffffffffffffffffffffffce7ffffffffffffffffffffffffffffffff
       +ff9fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889fffffffffffffffffffffffe0fffffffffffffffffffffffffffffffff
       +ff3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889fe3fffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221fcffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889fcffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221fcffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889fcffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221f8ffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889fcffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221fcffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889fcffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221fcffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889fe3fffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221ffffffffffffffffffc000000000000000000000000000000000000000
       +0000000000000000000000000000000000000000000000000000000000000000
       +000000000000000000000000003fff
       +fe2889ffffffffffffffffffc00007c000000001180000001860000010000000
       +0000000000000000000000000000000000000000000000000000000000000000
       +000000000000000000000000003fff
       +fe2221ffffffffffffffffffcf8000c00000000618000200186000000c000000
       +0000000000000000000000000000000000000000000000000000000000000000
       +000000000000000000000000003fff
       +fe2889ffffffffffffffffffccc000c00000000c180003001800000006000000
       +0000000000000000000000000000000000000000000000000000000000000000
       +000000000000000000000000003fff
       +fe2221ffffffffffffffffffcc61e0c0f07c1e0c1b000180fbe07c1e06070000
       +0000000000000000000000000000000000000000000000000000000000000000
       +000000000000000000000000003fff
       +fe2889ffffffffffffffffffcc6330c198c633181d8000c19860c63303070000
       +0000000000000000000000000000000000000000000000000000000000000000
       +000000000000000000000000003fff
       +fe2221ffffffffffffffffffcc6630c30cc0631818c000631860c06303070000
       +0000000000000000000000000000000000000000000000000000000000000000
       +000000000000000000000000003fff
       +fe2889ffffffffffffffffffcc6600c30ce0631818cfe0731860e06003000000
       +0000000000000000000000000000000000000000000000000000000000000000
       +000000000000000000000000003fff
       +fe2221ffffffffffffffffffcc6600c30c7c7f1818cfe06318607c6003000000
       +0000000000000000000000000000000000000000000000000000000000000000
       +000000000000000000000000003fff
       +fe2889ffffffffffffffffffcc6600c30c0e601818c000c318600e6003000000
       +0000000000000000000000000000000000000000000000000000000000000000
       +000000000000000000000000003fff
       +fe2221ffffffffffffffffffcc6600c30c06601818c001831860066003070000
       +0000000000000000000000000000000000000000000000000000000000000000
       +000000000000000000000000003fff
       +fe2889ffffffffffffffffffccc330c198c6331819800301b860c63303070000
       +0000000000000000000000000000000000000000000000000000000000000000
       +000000000000000000000000003fff
       +fe2221ffffffffffffffffffcf81e7f8f07c1e0c1f000200dbfc7c1e06070000
       +0000000000000000000000000000000000000000000000000000000000000000
       +000000000000000000000000003fff
       +fe2889ffffffffffffffffffc00000000000000c000000000000000006030000
       +0000000000000000000000000000000000000000000000000000000000000000
       +000000000000000000000000003fff
       +fe2221ffffffffffffffffffc00000000000000600000000000000000c030000
       +0000000000000000000000000000000000000000000000000000000000000000
       +000000000000000000000000003fff
       +fe2889ffffffffffffffffffc000000000000001000000000000000010060000
       +0000000000000000000000000000000000000000000000000000000000000000
       +000000000000000000000000003fff
       +fe2221c000000000000000000000000000000000000000000000000000000000
       +0000000000000000000000000000000000000000000000000000000000000000
       +000000000000000000000000003fff
       +fe2889c000000000000000000000000001f00000000040030000000000000c00
       +0100000000000000000000000000000000000000000000000000000000000000
       +000000000000000000000000003fff
       +fe2221c000000000000000000000800000300000000187830000400000000c00
       +00c0000000000000000000000000000000000000000000000000000000000000
       +000000000000000000000000003fff
       +fe2889c00000000000000000000180000030000000030cc30000600000000c00
       +0060000000000000000000000000000000000000000000000000000000000000
       +000000000000000000000000003fff
       +fe2221c0000000000000000007c7f39878303c1f07830cc36000301e1f078dc1
       +e060700000000000000000000000000000000000000000000000000000000000
       +000000000000000000000000003fff
       +fe2889c000000000000000000c6181f8cc3066318cc60c83b0001833318ccee3
       +3030700000000000000000000000000000000000000000000000000000000000
       +000000000000000000000000003fff
       +fe2221c000000000000000000c0181998c30c33018c6070318000c630198cc66
       +3030700000000000000000000000000000000000000000000000000000000000
       +000000000000000000000000003fff
       +fe2889c000000000000000000e0181818030c33818c605b319fc0e6001980c66
       +3030000000000000000000000000000000000000000000000000000000000000
       +000000000000000000000000003fff
       +fe2221c0000000000000000007c181818030c31f1fc60da319fc0c601f980c67
       +f030000000000000000000000000000000000000000000000000000000000000
       +000000000000000000000000003fff
       +fe2889c0000000000000000000e181818030c30398060cc31800186031980c66
       +0030000000000000000000000000000000000000000000000000000000000000
       +000000000000000000000000003fff
       +fe2221c00000000000000000006181818030c30198060cc31800306031980c66
       +0030700000000000000000000000000000000000000000000000000000000000
       +000000000000000000000000003fff
       +fe2889c000000000000000000c618180cc3066318cc60ee330006033338ccc63
       +3030700000000000000000000000000000000000000000000000000000000000
       +000000000000000000000000003fff
       +fe2221c0000000000000000007c0f3e079fe3c1f07830773e000401e1d878c61
       +e060700000000000000000000000000000000000000000000000000000000000
       +000000000000000000000000003fff
       +fe2889c000000000000000000000000000000000000300000000000000000000
       +0060300000000000000000000000000000000000000000000000000000000000
       +000000000000000000000000003fff
       +fe2221c000000000000000000000000000000000000180000000000000000000
       +00c0300000000000000000000000000000000000000000000000000000000000
       +000000000000000000000000003fff
       +fe2889c000000000000000000000000000000000000040000000000000000000
       +0100600000000000000000000000000000000000000000000000000000000000
       +000000000000000000000000003fff
       +fe2221c00000000000000000000000000000000000000000000000000000001f
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889c0000000000000000001e0000000040200000c0000000000203008001f
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221c000000000000000000300000000180c00000c0000000000183006001f
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889c000000000000000000300000000301800000c00000000000c3003001f
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221c000000000000000000fe730f078301831878dc3e39800100c3603039f
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889c000000000000000000303f198cc6030318ccee631f80054063b01839f
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221c00000000000000000030333198c60303198cc6031980038063181839f
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889c00000000000000000030303198c603031980c60318000ee063181801f
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221c00000000000000000030303f9fc603031980c63f1800038063181801f
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889c000000000000000000303030180603031980c6631800054063181801f
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221c000000000000000000303030180603031980c6631800010063181839f
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889c0000000000000000003030198cc60303b8ccc6671800000063301839f
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221c000000000000000000fc7c0f07830181d878c63b3e000000c3e03039f
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889c000000000000000000000000000301800000000000000000c0003019f
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221c000000000000000000000000000180c0000000000000000180006019f
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889c00000000000000000000000000004020000000000000000200008031f
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889f8ffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221fc7fffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221fcffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889fcffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221fe3fffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889fcffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221fcffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889f8ffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2889ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2221ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe2001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +fffffffffffffffffffffffffe3fff
       +fe00000000000000000000000000000000000000000000000000000000000000
       +0000000000000000000000000000000000000000000000000000000000000000
       +000000000000000000000000003fff
       +fe00000000000000000000000000000000000000000000000000000000000000
       +0000000000000000000000000000000000000000000000000000000000000000
       +000000000000000000000000003fff
       +fe00000000000000000000000000000000000000000000000000000000000000
       +0000000000000000000000000000000000000000000000000000000000000000
       +000000000000000000000000003fff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffff
       +showpage
       +%ps_include: inclusion end
       +grestore
       +PS-include-dict-dw begin
       +o 0 ne {gsave A defaultmatrix /A ed llx lly nice urx ury nice
       +        initgraphics 0.1 setlinewidth boxpath stroke grestore} if
       +clear o-stack aload pop
       +context end restore
       +%ps_include: end
       +/saveobj save def
       +mark
       +8 I f
       +(Figure 2. A)2 370 1 720 4744 t
       +8 CW f
       +(sam)1112 4744 w
       +8 I f
       +( bar down the left represents the file, with the bubble showing the fraction visible in the window.)17 3127( scroll)1 203(window. The)1 432 3 1278 4744 t
       +( current text, which is highlighted, need not fit on a)10 1703( The)1 167( be manipulated by the mouse for convenient browsing.)8 1821(The scroll bar may)3 629 4 720 4844 t
       +( it consists of one partial line, one complete line, and final partial line.)13 2252(line. Here)1 338 2 720 4944 t
       +10 R f
       +( in all)2 236( any time, only one window)5 1131( At)1 153(Each file may have zero or more windows open on the display.)11 2550 4 970 5220 t
       +(of)720 5340 w
       +10 CW f
       +(sam)830 5340 w
       +10 R f
       +(is the)1 216 1 1037 5340 t
       +10 I f
       +(current window,)1 658 1 1280 5340 t
       +10 R f
       +( may be the)3 466(that is, the window to which typing and mouse actions refer; this)11 2609 2 1965 5340 t
       +10 CW f
       +(sam)720 5460 w
       +10 R f
       +( a file has multiple)4 752( When)1 290( file windows.)2 573(window \(that in which commands may be typed\) or one of the)11 2499 4 926 5460 t
       +( current file is the last file)6 1074( The)1 213(windows, the image of the file in each window is always kept up to date.)14 3033 3 720 5580 t
       +(affected by a command, so if the)6 1401 1 720 5700 t
       +10 CW f
       +(sam)2162 5700 w
       +10 R f
       +( the)1 164(window is current, the current window is not a window on)10 2493 2 2383 5700 t
       +( window on a file has its own value of dot, and when switching between)14 3148( each)1 227( However,)1 460(current file.)1 485 4 720 5820 t
       +( flipping between)2 711( Thus,)1 281( file, the file's value of dot is changed to that of the window.)13 2494(windows on a single)3 834 4 720 5940 t
       +(windows behaves in the obvious, convenient way.)6 2003 1 720 6060 t
       +( 3 has a list of commands to)7 1154( Button)1 328( numbered left to right.)4 944(The mouse on the Blit has three buttons,)7 1644 4 970 6216 t
       +( as printed by the)4 708(manipulate windows, followed by a list of `menu lines' exactly)9 2579 2 720 6336 t
       +10 CW f
       +(f)4037 6336 w
       +10 R f
       +(command, one per file)3 913 1 4127 6336 t
       +( the list is long, the Blit menu software)8 1577( If)1 119( file name.)2 430( menu lines are sorted by)5 1011( These)1 290(\(not one per window\).)3 893 6 720 6456 t
       +( the)1 153( Using)1 295( manageable by generating a scrolling menu instead of an unwieldy long list.)12 3143(will make it more)3 729 4 720 6576 t
       +( makes that file the current file, and the most recently current window in)13 2967(menu to select a file from the list)7 1353 2 720 6696 t
       +( if that file is already current, selecting it in the menu cycles through the)14 2960( But)1 201( the current window.)3 844(that file)1 315 4 720 6816 t
       +( is no)2 239( there)1 234( If)1 126(windows on the file; this simple trick avoids a special menu to choose windows on a file.)16 3721 4 720 6936 t
       +(window open on the file,)4 996 1 720 7056 t
       +10 CW f
       +(sam)1741 7056 w
       +10 R f
       +(changes the mouse cursor to prompt the user to create one.)10 2342 1 1946 7056 t
       +( commands)1 465(The commands on the button 3 menu are straightforward \(see Figure 3\), and are like the)15 3605 2 970 7212 t
       +cleartomark
       +showpage
       +saveobj restore
       +%%EndPage: 11 11
       +%%Page: 12 12
       +/saveobj save def
       +mark
       +12 pagesetup
       +10 R f
       +(- 12 -)2 216 1 2772 480 t
       +(to manipulate windows in)3 1048 1 720 840 t
       +10 CW f
       +(mux)1797 840 w
       +10 R f
       +(,)1977 840 w
       +6 R f
       +(8)2002 790 w
       +10 R f
       +(the Blit's window system.)3 1057 1 2061 840 t
       +10 CW f
       +(New)3172 840 w
       +10 R f
       +( empty)1 278(makes a new file, and gives it one)7 1381 2 3381 840 t
       +(window, whose size is determined by a rectangle swept by the mouse.)11 2820 1 720 960 t
       +10 CW f
       +(Xerox)3592 960 w
       +10 R f
       +( a window to be)4 650(prompts for)1 471 2 3919 960 t
       +( multiple windows are created on one file.)7 1848(selected, and makes a clone of that window; this is how)10 2472 2 720 1080 t
       +10 CW f
       +(Reshape)720 1200 w
       +10 R f
       +( and)1 172(changes the size of the indicated window,)6 1678 2 1167 1200 t
       +10 CW f
       +(close)3045 1200 w
       +10 R f
       +( that is the last window open)6 1162( If)1 119(deletes it.)1 386 3 3373 1200 t
       +(on the file,)2 434 1 720 1320 t
       +10 CW f
       +(close)1180 1320 w
       +10 R f
       +(first does a)2 440 1 1506 1320 t
       +10 CW f
       +(D)1972 1320 w
       +10 R f
       +(command on the file.)3 852 1 2058 1320 t
       +10 CW f
       +(Write)2961 1320 w
       +10 R f
       +(is identical to a)3 611 1 3287 1320 t
       +10 CW f
       +(w)3924 1320 w
       +10 R f
       +(command on the file; it is)5 1030 1 4010 1320 t
       +( Finally,)1 373(in the menu purely for convenience.)5 1511 2 720 1440 t
       +10 CW f
       +(\304\304sam\304\304)2643 1440 w
       +10 R f
       +( the com-)2 407(is a menu item that appears between)6 1531 2 3102 1440 t
       +( it makes the)3 544( Selecting)1 434(mands and the file names.)4 1088 3 720 1560 t
       +10 CW f
       +(sam)2822 1560 w
       +10 R f
       +(window the current window, causing subsequent)5 2002 1 3038 1560 t
       +(typing to be interpreted as commands.)5 1526 1 720 1680 t
       +cleartomark
       +saveobj restore
       +%ps_include: begin
       +save
       +/ed {exch def} def
       +{} /showpage ed
       +{} /copypage ed
       +{} /erasepage ed
       +{} /letter ed
       +currentdict /findfont known systemdict /findfont known and {
       +        /findfont systemdict /findfont get def
       +} if
       +36 dict dup /PS-include-dict-dw ed begin
       +/context ed
       +count array astore /o-stack ed
       +%ps_include: variables begin
       +/llx 242 def
       +/lly 297 def
       +/urx 369.44 def
       +/ury 494.28 def
       +/w 0 def
       +/o 0 def
       +/s 0 def
       +/cx 2880 def
       +/cy -2906 def
       +/sx 4320 def
       +/sy 1972 def
       +/ax 0.5 def
       +/ay 0.5 def
       +/rot 0 def
       +%ps_include: variables end
       +{llx lly urx ury} /bbox ed
       +{newpath 2 index exch 2 index exch dup 6 index exch
       + moveto 3 {lineto} repeat closepath} /boxpath ed
       +{dup mul exch dup mul add sqrt} /len ed
       +{2 copy gt {exch} if pop} /min ed
       +{2 copy lt {exch} if pop} /max ed
       +{transform round exch round exch A itransform} /nice ed
       +{6 array} /n ed
       +n defaultmatrix n currentmatrix n invertmatrix n concatmatrix /A ed
       +urx llx sub 0 A dtransform len /Sx ed
       +0 ury lly sub A dtransform len /Sy ed
       +llx urx add 2 div lly ury add 2 div A transform /Cy ed /Cx ed
       +rot dup sin abs /S ed cos abs /C ed
       +Sx S mul Sy C mul add /H ed
       +Sx C mul Sy S mul add /W ed
       +sy H div /Scaley ed
       +sx W div /Scalex ed
       +s 0 eq {Scalex Scaley min dup /Scalex ed /Scaley ed} if
       +sx Scalex W mul sub 0 max ax 0.5 sub mul cx add /cx ed
       +sy Scaley H mul sub 0 max ay 0.5 sub mul cy add /cy ed
       +urx llx sub 0 A dtransform exch atan rot exch sub /rot ed
       +n currentmatrix initgraphics setmatrix
       +cx cy translate
       +Scalex Scaley scale
       +rot rotate
       +Cx neg Cy neg translate
       +A concat
       +bbox boxpath clip newpath
       +w 0 ne {gsave bbox boxpath 1 setgray fill grestore} if
       +end
       +gsave
       +%ps_include: inclusion begin
       +/picstr 23 string def
       +242 297 translate
       +127.44 197.28 scale
       +
       +177 274 1 [177 0 0 -274 0 274]
       +{currentfile picstr readhexstring pop} image
       +
       +ffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffff
       +ffc00000000000000000000000000000000000000fffff
       +ffc0000fffffffffffffffffffffffffffffffffefffff
       +ffc0000fffffffffffffffffffffffffffffffffefffff
       +ffc0000fffffffffffffffffffffffffffffffffefffff
       +ffc0000fffffffffffffffffffffffffffffffffefffff
       +ffc0000fffffffffffffffffffffffffffffffffefffff
       +ffc0000fffffffffffffe47c39cfffffffffffffefffff
       +ffc0000fffffffffffffe23999efffffffffffffefffff
       +ffc0000fffffffffffffe733992fffffffffffffefffff
       +ffc0000fffffffffffffe733992fffffffffffffefffff
       +ffc0000fffffffffffffe730192fffffffffffffefffff
       +ffc0000fffffffffffffe733fc9fffffffffffffefffff
       +ffc0000fffffffffffffe733fc9fffffffffffffefffff
       +ffc0000fffffffffffffe7399c9fffffffffffffefffff
       +ffc0000fffffffffffffe73c3c9fffffffffffffefffff
       +ffc0000fffffffffffffffffffffffffffffffffefffff
       +ffc0000fffffffffffffffffffffffffffffffffefffff
       +ffc0000fffffffffffffffffffffffffffffffffefffff
       +ffc0000fffffffffffffffffffffffffffffffffefffff
       +ffc0000fffffffffffffffffffffffffffffffffefffff
       +ffc0000fffffffffffffffffffffffffffffffffefffff
       +ffc0000fffffffffffffffffffffffffffffffffefffff
       +ffc0000fffffffffffce78719c3ce7ffffffffffefffff
       +ffc0000fffffffffffce7338199ce7ffffffffffefffff
       +ffc0000fffffffffffe6e73993ce6fffffffffffefffff
       +ffc0000fffffffffffe1e739f3ce1fffffffffffefffff
       +ffc0000ffffffffffffbe039f3cfbfffffffffffefffff
       +ffc0000ffffffffffff0e7f9f3cf0fffffffffffefffff
       +ffc0000fffffffffffece7f9f3cecfffffffffffefffff
       +ffc0000fffffffffffce7339f99ce7ffffffffffefffff
       +ffc0000fffffffffffce78707c3ce7ffffffffffefffff
       +ffc0000fffffffffffffffffffffffffffffffffefffff
       +ffc0000fffffffffffffffffffffffffffffffffefffff
       +ffc0000fffffffffffffffffffffffffffffffffefffff
       +ffc0000fffffffffffffffffffffffffffffffffefffff
       +ffc0000ffffffffffffffff3ffffffffffffffffefffff
       +ffc0000ffffffffffffffff3ffffffffffffffffefffff
       +ffc0000ffffffffffffffff3ffffffffffffffffefffff
       +ffc0000fffffffff8cf0f0723c1c9f87ffffffffefffff
       +ffc0000fffffffffc0e6673119cc4f33ffffffffefffff
       +ffc0000fffffffffccce67f39fcce673ffffffffefffff
       +ffc0000fffffffffcfce63f39fcce673ffffffffefffff
       +ffc0000fffffffffcfc070739c0ce603ffffffffefffff
       +ffc0000fffffffffcfcffe3399cce67fffffffffefffff
       +ffc0000fffffffffcfcfff3399cce67fffffffffefffff
       +ffc0000fffffffffcfe66733998ccf33ffffffffefffff
       +ffc0000fffffffff83f0f0739c4c1f87ffffffffefffff
       +ffc0000ffffffffffffffffffffcffffffffffffefffff
       +ffc0000ffffffffffffffffffffcffffffffffffefffff
       +ffc0000ffffffffffffffffffffcffffffffffffefffff
       +ffc0000fffffffffffffffffffffffffffffffffefffff
       +ffc0000fffffffffffffc1ffffffffffffffffffefffff
       +ffc0000ffffffffffffff9ffffffffffffffffffefffff
       +ffc0000ffffffffffffff9ffffffffffffffffffefffff
       +ffc0000ffffffffffff0f9f87c1f0fffffffffffefffff
       +ffc0000fffffffffffe679f339ce67ffffffffffefffff
       +ffc0000fffffffffffce79e799fce7ffffffffffefffff
       +ffc0000fffffffffffcff9e798fce7ffffffffffefffff
       +ffc0000fffffffffffcff9e79c1c07ffffffffffefffff
       +ffc0000fffffffffffcff9e79f8cffffffffffffefffff
       +ffc0000fffffffffffcff9e79fccffffffffffffefffff
       +ffc0000fffffffffffe679f339ce67ffffffffffefffff
       +ffc0000ffffffffffff0c0387c1f0fffffffffffefffff
       +ffc0000fffffffffffffffffffffffffffffffffefffff
       +ffc0000fffffffffffffffffffffffffffffffffefffff
       +ffc00008000000000000000000000000000000000fffff
       +ffc00008000000000000000000000000000000000fffff
       +ffc00008000000000000000300000000000000000fffff
       +ffc00008000000000000000300800000000000000fffff
       +ffc00008000000000000000001800000000000000fffff
       +ffc000080000000000319cdf07f0f000000000000fffff
       +ffc000080000000000308fc301819800000000000fffff
       +ffc000080000000000368cc30183180000ff80000fffff
       +ffc000080000000000368c030183180000fe00000fffff
       +ffc000080000000000368c030183f80000f800000fffff
       +ffc0000800000000001b0c030183000000fc00000fffff
       +ffc0000800000000001b0c030183000000fe00000fffff
       +ffc0000800000000001b0c030181980000df00000fffff
       +ffc0000800000000001b1f1fe0f0f00000cf80000fffff
       +ffc000080000000000000000000000000087c0000fffff
       +ffc000080000000000000000000000000083e0000fffff
       +ffc0000ffffffffffffffffffffffffffffe0fffefffff
       +ffc0000fffffffffffffffffffffffffffff07ffefffff
       +ffc0000fffffffffffffffffffffffffffff83ffefffff
       +ffc0000fffffffffffffffffffffffffffffc1ffefffff
       +ffc0000fffffffffffffffffffffffffffffe0ffefffff
       +ffc0000ffffffffffffff07832dffffffffff1ffefffff
       +ffc0000fffffffffeef76733900f77bbfffffbffefffff
       +ffc0000fffffffffc6e367ff924e371bffffffffefffff
       +ffc0000fffffffff80c063ff924c0603ffffffffefffff
       +ffc0000fffffffffb1d8f078124d8ec7ffffffffefffff
       +ffc0000fffffffffbbddfe33924ddeefffffffffefffff
       +ffc0000fffffffffffffff33924fffffffffffffefffff
       +ffc0000fffffffffffffe733124fffffffffffffefffff
       +ffc0000ffffffffffffff078924fffffffffffffefffff
       +ffc0000fffffffffffffffffffffffffffffffffefffff
       +ffc0000fffffffffffffffffffffffffffffffffefffff
       +ffc0000fffffffffffffffffffffffffffffffffefffff
       +ffc0000fffffffffffffffffffffffffffffffffefffff
       +ffc0000ffffffffffffff3f9ffffffffffffffffefffff
       +ffc0000ffffffffffffff3f9ffffffffffffffffefffff
       +ffc0000ffffffffffffff3f9ffffffffffffffffefffff
       +ffc0000ffff9fffffe0f83c18cf0f0783fff0fffefffff
       +ffc0000ffff9fffffce73399c0e667339ffe67ffefffff
       +ffc0000ffff9ffffffe67339ccce67f3fffce7ffefffff
       +ffc0000fffc03fffffe67339cfce63f1fffcffffefffff
       +ffc0000fffc03ffffe067339cfc070783ffcffffefffff
       +ffc0000ffff9fffffce67339cfcffe3f1ffcffffefffff
       +ffc0000ffff9fc7ffce67339cfcfff3f9e3cffffefffff
       +ffc0000ffff9fc7ffcc72391cfe667339e3e67ffefffff
       +ffc0000ffffffc7ffe2793c983f0f0783e3f0fffefffff
       +ffc0000fffffffffffffffffffffffffffffffffefffff
       +ffc0000fffffffffffffffffffffffffffffffffefffff
       +ffc0000fffffffffffffffffffffffffffffffffefffff
       +ffc0000fffffffffffffffffffffffffffffffffefffff
       +ffc0000ffffffffffffc1e0fffffffffffffffffefffff
       +ffc0000fffffffffffff9fcfffffffffffffffffefffff
       +ffc0000fffffffffffff9fcfffffffffffffffffefffff
       +ffc0000ffffffffffe0f9fcfc3f0fffc3fffffffefffff
       +ffc0000ffffffffffce79fcf99e67ff99fffffffefffff
       +ffc0000fffffffffffe79fcf3cce7ff39fffffffefffff
       +ffc0000fffe03fffffe79fcf3ccffff3ffffffffefffff
       +ffc0000fffe03ffffe079fcf3ccffff3ffffffffefffff
       +ffc0000ffffffffffce79fcf3ccffff3ffffffffefffff
       +ffc0000ffffffffffce79fcf3ccff8f3ffffffffefffff
       +ffdffffffffffffffcc79fcf99e678f99fffffffefffff
       +ffdffffffffffffffe240201c3f0f8fc3fffffffefffff
       +ffdfffffffffffffffffffffffffffffffffffffefffff
       +ffdfffffffffffffffffffffffffffffffffffffefffff
       +ffdfffffffffffffffffffffffffffffffffffffefffff
       +ffdfffffffffffffffffffffffffffffffffffffefffff
       +ffdffffffffffffffcffffe1f0ffffffffffffffefffff
       +ffdffffffffffffffcffffcfe7ffffffffffffffefffff
       +ffdffffffffffffffcffffcfe7ffffffffffffffefffff
       +ffdffffffffdfffffc9e730180f0e33ffe1fffffefffff
       +ffdffffffff57ffffc4e73cfe7e6703ffccfffffefffff
       +ffdffffffff8fffffce673cfe7ce733ff9cfffffefffff
       +ffdfffffffe23ffffce673cfe7ce73fff9ffffffefffff
       +ffdffffffff8fffffce673cfe7c073fff9ffffffefffff
       +ffdffffffff57ffffce673cfe7cff3fff9ffffffefffff
       +ffdffffffffdfffffce673cfe7cff3fc79ffffffefffff
       +ffdffffffffffffffcce23cfe7e673fc7ccfffffefffff
       +ffdffffffffffffffc1f130381f0e0fc7e1fffffefffff
       +ffdfffffffffffffffffffffffffffffffffffffefffff
       +ffdfffffffffffffffffffffffffffffffffffffefffff
       +ffdfffffffffffffffffffffffffffffffffffffefffff
       +ffdfffffffffffffffffffffffffffffffffffffefffff
       +ffdffffffffffffffffffff9ffffffffffffffffefffff
       +ffdffffffffffffffffffff9ffffffffffffffffefffff
       +ffdffffffffffffffffffff9ffffffffffffffffefffff
       +ffdfffffffffffffff0cb7c1fff0ffffffffffffefffff
       +ffdffffffffffffffe640399ffe67fffffffffffefffff
       +ffdffffffffffffffce49339ffce7fffffffffffefffff
       +ffdfffffffe03ffffcfc9339ffcfffffffffffffefffff
       +ffdfffffffe03ffffcfc9339ffcfffffffffffffefffff
       +ffdffffffffffffffcfc9339ffcfffffffffffffefffff
       +ffdffffffffffffffcfc9339e3cfffffffffffffefffff
       +ffdffffffffffffffe649391e3e67fffffffffffefffff
       +ffdfffffffffffffff0c93c9e3f0ffffffffffffefffff
       +ffdfffffffffffffffffffffffffffffffffffffefffff
       +ffdfffffffffffffffffffffffffffffffffffffefffff
       +ffdfffffffffffffffffffffffffffffffffffffefffff
       +ffdfffffffffffffffffffffffffffffffffffffefffff
       +ffdffffff1ffffffffe79fffffffffffffffffffefffff
       +ffdffffff1ffffffffe79fffffffffffffffffffefffff
       +ffdffffff1ffffffffe7ffffffffffffffffffffefffff
       +ffdffffff9f9ffffff041f83e1fff87fffffffffefffff
       +ffdffffff9f9fffffe679f39ccfff33fffffffffefffff
       +ffdffffff3f9fffffce79f3f9cffe73fffffffffefffff
       +ffdfffffffc03ffffce79f1f9fffe7ffffffffffefffff
       +ffdfffffffc03ffffce79f839fffe7ffffffffffefffff
       +ffdffffffff9fffffce79ff19fffe7ffffffffffefffff
       +ffdffffffff9fffffce79ff99ff1e7ffffffffffefffff
       +ffdffffffff9fffffe479f39ccf1f33fffffffffefffff
       +ffdfffffffffffffff240383e1f1f87fffffffffefffff
       +ffdfffffffffffffffffffffffffffffffffffffefffff
       +ffdfffffffffffffffffffffffffffffffffffffefffff
       +ffdfffffffffffffffffffffffffffffffffffffefffff
       +ffdfffffffffffffffffffffffffffffffffffffefffff
       +ffdfffffffffffffffffffffffffffffffffffffefffff
       +ffdfffffffffffffffffffffffffffffffffffffefffff
       +ffdfffffffffffffffffffffffffffffffffffffefffff
       +ffdfffffffffffffff0e3319c3c67ffc3fffffffefffff
       +ffdffffffffffffffe67038199e07ff99fffffffefffff
       +ffdffffffffffffffce733993ce67ff39fffffffefffff
       +ffdfffffffe03ffffce73f9f3ce7fff3ffffffffefffff
       +ffdfffffffe03ffffc073f9f3ce7fff3ffffffffefffff
       +ffdffffffffffffffcff3f9f3ce7fff3ffffffffefffff
       +ffdffffffffffffffcff3f9f3ce7f8f3ffffffffefffff
       +ffdffffffffffffffe673f9f99e7f8f99fffffffefffff
       +ffdfffffffffffffff0e0f07c3c1f8fc3fffffffefffff
       +ffdfffffffffffffffffffffffffffffffffffffefffff
       +ffdfffffffffffffffffffffffffffffffffffffefffff
       +ffdfffffffffffffffffffffffffffffffffffffefffff
       +ffdfffffffffffffffffffffffffffffffffffffefffff
       +ffdffffffffffffffffffffffffffffff9ffffffefffff
       +ffdffffffffffffffffffffffffffffff9ffffffefffff
       +ffdffffffffffffffffffffffffffffff9ffffffefffff
       +ffdfffffffffffffff0e3319c3c6707ff91fffffefffff
       +ffdffffffffffffffe67038199e0673ff88fffffefffff
       +ffdffffffffffffffce733993ce667fff9cfffffefffff
       +ffdfffffffe03ffffce73f9f3ce7e3fff9cfffffefffff
       +ffdfffffffe03ffffc073f9f3ce7f07ff9cfffffefffff
       +ffdffffffffffffffcff3f9f3ce7fe3ff9cfffffefffff
       +ffdffffffffffffffcff3f9f3ce7ff3c79cfffffefffff
       +ffdffffffffffffffe673f9f99e7e73c79cfffffefffff
       +ffdfffffffffffffff0e0f07c3c1f07c79cfffffefffff
       +ffdfffffffffffffffffffffffffffffffffffffefffff
       +ffdfffffffffffffffffffffffffffffffffffffefffff
       +ffdfffffffffffffffffffffffffffffffffffffefffff
       +ffdfffffffffffffffffffffffffffffffffffffefffff
       +ffdfffffffffffffff879e0fffffffffffffffffefffff
       +ffdfffffffffffffff3f9fcfffffffffffffffffefffff
       +ffdfffffffffffffff3fffcfffffffffffffffffefffff
       +ffdffffffffffffffc041fcfe1fff87fffffffffefffff
       +ffdfffffffffffffff3f9fcfccfff33fffffffffefffff
       +ffdfffffffffffffff3f9fcf9cffe73fffffffffefffff
       +ffdfffffffe03fffff3f9fcf9cffe7ffffffffffefffff
       +ffdfffffffe03fffff3f9fcf80ffe7ffffffffffefffff
       +ffdfffffffffffffff3f9fcf9fffe7ffffffffffefffff
       +ffdfffffffffffffff3f9fcf9ff1e7ffffffffffefffff
       +ffdfffffffffffffff3f9fcfccf1f33fffffffffefffff
       +ffdffffffffffffffc0c0201e1f1f87fffffffffefffff
       +ffdfffffffffffffffffffffffffffffffffffffefffff
       +ffdfffffffffffffffffffffffffffffffffffffefffff
       +ffdfffffffffffffffffffffffffffffffffffffefffff
       +ffdfffffffffffffffffffffffffffffffffffffefffff
       +ffdfffffffffffffffffffff0783ffffffffffffefffff
       +ffdfffffffffffffffffffffe7f3ffffffffffffefffff
       +ffdfffffffffffffffffffffe7f3ffffffffffffefffff
       +ffdfffffffffffffff078783e7f3f0fc3fff0fffefffff
       +ffdffffffffffffffe673339e7f3e6799ffe67ffefffff
       +ffdffffffffffffffce673f9e7f3cf339ffce7ffefffff
       +ffdfffffffe03ffffce67ff9e7f3cf33fffcffffefffff
       +ffdfffffffe03ffffce67f81e7f3cf33fffcffffefffff
       +ffdffffffffffffffce67f39e7f3cf33fffcffffefffff
       +ffdffffffffffffffce67f39e7f3cf33fe3cffffefffff
       +ffdffffffffffffffe473331e7f3e6799e3e67ffefffff
       +ffdfffffffffffffff278789008070fc3e3f0fffefffff
       +ffdfffffffffffffffe7ffffffffffffffffffffefffff
       +ffdffffffffffffffce7ffffffffffffffffffffefffff
       +ffdffffffffffffffe0fffffffffffffffffffffefffff
       +ffdfffffffffffffffffffffffffffffffffffffefffff
       +ffdfffffffffffffff3fffffffffffffffffffffefffff
       +ffdfffffffffffffff3fffffffffffffffffffffefffff
       +ffdfffffffffffffffffffffffffffffffffffffefffff
       +ffdffffffffffffff83f0fffe1ffffffffffffffefffff
       +ffdfffffffffffffff3e67ffccffffffffffffffefffff
       +ffdfffffffffffffff3cf3ff9cffffffffffffffefffff
       +ffdfffffffe03fffff3cf3ff9fffffffffffffffefffff
       +ffdfffffffe03fffff3cf3ff9fffffffffffffffefffff
       +ffdfffffffffffffff3cf3ff9fffffffffffffffefffff
       +ffdfffffffffffffff3cf3c79fffffffffffffffefffff
       +ffdfffffffffffffff3e67c7ccffffffffffffffefffff
       +ffdffffffffffffff8070fc7e1ffffffffffffffefffff
       +ffdfffffffffffffffffffffffffffffffffffffefffff
       +ffdfffffffffffffffffffffffffffffffffffffefffff
       +ffc00000000000000000000000000000000000000fffff
       +ffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffff
       +ffffffffffffffffffffffffffffffffffffffffffffff
       +showpage
       +%ps_include: inclusion end
       +grestore
       +PS-include-dict-dw begin
       +o 0 ne {gsave A defaultmatrix /A ed llx lly nice urx ury nice
       +        initgraphics 0.1 setlinewidth boxpath stroke grestore} if
       +clear o-stack aload pop
       +context end restore
       +%ps_include: end
       +/saveobj save def
       +mark
       +8 I f
       +( prevent its)2 361( black rectangle on the left is a scroll bar; the menu is limited to the length shown to)18 2744( The)1 163(Figure 3. The menu on button 3.)6 1052 4 720 3992 t
       +( the)1 121( Above)1 243(becoming unwieldy.)1 642 3 720 4092 t
       +8 CW f
       +(\304\304sam\304\304)1750 4092 w
       +8 I f
       +( a list of files, presented exactly as with the)9 1397(line is a list of commands; beneath it is)8 1280 2 2110 4092 t
       +8 CW f
       +(f)4810 4092 w
       +8 I f
       +(com-)4881 4092 w
       +(mand.)720 4192 w
       +10 R f
       +(When)970 4468 w
       +10 CW f
       +(sam)1241 4468 w
       +10 R f
       +(requests that a window be swept, in response to)8 1972 1 1454 4468 t
       +10 CW f
       +(new)3460 4468 w
       +10 R f
       +(,)3640 4468 w
       +10 CW f
       +(xerox)3699 4468 w
       +10 R f
       +(or)4033 4468 w
       +10 CW f
       +(reshape)4150 4468 w
       +10 R f
       +(, it changes)2 470 1 4570 4468 t
       +( may be used to)4 631( this state, the mouse)4 844( In)1 135(the mouse cursor from the usual arrow to a box with a small arrow.)13 2710 4 720 4588 t
       +( one corner and releasing it at the opposite corner.)9 2103(indicate an arbitrary rectangle by pressing button 3 at)8 2217 2 720 4708 t
       +( button 3 may simply be clicked, whereupon)7 1860(More conveniently,)1 795 2 720 4828 t
       +10 CW f
       +(sam)3412 4828 w
       +10 R f
       +(creates the maximal rectangle that)4 1411 1 3629 4828 t
       +( the)1 155(contains the cursor and abuts)4 1187 2 720 4948 t
       +10 CW f
       +(sam)2095 4948 w
       +10 R f
       +( placing the)2 482(window. By)1 522 2 2308 4948 t
       +10 CW f
       +(sam)3345 4948 w
       +10 R f
       +(window in the middle of the screen,)6 1482 1 3558 4948 t
       +( stacked fully-overlapping windows can be)5 1721(the user can define two regions \(one above, one below\) in which)11 2599 2 720 5068 t
       +( simple user interface trick makes window creation notice-)8 2403( This)1 236(created with minimal fuss \(see Figure 1\).)6 1681 3 720 5188 t
       +(ably easier.)1 454 1 720 5308 t
       +(The cut-and-paste editor is essentially the same as that in Smalltalk-80.)10 2944 1 970 5464 t
       +6 R f
       +(11)3914 5414 w
       +10 R f
       +( always)1 313(The text in dot is)4 718 2 4009 5464 t
       +( after the)2 358( a character is typed it replaces dot, and sets dot to the null string)14 2630( When)1 291(highlighted on the screen.)3 1041 4 720 5584 t
       +( button, moving)2 657( 1 is used for selection: pressing the)7 1492( Button)1 332( ordinary typing inserts text.)4 1166(character. Thus,)1 673 5 720 5704 t
       +( to\) the text between the points where the button was)10 2248(the mouse, and lifting the button selects \(sets dot)8 2072 2 720 5824 t
       +( a null string; this is called clicking.)7 1477( and releasing at the same point selects)7 1592( Pressing)1 395(pressed and released.)2 856 4 720 5944 t
       +(Clicking twice quickly, or)3 1077 1 720 6064 t
       +10 I f
       +(double clicking,)1 648 1 1833 6064 t
       +10 R f
       +(selects larger objects; for example, double clicking in a word)9 2524 1 2516 6064 t
       +( clicking just inside an opening bracket selects the text contained in the brackets)13 3328(selects the word, double)3 992 2 720 6184 t
       +( double-clicking)1 656( The)1 209( similarly for parentheses, quotes, and so on.)7 1809(\(handling nested brackets correctly\), and)4 1646 4 720 6304 t
       +( If)1 127(rules reflect a bias toward programmers.)5 1667 2 720 6424 t
       +10 CW f
       +(sam)2550 6424 w
       +10 R f
       +( more for word processing, double-clicks)5 1701(were intended)1 573 2 2766 6424 t
       +(would probably select linguistic structures such as sentences.)7 2441 1 720 6544 t
       +( is the)2 245( This)1 231( outside the current window, it makes the indicated window current.)10 2748(If button 1 is pressed)4 846 4 970 6700 t
       +(easiest way to switch between windows and files.)7 1980 1 720 6820 t
       +( mostly apply to the)4 835( These)1 298( of editing functions \(see Figure 4\).)6 1472(Pressing button 2 brings up a menu)6 1465 4 970 6976 t
       +(selected text:)1 537 1 720 7096 t
       +10 CW f
       +(cut)1295 7096 w
       +10 R f
       +( remembers it in a hidden buffer called the)8 1792(deletes the selected text, and)4 1191 2 1513 7096 t
       +10 I f
       +(snarf buffer,)1 507 1 4533 7096 t
       +10 CW f
       +(paste)720 7216 w
       +10 R f
       +(replaces the selected text by the contents of the snarf buffer,)10 2426 1 1047 7216 t
       +10 CW f
       +(snarf)3501 7216 w
       +10 R f
       +(just copies the selected text to)5 1211 1 3829 7216 t
       +cleartomark
       +showpage
       +saveobj restore
       +%%EndPage: 12 12
       +%%Page: 13 13
       +/saveobj save def
       +mark
       +13 pagesetup
       +10 R f
       +(- 13 -)2 216 1 2772 480 t
       +(the snarf buffer,)2 667 1 720 840 t
       +10 CW f
       +(look)1426 840 w
       +10 R f
       +( next literal occurrence of the selected text, and)8 1990(searches forward for the)3 1007 2 1705 840 t
       +10 CW f
       +(<mux>)4740 840 w
       +10 R f
       +(exchanges snarf buffers with the window system in which)8 2510 1 720 960 t
       +10 CW f
       +(sam)3280 960 w
       +10 R f
       +( the last regular)3 693( Finally,)1 384(is running.)1 453 3 3510 960 t
       +( occurrence of a match for the)6 1291(expression used appears as a menu entry to search forward for the next)12 3029 2 720 1080 t
       +(expression.)720 1200 w
       +cleartomark
       +saveobj restore
       +%ps_include: begin
       +save
       +/ed {exch def} def
       +{} /showpage ed
       +{} /copypage ed
       +{} /erasepage ed
       +{} /letter ed
       +currentdict /findfont known systemdict /findfont known and {
       +        /findfont systemdict /findfont get def
       +} if
       +36 dict dup /PS-include-dict-dw ed begin
       +/context ed
       +count array astore /o-stack ed
       +%ps_include: variables begin
       +/llx 268 def
       +/lly 352 def
       +/urx 342.16 def
       +/ury 438.4 def
       +/w 0 def
       +/o 0 def
       +/s 0 def
       +/cx 2880 def
       +/cy -1872 def
       +/sx 4320 def
       +/sy 864 def
       +/ax 0.5 def
       +/ay 0.5 def
       +/rot 0 def
       +%ps_include: variables end
       +{llx lly urx ury} /bbox ed
       +{newpath 2 index exch 2 index exch dup 6 index exch
       + moveto 3 {lineto} repeat closepath} /boxpath ed
       +{dup mul exch dup mul add sqrt} /len ed
       +{2 copy gt {exch} if pop} /min ed
       +{2 copy lt {exch} if pop} /max ed
       +{transform round exch round exch A itransform} /nice ed
       +{6 array} /n ed
       +n defaultmatrix n currentmatrix n invertmatrix n concatmatrix /A ed
       +urx llx sub 0 A dtransform len /Sx ed
       +0 ury lly sub A dtransform len /Sy ed
       +llx urx add 2 div lly ury add 2 div A transform /Cy ed /Cx ed
       +rot dup sin abs /S ed cos abs /C ed
       +Sx S mul Sy C mul add /H ed
       +Sx C mul Sy S mul add /W ed
       +sy H div /Scaley ed
       +sx W div /Scalex ed
       +s 0 eq {Scalex Scaley min dup /Scalex ed /Scaley ed} if
       +sx Scalex W mul sub 0 max ax 0.5 sub mul cx add /cx ed
       +sy Scaley H mul sub 0 max ay 0.5 sub mul cy add /cy ed
       +urx llx sub 0 A dtransform exch atan rot exch sub /rot ed
       +n currentmatrix initgraphics setmatrix
       +cx cy translate
       +Scalex Scaley scale
       +rot rotate
       +Cx neg Cy neg translate
       +A concat
       +bbox boxpath clip newpath
       +w 0 ne {gsave bbox boxpath 1 setgray fill grestore} if
       +end
       +gsave
       +%ps_include: inclusion begin
       +/picstr 13 string def
       +268 352 translate
       + 74.16  86.40 scale
       +
       +103 120 1 [103 0 0 -120 0 120]
       +{currentfile picstr readhexstring pop} image
       +
       +ffffffffffffffffffffffffff
       +ffffffffffffffffffffffffff
       +ffffffffffffffffffffffffff
       +ffffffffffffffffffffffffff
       +ffffffffffffffffffffffffff
       +f800000000000000000001ffff
       +fbfffffffffffffffffffdffff
       +fbfffffffffffffffffffdffff
       +fbfffffffffffffffffffdffff
       +fbfffffffffffdfffffffdffff
       +fbfffffffffff9fffffffdffff
       +fbffffffe1ce603ffffffdffff
       +fbffffffccce79fffffffdffff
       +fbffffff9cce79fffffffdffff
       +fbffffff9fce79fffffffdffff
       +fbffffff9fce79fffffffdffff
       +fbffffff9fce79fffffffdffff
       +fbffffff9fce79fffffffdffff
       +fbffffffccc479fffffffdffff
       +fbffffffe1e27c3ffffffdffff
       +fbfffffffffffffffffffdffff
       +fbfffffffffffffffffffdffff
       +fbfffffffffffffffffffdffff
       +fbfffffffffffffffffffdffff
       +fbfffffffffffffffffffdffff
       +fbfffffffffffdfffffffdffff
       +fbfffffffffff9fffffffdffff
       +fbffff27c1e0e03c3ffffdffff
       +fbffff139cce79f99ffffdffff
       +fbffff39fccff9f39ffffdffff
       +fbffff39fcc7f9f39ffffdffff
       +fbffff39c0e0f9f01ffffdffff
       +fbffff399cfc79f3fffffdffff
       +fbffff399cfe79f3fffffdffff
       +fbffff3398ce79f99ffffdffff
       +fbffff07c4e0fc3c3ffffdffff
       +fbffff3ffffffffffffffdffff
       +fbffff3ffffffffffffffdffff
       +fbffff3ffffffffffffffdffff
       +fbfffffffffffffffffffdffff
       +fbfffffffffffffe1ffffdffff
       +fbfffffffffffffcfffffdffff
       +fbfffffffffffffcfffffdffff
       +fbffff8391e0e3301ffffdffff
       +fbffff3988ce703cfffffdffff
       +fbffff3f9cfe733cfffffdffff
       +fbffff1f9cfe73fcfffffdffff
       +fbffff839ce073fcfffffdffff
       +fbfffff19cce73fcfffffdffff
       +fbfffff99cce73fcfffffdffff
       +fbffff399ccc73fcfffffdffff
       +fbffff839ce260f03ffffdffff
       +fbfffffffffffffffffffdffff
       +fbfffffffffffffffffffdffff
       +fbfffffffffffffffffffdffff
       +fbfffffffffffffffffffdffff
       +fbfffff07fffff3ffffffdffff
       +fbfffffe7fffff3ffffffdffff
       +fbfffffe7fffff3ffffffdffff
       +fbfffffe7e1f0f39fffffdffff
       +fbfffffe7cce673bfffffdffff
       +fbfffffe79e4f337fffffdffff
       +fbfffffe79e4f32ffffffdffff
       +fbfffffe79e4f30ffffffdffff
       +fbfffffe79e4f327fffffdffff
       +fbfffffe79e4f333fffffdffff
       +fbfffffe7cce6739fffffdffff
       +fbfffff00e1f0f39fffffdffff
       +fbfffffffffffffffffffdffff
       +fbfffffffffffffffffffdffff
       +fbfffffffffffffffffffdffff
       +fbfffffffffffffffffffdffff
       +fbfffffffffffffffffffdffff
       +fbfffffbfffffffbfffffdffff
       +fbfffff3fffffff9fffffdffff
       +fbffffe72dce673cfffffdffff
       +fbffffcf00ce673e7ffffdffff
       +fbffff9f24ce737f3ffffdffff
       +fbffff1f24ce70ff1ffffdffff
       +fbffff9f24ce7dff3ffffdffff
       +fbffffcf24ce787e7ffffdffff
       +fbffffe724ce767cfffffdffff
       +fbfffff324c46739fffffdffff
       +fbfffffb24e2673bfffffdffff
       +fbfffffffffffffffffffdffff
       +fbfffffffffffffffffffdffff
       +f800000000000000000001ffff
       +f800000000000000000001ffff
       +f800c04000030000200001ffff
       +f800c18000030000180001ffff
       +f8018300000180000c0001ffff
       +f801830000c180dc0c0c01ffff
       +f803060000c0c0ee060c01ffff
       +f803060000c0c0c6060c01ffff
       +f806060007f860c6067f81ffff
       +f806060007f860c6067f81ffff
       +f80c060000c030c6060c01ffff
       +f80c0601c0c030c6060c01ffff
       +f8180601c0c018c6060c01ffff
       +f8180301c00018c60c001e0fff
       +f830030000000c000c001e3fff
       +f830018000000c0018001effff
       +f80000400000000020001e7fff
       +ffffffffffffffffffffe03fff
       +ffffffffffffffffffffe41fff
       +ffffffffffffffffffffe60fff
       +ffffffffffffffffffffef07ff
       +ffffffffffffffffffffef83ff
       +ffffffffffffffffffffffc1ff
       +ffffffffffffffffffffffe0ff
       +fffffffffffffffffffffff07f
       +fffffffffffffffffffffff83f
       +fffffffffffffffffffffffc1f
       +fffffffffffffffffffffffe3f
       +ffffffffffffffffffffffff7f
       +ffffffffffffffffffffffffff
       +ffffffffffffffffffffffffff
       +ffffffffffffffffffffffffff
       +ffffffffffffffffffffffffff
       +ffffffffffffffffffffffffff
       +showpage
       +%ps_include: inclusion end
       +grestore
       +PS-include-dict-dw begin
       +o 0 ne {gsave A defaultmatrix /A ed llx lly nice urx ury nice
       +        initgraphics 0.1 setlinewidth boxpath stroke grestore} if
       +clear o-stack aload pop
       +context end restore
       +%ps_include: end
       +/saveobj save def
       +mark
       +8 I f
       +( bottom entry tracks the most recently used regular expression, which may be literal text.)14 2834( The)1 160(Figure 4. The menu on button 2.)6 1034 3 720 2404 t
       +10 R f
       +( mouse language is entirely due to the equal-)8 1794(The relationship between the command language and the)7 2276 2 970 2680 t
       +( example, to make a set of changes)7 1419( For)1 193( button 1 on the mouse.)5 959(ity of dot and the selected text chosen with)8 1749 4 720 2800 t
       +( be set by double clicking on the left brace that begins the subroutine, which sets)15 3278(in a C subroutine, dot can)5 1042 2 720 2920 t
       +( address-free command then typed in the)6 1660( An)1 178(dot for the command language.)4 1269 3 720 3040 t
       +10 CW f
       +(sam)3858 3040 w
       +10 R f
       +(window will apply only)3 971 1 4069 3040 t
       +( idea is to select what you want, and)8 1453( The)1 206( text between the opening and closing braces of the function.)10 2436(to the)1 225 4 720 3160 t
       +( of)1 108( And)1 222( want to do with it, whether invoked by a menu selection or by a typed command.)16 3263(then say what you)3 727 4 720 3280 t
       +( relationship)1 516( This)1 247( command completes.)2 912(course, the value of dot is highlighted on the display after the)11 2645 4 720 3400 t
       +( to explain, but comfortable, even natural, in)7 1859(between mouse interface and command language is clumsy)7 2461 2 720 3520 t
       +(practice.)720 3640 w
       +10 B f
       +(The Implementation)1 875 1 720 3880 t
       +10 R f
       +(The next few sections describe how)5 1447 1 720 4036 t
       +10 CW f
       +(sam)2196 4036 w
       +10 R f
       +( together, first the host part, then the inter-component com-)9 2411(is put)1 224 2 2405 4036 t
       +( dis-)1 181( explaining how the command language is implemented, the)8 2462( After)1 267(munication, then the terminal part.)4 1410 4 720 4156 t
       +( presen-)1 320( The)1 207(cussion follows \(roughly\) the path of a character from the temporary file on disc to the screen.)16 3793 3 720 4276 t
       +( because that is how the program was designed and because the algo-)12 2844(tation centers on the data structures,)5 1476 2 720 4396 t
       +(rithms are easy to provide, given the right data structures.)9 2299 1 720 4516 t
       +10 B f
       +(Parsing and execution)2 945 1 720 4756 t
       +10 R f
       +( recursive descent)2 754(The command language is interpreted by parsing each command with a table-driven)11 3566 2 720 4912 t
       +( editors instead)2 617( Most)1 262( top-down executor.)2 813(parser, and when a complete command is assembled, invoking a)9 2628 4 720 5032 t
       +( and unambiguous to)3 875( of a parser makes it easy)6 1080( Use)1 217(employ a simple character-at-a-time lexical scanner.)5 2148 4 720 5152 t
       +( conventions such as back-)4 1102( escape)1 299( First,)1 268(detect when a command is complete, which has two advantages.)9 2651 4 720 5272 t
       +( the command isn't finished, the parser keeps)7 1850( if)1 117( multiple-line commands are unnecessary;)4 1710(slashes to quote)2 643 4 720 5392 t
       +( example, a multiple-line append driven by an)7 1836(reading. For)1 513 2 720 5512 t
       +10 CW f
       +(x)3094 5512 w
       +10 R f
       +(command is straightforward:)2 1154 1 3179 5512 t
       +9 CW f
       +(x/.*\\n/ g/Peter/ a)2 972 1 1008 5682 t
       +(one line about Peter)3 1080 1 1008 5792 t
       +(another line about Peter)3 1296 1 1008 5902 t
       +(.)1008 6012 w
       +10 R f
       +(Other Unix editors would require a backslash after all but the last line.)12 2809 1 720 6192 t
       +( advantage is specific to the two-process structure of)8 2167(The other)1 393 2 970 6348 t
       +10 CW f
       +(sam)3564 6348 w
       +10 R f
       +( host process must decide)4 1057(. The)1 239 2 3744 6348 t
       +( easily resolved)2 631( problem is)2 462( This)1 234(when a command is completed so the command interpreter can be called.)11 2993 4 720 6468 t
       +(by having the lexical analyzer read the single stream of events from the terminal, directly executing all typ-)17 4320 1 720 6588 t
       +(ing and mouse commands, but passing to the parser characters typed to the)12 3071 1 720 6708 t
       +10 CW f
       +(sam)3823 6708 w
       +10 R f
       +( This)1 234(command window.)1 772 2 4034 6708 t
       +( complicated by the availability of cut-and-paste editing in the)9 2510(scheme is slightly)2 721 2 720 6828 t
       +10 CW f
       +(sam)3979 6828 w
       +10 R f
       +(window, but that dif-)3 853 1 4187 6828 t
       +(ficulty is resolved by applying the rules used in)8 1925 1 720 6948 t
       +10 CW f
       +(mux)2674 6948 w
       +10 R f
       +(: when a newline is typed to the)7 1296 1 2854 6948 t
       +10 CW f
       +(sam)4179 6948 w
       +10 R f
       +(window, all text)2 653 1 4387 6948 t
       +( permits arbi-)2 554( This)1 236( newline is made available to the parser.)7 1658(between the newline and the previously typed)6 1872 4 720 7068 t
       +(trary editing to be done to a command before typing newline and thereby requesting execution.)14 3796 1 720 7188 t
       +cleartomark
       +showpage
       +saveobj restore
       +%%EndPage: 13 13
       +%%Page: 14 14
       +/saveobj save def
       +mark
       +14 pagesetup
       +10 R f
       +(- 14 -)2 216 1 2772 480 t
       +( and commands is regular enough to be)7 1574(The parser is driven by a table because the syntax of addresses)11 2496 2 970 840 t
       +( replacement text in a substitution, so the syn-)8 1859( are few special cases, such as the)7 1373( There)1 286(encoded compactly.)1 802 4 720 960 t
       +( include whether the command allows)5 1542( These)1 293( can be encoded with a few flags.)7 1364(tax of almost all commands)4 1121 4 720 1080 t
       +(an address \(for example,)3 989 1 720 1200 t
       +10 CW f
       +(e)1737 1200 w
       +10 R f
       +( in)1 105(does not\), whether it takes a regular expression \(as)8 2044 2 1825 1200 t
       +10 CW f
       +(x)4001 1200 w
       +10 R f
       +(and)4088 1200 w
       +10 CW f
       +(s)4259 1200 w
       +10 R f
       +(\), whether it takes)3 721 1 4319 1200 t
       +(replacement text \(as in)3 915 1 720 1320 t
       +10 CW f
       +(c)1663 1320 w
       +10 R f
       +(or)1751 1320 w
       +10 CW f
       +(i)1862 1320 w
       +10 R f
       +( syntax of regular expres-)4 1035( internal)1 333( The)1 208(\), which may be multi-line, and so on.)7 1542 4 1922 1320 t
       +( Regular)1 377( parser; a regular expression is a leaf of the command parse tree.)12 2699(sions is handled by a separate)5 1244 3 720 1440 t
       +(expressions are discussed fully in the next section.)7 2014 1 720 1560 t
       +( a com-)2 315(The parser table also has information about defaults, so the interpreter is always called with)14 3755 2 970 1716 t
       +( example, the parser fills in the implicit)7 1676( For)1 204(plete tree.)1 408 3 720 1836 t
       +10 CW f
       +(0)3048 1836 w
       +10 R f
       +(and)3148 1836 w
       +10 CW f
       +($)3332 1836 w
       +10 R f
       +(in the abbreviated address)3 1084 1 3432 1836 t
       +10 CW f
       +(,)4556 1836 w
       +10 R f
       +(\(comma\),)4655 1836 w
       +(inserts a)1 345 1 720 1956 t
       +10 CW f
       +(+)1105 1956 w
       +10 R f
       +( default)1 318(to the left of an unadorned regular expression in an address, and provides the usual)14 3517 2 1205 1956 t
       +(address)720 2076 w
       +10 CW f
       +(.)1044 2076 w
       +10 R f
       +(\(dot\) for commands that expect an address but are not given one.)11 2589 1 1129 2076 t
       +( address is evaluated left-to-right)4 1358( The)1 216( complete command is parsed, the evaluation is easy.)8 2207(Once a)1 289 4 970 2232 t
       +( the)1 156( like many of)3 560( Addresses,)1 495(starting from the value of dot, with a mostly ordinary expression evaluator.)11 3109 4 720 2352 t
       +(data structures in)2 682 1 720 2472 t
       +10 CW f
       +(sam)1427 2472 w
       +10 R f
       +(, are held in a C structure and passed around by value:)11 2162 1 1607 2472 t
       +9 CW f
       +( Position in a file */)5 1188( /*)1 270( Posn;)1 918(typedef long)1 648 4 1008 2642 t
       +(typedef struct Range{)2 1134 1 1008 2752 t
       +( p2;)1 216(Posn p1,)1 1026 2 1440 2862 t
       +(}Range;)1008 2972 w
       +(typedef struct Address{)2 1242 1 1008 3082 t
       +(Range r;)1 540 1 1440 3192 t
       +(File *f;)1 1026 1 1440 3302 t
       +(}Address;)1008 3412 w
       +10 R f
       +( encoded as a substring \(character positions)6 1795(An address is)2 556 2 720 3592 t
       +10 CW f
       +(p1)3106 3592 w
       +10 R f
       +(to)3261 3592 w
       +10 CW f
       +(p2)3374 3592 w
       +10 R f
       +(\) in a file)3 393 1 3494 3592 t
       +10 CW f
       +(f)3922 3592 w
       +10 R f
       +( data type)2 408(. \(The)1 273 2 3982 3592 t
       +10 CW f
       +(File)4698 3592 w
       +10 R f
       +(is)4973 3592 w
       +(described in detail below.\))3 1059 1 720 3712 t
       +( interpreter is an)3 690(The address)1 491 2 970 3868 t
       +10 CW f
       +(Address)2189 3868 w
       +10 R f
       +(-valued function that traverses the parse tree describing an)8 2431 1 2609 3868 t
       +(address \(the parse tree for the address has type)8 1855 1 720 3988 t
       +10 CW f
       +(Addrtree)2600 3988 w
       +10 R f
       +(\):)3080 3988 w
       +cleartomark
       +showpage
       +saveobj restore
       +%%EndPage: 14 14
       +%%Page: 15 15
       +/saveobj save def
       +mark
       +15 pagesetup
       +10 R f
       +(- 15 -)2 216 1 2772 480 t
       +9 CW f
       +(Address)1008 830 w
       +(address\(ap, a, sign\))2 1080 1 1008 940 t
       +(Addrtree *ap;)1 702 1 1440 1050 t
       +(Address a;)1 540 1 1440 1160 t
       +(int sign;)1 486 1 1440 1270 t
       +({)1008 1380 w
       +(Address a2;)1 594 1 1440 1490 t
       +(do)1440 1600 w
       +(switch\(ap->type\){)1872 1710 w
       +(case '.':)1 486 1 1872 1820 t
       +(a=a.f->dot;)2304 1930 w
       +(break;)2304 2040 w
       +(case '$':)1 486 1 1872 2150 t
       +(a.r.p1=a.r.p2=a.f->nbytes;)2304 2260 w
       +(break;)2304 2370 w
       +(case '"':)1 486 1 1872 2480 t
       +(a=matchfile\(a, ap->aregexp\)->dot;)1 1782 1 2304 2590 t
       +(break;)2304 2700 w
       +(case ',':)1 486 1 1872 2810 t
       +(a2=address\(ap->right, a, 0\);)2 1512 1 2304 2920 t
       +(a=address\(ap->left, a, 0\);)2 1404 1 2304 3030 t
       +(if\(a.f!=a2.f || a2.r.p2<a.r.p1\))2 1674 1 2304 3140 t
       +(error\(Eorder\);)2736 3250 w
       +(a.r.p2=a2.r.p2;)2304 3360 w
       +(return a;)1 486 1 2304 3470 t
       +(/* and so on */)4 810 1 1872 3580 t
       +(})1872 3690 w
       +(while\(\(ap=ap->right\)!=0\);)1440 3800 w
       +(return a;)1 486 1 1440 3910 t
       +(})1008 4020 w
       +10 R f
       +( non-local)1 408(Throughout, errors are handled by a)5 1460 2 970 4236 t
       +10 CW f
       +(goto)2869 4236 w
       +10 R f
       +(\(a)3140 4236 w
       +10 CW f
       +(setjmp/longjmp)3248 4236 w
       +10 R f
       +(in C terminology\) hid-)3 921 1 4119 4236 t
       +( routine called)2 587(den in a)2 334 2 720 4356 t
       +10 CW f
       +(error)1674 4356 w
       +10 R f
       +(that immediately aborts the execution, retracts any partially made changes)9 3033 1 2007 4356 t
       +( argument to)2 513( The)1 209( level of the parser.)4 783(\(see the section below on `undoing'\), and returns to the top)10 2390 4 720 4476 t
       +10 CW f
       +(error)4644 4476 w
       +10 R f
       +(is)4973 4476 w
       +( possibly helpful message such as `?addresses out of)8 2179(an enumeration type that is translated to a terse but)9 2141 2 720 4596 t
       +( common messages are kept short; for example the message for a failed regular expression)14 3790(order.' Very)1 530 2 720 4716 t
       +(search is `?search.')2 760 1 720 4836 t
       +(Character addresses such as)3 1125 1 970 4992 t
       +10 CW f
       +(#3)2126 4992 w
       +10 R f
       +(are trivial to implement, as the)5 1251 1 2277 4992 t
       +10 CW f
       +(File)3559 4992 w
       +10 R f
       +(data structure is accessible by)4 1210 1 3830 4992 t
       +( However,)1 447(character number.)1 726 2 720 5112 t
       +10 CW f
       +(sam)1925 5112 w
       +10 R f
       +( of newlines \320 it is too expen-)7 1277(keeps no information about the position)5 1626 2 2137 5112 t
       +( Except)1 330( are computed by reading the file, counting newlines.)8 2148(sive to track dynamically \320 so line addresses)7 1842 3 720 5232 t
       +( access is fast enough to make the technique practical, and)10 2325(in very large files, this has proven acceptable: file)8 1995 2 720 5352 t
       +(lines are not central to the structure of the command language.)10 2492 1 720 5472 t
       +(The command interpreter, called)3 1308 1 970 5628 t
       +10 CW f
       +(cmdexec)2305 5628 w
       +10 R f
       +( parse table includes a func-)5 1131( The)1 208( also straightforward.)2 857(, is)1 119 4 2725 5628 t
       +( as arguments the calculated address)5 1484( function receives)2 725( That)1 241(tion to call to interpret a particular command.)7 1870 4 720 5748 t
       +( command and the command tree \(of type)7 1729(for the)1 271 2 720 5868 t
       +10 CW f
       +(Cmdtree)2754 5868 w
       +10 R f
       +(\), which may contain information such as the)7 1866 1 3174 5868 t
       +( for example, is the function for the)7 1414( Here,)1 268(subtree for compound commands.)3 1359 3 720 5988 t
       +10 CW f
       +(g)3786 5988 w
       +10 R f
       +(and)3871 5988 w
       +10 CW f
       +(v)4040 5988 w
       +10 R f
       +(commands:)4125 5988 w
       +cleartomark
       +showpage
       +saveobj restore
       +%%EndPage: 15 15
       +%%Page: 16 16
       +/saveobj save def
       +mark
       +16 pagesetup
       +10 R f
       +(- 16 -)2 216 1 2772 480 t
       +9 CW f
       +(int)1008 830 w
       +(g_cmd\(a, cp\))1 648 1 1008 940 t
       +(Address a;)1 540 1 1440 1050 t
       +(Cmdtree *cp;)1 648 1 1440 1160 t
       +({)1008 1270 w
       +(compile\(cp->regexp\);)1440 1380 w
       +(if\(execute\(a.f, a.r.p1, a.r.p2\) != \(cp->cmdchar=='v'\)\){)4 2970 1 1440 1490 t
       +(a.f->dot=a;)1872 1600 w
       +(return cmdexec\(a, cp->subcmd\);)2 1620 1 1872 1710 t
       +(})1440 1820 w
       +( indicate that execution is to continue */)7 2268( /*)1 324(return TRUE;)1 648 3 1440 1930 t
       +(})1008 2040 w
       +10 R f
       +(\()720 2220 w
       +10 CW f
       +(Compile)753 2220 w
       +10 R f
       +(and)1202 2220 w
       +10 CW f
       +(execute)1375 2220 w
       +10 R f
       +( Because)1 387(are part of the regular expression code, described in the next section.\))11 2828 2 1825 2220 t
       +(the parser and the)3 706 1 720 2340 t
       +10 CW f
       +(File)1451 2340 w
       +10 R f
       +(data structure do most of the work, most commands are similarly brief.)11 2838 1 1716 2340 t
       +10 B f
       +(Regular expressions)1 858 1 720 2580 t
       +10 R f
       +(The regular expression code in)4 1266 1 720 2736 t
       +10 CW f
       +(sam)2020 2736 w
       +10 R f
       +( than compiled on-the-fly, implementation of)5 1854(is an interpreted, rather)3 952 2 2234 2736 t
       +(Thompson's non-deterministic finite automaton algorithm.)4 2392 1 720 2856 t
       +6 R f
       +(12)3112 2806 w
       +10 R f
       +( the expressions)2 656(The syntax and semantics of)4 1177 2 3207 2856 t
       +(are as in the Unix program)5 1092 1 720 2976 t
       +10 CW f
       +(egrep)1842 2976 w
       +10 R f
       +( only)1 209( The)1 211( alternation, closures, character classes, and so on.)7 2045(, including)1 433 4 2142 2976 t
       +(changes in the notation are two additions:)6 1755 1 720 3096 t
       +10 CW f
       +(\\n)2515 3096 w
       +10 R f
       +( and)1 183(is translated to, and matches, a newline character,)7 2083 2 2675 3096 t
       +10 CW f
       +(@)4980 3096 w
       +10 R f
       +( In)1 140(matches any character.)2 924 2 720 3216 t
       +10 CW f
       +(egrep)1816 3216 w
       +10 R f
       +(, the character)2 575 1 2116 3216 t
       +10 CW f
       +(.)2723 3216 w
       +10 R f
       +(matches any character except newline, and in)6 1856 1 2816 3216 t
       +10 CW f
       +(sam)4705 3216 w
       +10 R f
       +(the)4918 3216 w
       +(same rule seemed safest, to prevent idioms like)7 2033 1 720 3336 t
       +10 CW f
       +(.*)2799 3336 w
       +10 R f
       +(from spanning newlines.)2 1027 1 2965 3336 t
       +10 CW f
       +(Egrep)4063 3336 w
       +10 R f
       +(expressions are)1 632 1 4408 3336 t
       +( certainly it would make sense if all the special charac-)10 2209(arguably too complicated for an interactive editor \320)7 2111 2 720 3456 t
       +( wouldn't have peculiar mean-)4 1223(ters were two-character sequences, so that most of the punctuation characters)10 3097 2 720 3576 t
       +( regular expressions are necessary, and)5 1660(ings \320 but for an interesting command language, full)8 2313 2 720 3696 t
       +10 CW f
       +(egrep)4740 3696 w
       +10 R f
       +( it seemed superfluous to define a new)7 1568( Also,)1 269( Unix programs.)2 662(defines the full regular expression syntax for)6 1821 4 720 3816 t
       +(syntax, since various Unix programs \()5 1520 1 720 3936 t
       +10 CW f
       +(ed)2240 3936 w
       +10 R f
       +(,)2360 3936 w
       +10 CW f
       +(egrep)2410 3936 w
       +10 R f
       +(and)2735 3936 w
       +10 CW f
       +(vi)2904 3936 w
       +10 R f
       +(\) define too many already.)4 1050 1 3024 3936 t
       +(The expressions are compiled by a routine,)6 1812 1 970 4092 t
       +10 CW f
       +(compile)2823 4092 w
       +10 R f
       +( the description of the non-)5 1164(, that generates)2 633 2 3243 4092 t
       +( second routine,)2 669( A)1 139(deterministic finite state machine.)3 1405 3 720 4212 t
       +10 CW f
       +(execute)2975 4212 w
       +10 R f
       +(, interprets the machine to generate the)6 1645 1 3395 4212 t
       +( else-)1 238( algorithm is described)3 985( The)1 229(leftmost-longest match of the expression in a substring of the file.)10 2868 4 720 4332 t
       +(where.)720 4452 w
       +6 R f
       +(12,13)988 4402 w
       +10 CW f
       +(Execute)1152 4452 w
       +10 R f
       +( sets a global variable, of type)6 1213(reports whether a match was found, and)6 1617 2 1601 4452 t
       +10 CW f
       +(Range)4459 4452 w
       +10 R f
       +(, to the)2 281 1 4759 4452 t
       +(substring matched.)1 755 1 720 4572 t
       +( such as when searching backwards for an)7 1752(A trick is required to evaluate the expression in reverse,)9 2318 2 970 4728 t
       +( example,)1 388(expression. For)1 641 2 720 4848 t
       +9 CW f
       +(-/P.*r/)1008 5018 w
       +10 R f
       +( expression, however, is defined for a)6 1532( The)1 211( file for a match of the expression.)7 1411(looks backwards through the)3 1166 4 720 5198 t
       +( solution is to construct a machine identical to the machine for a forward search except)15 3492( The)1 207(forward search.)1 621 3 720 5318 t
       +( the concatenation operators \(the other operators are symmetric under direction reversal\),)11 3562(for a reversal of all)4 758 2 720 5438 t
       +(to exchange the meaning of the operators)6 1652 1 720 5558 t
       +10 CW f
       +(\303)2398 5558 w
       +10 R f
       +(and)2484 5558 w
       +10 CW f
       +($)2654 5558 w
       +10 R f
       +(, and then to read the file backwards, looking for the usual)11 2326 1 2714 5558 t
       +(earliest longest match.)2 896 1 720 5678 t
       +10 CW f
       +(Execute)970 5834 w
       +10 R f
       +( as)1 118( interpret looping constructs such)4 1368( To)1 170(generates only one match each time it is called.)8 1960 4 1424 5834 t
       +(the)720 5954 w
       +10 CW f
       +(x)874 5954 w
       +10 R f
       +(command,)966 5954 w
       +10 CW f
       +(sam)1417 5954 w
       +10 R f
       +(must therefore synchronize between calls of)5 1794 1 1629 5954 t
       +10 CW f
       +(execute)3455 5954 w
       +10 R f
       +( null)1 187(to avoid problems with)3 946 2 3907 5954 t
       +( example, even given the leftmost-longest rule, the expression)8 2522(matches. For)1 547 2 720 6074 t
       +10 CW f
       +(a*)3820 6074 w
       +10 R f
       +( in the)2 264(matches three times)2 805 2 3971 6074 t
       +(string)720 6194 w
       +10 CW f
       +(ab)978 6194 w
       +10 R f
       +(\(the character)1 549 1 1128 6194 t
       +10 CW f
       +(a)1706 6194 w
       +10 R f
       +(, the null string between the)5 1130 1 1766 6194 t
       +10 CW f
       +(a)2925 6194 w
       +10 R f
       +(and)3014 6194 w
       +10 CW f
       +(b)3187 6194 w
       +10 R f
       +( returning a)2 468( After)1 264(, and the final null string\).)5 1061 3 3247 6194 t
       +(match for the)2 532 1 720 6314 t
       +10 CW f
       +(a)1277 6314 w
       +10 R f
       +(,)1337 6314 w
       +10 CW f
       +(sam)1387 6314 w
       +10 R f
       +(must not match the null string before the)7 1624 1 1592 6314 t
       +10 CW f
       +(b)3241 6314 w
       +10 R f
       +( algorithm starts)2 650(. The)1 230 2 3301 6314 t
       +10 CW f
       +(execute)4206 6314 w
       +10 R f
       +( end)1 170(at the)1 219 2 4651 6314 t
       +( match, and if the match it returns is null and abuts the previous match, rejects the match and)18 3742(of its previous)2 578 2 720 6434 t
       +(advances the initial position one character.)5 1702 1 720 6554 t
       +10 B f
       +(Memory allocation)1 807 1 720 6794 t
       +10 R f
       +(The C language has no memory allocation primitives, although a standard library routine,)12 3699 1 720 6950 t
       +10 CW f
       +(malloc)4454 6950 w
       +10 R f
       +(, pro-)1 226 1 4814 6950 t
       +( however, it can be better to write a custom)9 1769( specific uses,)2 569( For)1 195(vides adequate service for simple programs.)5 1787 4 720 7070 t
       +( rather, pair of allocators\) described here work in both the terminal and host)13 3190( allocator \(or)2 539(allocator. The)1 591 3 720 7190 t
       +(parts of)1 325 1 720 7310 t
       +10 CW f
       +(sam)1093 7310 w
       +10 R f
       +( are designed for efficient manipulation of strings, which are allocated and freed)12 3464(. They)1 303 2 1273 7310 t
       +cleartomark
       +showpage
       +saveobj restore
       +%%EndPage: 16 16
       +%%Page: 17 17
       +/saveobj save def
       +mark
       +17 pagesetup
       +10 R f
       +(- 17 -)2 216 1 2772 480 t
       +( \(very large strings are written to disc\).)7 1643(frequently and vary in length from essentially zero to 32 Kbytes)10 2677 2 720 840 t
       +(More important, strings may be large and change size often, so to minimize memory usage it is helpful to)18 4320 1 720 960 t
       +(reclaim and to coalesce the unused portions of strings when they are truncated.)12 3146 1 720 1080 t
       +(Objects to be allocated in)4 1023 1 970 1236 t
       +10 CW f
       +(sam)2020 1236 w
       +10 R f
       +( the first is C)4 529(are of two flavors:)3 740 2 2227 1236 t
       +10 CW f
       +(structs)3524 1236 w
       +10 R f
       +(, which are small and often)5 1096 1 3944 1236 t
       +( integers whose base)3 868(addressed by pointer variables; the second is variable-sized arrays of characters or)11 3452 2 720 1356 t
       +( memory allocator in)3 841( The)1 207( used to access them.)4 848(pointer is always)2 679 4 720 1476 t
       +10 CW f
       +(sam)3322 1476 w
       +10 R f
       +(is therefore in two parts: first, a tradi-)7 1511 1 3529 1476 t
       +(tional first-fit allocator that provides fixed storage for)7 2145 1 720 1596 t
       +10 CW f
       +(structs)2891 1596 w
       +10 R f
       +( second, a garbage-compacting alloca-)4 1531(; and)1 198 2 3311 1596 t
       +( two)1 190( The)1 220( reduces storage overhead for variable-sized objects, at the cost of some bookkeeping.)12 3610(tor that)1 300 4 720 1716 t
       +( allocator controlling the)3 997(types of objects are allocated from adjoining arenas, with the garbage-compacting)10 3323 2 720 1836 t
       +( prevents fragmentation)2 958( into two arenas simplifies compaction and)6 1739( Separating)1 481(arena with higher addresses.)3 1142 4 720 1956 t
       +( garbage-compactable objects \(discussed in the next para-)7 2358( access rules for)3 666( The)1 214(due to immovable objects.)3 1082 4 720 2076 t
       +( so when the first-fit arena needs space, it moves the garbage-compacted)11 2936(graph\) allow them to be relocated,)5 1384 2 720 2196 t
       +( is therefore created only at successively higher addresses,)8 2340( Storage)1 357( higher addresses to make room.)5 1302(arena to)1 321 4 720 2316 t
       +(either when more garbage-compacted space is needed or when the first-fit arena pushes up the other arena.)16 4256 1 720 2436 t
       +( the sole reposi-)3 647(Objects that may be compacted declare to the allocator a cell that is guaranteed to be)15 3423 2 970 2592 t
       +( then update the)3 668( compactor can)2 633( The)1 217(tory of the address of the object whenever a compaction can occur.)11 2802 4 720 2712 t
       +( type)1 199( example, the implementation of)4 1300( For)1 190(address when the object is moved.)5 1375 4 720 2832 t
       +10 CW f
       +(List)3811 2832 w
       +10 R f
       +(\(really a variable-length)2 962 1 4078 2832 t
       +(array\) is:)1 357 1 720 2952 t
       +9 CW f
       +(typedef struct List{)2 1080 1 1008 3122 t
       +(int nused;)1 1188 1 1440 3232 t
       +(long *ptr;)1 1134 1 1440 3342 t
       +(}List;)1008 3452 w
       +10 R f
       +(The)720 3632 w
       +10 CW f
       +(ptr)910 3632 w
       +10 R f
       +( a)1 79( When)1 298(cell must always be used directly, and never copied.)8 2159 3 1125 3632 t
       +10 CW f
       +(List)3696 3632 w
       +10 R f
       +(is to be created the)4 792 1 3972 3632 t
       +10 CW f
       +(List)4800 3632 w
       +10 R f
       +(structure is allocated in the ordinary first-fit arena and its)9 2303 1 720 3752 t
       +10 CW f
       +(ptr)3051 3752 w
       +10 R f
       +(is allocated in the garbage-compacted arena.)5 1782 1 3258 3752 t
       +(A similar data type for strings, called)6 1520 1 720 3872 t
       +10 CW f
       +(String)2271 3872 w
       +10 R f
       +( ele-)1 181(, stores variable-length character arrays of up to 32767)8 2228 2 2631 3872 t
       +(ments.)720 3992 w
       +( matter of programming style:)4 1227(A related)1 374 2 970 4148 t
       +10 CW f
       +(sam)2603 4148 w
       +10 R f
       +(frequently passes structures by value, which simplifies)6 2225 1 2815 4148 t
       +( programs have passed structures by reference, but implicit allocation on the)11 3220( C)1 109( Traditionally,)1 614(the code.)1 377 4 720 4268 t
       +( passing is a relatively new feature of C \(it is not in the standard reference)15 3036( Structure)1 422( to use.)2 298(stack is easier)2 564 4 720 4388 t
       +(manual for C)2 537 1 720 4508 t
       +6 R f
       +(14)1257 4458 w
       +10 R f
       +( convenient and expressive,)3 1115( It's)1 187( commercial C compilers.)3 1044(\), and is poorly supported in most)6 1377 4 1317 4508 t
       +(though, and simplifies memory management by avoiding the allocator altogether and eliminating pointer)12 4320 1 720 4628 t
       +(aliases.)720 4748 w
       +10 B f
       +(Data structures for manipulating files)4 1610 1 720 4988 t
       +10 R f
       +(Experience with)1 651 1 720 5144 t
       +10 CW f
       +(jim)1396 5144 w
       +10 R f
       +( files)1 198( First,)1 260( requirements of the file data structure were few, but strict.)10 2354(showed that the)2 627 4 1601 5144 t
       +( the implementation must)3 1026( Second,)1 371( read and written quickly; adding a fresh file must be painless.)11 2507(need to be)2 416 4 720 5264 t
       +( should be practical to edit many files, and)8 1719( \(It)1 147( sizes of files.)3 558(place no arbitrary upper limit on the number or)8 1896 4 720 5384 t
       +( implies that files be stored on disc, not)8 1593( This)1 231(files up to megabytes in length should be handled gracefully.\))9 2496 3 720 5504 t
       +( of virtual memory may argue otherwise, but the implementation of virtual)11 3046( \(Aficionados)1 577( memory.)1 389(in main)1 308 4 720 5624 t
       +( changes to files need)4 879( Third,)1 302( system is not something to depend on for good performance.\))10 2533(memory in our)2 606 4 720 5744 t
       +( are inverses of each other, which simplifies)7 1785( These)1 292( only two primitives: deletion and insertion.)6 1777(be made by)2 466 4 720 5864 t
       +( the file, either)3 609( it must be easy and efficient to access)8 1610( Finally,)1 369(the implementation of the undo operation.)5 1732 4 720 5984 t
       +(forwards or backwards, a byte at a time.)7 1598 1 720 6104 t
       +(The)970 6260 w
       +10 CW f
       +(File)1157 6260 w
       +10 R f
       +( characters.)1 461(data type is constructed from three simpler data structures that hold arrays of)12 3150 2 1429 6260 t
       +( has an insertion and deletion operator, and the insertion and deletion operators of the)14 3520(Each of these types)3 800 2 720 6380 t
       +10 CW f
       +(File)720 6500 w
       +10 R f
       +(type itself are constructed from them.)5 1497 1 985 6500 t
       +(The simplest type is the)4 950 1 970 6656 t
       +10 CW f
       +(String)1945 6656 w
       +10 R f
       +( code that man-)3 621( The)1 206( hold strings in main memory.)5 1211(, which is used to)4 697 4 2305 6656 t
       +(ages)720 6776 w
       +10 CW f
       +(Strings)926 6776 w
       +10 R f
       +( some moderate size, and in practice they are)8 1816(guarantees that they will never be longer than)7 1849 2 1375 6776 t
       +(rarely larger than 8 Kbytes.)4 1098 1 720 6896 t
       +10 CW f
       +(Strings)1869 6896 w
       +10 R f
       +( little)1 211(have two purposes: they hold short strings like file names with)10 2514 2 2315 6896 t
       +( are therefore used as)4 862( They)1 259( efficient to modify.)3 811(overhead, and because they are deliberately small, they are)8 2388 4 720 7016 t
       +(the data structure for in-memory caches.)5 1612 1 720 7136 t
       +(The disc copy of the file is managed by a data structure called a)13 2749 1 970 7292 t
       +10 CW f
       +(Disc)3760 7292 w
       +10 R f
       +( corresponds to a)3 730(, which)1 310 2 4000 7292 t
       +cleartomark
       +showpage
       +saveobj restore
       +%%EndPage: 17 17
       +%%Page: 18 18
       +/saveobj save def
       +mark
       +18 pagesetup
       +10 R f
       +(- 18 -)2 216 1 2772 480 t
       +( A)1 133(temporary file.)1 604 2 720 840 t
       +10 CW f
       +(Disc)1493 840 w
       +10 R f
       +( storage in main memory other than bookkeeping information; the actual)10 3002(has no)1 269 2 1769 840 t
       +( reduce the number of open files needed,)7 1665( To)1 167(data being held is all on the disc.)7 1352 3 720 960 t
       +10 CW f
       +(sam)3935 960 w
       +10 R f
       +(opens a dozen tempo-)3 894 1 4146 960 t
       +(rary Unix files and multiplexes the)5 1395 1 720 1080 t
       +10 CW f
       +(Discs)2141 1080 w
       +10 R f
       +( many files to be edited; the entire)7 1362( permits)1 326( This)1 229(upon them.)1 451 4 2467 1080 t
       +10 CW f
       +(sam)4860 1080 w
       +10 R f
       +(source \(48 files\) may be edited comfortably with a single instance of)11 2819 1 720 1200 t
       +10 CW f
       +(sam)3571 1200 w
       +10 R f
       +( temporary file)2 609( one)1 176(. Allocating)1 504 3 3751 1200 t
       +(per)720 1320 w
       +10 CW f
       +(Disc)874 1320 w
       +10 R f
       +( spreading the traffic)3 831( Also,)1 265( on the number of open files.)6 1157(would strain the operating system's limit)5 1646 4 1141 1320 t
       +(among temporary files keeps the files shorter, and shorter files are more efficiently implemented by the)15 4320 1 720 1440 t
       +(Unix I/O subsystem.)2 825 1 720 1560 t
       +(A)970 1716 w
       +10 CW f
       +(Disc)1070 1716 w
       +10 R f
       +( between 1 and 4096 characters of)6 1386(is an array of fixed-length blocks, each of which contains)9 2316 2 1338 1716 t
       +( block addresses within the tempo-)5 1400( The)1 207( file system is 4096 bytes.\))5 1082( block size of our Unix)5 933( \(The)1 241(active data.)1 457 6 720 1836 t
       +( stored in a)3 450(rary file and the length of each block are)8 1633 2 720 1956 t
       +10 CW f
       +(List)2831 1956 w
       +10 R f
       +( changes are made the live part of blocks)8 1653(. When)1 316 2 3071 1956 t
       +( to keep the sizes between 2048)6 1300( are created and coalesced when necessary to try)8 1987( Blocks)1 335(may change size.)2 698 4 720 2076 t
       +( actively changing part of the)5 1172( An)1 173(and 4096 bytes.)2 632 3 720 2196 t
       +10 CW f
       +(Disc)2723 2196 w
       +10 R f
       +( has about a kilobyte of slop that)7 1316(therefore typically)1 735 2 2989 2196 t
       +( an)1 130( When)1 299( inserted or deleted without changing more than one block or affecting the block order.)14 3622(can be)1 269 4 720 2316 t
       +( one is allocated to receive the overflow, and the)9 1970(insertion would overflow a block, the block is split, a new)10 2350 2 720 2436 t
       +(memory-resident list of blocks is rearranged to reflect the insertion of the new block.)13 3388 1 720 2556 t
       +( data)1 200( The)1 214( modification to the file is prohibitively expensive.)7 2090(Obviously, going to the disc for every)6 1566 4 970 2712 t
       +(type)720 2832 w
       +10 CW f
       +(Buffer)921 2832 w
       +10 R f
       +(consists of a)2 502 1 1310 2832 t
       +10 CW f
       +(Disc)1841 2832 w
       +10 R f
       +(to hold the data and a)5 872 1 2109 2832 t
       +10 CW f
       +(String)3009 2832 w
       +10 R f
       +( is the first of a)5 617( This)1 231(that acts as a cache.)4 795 3 3397 2832 t
       +( throughout the data structures in)5 1378(series of caches)2 647 2 720 2952 t
       +10 CW f
       +(sam.)2782 2952 w
       +10 R f
       +(The caches not only improve performance, they)6 1981 1 3059 2952 t
       +( the flow of data, particularly in the communication between the host and termi-)13 3260(provide a way to organize)4 1060 2 720 3072 t
       +( idea is developed below, in the section on communications.)9 2406(nal. This)1 375 2 720 3192 t
       +( traffic, changes to a)4 855(To reduce disc)2 607 2 970 3348 t
       +10 CW f
       +(Buffer)2468 3348 w
       +10 R f
       +(are mediated by a variable-length string, in memory,)7 2176 1 2864 3348 t
       +( an insertion or deletion is made to a)8 1502( When)1 294(that acts as a cache.)4 807 3 720 3468 t
       +10 CW f
       +(Buffer)3354 3468 w
       +10 R f
       +( can be accommo-)3 743(, if the change)3 583 2 3714 3468 t
       +( cache becomes bigger than a block because of an insertion, some)11 2643( the)1 148( If)1 117(dated by the cache, it is done there.)7 1412 4 720 3588 t
       +( written to the)3 558(of it is)2 258 2 720 3708 t
       +10 CW f
       +(Disc)1561 3708 w
       +10 R f
       +( the change does not intersect the cache, the cache)9 1999( If)1 116(and deleted from the cache.)4 1099 3 1826 3708 t
       +( the new position if the change is smaller than a block; otherwise, it)13 2749( cache is only loaded at)5 949( The)1 208(is flushed.)1 414 4 720 3828 t
       +( the)1 158(is sent directly to)3 722 2 720 3948 t
       +10 CW f
       +(Disc)1636 3948 w
       +10 R f
       +( is because large changes are typically sequential, whereupon the next)10 2900(. This)1 264 2 1876 3948 t
       +(change is unlikely to overlap the current one.)7 1802 1 720 4068 t
       +(A)970 4224 w
       +10 CW f
       +(File)1067 4224 w
       +10 R f
       +(comprises a)1 474 1 1332 4224 t
       +10 CW f
       +(String)1831 4224 w
       +10 R f
       +( such as dot and the mod-)6 1027(to hold the file name and some ancillary data)8 1797 2 2216 4224 t
       +( most important components, though, are a pair of)8 2006( The)1 206(ified bit.)1 340 3 720 4344 t
       +10 CW f
       +(Buffers)3298 4344 w
       +10 R f
       +(, one called the transcript and the)6 1322 1 3718 4344 t
       +( use is described in the next section.)7 1437( Their)1 266(other the contents.)2 735 3 720 4464 t
       +( times)1 243( it may seem that the data is touched many)9 1701( Although)1 428(The overall structure is shown in Figure 5.)7 1698 4 970 4620 t
       +(on its way from the)4 825 1 720 4740 t
       +10 CW f
       +(Disc)1582 4740 w
       +10 R f
       +( \(by one Unix system call\) directly into the cache of the associated)12 2788(, it is read)3 430 2 1822 4740 t
       +10 CW f
       +(Buffer)720 4860 w
       +10 R f
       +( the cache, the text is written directly from the)9 1922( when flushing)2 610( Similarly,)1 456(; no extra copy is done.)5 972 4 1080 4860 t
       +( principle applied throughout)3 1172( A)1 125( operations act directly on the text in the cache.)9 1912( Most)1 260(cache to disc.)2 548 5 720 4980 t
       +10 CW f
       +(sam)4765 4980 w
       +10 R f
       +(is)4973 4980 w
       +(that the fewer times the data is copied, the faster the program will run \(see also the paper by Waite)19 3930 1 720 5100 t
       +6 R f
       +(15)4650 5050 w
       +10 R f
       +(\).)4710 5100 w
       +cleartomark
       +saveobj restore
       +%%BeginGlobal
       +%
       +% Version 3.3.2 drawing procedures for dpost. Automatically pulled in when
       +% needed.
       +%
       +
       +/inpath false def
       +/savematrix matrix def
       +
       +/Dl {
       +        inpath
       +                {pop pop neg lineto}
       +                {newpath neg moveto neg lineto stroke}
       +        ifelse
       +} bind def
       +
       +/De {
       +        /y1 exch 2 div def
       +        /x1 exch 2 div def
       +        /savematrix savematrix currentmatrix def
       +        neg exch x1 add exch translate
       +        x1 y1 scale
       +        0 0 1 0 360
       +        inpath
       +                {1 0 moveto arc savematrix setmatrix}
       +                {newpath arc savematrix setmatrix stroke}
       +        ifelse
       +} bind def
       +
       +/Da {
       +        /dy2 exch def
       +        /dx2 exch def
       +        /dy1 exch def
       +        /dx1 exch def
       +        dy1 add neg exch dx1 add exch
       +        dx1 dx1 mul dy1 dy1 mul add sqrt
       +        dy1 dx1 neg atan
       +        dy2 neg dx2 atan
       +        inpath
       +                {arc}
       +                {newpath arc stroke}
       +        ifelse
       +} bind def
       +
       +/DA {
       +        /dy2 exch def
       +        /dx2 exch def
       +        /dy1 exch def
       +        /dx1 exch def
       +        dy1 add neg exch dx1 add exch
       +        dx1 dx1 mul dy1 dy1 mul add sqrt
       +        dy1 dx1 neg atan
       +        dy2 neg dx2 atan
       +        inpath
       +                {arcn}
       +                {newpath arcn stroke}
       +        ifelse
       +} bind def
       +
       +/Ds {
       +        /y2 exch def
       +        /x2 exch def
       +        /y1 exch def
       +        /x1 exch def
       +        /y0 exch def
       +        /x0 exch def
       +        x0 5 x1 mul add 6 div
       +        y0 5 y1 mul add -6 div
       +        x2 5 x1 mul add 6 div
       +        y2 5 y1 mul add -6 div
       +        x1 x2 add 2 div
       +        y1 y2 add -2 div
       +        inpath
       +                {curveto}
       +                {newpath x0 x1 add 2 div y0 y1 add -2 div moveto curveto stroke}
       +        ifelse
       +} bind def
       +%%EndGlobal
       +/saveobj save def
       +mark
       +10 R f
       +2966 6183 2966 6471 Dl
       +3599 6183 2966 6183 Dl
       +3600 6471 3600 6183 Dl
       +2967 6471 3600 6471 Dl
       +10 CW f
       +(Disc)3163 6347 w
       +2966 6586 2966 6874 Dl
       +3599 6586 2966 6586 Dl
       +3600 6874 3600 6586 Dl
       +2967 6874 3600 6874 Dl
       +10 R f
       +(temp. file)1 383 1 3092 6750 t
       +3283 6471 3283 6586 Dl
       +1642 6183 1642 6471 Dl
       +2275 6183 1642 6183 Dl
       +2275 6471 2275 6183 Dl
       +1642 6471 2275 6471 Dl
       +10 CW f
       +(Disc)1838 6347 w
       +1642 6586 1642 6874 Dl
       +2275 6586 1642 6586 Dl
       +2275 6874 2275 6586 Dl
       +1642 6874 2275 6874 Dl
       +10 R f
       +(temp. file)1 383 1 1767 6750 t
       +1958 6471 1958 6586 Dl
       +1642 5722 1642 6010 Dl
       +2275 5722 1642 5722 Dl
       +2275 6010 2275 5722 Dl
       +1642 6010 2275 6010 Dl
       +10 CW f
       +(Buffer)1778 5826 w
       +10 R f
       +(\(transcript\))1737 5946 w
       +2390 5722 2390 6010 Dl
       +2793 5722 2390 5722 Dl
       +2794 6010 2794 5722 Dl
       +2391 6010 2794 6010 Dl
       +10 CW f
       +(String)2412 5826 w
       +10 R f
       +(\(cache\))2446 5946 w
       +2275 5866 2390 5866 Dl
       +2966 5722 2966 6010 Dl
       +3599 5722 2966 5722 Dl
       +3600 6010 3600 5722 Dl
       +2967 6010 3600 6010 Dl
       +10 CW f
       +(Buffer)3103 5826 w
       +10 R f
       +(\(contents\))3084 5946 w
       +3715 5722 3715 6010 Dl
       +4118 5722 3715 5722 Dl
       +4118 6010 4118 5722 Dl
       +3715 6010 4118 6010 Dl
       +10 CW f
       +(String)3737 5826 w
       +10 R f
       +(\(cache\))3771 5946 w
       +3600 5866 3715 5866 Dl
       +2182 5262 2182 5550 Dl
       +2988 5262 2182 5262 Dl
       +2988 5550 2988 5262 Dl
       +2182 5550 2988 5550 Dl
       +10 CW f
       +(File)2465 5426 w
       +1958 6011 1958 6183 Dl
       +3283 6011 3283 6183 Dl
       +1958 5636 1958 5722 Dl
       +2318 5636 1958 5636 Dl
       +2318 5550 2318 5636 Dl
       +3283 5636 3283 5722 Dl
       +2851 5636 3283 5636 Dl
       +2851 5550 2851 5636 Dl
       +8 I f
       +( temporary files are stored in the standard repository for such files on the host system.)15 2752( The)1 160(Figure 5. File data structures.)4 965 3 720 7052 t
       +cleartomark
       +showpage
       +saveobj restore
       +%%EndPage: 18 18
       +%%Page: 19 19
       +/saveobj save def
       +mark
       +19 pagesetup
       +10 R f
       +(- 19 -)2 216 1 2772 480 t
       +( a)1 73(The contents of)2 627 2 970 840 t
       +10 CW f
       +(File)1699 840 w
       +10 R f
       +(are accessed by a routine that copies to a buffer a substring of a file starting)15 3072 1 1968 840 t
       +( read a byte at a time, a per-)8 1166( To)1 168(at a specified offset.)3 824 3 720 960 t
       +10 CW f
       +(File)2878 960 w
       +10 R f
       +( a specified initial)3 731(array is loaded starting from)4 1159 2 3150 960 t
       +( implementation is done by a macro similar to the)9 2012( The)1 208(position, and bytes may then be read from the array.)9 2100 3 720 1080 t
       +(C standard I/O)2 616 1 720 1200 t
       +10 CW f
       +(getc)1375 1200 w
       +10 R f
       +(macro.)1654 1200 w
       +6 R f
       +(14)1928 1150 w
       +10 R f
       +( reading may be done at any address, a minor change to the)12 2520(Because the)1 493 2 2027 1200 t
       +( array is read-only; there is no)6 1197( This)1 228(macro allows the file to be read backwards.)7 1734 3 720 1320 t
       +10 CW f
       +(putc)3904 1320 w
       +10 R f
       +(.)4144 1320 w
       +10 B f
       +(Doing and undoing)2 820 1 720 1560 t
       +10 CW f
       +(Sam)720 1716 w
       +10 R f
       +( command language makes it easy to spec-)7 1733( The)1 209( to files.)2 333(has an unusual method for managing changes)6 1837 4 928 1716 t
       +( variable-length changes to a file millions of bytes long, and such changes must be made effi-)16 3842(ify multiple)1 478 2 720 1836 t
       +( usual techniques for inserting and deleting strings are inadequate)9 2633( The)1 207( to be practical.)3 621(ciently if the editor is)4 859 4 720 1956 t
       +( The)1 207(under these conditions.)2 930 2 720 2076 t
       +10 CW f
       +(Buffer)1884 2076 w
       +10 R f
       +(and)2271 2076 w
       +10 CW f
       +(Disc)2442 2076 w
       +10 R f
       +(data structures are designed for efficient random access to)8 2331 1 2709 2076 t
       +( be taken to avoid super-linear behavior when making many changes simultane-)11 3246(long strings, but care must)4 1074 2 720 2196 t
       +(ously.)720 2316 w
       +10 CW f
       +(Sam)970 2472 w
       +10 R f
       +(uses a two-pass algorithm for making changes, and treats each file as a database against which)15 3859 1 1181 2472 t
       +( when a command is)4 865( Instead,)1 374( the contents.)2 552( are not made directly to)5 1033( Changes)1 406(transactions are registered.)2 1090 6 720 2592 t
       +(started, a `mark' containing a sequence number is placed in the transcript)11 2965 1 720 2712 t
       +10 CW f
       +(Buffer)3714 2712 w
       +10 R f
       +( made)1 246(, and each change)3 720 2 4074 2712 t
       +( name, is appended to the end of the tran-)9 1715(to the file, either an insertion or deletion or a change to the file)13 2605 2 720 2832 t
       +( the command is complete, the transcript is rewound to the mark and applied to the contents.)16 3690(script. When)1 535 2 720 2952 t
       +( is to simplify tracking the)5 1198(One reason for separating evaluation from application in this way)9 2872 2 970 3108 t
       +( two-pass algorithm also allows all)5 1481( The)1 224(addresses of changes made in the middle of a long sequence.)10 2615 3 720 3228 t
       +(changes to apply to the)4 965 1 720 3348 t
       +10 I f
       +(original)1721 3348 w
       +10 R f
       +( the same command.)3 857(data: no change can affect another change made in)8 2103 2 2080 3348 t
       +( evaluating an)2 606(This is particularly important when)4 1506 2 720 3468 t
       +10 CW f
       +(x)2880 3468 w
       +10 R f
       +(command because it prevents regular expression)5 2052 1 2988 3468 t
       +( is)1 114( the two-pass algorithm)3 1004( Also,)1 285(matches from stumbling over changes made earlier in the execution.)9 2917 4 720 3588 t
       +( to affect each other; for example,)6 1366(cleaner than the way other Unix editors allow changes)8 2199 2 720 3708 t
       +10 CW f
       +(ed)4313 3708 w
       +10 R f
       +('s idioms to do)3 607 1 4433 3708 t
       +( Instead,)1 364( on the implementation.)3 953(things like delete every other line depend critically)7 2027 3 720 3828 t
       +10 CW f
       +(sam)4090 3828 w
       +10 R f
       +('s simple model, in)3 770 1 4270 3828 t
       +(which all changes in a command occur effectively simultaneously, is easy to explain and to understand.)15 4135 1 720 3948 t
       +( substring from locations 123 to 456'' and)7 1825(The records in the transcript are of the form ``delete)9 2245 2 970 4104 t
       +( changes are not at monotonically)5 1370( is an error if the)5 692( \(It)1 150(``insert 11 characters `hello there' at location 789.'')7 2108 4 720 4224 t
       +( the update is occurring, these numbers must be offset by earlier)11 2633( While)1 301(greater positions through the file.\))4 1386 3 720 4344 t
       +( to the update routine; moreover, all the numbers have been)10 2466(changes, but that is straightforward and local)6 1854 2 720 4464 t
       +(computed before the first is examined.)5 1536 1 720 4584 t
       +( it takes is to)4 566( All)1 193(Treating the file as a transaction system has another advantage: undo is trivial.)12 3311 3 970 4740 t
       +( deletions and vice versa, and)5 1200(invert the transcript after it has been implemented, converting insertions into)10 3120 2 720 4860 t
       +(saving them in a holding)4 989 1 720 4980 t
       +10 CW f
       +(Buffer)1734 4980 w
       +10 R f
       +( can then be deleted from the transcript)7 1567( `do' transcript)2 593(. The)1 230 3 2094 4980 t
       +10 CW f
       +(Buffer)4510 4980 w
       +10 R f
       +(and)4896 4980 w
       +( an undo is requested, the transcript is rewound and the undo transcript)12 2862( If)1 119(replaced by the `undo' transcript.)4 1339 3 720 5100 t
       +( the transcript)2 561(executed. Because)1 767 2 720 5220 t
       +10 CW f
       +(Buffer)2079 5220 w
       +10 R f
       +( it accumulates successive)3 1066(is not truncated after each command,)5 1504 2 2470 5220 t
       +( can therefore back up the file arbitrarily, which is more helpful)11 2626( sequence of undo commands)4 1217(changes. A)1 477 3 720 5340 t
       +( \()1 87( form of undo.)3 589(than the more commonly implemented self-inverse)5 2054 3 720 5460 t
       +10 CW f
       +(Sam)3450 5460 w
       +10 R f
       +(provides no way to undo an undo,)6 1381 1 3659 5460 t
       +( mark in the)3 495( Each)1 254( re-interpreting the `do' transcript.\))4 1419(but if it were desired, it would be easy to provide by)11 2152 4 720 5580 t
       +( offset into the transcript of the previous mark, to aid in)11 2390(transcript contains a sequence number and the)6 1930 2 720 5700 t
       +( also contain the value of dot and the modified bit so these can be restored)15 3004( Marks)1 308(unwinding the transcript.)2 1008 3 720 5820 t
       +( merely demands undoing all files whose latest change has the)10 2625( multiple files is easy; it)5 1024(easily. Undoing)1 671 3 720 5940 t
       +(same sequence number as the current file.)6 1670 1 720 6060 t
       +( encountered in the middle of a complicated com-)8 2017(Another benefit of having a transcript is that errors)8 2053 2 970 6216 t
       +( rewinding the transcript to the mark beginning)7 1933( By)1 174( files in an intermediate state.)5 1211(mand need not leave the)4 1002 4 720 6336 t
       +(the command, the partial command can be trivially undone.)8 2380 1 720 6456 t
       +( implemented, it was unacceptably slow, so a cache was added to)11 2614(When the update algorithm was first)5 1456 2 970 6612 t
       +( reduced the number)3 820( This)1 229( small changes by a single larger one.)7 1504(coalesce nearby changes, replacing multiple)4 1767 4 720 6732 t
       +(of insertions into the transaction)4 1308 1 720 6852 t
       +10 CW f
       +(Buffer)2058 6852 w
       +10 R f
       +( in performance, but made it)5 1161(, and made a dramatic improvement)5 1461 2 2418 6852 t
       +( the caching method only works if changes)7 1718(impossible to handle changes in non-monotonic order in the file;)9 2602 2 720 6972 t
       +( cache was added, the transaction could in principle be sorted if the changes were)14 3286( the)1 149( Before)1 323(don't overlap.)1 562 4 720 7092 t
       +( therefore acceptable performance with a)5 1664( current status is)3 676( The)1 213(out of order, although this was never done.)7 1767 4 720 7212 t
       +cleartomark
       +showpage
       +saveobj restore
       +%%EndPage: 19 19
       +%%Page: 20 20
       +/saveobj save def
       +mark
       +20 pagesetup
       +10 R f
       +(- 20 -)2 216 1 2772 480 t
       +(minor restriction on global changes, which is sometimes, but rarely, an annoyance.)11 3309 1 720 840 t
       +( simpler algorithms, but it is not pro-)7 1627(The update algorithm obviously paws the data more than)8 2443 2 970 996 t
       +( principle of avoiding copying the data is still honored here,)10 2519( \(The)1 251( help.)1 235(hibitively expensive; the caches)3 1315 4 720 1116 t
       +( through)1 343(although not as piously: the data is moved from contents' cache to the transcript's all at once and)17 3977 2 720 1236 t
       +( dead start a hundred)4 839( read from a)3 493( To)1 164( figures confirm the efficiency.)4 1250( Performance)1 562(only one internal buffer.\))3 1012 6 720 1356 t
       +( a VAX-11/750 takes 1.4 seconds of user time, 2.5 seconds of system time, and 5 seconds of)17 3709(kilobyte file on)2 611 2 720 1476 t
       +( the same file in)4 670( Reading)1 391(real time.)1 385 3 720 1596 t
       +10 CW f
       +(ed)2199 1596 w
       +10 R f
       +( of system time, and 8)5 918(takes 6.0 seconds of user time, 1.7 seconds)7 1770 2 2352 1596 t
       +(seconds of real time.)3 862 1 720 1716 t
       +10 CW f
       +(Sam)1644 1716 w
       +10 R f
       +( stated)1 271( more interesting example is the one)6 1520( A)1 134(uses about half the CPU time.)5 1254 4 1861 1716 t
       +( The)1 205(above: inserting a character between every pair of characters in the file.)11 2851 2 720 1836 t
       +10 CW f
       +(sam)3801 1836 w
       +10 R f
       +(command is)1 486 1 4006 1836 t
       +9 CW f
       +(,y/@/ a/x/)1 540 1 1008 2006 t
       +10 R f
       +( 3 CPU seconds per kilobyte of input file, of which about a third is spent in the regular expression)19 3945(and takes)1 375 2 720 2186 t
       +( translates to about 500 changes per second.)7 1759(code. This)1 442 2 720 2306 t
       +10 CW f
       +(Ed)2972 2306 w
       +10 R f
       +( make a similar)3 613(takes 1.5 seconds per kilobyte to)5 1309 2 3118 2306 t
       +( same example in)3 702( The)1 207(change \(ignoring newlines\), but cannot undo it.)6 1898 3 720 2426 t
       +10 CW f
       +(ex)3554 2426 w
       +10 R f
       +(,)3674 2426 w
       +6 R f
       +(9)3699 2376 w
       +10 R f
       +(a variant of)2 458 1 3756 2426 t
       +10 CW f
       +(ed)4241 2426 w
       +10 R f
       +(done at the Uni-)3 652 1 4388 2426 t
       +( summary,)1 428( In)1 139( takes 3 seconds.)3 689(versity of California at Berkeley, which allows one level of undoing, again)11 3064 4 720 2546 t
       +10 CW f
       +(sam)720 2666 w
       +10 R f
       +('s performance is comparable to that of other Unix editors, although it solves a harder problem.)15 3807 1 900 2666 t
       +10 B f
       +(Communications)720 2906 w
       +10 R f
       +(The discussion so far has described the implementation of the host part of)12 3138 1 720 3062 t
       +10 CW f
       +(sam)3899 3062 w
       +10 R f
       +(; the next few sections)4 961 1 4079 3062 t
       +( machine with mouse and bitmap display can be engaged to improve interaction.)12 3263(explain how a)2 570 2 720 3182 t
       +10 CW f
       +(Sam)4607 3182 w
       +10 R f
       +(is not)1 224 1 4816 3182 t
       +(the first editor to be written as two processes,)8 1811 1 720 3302 t
       +6 R f
       +(16)2531 3252 w
       +10 R f
       +(but its implementation has some unusual aspects.)6 1969 1 2616 3302 t
       +(There are several ways)3 924 1 970 3458 t
       +10 CW f
       +(sam)1922 3458 w
       +10 R f
       +( first and simplest is to)5 929( The)1 208('s host and terminal parts may be connected.)7 1801 3 2102 3458 t
       +( This)1 229( command language to edit text on an ordinary terminal.)9 2256(forgo the terminal part and use the host part's)8 1835 3 720 3578 t
       +(mode is invoked by starting)4 1111 1 720 3698 t
       +10 CW f
       +(sam)1856 3698 w
       +10 R f
       +(with the)1 325 1 2061 3698 t
       +10 CW f
       +(-d)2412 3698 w
       +10 R f
       +( no options,)2 472(option. With)1 532 2 2558 3698 t
       +10 CW f
       +(sam)3588 3698 w
       +10 R f
       +(runs separate host and terminal)4 1246 1 3794 3698 t
       +( Typically,)1 458( over the physical connection that joins them.)7 1815(programs, communicating with a message protocol)5 2047 3 720 3818 t
       +( display for)2 469(the connection is an RS-232 link between a Blit \(the prototypical)10 2658 2 720 3938 t
       +10 CW f
       +(sam)3879 3938 w
       +10 R f
       +(\) and a host running the)5 981 1 4059 3938 t
       +(Ninth Edition of the Unix operating system.)6 1782 1 720 4058 t
       +6 R f
       +(8)2502 4008 w
       +10 R f
       +(\(This is the version of the system used in the Computing Sci-)11 2480 1 2560 4058 t
       +( are discussed in the)4 828( relevant aspects)2 669( Its)1 155(ences Research Center at AT&T Bell Laboratories, where I work.)9 2668 4 720 4178 t
       +(Blit paper.)1 432 1 720 4298 t
       +6 R f
       +(1)1152 4248 w
       +10 R f
       +( implementation of)2 779(\) The)1 223 2 1182 4298 t
       +10 CW f
       +(sam)2218 4298 w
       +10 R f
       +(for the Sun computer runs both processes on the same machine)10 2608 1 2432 4298 t
       +(and connects them by a pipe.)5 1159 1 720 4418 t
       +( division)1 351( The)1 208( of an RS-232 link necessitated the split between the two programs.)11 2730(The low bandwidth)2 781 4 970 4574 t
       +( a self-contained one,)3 859(is a mixed blessing: a program in two parts is much harder to write and to debug than)17 3461 2 720 4694 t
       +( terminal may be physically separated from)6 1736( The)1 206( configurations possible.)2 982(but the split makes several unusual)5 1396 4 720 4814 t
       +( to be taken home while leaving the files)8 1628(the host, allowing the conveniences of a mouse and bitmap display)10 2692 2 720 4934 t
       +( is also possible to run the host part on a remote machine:)12 2298( It)1 111(at work.)1 327 3 720 5054 t
       +9 CW f
       +(sam -r host)2 594 1 1008 5224 t
       +10 R f
       +( the network to establish the host part)7 1503(connects to the terminal in the usual way, and then makes a call across)13 2817 2 720 5404 t
       +(of)720 5524 w
       +10 CW f
       +(sam)831 5524 w
       +10 R f
       +( allows)1 288( This)1 230( parts.)1 246( it cross-connects the I/O to join the two)8 1628( Finally,)1 362(on the named machine.)3 935 6 1039 5524 t
       +10 CW f
       +(sam)4755 5524 w
       +10 R f
       +(to)4962 5524 w
       +(be run on machines that do not support bitmap displays; for example,)11 2869 1 720 5644 t
       +10 CW f
       +(sam)3623 5644 w
       +10 R f
       +( our)1 168(is the editor of choice on)5 1035 2 3837 5644 t
       +(Cray X-MP/24.)1 632 1 720 5764 t
       +10 CW f
       +(Sam -r)1 334 1 1412 5764 t
       +10 R f
       +(involves)1780 5764 w
       +10 I f
       +(three)2153 5764 w
       +10 R f
       +( The)1 214(machines: the remote host, the terminal, and the local host.)9 2434 2 2392 5764 t
       +(local host's job is simple but vital: it passes the data between the remote host and terminal.)16 3628 1 720 5884 t
       +( exchange messages asynchronously \(rather than, say, as remote procedure)9 3145(The host and terminal)3 925 2 970 6040 t
       +( correction because, whatever the configuration, the connection is)8 2709(calls\) but there is no error detection or)7 1611 2 720 6160 t
       +( mundane interaction tasks such as popping up menus and interpret-)10 2763( the terminal handles)3 847(reliable. Because)1 710 3 720 6280 t
       +( example, the host knows nothing about)6 1657( For)1 200( not actions.)2 508(ing the responses, the messages are about data,)7 1955 4 720 6400 t
       +( says)1 211(what is displayed on the screen, and when the user types a character, the message sent to the host)18 4109 2 720 6520 t
       +( position in the)3 601(``insert a one-byte string at location 123 in file 7,'' not ``a character was typed at the current)17 3719 2 720 6640 t
       +( other words, the messages look very much like the transaction records in the transcripts.)14 3544( In)1 133(current file.'')1 531 3 720 6760 t
       +( host or terminal part of)5 961(Either the)1 393 2 970 6916 t
       +10 CW f
       +(sam)2352 6916 w
       +10 R f
       +( command language oper-)3 1048( The)1 208(may initiate a change to a file.)6 1224 3 2560 6916 t
       +( mouse operations are executed directly in the terminal to optimize)10 2708(ates on the host, while typing and some)7 1612 2 720 7036 t
       +( \(A)1 165( the terminal, and vice versa.)5 1200( initiated by the host program must be transmitted to)9 2178(response. Changes)1 777 4 720 7156 t
       +( which means that characters typed while a time-)8 1999(token is exchanged to determine which end is in control,)9 2321 2 720 7276 t
       +cleartomark
       +showpage
       +saveobj restore
       +%%EndPage: 20 20
       +%%Page: 21 21
       +/saveobj save def
       +mark
       +21 pagesetup
       +10 R f
       +(- 21 -)2 216 1 2772 480 t
       +( main-)1 264( To)1 167( buffered and do not appear until the command is complete.\))10 2475(consuming command runs must be)4 1414 4 720 840 t
       +( track changes through a per-file data structure that records)9 2351(tain consistent information, the host and terminal)6 1969 2 720 960 t
       +( data structure, called a)4 954( The)1 213(what portions of the file the terminal has received.)8 2074 3 720 1080 t
       +10 CW f
       +(Rasp)3994 1080 w
       +10 R f
       +(\(a weak pun: it's a)4 773 1 4267 1080 t
       +( A)1 128(file with holes\) is held and updated by both the host and terminal.)12 2695 2 720 1200 t
       +10 CW f
       +(Rasp)3574 1200 w
       +10 R f
       +( of)1 113(is a list)2 296 2 3845 1200 t
       +10 CW f
       +(Strings)4284 1200 w
       +10 R f
       +(holding)4734 1200 w
       +( number of bytes in the interstices.)6 1415(those parts of the file known to the terminal, separated by counts of the)13 2905 2 720 1320 t
       +( needs the lengths of the various pieces\),)7 1619(Of course, the host doesn't keep a separate copy of the data \(it only)13 2701 2 720 1440 t
       +(but the structure is the same on both ends.)8 1679 1 720 1560 t
       +(The)970 1716 w
       +10 CW f
       +(Rasp)1158 1716 w
       +10 R f
       +( the terminal keeps the text for portions of the)9 1909( Since)1 280(in the terminal doubles as a cache.)6 1420 3 1431 1716 t
       +(file it has displayed, it need not request data from the host when revisiting old parts of the file or redrawing)20 4320 1 720 1836 t
       +(obscured windows, which speeds things up considerably over low-speed links.)9 3143 1 720 1956 t
       +(It's trivial for the terminal to maintain its)7 1683 1 970 2112 t
       +10 CW f
       +(Rasp)2684 2112 w
       +10 R f
       +( on the terminal apply to)5 1015(, because all changes made)4 1101 2 2924 2112 t
       +( made by the host are compared against the)8 1740( Changes)1 396( already loaded there.)3 864(parts of the file)3 616 4 720 2232 t
       +10 CW f
       +(Rasp)4363 2232 w
       +10 R f
       +(during the)1 410 1 4630 2232 t
       +( changes to pieces of the file loaded in the terminal are sent in)13 2509( Small)1 287( each command.)2 657(update sequence after)2 867 4 720 2352 t
       +( in the holes, are transmitted as messages with-)8 1894( changes, and changes that fall entirely)6 1567( Larger)1 318(their entirety.)1 541 4 720 2472 t
       +( a command is)3 610( When)1 298( the lengths of the deleted and inserted strings are transmitted.)10 2577(out literal data: only)3 835 4 720 2592 t
       +( in their)2 319(completed, the terminal examines its visible windows to see if any holes)11 2947 2 720 2712 t
       +10 CW f
       +(Rasps)4015 2712 w
       +10 R f
       +(intersect the visi-)2 696 1 4344 2712 t
       +( 512 bytes of sur-)4 731( then requests the missing data from the host, along with up to)12 2569( It)1 118(ble portion of the file.)4 902 4 720 2832 t
       +( technique)1 414( This)1 229( the file.)2 332(rounding data, to minimize the number of messages when visiting a new portion of)13 3345 4 720 2952 t
       +( first level sends a minimum of informa-)7 1646( The)1 209(provides a kind of two-level lazy evaluation for the terminal.)9 2465 3 720 3072 t
       +( the file not being edited interactively; the second level waits until a change is displayed)15 3575(tion about parts of)3 745 2 720 3192 t
       +( is also helped by having the terminal respond)8 1933( course, performance)2 860( Of)1 166(before transmitting the new data.)4 1361 4 720 3312 t
       +( for small changes to active pieces of the file,)9 1906( Except)1 338( and simple mouse requests.)4 1168(immediately to typing)2 908 4 720 3432 t
       +(which are transmitted to the terminal without negotiation, the terminal is wholly responsible for deciding)14 4320 1 720 3552 t
       +(what is displayed; the host uses the)6 1405 1 720 3672 t
       +10 CW f
       +(Rasp)2150 3672 w
       +10 R f
       +(only to tell the terminal what might be relevant.)8 1907 1 2415 3672 t
       +( host, the messages to the terminal describing the change are gener-)11 2727(When a change is initiated by the)6 1343 2 970 3828 t
       +( the transcript of the changes to the contents of the)10 2023(ated by the routine that applies)5 1239 2 720 3948 t
       +10 CW f
       +(File)4008 3948 w
       +10 R f
       +( changes are)2 494(. Since)1 298 2 4248 3948 t
       +( no extra code in the communications; the usual mes-)9 2173(undone by the same update routine, undoing requires)7 2147 2 720 4068 t
       +(sages describing changes to the file are sufficient to back up the screen image.)13 3120 1 720 4188 t
       +(The)970 4344 w
       +10 CW f
       +(Rasp)1164 4344 w
       +10 R f
       +(is a particularly good example of the way caches are used in)11 2556 1 1443 4344 t
       +10 CW f
       +(sam)4038 4344 w
       +10 R f
       +( it facilitates)2 524(. First,)1 298 2 4218 4344 t
       +( so doing, it provides)4 870( In)1 140( portion of the text by placing the busy text in main memory.)12 2519(access to the active)3 791 4 720 4464 t
       +( be)1 138( the form of data is to)6 968( Since)1 290(efficient access to a large data structure that does not fit in memory.)12 2924 4 720 4584 t
       +( characters will frequently be scanned sequentially,)6 2090(imposed by the user, not by the program, and because)9 2230 2 720 4704 t
       +( help keep performance good and linear when working with such)10 2724( Caches)1 350(files are stored as flat objects.)5 1246 3 720 4824 t
       +(data.)720 4944 w
       +(Second, the)1 468 1 970 5100 t
       +10 CW f
       +(Rasp)1465 5100 w
       +10 R f
       +(and several of the other caches have some)7 1696 1 1732 5100 t
       +10 I f
       +(read-ahead;)3456 5100 w
       +10 R f
       +(that is, the cache is loaded)5 1063 1 3977 5100 t
       +( manipulating linear struc-)3 1090( When)1 299( than is needed for the job immediately at hand.)9 2002(with more information)2 929 4 720 5220 t
       +( time to access)3 588(tures, the accesses are usually sequential, and read-ahead can significantly reduce the average)12 3732 2 720 5340 t
       +( mode for people as well as programs; con-)8 1764( access is a common)4 839( Sequential)1 478(the next element of the object.)5 1239 4 720 5460 t
       +(sider scrolling through a document while looking for something.)8 2579 1 720 5580 t
       +( at least the implementation.)4 1158(Finally, like any good data structure, the cache guides the algorithm, or)11 2912 2 970 5736 t
       +(The)720 5856 w
       +10 CW f
       +(Rasp)906 5856 w
       +10 R f
       +( between the host and terminal parts, but I)8 1718(was actually invented to control the communications)6 2145 2 1177 5856 t
       +( caches were more explicitly intended to serve a)8 1957( Other)1 282( was also a form of cache.)6 1068(realized very early that it)4 1013 4 720 5976 t
       +(double purpose: for example, the caches in)6 1728 1 720 6096 t
       +10 CW f
       +(Files)2476 6096 w
       +10 R f
       +(that coalesce updates not only reduce traffic to the tran-)9 2237 1 2803 6096 t
       +(script and contents)2 749 1 720 6216 t
       +10 CW f
       +(Buffers)1494 6216 w
       +10 R f
       +( clump screen updates so that complicated changes to the screen are)11 2718(, they also)2 408 2 1914 6216 t
       +( not need to write)4 715( saved me considerable work: I did)6 1427( This)1 233(achieved in just a few messages to the terminal.)8 1945 4 720 6336 t
       +( they)1 200( Also,)1 267( pay off in surprising ways.)5 1108( Caches)1 341( optimize the message traffic to the terminal.)7 1807(special code to)2 597 6 720 6456 t
       +(tend to be independent, so their performance improvements are multiplicative.)9 3131 1 720 6576 t
       +10 B f
       +(Data structures in the terminal)4 1320 1 720 6816 t
       +10 R f
       +(The terminal's job is to display and to maintain a consistent image of pieces of the files being edited.)18 4320 1 720 6972 t
       +( text is always in memory, the data structures are considerably simpler than those in the host)16 3831(Because the)1 489 2 720 7092 t
       +(part.)720 7212 w
       +cleartomark
       +showpage
       +saveobj restore
       +%%EndPage: 21 21
       +%%Page: 22 22
       +/saveobj save def
       +mark
       +22 pagesetup
       +10 R f
       +(- 22 -)2 216 1 2772 480 t
       +10 CW f
       +(Sam)970 840 w
       +10 R f
       +( does)1 211(typically has far more windows than)5 1466 2 1177 840 t
       +10 CW f
       +(mux)2882 840 w
       +10 R f
       +(, the window system within which its Blit imple-)8 1978 1 3062 840 t
       +(mentation runs.)1 633 1 720 960 t
       +10 CW f
       +(Mux)1414 960 w
       +10 R f
       +(has a fairly small number of asynchronously updated windows;)8 2618 1 1630 960 t
       +10 CW f
       +(sam)4284 960 w
       +10 R f
       +(needs a large)2 540 1 4500 960 t
       +( different)1 376( The)1 213( static and often fully obscured.)5 1299(number of synchronously updated windows that are usually)7 2432 4 720 1080 t
       +(tradeoffs guided)1 670 1 720 1200 t
       +10 CW f
       +(sam)1434 1200 w
       +10 R f
       +( windows, called)2 710(away from the memory-intensive implementation of)5 2184 2 1658 1200 t
       +10 CW f
       +(Layers)4595 1200 w
       +10 R f
       +(,)4955 1200 w
       +6 R f
       +(17)4980 1150 w
       +10 R f
       +(used in)1 297 1 720 1320 t
       +10 CW f
       +(mux.)1053 1320 w
       +10 R f
       +( window,)1 384(Rather than depending on a complete bitmap image of the display for each)12 3110 2 1329 1320 t
       +10 CW f
       +(sam)4860 1320 w
       +10 R f
       +( image from its in-memory text \(stored in the)8 1876(regenerates the)1 610 2 720 1440 t
       +10 CW f
       +(Rasp)3240 1440 w
       +10 R f
       +(\) when necessary, although it will use)6 1560 1 3480 1440 t
       +( Like)1 243(such an image if it is available.)6 1300 2 720 1560 t
       +10 CW f
       +(Layers)2299 1560 w
       +10 R f
       +(, though,)1 364 1 2659 1560 t
       +10 CW f
       +(sam)3059 1560 w
       +10 R f
       +(uses the screen bitmap as active storage in)7 1765 1 3275 1560 t
       +(which to update the image using)5 1306 1 720 1680 t
       +10 CW f
       +(bitblt)2053 1680 w
       +10 R f
       +(.)2413 1680 w
       +6 R f
       +(18,19)2438 1630 w
       +10 R f
       +(The resulting organization, pictured in Figure 6, has a global)9 2440 1 2600 1680 t
       +(array of windows, called)3 989 1 720 1800 t
       +10 CW f
       +(Flayers)1735 1800 w
       +10 R f
       +( structure)1 376(, each of which holds an image of a piece of text held in a data)15 2509 2 2155 1800 t
       +(called a)1 319 1 720 1920 t
       +10 CW f
       +(Frame)1076 1920 w
       +10 R f
       +( rectangular window full of text displayed in some)8 2102(, which in turn represents a)5 1141 2 1376 1920 t
       +10 CW f
       +(Bitmap)4655 1920 w
       +10 R f
       +(.)5015 1920 w
       +(Each)720 2040 w
       +10 CW f
       +(Flayer)947 2040 w
       +10 R f
       +( display, and simultaneously)3 1151(appears in a global list that orders them all front-to-back on the)11 2554 2 1335 2040 t
       +( in the termi-)3 519( complement)1 520( The)1 206(as an element of a per-file array that holds all the open windows for that file.)15 3075 4 720 2160 t
       +(nal of the)2 377 1 720 2280 t
       +10 CW f
       +(File)1122 2280 w
       +10 R f
       +(on the host is called a)5 863 1 1387 2280 t
       +10 CW f
       +(Text)2275 2280 w
       +10 R f
       +(; each connects its)3 729 1 2515 2280 t
       +10 CW f
       +(Flayers)3269 2280 w
       +10 R f
       +(to the associated)2 660 1 3714 2280 t
       +10 CW f
       +(Rasp)4399 2280 w
       +10 R f
       +(.)4639 2280 w
       +1800 2478 1800 2766 Dl
       +2606 2478 1800 2478 Dl
       +2606 2766 2606 2478 Dl
       +1800 2766 2606 2766 Dl
       +10 CW f
       +(Text)2083 2642 w
       +2722 2478 2722 2766 Dl
       +3370 2478 2722 2478 Dl
       +3370 2766 3370 2478 Dl
       +2722 2766 3370 2766 Dl
       +(Rasp)2926 2642 w
       +2721 2622 2606 2622 Dl
       +3542 2622 3370 2622 Dl
       +3542 2622 3470 2640 Dl
       +3542 2622 3470 2604 Dl
       +10 R f
       +(to host)1 270 1 3677 2642 t
       +2203 2881 2203 2766 Dl
       +1987 2881 2203 2881 Dl
       +1987 3097 1987 2881 Dl
       +2102 3097 1987 3097 Dl
       +2102 2953 2102 3241 Dl
       +2642 2953 2102 2953 Dl
       +2642 3241 2642 2953 Dl
       +2102 3241 2642 3241 Dl
       +10 CW f
       +(Flayer)2192 3117 w
       +2642 2953 2642 3241 Dl
       +3182 2953 2642 2953 Dl
       +3182 3241 3182 2953 Dl
       +2642 3241 3182 3241 Dl
       +3182 2953 3182 3241 Dl
       +3722 2953 3182 2953 Dl
       +3722 3241 3722 2953 Dl
       +3182 3241 3722 3241 Dl
       +3722 2953 3722 3241 Dl
       +4262 2953 3722 2953 Dl
       +4262 3241 4262 2953 Dl
       +3722 3241 4262 3241 Dl
       +(...)3902 3117 w
       +(...)2822 3578 w
       +2102 3414 2102 3702 Dl
       +2642 3414 2102 3414 Dl
       +2642 3702 2642 3414 Dl
       +2102 3702 2642 3702 Dl
       +(Frame)2222 3578 w
       +2372 3413 2372 3241 Dl
       +2912 3413 2912 3241 Dl
       +3452 3413 3452 3241 Dl
       +3992 3413 3992 3241 Dl
       +1498 3414 1498 3702 Dl
       +1930 3414 1498 3414 Dl
       +1930 3702 1930 3414 Dl
       +1498 3702 1930 3702 Dl
       +(Bitmap)1534 3518 w
       +10 R f
       +(\(cache\))1568 3638 w
       +2102 3558 1930 3558 Dl
       +2372 3817 2372 3702 Dl
       +2012 3817 2372 3817 Dl
       +2012 4033 2012 3817 Dl
       +2127 4033 2012 4033 Dl
       +2128 3889 2128 4177 Dl
       +2452 3889 2128 3889 Dl
       +2452 4177 2452 3889 Dl
       +2128 4177 2452 4177 Dl
       +10 CW f
       +(Box)2200 4053 w
       +2452 3889 2452 4177 Dl
       +2776 3889 2452 3889 Dl
       +2776 4177 2776 3889 Dl
       +2452 4177 2776 4177 Dl
       +2776 3889 2776 4177 Dl
       +3100 3889 2776 3889 Dl
       +3100 4177 3100 3889 Dl
       +2776 4177 3100 4177 Dl
       +3100 3889 3100 4177 Dl
       +3424 3889 3100 3889 Dl
       +3424 4177 3424 3889 Dl
       +3100 4177 3424 4177 Dl
       +(...)3172 4053 w
       +8 I f
       +(Figure 6. Data structures in the terminal.)6 1360 1 720 4355 t
       +8 CW f
       +(Flayers)2126 4355 w
       +8 I f
       +(are also linked together into a front-to-back list.)7 1587 1 2488 4355 t
       +8 CW f
       +(Boxes)4122 4355 w
       +8 I f
       +(are discussed in the)3 651 1 4389 4355 t
       +(next section.)1 397 1 720 4455 t
       +10 R f
       +(The)970 4731 w
       +10 CW f
       +(Bitmap)1155 4731 w
       +10 R f
       +(for a)1 190 1 1545 4731 t
       +10 CW f
       +(Frame)1765 4731 w
       +10 R f
       +( a fully visible window, the)5 1124( For)1 195(contains the image of the text.)5 1234 3 2096 4731 t
       +10 CW f
       +(Bitmap)4680 4731 w
       +10 R f
       +(will be the screen \(or at least the)7 1315 1 720 4851 t
       +10 CW f
       +(Layer)2063 4851 w
       +10 R f
       +(in which)1 350 1 2391 4851 t
       +10 CW f
       +(sam)2769 4851 w
       +10 R f
       +( while for partially obscured windows)5 1527(is being run\),)2 536 2 2977 4851 t
       +(the)720 4971 w
       +10 CW f
       +(Bitmap)867 4971 w
       +10 R f
       +( the window is fully obscured, the)6 1357( If)1 116(will be off-screen.)2 728 3 1252 4971 t
       +10 CW f
       +(Bitmap)3478 4971 w
       +10 R f
       +(will be null.)2 481 1 3863 4971 t
       +(The)970 5127 w
       +10 CW f
       +(Bitmap)1159 5127 w
       +10 R f
       +( image)1 279( making changes to the display, most of the original)9 2152( When)1 297(is a kind of cache.)4 759 4 1553 5127 t
       +( The)1 207( this.)1 197(will look the same in the final image, and the update algorithms exploit)12 2887 3 720 5247 t
       +10 CW f
       +(Frame)4038 5247 w
       +10 R f
       +(software updates)1 675 1 4365 5247 t
       +(the image in the)3 644 1 720 5367 t
       +10 CW f
       +(Bitmap)1390 5367 w
       +10 R f
       +(incrementally; the)1 725 1 1776 5367 t
       +10 CW f
       +(Bitmap)2528 5367 w
       +10 R f
       +(is not just an image, it is a data structure.)9 1653 1 2915 5367 t
       +6 R f
       +(18,19)4568 5317 w
       +10 R f
       +(The job)1 310 1 4730 5367 t
       +( therefore to use as much as possible of the existing image \(con-)12 2622(of the software that updates the display is)7 1698 2 720 5487 t
       +( a sort of two-dimensional string insertion)6 1713(verting the text from ASCII characters to pixels is expensive\) in)10 2607 2 720 5607 t
       +( details of this process are described in the next section.)10 2221(algorithm. The)1 619 2 720 5727 t
       +(The)970 5883 w
       +10 CW f
       +(Frame)1173 5883 w
       +10 R f
       +(software has no code to support overlapping windows; its job is to keep a single)14 3519 1 1521 5883 t
       +10 CW f
       +(Bitmap)720 6003 w
       +10 R f
       +( falls to the)3 471( It)1 119(up to date.)2 435 3 1113 6003 t
       +10 CW f
       +(Flayer)2171 6003 w
       +10 R f
       +(software to multiplex the various)4 1349 1 2564 6003 t
       +10 CW f
       +(Bitmaps)3945 6003 w
       +10 R f
       +(onto the screen.)2 643 1 4397 6003 t
       +(The problem of maintaining overlapping)4 1638 1 720 6123 t
       +10 CW f
       +(Flayers)2386 6123 w
       +10 R f
       +(is easier than for)3 671 1 2834 6123 t
       +10 CW f
       +(Layers)3533 6123 w
       +6 R f
       +(17)3893 6073 w
       +10 R f
       +( are made)2 395(because changes)1 664 2 3981 6123 t
       +( reconstructed from the data stored in the)7 1722(synchronously and because the contents of the window can be)9 2598 2 720 6243 t
       +10 CW f
       +(Frame)720 6363 w
       +10 R f
       +(; the)1 180 1 1020 6363 t
       +10 CW f
       +(Layers)1231 6363 w
       +10 R f
       +( In)1 139(software makes no such assumptions.)4 1525 2 1622 6363 t
       +10 CW f
       +(sam)3317 6363 w
       +10 R f
       +(, the window being changed is almost)6 1543 1 3497 6363 t
       +( when)1 247( However,)1 446(always fully visible, because the current window is always fully visible, by construction.)12 3627 3 720 6483 t
       +( it may be necessary to)5 927(multi-file changes are being made, or when more than one window is open on a file,)15 3393 2 720 6603 t
       +(update partially obscured windows.)3 1420 1 720 6723 t
       +( If)1 125( fully visible, invisible \(fully obscured\), or partially visible.)8 2446(There are three cases: the window is)6 1499 3 970 6879 t
       +(fully visible, the)2 687 1 720 6999 t
       +10 CW f
       +(Bitmap)1449 6999 w
       +10 R f
       +(is part of the screen, so when the)7 1427 1 1851 6999 t
       +10 CW f
       +(Flayer)3320 6999 w
       +10 R f
       +(update routine calls the)3 977 1 3722 6999 t
       +10 CW f
       +(Frame)4740 6999 w
       +10 R f
       +( is invisible, there is no associated)6 1489( the window)2 534( If)1 136(update routine, the screen will be updated directly.)7 2161 4 720 7119 t
       +10 CW f
       +(Bitmap)720 7239 w
       +10 R f
       +( necessary is to update the)5 1075(, and all that is)4 614 2 1080 7239 t
       +10 CW f
       +(Frame)2800 7239 w
       +10 R f
       +( the window is)3 604( If)1 122(data structure, not the image.)4 1183 3 3131 7239 t
       +cleartomark
       +showpage
       +saveobj restore
       +%%EndPage: 22 22
       +%%Page: 23 23
       +/saveobj save def
       +mark
       +23 pagesetup
       +10 R f
       +(- 23 -)2 216 1 2772 480 t
       +(partially visible, the)2 817 1 720 840 t
       +10 CW f
       +(Frame)1572 840 w
       +10 R f
       +( called to update the image in the off-screen)8 1839(routine is)1 385 2 1907 840 t
       +10 CW f
       +(Bitmap)4167 840 w
       +10 R f
       +(, which may)2 513 1 4527 840 t
       +( The)1 207(require regenerating it from the text of the window.)8 2078 2 720 960 t
       +10 CW f
       +(Flayer)3032 960 w
       +10 R f
       +(code then clips this)3 775 1 3419 960 t
       +10 CW f
       +(Bitmap)4221 960 w
       +10 R f
       +(against the)1 432 1 4608 960 t
       +10 CW f
       +(Bitmaps)720 1080 w
       +10 R f
       +(of all)1 208 1 1165 1080 t
       +10 CW f
       +(Frames)1398 1080 w
       +10 R f
       +(in front of the)3 552 1 1783 1080 t
       +10 CW f
       +(Frame)2360 1080 w
       +10 R f
       +(being modified, and the remainder is copied to the display.)9 2350 1 2685 1080 t
       +( than recreating the image off-screen for every change, or clipping all the changes)13 3301(This is much faster)3 769 2 970 1236 t
       +( amounts of)2 476( these caches can also consume prohibitive)6 1731( Unfortunately,)1 638(made to the image during its update.)6 1475 4 720 1356 t
       +( freed fairly liberally \320 after every change to the front-to-back order of the)13 3030(memory, so they are)3 818 2 720 1476 t
       +10 CW f
       +(Flayers)4595 1476 w
       +10 R f
       +(.)5015 1476 w
       +(The result is that the off-screen)5 1274 1 720 1596 t
       +10 CW f
       +(Bitmaps)2025 1596 w
       +10 R f
       +(exist only while multi-window changes are occurring, which is)8 2564 1 2476 1596 t
       +( the user interface causes fully-)5 1278( Also,)1 271( improvement they provide is needed.)5 1544(the only time the performance)4 1227 4 720 1716 t
       +( creating a canonically sized and placed window requires)8 2357(obscured windows to be the easiest to make \320)8 1963 2 720 1836 t
       +(only a button click \320 which reduces the need for caching still further.)12 2803 1 720 1956 t
       +10 B f
       +(Screen update)1 608 1 720 2232 t
       +10 R f
       +( update:)1 321(Only two low-level primitives are needed for incremental)7 2305 2 720 2388 t
       +10 CW f
       +(bitblt)3373 2388 w
       +10 R f
       +(, which copies rectangles of pix-)5 1307 1 3733 2388 t
       +(els, and)1 328 1 720 2508 t
       +10 CW f
       +(string)1096 2508 w
       +10 R f
       +(\(which in turn calls)3 840 1 1503 2508 t
       +10 CW f
       +(bitblt)2390 2508 w
       +10 R f
       +(\), which draws a null-terminated character string in a)8 2290 1 2750 2508 t
       +10 CW f
       +(Bitmap)720 2628 w
       +10 R f
       +(. A)1 153 1 1080 2628 t
       +10 CW f
       +(Frame)1264 2628 w
       +10 R f
       +(contains a list of)3 676 1 1595 2628 t
       +10 CW f
       +(Boxes)2302 2628 w
       +10 R f
       +( win-)1 215(, each of which defines a horizontal strip of text in the)11 2223 2 2602 2628 t
       +( A)1 126(dow \(see Figure 7\).)3 788 2 720 2748 t
       +10 CW f
       +(Box)1663 2748 w
       +10 R f
       +(has a character string)3 856 1 1872 2748 t
       +10 CW f
       +(str)2757 2748 w
       +10 R f
       +(, and a)2 271 1 2937 2748 t
       +10 CW f
       +(Rectangle rect)1 809 1 3237 2748 t
       +10 R f
       +(that defines the location)3 966 1 4074 2748 t
       +( text in)2 280( \(The)1 239(of the strip in the window.)5 1060 3 720 2868 t
       +10 CW f
       +(str)2325 2868 w
       +10 R f
       +(is stored in the)3 592 1 2531 2868 t
       +10 CW f
       +(Box)3150 2868 w
       +10 R f
       +(separately from the)2 774 1 3357 2868 t
       +10 CW f
       +(Rasp)4158 2868 w
       +10 R f
       +(associated with)1 615 1 4425 2868 t
       +(the window's file, so)3 853 1 720 2988 t
       +10 CW f
       +(Boxes)1603 2988 w
       +10 R f
       +( the)1 151( invariant is that the image of)6 1201( The)1 210(are self-contained.\))1 774 4 1933 2988 t
       +10 CW f
       +(Box)4298 2988 w
       +10 R f
       +(can be repro-)2 533 1 4507 2988 t
       +(duced by calling)2 660 1 720 3108 t
       +10 CW f
       +(string)1405 3108 w
       +10 R f
       +(with argument)1 581 1 1790 3108 t
       +10 CW f
       +(str)2397 3108 w
       +10 R f
       +(to draw the string in)4 809 1 2603 3108 t
       +10 CW f
       +(rect)3438 3108 w
       +10 R f
       +(, and the resulting picture fits per-)6 1362 1 3678 3108 t
       +(fectly within)1 509 1 720 3228 t
       +10 CW f
       +(rect)1255 3228 w
       +10 R f
       +( other words, the)3 674(. In)1 159 2 1495 3228 t
       +10 CW f
       +(Boxes)2354 3228 w
       +10 R f
       +( tiling may be compli-)4 889( The)1 206(define the tiling of the window.)5 1265 3 2680 3228 t
       +( editors use horizontal scrolling to)5 1413( Some)1 288( of text, which are folded onto the next line.)9 1840(cated by long lines)3 779 4 720 3348 t
       +( be)1 119(avoid this complication, but to be comfortable this technique requires that lines not)12 3329 2 720 3468 t
       +10 I f
       +(too)4193 3468 w
       +10 R f
       +(long;)4346 3468 w
       +10 CW f
       +(sam)4577 3468 w
       +10 R f
       +(has no)1 258 1 4782 3468 t
       +( programs and terminals traditionally fold long)6 1907( and perhaps more importantly, Unix)5 1501( Also,)1 269(such restriction.)1 643 4 720 3588 t
       +(lines to make their contents fully visible.)6 1630 1 720 3708 t
       +(Two special kinds of)3 841 1 970 3864 t
       +10 CW f
       +(Boxes)1839 3864 w
       +10 R f
       +( and tabs)2 361( Newlines)1 430(contain a single character: either a newline or a tab.)9 2082 3 2167 3864 t
       +( newline)1 353( A)1 134(are white space.)2 663 3 720 3984 t
       +10 CW f
       +(Box)1907 3984 w
       +10 R f
       +( following)1 425(always extends to the right edge of the window, forcing the)10 2491 2 2124 3984 t
       +10 CW f
       +(Box)720 4104 w
       +10 R f
       +( on where it is located: it forces the next)9 1618( width of a tab depends)5 934( The)1 206(to the next line.)3 625 4 926 4104 t
       +10 CW f
       +(Box)4336 4104 w
       +10 R f
       +(to begin at a)3 497 1 4543 4104 t
       +( a minimum width equivalent to a blank \(blanks are drawn by)11 2472( also have)2 403( Tabs)1 246(tab location.)1 496 4 720 4224 t
       +10 CW f
       +(string)4363 4224 w
       +10 R f
       +(and are)1 291 1 4749 4224 t
       +(not treated specially\); newlines have a minimum width of zero.)9 2524 1 720 4344 t
       +929 4506 929 4614 Dl
       +1505 4506 929 4506 Dl
       +1505 4614 1505 4506 Dl
       +929 4614 1505 4614 Dl
       +1505 4506 1505 4614 Dl
       +2700 4506 1505 4506 Dl
       +2700 4614 2700 4506 Dl
       +1505 4614 2700 4614 Dl
       +10 CW f
       +(for\(i=0; i<NL; i++\){)2 1200 1 1502 4580 t
       +2700 4506 2700 4614 Dl
       +3132 4506 2700 4506 Dl
       +3132 4614 3132 4506 Dl
       +2700 4614 3132 4614 Dl
       +3132 4506 3132 4614 Dl
       +4471 4506 3132 4506 Dl
       +4471 4614 4471 4506 Dl
       +3132 4614 4471 4614 Dl
       +(/* for each element */)4 1320 1 3141 4580 t
       +4471 4506 4471 4614 Dl
       +4831 4506 4471 4506 Dl
       +4831 4614 4831 4506 Dl
       +4471 4614 4831 4614 Dl
       +8 I f
       +( line of text showing its)5 747(Figure 7. A)2 368 2 720 4852 t
       +8 CW f
       +(Boxes)1857 4852 w
       +8 I f
       +( first two blank)3 487(. The)1 182 2 2097 4852 t
       +8 CW f
       +(Boxes)2788 4852 w
       +8 I f
       +( are handled)2 407( Spaces)1 263(contain tabs; the last contains a newline.)6 1320 3 3050 4852 t
       +(as ordinary characters.)2 750 1 720 4952 t
       +10 R f
       +(The update algorithms always use the)5 1631 1 970 5228 t
       +10 CW f
       +(Bitmap)2651 5228 w
       +10 R f
       +(image of the text \(either the display or cache)8 1979 1 3061 5228 t
       +10 CW f
       +(Bitmap)720 5348 w
       +10 R f
       +( examine the characters within a)5 1318(\); they never)2 518 2 1080 5348 t
       +10 CW f
       +(Box)2947 5348 w
       +10 R f
       +(except when the)2 660 1 3158 5348 t
       +10 CW f
       +(Box)3849 5348 w
       +10 R f
       +(needs to be split in two.)5 980 1 4060 5348 t
       +( the window consists of a tiling of)7 1365(Before a change,)2 672 2 720 5468 t
       +10 CW f
       +(Boxes)2783 5468 w
       +10 R f
       +(; after the change the window is tiled differently.)8 1957 1 3083 5468 t
       +( algorithms are not strictly)4 1075( The)1 210( rearrange the tiles in place, without backup storage.)8 2124(The update algorithms)2 911 4 720 5588 t
       +( move)1 250(optimal \320 for example, they can clear a pixel that is later going to be written upon \320 but they never)20 4070 2 720 5708 t
       +( that doesn't need to be moved, and they move each tile at most once.)14 2975(a tile)1 212 2 720 5828 t
       +10 CW f
       +(Frinsert)3971 5828 w
       +10 R f
       +(on a Blit can)3 550 1 4490 5828 t
       +(absorb over a thousand characters a second if the strings being inserted are a few tens of characters long.)18 4174 1 720 5948 t
       +(Consider)970 6104 w
       +10 CW f
       +(frdelete)1368 6104 w
       +10 R f
       +( job is to delete a substring from a)8 1456(. Its)1 187 2 1848 6104 t
       +10 CW f
       +(Frame)3528 6104 w
       +10 R f
       +( the image of the)4 723(and restore)1 452 2 3865 6104 t
       +10 CW f
       +(Frame)720 6224 w
       +10 R f
       +( comprising possibly a partial line,)5 1428( image of a substring has a peculiar shape \(see Figure 2\))11 2352(. The)1 240 3 1020 6224 t
       +( reference, call this the)4 937( For)1 197( line.)1 208(zero or more full lines, and possibly a final partial)9 2060 4 720 6344 t
       +10 I f
       +(Z-shape.)4155 6344 w
       +10 CW f
       +(Frdelete)4560 6344 w
       +10 R f
       +( necessary, the)2 584(begins by splitting, if)3 854 2 720 6464 t
       +10 CW f
       +(Boxes)2183 6464 w
       +10 R f
       +(containing the ends of the substring so the substring begins and)10 2532 1 2508 6464 t
       +(ends on)1 311 1 720 6584 t
       +10 CW f
       +(Box)1059 6584 w
       +10 R f
       +( Z-shape is)2 446( the substring is being deleted, its image is not needed, so the)12 2479(boundaries. Because)1 848 3 1267 6584 t
       +( tiles \(that is, the images of)6 1092( Then,)1 282(then cleared.)1 511 3 720 6704 t
       +10 CW f
       +(Boxes)2632 6704 w
       +10 R f
       +(\) are copied, using)3 743 1 2932 6704 t
       +10 CW f
       +(bitblt)3701 6704 w
       +10 R f
       +(, from immediately after)3 979 1 4061 6704 t
       +( \()1 92(the Z-shape to the beginning of the Z-shape, resulting in a new Z-shape.)12 2986 2 720 6824 t
       +10 CW f
       +(Boxes)3798 6824 w
       +10 R f
       +(whose contents would)2 908 1 4132 6824 t
       +(span two lines in the new position must first be split.\))10 2142 1 720 6944 t
       +( the)1 158(Copying the remainder of)3 1059 2 970 7100 t
       +10 CW f
       +(Frame)2223 7100 w
       +10 R f
       +(tile by tile this way will clearly accomplish the deletion but)10 2481 1 2559 7100 t
       +( new)1 221(eventually, typically when the copying algorithm encounters a tab or newline, the old and)13 3984 2 720 7220 t
       +10 CW f
       +(x)4980 7220 w
       +cleartomark
       +showpage
       +saveobj restore
       +%%EndPage: 23 23
       +%%Page: 24 24
       +/saveobj save def
       +mark
       +24 pagesetup
       +10 R f
       +(- 24 -)2 216 1 2772 480 t
       +( correspondence implies that the Z-shape has its)7 2021( This)1 243( same.)1 270(coordinates of the tile to be copied are the)8 1786 4 720 840 t
       +( aligned vertically, and a sequence of at most two)9 2073(beginning and ending edges)3 1154 2 720 960 t
       +10 CW f
       +(bitblts)3983 960 w
       +10 R f
       +(can be used to)3 601 1 4439 960 t
       +( clear out the resulting empty space at the bottom of the win-)12 2526( last step is to)4 573( The)1 212(copy the remaining tiles.)3 1009 4 720 1080 t
       +( number of complete lines in the Z-shape closed by the final)11 2476(dow; the number of lines to be cleared is the)9 1844 2 720 1200 t
       +10 CW f
       +(bitblts.)720 1320 w
       +10 R f
       +( horizontally adjacent)2 877(The final step is to merge)5 1043 2 1230 1320 t
       +10 CW f
       +(Boxes)3181 1320 w
       +10 R f
       +( complete source to)3 797( The)1 211(of plain text.)2 520 3 3512 1320 t
       +10 CW f
       +(frdelete)720 1440 w
       +10 R f
       +(is less than 100 lines of C.)6 1053 1 1225 1440 t
       +10 CW f
       +(frinsert)970 1596 w
       +10 R f
       +( the)1 150(is more complicated because it must do four passes: one to construct)11 2760 2 1477 1596 t
       +10 CW f
       +(Box)4415 1596 w
       +10 R f
       +(list for the)2 417 1 4623 1596 t
       +( one to reconnoitre, one to copy \(in opposite order to)10 2175(inserted string,)1 602 2 720 1716 t
       +10 CW f
       +(frdelete)3529 1716 w
       +10 R f
       +(\) the)1 187 1 4009 1716 t
       +10 CW f
       +(Boxes)4228 1716 w
       +10 R f
       +(to make the)2 480 1 4560 1716 t
       +( though,)1 335( Overall,)1 381(hole for the new text, and finally one to copy the new text into place.)14 2849 3 720 1836 t
       +10 CW f
       +(frinsert)4317 1836 w
       +10 R f
       +(has a)1 210 1 4830 1836 t
       +(similar flavor to)2 676 1 720 1956 t
       +10 CW f
       +(frdelete)1437 1956 w
       +10 R f
       +(, and needn't be described further.)5 1445 1 1917 1956 t
       +10 CW f
       +(Frinsert)3428 1956 w
       +10 R f
       +(and its subsidiary routines)3 1092 1 3948 1956 t
       +(comprise 211 lines of C.)4 980 1 720 2076 t
       +(The terminal source code is 3024 lines of C, and the host source is 5797 lines.)15 3116 1 970 2232 t
       +10 B f
       +(Discussion)720 2472 w
       +(History)720 2712 w
       +10 R f
       +( of)1 113(The immediate ancestor)2 967 2 720 2868 t
       +10 CW f
       +(sam)1830 2868 w
       +10 R f
       +(was the original text editor for the Blit, called)8 1863 1 2040 2868 t
       +10 CW f
       +(jim)3933 2868 w
       +10 R f
       +(.)4113 2868 w
       +10 CW f
       +(Sam)4193 2868 w
       +10 R f
       +(inherited)4403 2868 w
       +10 CW f
       +(jim)4788 2868 w
       +10 R f
       +('s)4968 2868 w
       +(two-process structure and mouse language almost unchanged, but)7 2721 1 720 2988 t
       +10 CW f
       +(jim)3480 2988 w
       +10 R f
       +(suffered from several drawbacks)3 1342 1 3698 2988 t
       +( addressed in the design of)5 1112(that were)1 377 2 720 3108 t
       +10 CW f
       +(sam)2244 3108 w
       +10 R f
       +( most important of these was the lack of a command lan-)11 2376(. The)1 240 2 2424 3108 t
       +(guage. Although)1 694 1 720 3228 t
       +10 CW f
       +(jim)1442 3228 w
       +10 R f
       +( direct help with large or repetitive)6 1398(was easy to use for simple editing, it provided no)9 1992 2 1650 3228 t
       +( but this was no)4 640( it provided a command to pass selected text through a shell pipeline,)12 2785( Instead,)1 365(editing tasks.)1 530 4 720 3348 t
       +(more satisfactory than could be expected of a stopgap measure.)9 2527 1 720 3468 t
       +10 CW f
       +(Jim)970 3624 w
       +10 R f
       +( interface to text, and)4 877(was written primarily as a vehicle for experimenting with a mouse-based)10 2981 2 1182 3624 t
       +(the experiment was successful.)3 1263 1 720 3744 t
       +10 CW f
       +(Jim)2041 3744 w
       +10 R f
       +(had some spin-offs:)2 804 1 2254 3744 t
       +10 CW f
       +(mux)3091 3744 w
       +10 R f
       +(, the second window system for the Blit, is)8 1769 1 3271 3744 t
       +( the terminal part of)4 829(essentially a multiplexed version of)4 1453 2 720 3864 t
       +10 CW f
       +(jim)3036 3864 w
       +10 R f
       +(; and the debugger)3 767 1 3216 3864 t
       +10 CW f
       +(pi)4017 3864 w
       +10 R f
       +('s user interface)2 654 1 4137 3864 t
       +6 R f
       +(20)4791 3814 w
       +10 R f
       +(was)4885 3864 w
       +(closely modeled on)2 785 1 720 3984 t
       +10 CW f
       +(jim)1534 3984 w
       +10 R f
       +( after a couple of years,)5 955('s. But)1 296 2 1714 3984 t
       +10 CW f
       +(jim)2994 3984 w
       +10 R f
       +( maintain and limiting)3 896(had become difficult to)3 941 2 3203 3984 t
       +(to use, and its replacement was overdue.)6 1613 1 720 4104 t
       +(I began the design of)4 837 1 970 4260 t
       +10 CW f
       +(sam)1832 4260 w
       +10 R f
       +(by asking)1 386 1 2037 4260 t
       +10 CW f
       +(jim)2448 4260 w
       +10 R f
       +( was probably a mistake;)4 997( This)1 229( they wanted.)2 537(customers what)1 624 4 2653 4260 t
       +( of the)2 259(the answers were essentially a list of features to be found in other editors, which did not provide any)18 4061 2 720 4380 t
       +( for a ``global substitute,'' but no)6 1359( instance, one common request was)5 1434( For)1 193(guiding principles I was seeking.)4 1334 4 720 4500 t
       +( was looking for a scheme that would sup-)8 1724( I)1 87( it within a cut-and-paste editor.)5 1296(one suggested how to provide)4 1213 4 720 4620 t
       +( were)1 225( Ideas)1 267( context of some general command language.)6 1852(port such specialized features comfortably in the)6 1976 4 720 4740 t
       +( line lengths)2 499(not forthcoming, though, particularly given my insistence on removing all limits on file sizes,)13 3821 2 720 4860 t
       +( a region of the screen that)6 1088( worse, I recognized that, since the mouse could easily indicate)10 2557( Even)1 259(and so on.)2 416 4 720 4980 t
       +(was not an integral number of lines, the command language would best forget about newlines altogether,)15 4320 1 720 5100 t
       +(and that meant the command language had to treat the file as a single string, not an array of lines.)19 3885 1 720 5220 t
       +( I)1 96( very far and it was time to try building.)9 1710(Eventually, I decided that thinking was not getting me)8 2264 3 970 5376 t
       +( of)1 115(knew that the terminal part could be built easily \320 that part)11 2477 2 720 5496 t
       +10 CW f
       +(jim)3344 5496 w
       +10 R f
       +(behaved acceptably well \320 and that)5 1484 1 3556 5496 t
       +( hard work was going to be in the host part: the file interface, command interpreter and so on.)18 3858(most of the)2 462 2 720 5616 t
       +(Moreover, I had some ideas about how the architecture of)9 2386 1 720 5736 t
       +10 CW f
       +(jim)3140 5736 w
       +10 R f
       +(could be improved without destroying its)5 1687 1 3353 5736 t
       +( I began)2 331( So)1 161( worked out as well as I had hoped.)8 1451(basic structure, which I liked in principle but which hadn't)9 2377 4 720 5856 t
       +( with the way)3 556(by designing the file data structure, starting)6 1770 2 720 5976 t
       +10 CW f
       +(jim)3076 5976 w
       +10 R f
       +(worked \320 comparable to a single structure)6 1754 1 3286 5976 t
       +(merging)720 6096 w
       +10 CW f
       +(Disc)1080 6096 w
       +10 R f
       +(and)1347 6096 w
       +10 CW f
       +(Buffer)1518 6096 w
       +10 R f
       +( cache more general \320 and thinking about how glo-)9 2109(, which I split to make the)6 1053 2 1878 6096 t
       +( answer was clearly that it had to be done in two passes, and the)14 2618( The)1 210( implemented.)1 577(bal substitute could be)3 915 4 720 6216 t
       +(transcript-oriented implementation fell out naturally.)4 2106 1 720 6336 t
       +10 CW f
       +(Sam)970 6492 w
       +10 R f
       +( data structures and algorithms for manipulating text,)7 2190(was written bottom-up, starting from the)5 1666 2 1184 6492 t
       +( retrospect, it turned out)4 973( In)1 138(through the command language and up to the code for maintaining the display.)12 3209 3 720 6612 t
       +( were several times when I had)6 1247( There)1 284( general.)1 345(well, but this implementation method is not recommended in)8 2444 4 720 6732 t
       +( command language, in)3 938( The)1 207( proceed with it.)3 655(a large body of interesting code assembled and no clue how to)11 2520 4 720 6852 t
       +( beginning)1 429(particular, took almost a year to figure out, but can be implemented \(given what was there at the)17 3891 2 720 6972 t
       +( inventing the)2 568( Similarly,)1 457(of that year\) in a day or two.)7 1199 3 720 7092 t
       +10 CW f
       +(Rasp)2978 7092 w
       +10 R f
       +(data structure delayed the connection of the)6 1788 1 3252 7092 t
       +(host and terminal pieces by another few months.)7 1978 1 720 7212 t
       +10 CW f
       +(Sam)2754 7212 w
       +10 R f
       +(took about two years to write, although only about)8 2074 1 2966 7212 t
       +cleartomark
       +showpage
       +saveobj restore
       +%%EndPage: 24 24
       +%%Page: 25 25
       +/saveobj save def
       +mark
       +25 pagesetup
       +10 R f
       +(- 25 -)2 216 1 2772 480 t
       +(four months were spent actually working on it.)7 1870 1 720 840 t
       +( process was unusual: the subset of the protocol that maintains the)11 2661(Part of the design)3 705 2 970 996 t
       +10 CW f
       +(Rasp)4363 996 w
       +10 R f
       +(was simu-)1 410 1 4630 996 t
       +(lated, debugged and verified by an automatic protocol analyzer,)8 2568 1 720 1116 t
       +6 R f
       +(21)3288 1066 w
       +10 R f
       +( rest)1 171( The)1 207( from the start.)3 594(and was bug-free)2 692 4 3376 1116 t
       +( keeping menus up to date, was unfortunately too unwieldy for such)11 2757(of the protocol, concerned mostly with)5 1563 2 720 1236 t
       +( in and)2 276(analysis, and was debugged by more traditional methods, primarily by logging in a file all messages)15 4044 2 720 1356 t
       +(out of the host.)3 600 1 720 1476 t
       +10 B f
       +(Reflections)720 1716 w
       +10 CW f
       +(Sam)720 1872 w
       +10 R f
       +( of the computing science)4 1078(is essentially the only interactive editor used by the sixty or so members)12 3025 2 937 1872 t
       +( same could not be said of)6 1055( The)1 207(research center in which I work.)5 1294 3 720 1992 t
       +10 CW f
       +(jim)3303 1992 w
       +10 R f
       +( kept)1 198(; the lack of a command language)6 1359 2 3483 1992 t
       +( of a user interface as comfortable as)7 1485( union)1 255( The)1 207(some people from adopting it.)4 1210 4 720 2112 t
       +10 CW f
       +(jim)3905 2112 w
       +10 R f
       +('s with a command lan-)4 955 1 4085 2112 t
       +(guage as powerful as)3 851 1 720 2232 t
       +10 CW f
       +(ed)1600 2232 w
       +10 R f
       +( to)1 108('s\262 is essential)2 591 2 1720 2232 t
       +10 CW f
       +(sam)2449 2232 w
       +10 R f
       +( When)1 293('s success.)1 426 2 2629 2232 t
       +10 CW f
       +(sam)3378 2232 w
       +10 R f
       +(was first made available to the)5 1242 1 3588 2232 t
       +10 CW f
       +(jim)4860 2232 w
       +10 R f
       +( the months that followed, even)5 1315( In)1 144( to it within two or three days.)7 1282(community, almost everyone switched)3 1579 4 720 2352 t
       +(people who had never adopted)4 1219 1 720 2472 t
       +10 CW f
       +(jim)1964 2472 w
       +10 R f
       +(started using)1 508 1 2169 2472 t
       +10 CW f
       +(sam)2702 2472 w
       +10 R f
       +(exclusively.)2907 2472 w
       +(To be honest,)2 555 1 970 2628 t
       +10 CW f
       +(ed)1558 2628 w
       +10 R f
       +(still gets occasional use, but usually when something quick needs to be done and)13 3329 1 1711 2628 t
       +(the overhead of downloading the terminal part of)7 1981 1 720 2748 t
       +10 CW f
       +(sam)2729 2748 w
       +10 R f
       +( as a `line' editor,)4 709( Also,)1 266(isn't worth the trouble.)3 922 3 2936 2748 t
       +10 CW f
       +(sam)4860 2748 w
       +(-d)720 2868 w
       +10 R f
       +( it is)2 183( But)1 200( line editor.)2 468(is a bit odd; when using a good old ASCII terminal, it's comforting to have a true)16 3320 4 869 2868 t
       +(fair to say that)3 598 1 720 2988 t
       +10 CW f
       +(sam)1351 2988 w
       +10 R f
       +('s command language has displaced)4 1464 1 1531 2988 t
       +10 CW f
       +(ed)3027 2988 w
       +10 R f
       +('s for most of the complicated editing that has)8 1893 1 3147 2988 t
       +(kept line editors \(that is, command-driven editors\) with us.)8 2348 1 720 3108 t
       +10 CW f
       +(Sam)970 3264 w
       +10 R f
       +('s command language is even fancier than)6 1745 1 1150 3264 t
       +10 CW f
       +(ed)2931 3264 w
       +10 R f
       +('s, and most)2 508 1 3051 3264 t
       +10 CW f
       +(sam)3595 3264 w
       +10 R f
       +(customers don't come near to)4 1229 1 3811 3264 t
       +( think the answer is yes, for two reasons.)8 1625( I)1 83( it need to be so sophisticated?)6 1221( Does)1 255(using all its capabilities.)3 967 5 720 3384 t
       +(First, the)1 362 1 970 3540 t
       +10 I f
       +(model)1363 3540 w
       +10 R f
       +(for)1638 3540 w
       +10 CW f
       +(sam)1786 3540 w
       +10 R f
       +('s command language is really relatively simple, and certainly simpler than)10 3074 1 1966 3540 t
       +(that of)1 262 1 720 3660 t
       +10 CW f
       +(ed)1011 3660 w
       +10 R f
       +( instance, there is only one kind of textual loop in)10 2009(. For)1 217 2 1131 3660 t
       +10 CW f
       +(sam)3385 3660 w
       +10 R f
       +(\320 the)1 250 1 3593 3660 t
       +10 CW f
       +(x)3871 3660 w
       +10 R f
       +(command \320 while)2 772 1 3959 3660 t
       +10 CW f
       +(ed)4759 3660 w
       +10 R f
       +(has)4907 3660 w
       +(three \(the)1 381 1 720 3780 t
       +10 CW f
       +(g)1128 3780 w
       +10 R f
       +( implicit loop over lines in multi-line substi-)7 1792(command, the global flag on substitutions, and the)7 2033 2 1215 3780 t
       +(tutions\). Also,)1 598 1 720 3900 t
       +10 CW f
       +(ed)1346 3900 w
       +10 R f
       +( within lines, but in)4 784('s substitute command is necessary to make changes)7 2115 2 1466 3900 t
       +10 CW f
       +(sam)4392 3900 w
       +10 R f
       +(the)4599 3900 w
       +10 CW f
       +(s)4748 3900 w
       +10 R f
       +(com-)4835 3900 w
       +(mand is more of a familiar convenience than a necessity;)9 2270 1 720 4020 t
       +10 CW f
       +(c)3015 4020 w
       +10 R f
       +(and)3100 4020 w
       +10 CW f
       +(t)3269 4020 w
       +10 R f
       +(can do all the work.)4 790 1 3354 4020 t
       +( to be about as powerful as)6 1082(Second, given a community that expects an editor)7 1999 2 970 4176 t
       +10 CW f
       +(ed)4078 4176 w
       +10 R f
       +(, it's hard to see how)5 842 1 4198 4176 t
       +10 CW f
       +(sam)720 4296 w
       +10 R f
       +( want to do ``global substi-)5 1150( People)1 336( that expectation.)2 713(could really be much simpler and still satisfy)7 1901 4 940 4296 t
       +( sophistication)1 587( The)1 211( fancy changes.)2 629(tutes,'' and most are content to have the recipe for that and a few other)14 2893 4 720 4416 t
       +( do global substitutes)3 848(of the command language is really just a veneer over a design that makes it possible to)16 3472 2 720 4536 t
       +( always want something more, however, and it's gratifying to be able)11 2831( people will)2 482( Some)1 283(in a screen editor.)3 724 4 720 4656 t
       +( real power of)3 595( The)1 218(to provide it.)2 542 3 720 4776 t
       +10 CW f
       +(sam)2113 4776 w
       +10 R f
       +('s command language comes from composability of the operators,)8 2747 1 2293 4776 t
       +( other words,)2 542( In)1 142( orthogonal to the underlying model.)5 1511(which is by nature)3 759 4 720 4896 t
       +10 CW f
       +(sam)3708 4896 w
       +10 R f
       +(is not itself complex, but it)5 1118 1 3922 4896 t
       +( you don't want to do anything complex, you can ignore the complexity)12 2915( If)1 120( things possible.)2 656(makes complex)1 629 4 720 5016 t
       +(altogether, and many people do so.)5 1395 1 720 5136 t
       +(Sometimes I am asked the opposite question: why didn't I just make)11 2801 1 970 5292 t
       +10 CW f
       +(sam)3802 5292 w
       +10 R f
       +(a real programmable edi-)3 1026 1 4014 5292 t
       +( main reason is a matter of taste: I like the editor to be the)14 2400( The)1 212( macros and variables and so on?)6 1361(tor, with)1 347 4 720 5412 t
       +( a wor-)2 284( is one technical reason, though: programmability in editors is largely)10 2778( There)1 282(same every time I use it.)5 976 4 720 5532 t
       +( usually short-)2 604( editors are used to make particular,)6 1529( Programmable)1 655(karound for insufficient interactivity.)3 1532 4 720 5652 t
       +( things are generally easy)4 1038( If)1 122( providing shorthands for common actions.)5 1751(term, things easy to do, such as by)7 1409 4 720 5772 t
       +(to do in the first place, shorthands are not as helpful.)10 2197 1 720 5892 t
       +10 CW f
       +(Sam)2977 5892 w
       +10 R f
       +(makes common editing operations very easy,)5 1848 1 3192 5892 t
       +( Also,)1 274( to complex editing problems seem commensurate with the problems themselves.)10 3350(and the solutions)2 696 3 720 6012 t
       +(the ability to edit the)4 840 1 720 6132 t
       +10 CW f
       +(sam)1588 6132 w
       +10 R f
       +( only takes a mouse button click)6 1300(window makes it easy to repeat commands \320 it)8 1944 2 1796 6132 t
       +(to execute a command again.)4 1161 1 720 6252 t
       +8 S1 f
       +(__________________)720 6900 w
       +8 R f
       +(\262 The people who criticize)4 872 1 720 6990 t
       +8 CW f
       +(ed)1618 6990 w
       +8 R f
       +( and its close relative)4 698(as an interactive program often forget that it)7 1441 2 1740 6990 t
       +8 CW f
       +(sed)3906 6990 w
       +4 R f
       +(7)4050 6950 w
       +8 R f
       +(still thrive as pro-)3 583 1 4097 6990 t
       +( strength of these programs is independent of their convenience for interactive editing.)12 2742( The)1 164(grammable editors.)1 613 3 720 7080 t
       +cleartomark
       +showpage
       +saveobj restore
       +%%EndPage: 25 25
       +%%Page: 26 26
       +/saveobj save def
       +mark
       +26 pagesetup
       +10 R f
       +(- 26 -)2 216 1 2772 480 t
       +10 B f
       +(Pros and cons)2 595 1 720 840 t
       +10 CW f
       +(Sam)720 996 w
       +10 R f
       +( the good things is the idea of struc-)8 1472( Among)1 355( its share of problems.)4 905(has several other good points, and)5 1379 4 929 996 t
       +( were arrived at serendipi-)4 1054( They)1 258(tural regular expressions, whose usefulness has only begun to be explored.)10 3008 3 720 1116 t
       +(tously when I attempted to distill the essence of)8 1936 1 720 1236 t
       +10 CW f
       +(ed)2685 1236 w
       +10 R f
       +( global substitution and recognized that)5 1599('s way of doing)3 636 2 2805 1236 t
       +(the looping command in)3 975 1 720 1356 t
       +10 CW f
       +(ed)1720 1356 w
       +10 R f
       +(was implicitly imposing a structure \(an array of lines\) on the file.)11 2602 1 1865 1356 t
       +(Another of)1 444 1 970 1512 t
       +10 CW f
       +(sam)1448 1512 w
       +10 R f
       +( used an editor with a true)6 1097( had never before)3 721( I)1 92('s good things is its undo capability.)6 1502 4 1628 1512 t
       +( Undo)1 276(undo, but I would never go back now.)7 1545 2 720 1632 t
       +10 I f
       +(must)2570 1632 w
       +10 R f
       +( exam-)1 278( For)1 193(be done well, but if it is, it can be relied on.)11 1781 3 2788 1632 t
       +( not sure how to write some intricate command, because if you make a)13 2869(ple, it's safe to experiment if you're)6 1451 2 720 1752 t
       +( from writing)2 545( learned two things about undo)5 1270( I)1 90(mistake, it can be fixed simply and reliably.)7 1800 4 720 1872 t
       +10 CW f
       +(sam)4456 1872 w
       +10 R f
       +(: first, it's)2 404 1 4636 1872 t
       +( the system)2 458(easy to provide if you design it in from the beginning, and second, it's necessary, particularly if)16 3862 2 720 1992 t
       +(has some subtle properties that may be unfamiliar or error-prone for users.)11 2971 1 720 2112 t
       +10 CW f
       +(Sam)970 2268 w
       +10 R f
       +( avoids all fixed-size tables and data)6 1519( it)1 92( Because)1 393('s lack of internal limits and sizes is a virtue.)9 1886 4 1150 2268 t
       +(structures,)720 2388 w
       +10 CW f
       +(sam)1159 2388 w
       +10 R f
       +( More-)1 299( to make global changes to files that some of our other tools cannot even read.)15 3117(is able)1 259 3 1365 2388 t
       +( admit)1 255(over, the design keeps the performance linear when doing such operations, although I must)13 3648 2 720 2508 t
       +10 CW f
       +(sam)4650 2508 w
       +10 R f
       +(does)4857 2508 w
       +(get slow when editing a huge file.)6 1351 1 720 2628 t
       +( is poorly integrated into the surrounding)6 1680( the most obvious is that it)6 1093( Externally,)1 497(Now, the problems.)2 800 4 970 2784 t
       +( design, the user interface in)5 1135( By)1 169(window system.)1 653 3 720 2904 t
       +10 CW f
       +(sam)2704 2904 w
       +10 R f
       +(feels almost identical to that of)5 1245 1 2911 2904 t
       +10 CW f
       +(mux)4183 2904 w
       +10 R f
       +(, but a thick wall)4 677 1 4363 2904 t
       +(separates text in)2 655 1 720 3024 t
       +10 CW f
       +(sam)1406 3024 w
       +10 R f
       +(from the programs running in)4 1206 1 1617 3024 t
       +10 CW f
       +(mux)2854 3024 w
       +10 R f
       +( instance, the `snarf buffer' in)5 1220(. For)1 221 2 3034 3024 t
       +10 CW f
       +(sam)4507 3024 w
       +10 R f
       +(must be)1 321 1 4719 3024 t
       +( that in)2 284(maintained separately from)2 1100 2 720 3144 t
       +10 CW f
       +(mux)2132 3144 w
       +10 R f
       +( is regrettable, but probably necessary given the unusual con-)9 2472(. This)1 256 2 2312 3144 t
       +(figuration of the system, with a programmable terminal on the far end of an RS-232 link.)15 3554 1 720 3264 t
       +10 CW f
       +(Sam)970 3420 w
       +10 R f
       +( it was written over such a long time, and has)10 1846( But)1 199( it.)1 110(is reliable; otherwise, people wouldn't use)5 1707 4 1178 3420 t
       +( to clean up the code and remove)7 1323(so many new \(to me\) ideas in it, that I would like to see it done over again)17 2997 2 720 3540 t
       +( worst part is in the interconnection of the host)9 1898( The)1 209( problems in the implementation.)4 1341(many of the lingering)3 872 4 720 3660 t
       +( a redesign for a more conventional window sys-)8 1946(and terminal parts, which might even be able to go away in)11 2374 2 720 3780 t
       +( of the connec-)3 598( program must be split in two to use the terminal effectively, but the low bandwidth)15 3342(tem. The)1 380 3 720 3900 t
       +( design if performance is to be acceptable.)7 1712(tion forces the separation to occur in an inconvenient part of the)11 2608 2 720 4020 t
       +( procedure call protocol driven by the host, emitting only graphics commands, would be)13 3638(A simple remote)2 682 2 720 4140 t
       +( the other hand, if the terminal)6 1284( On)1 184(easy to write but wouldn't have nearly the necessary responsiveness.)9 2852 3 720 4260 t
       +( simpler file services from the host, regular expression searches would)10 2868(were in control and requested much)5 1452 2 720 4380 t
       +( A)1 131( would be unreasonably slow.)4 1226(require that the terminal read the entire file over its RS-232 link, which)12 2963 3 720 4500 t
       +( retrospect, the communications protocol)4 1658( In)1 139(compromise in which either end can take control is necessary.)9 2523 3 720 4620 t
       +( designed and verified formally, although I do not know of any tool that can adequately)15 3609(should have been)2 711 2 720 4740 t
       +(relate the protocol to its implementation.)5 1627 1 720 4860 t
       +(Not all of)2 385 1 970 5016 t
       +10 CW f
       +(sam)1382 5016 w
       +10 R f
       +( \(vener-)1 314( Some)1 280('s users are comfortable with its command language, and few are adept.)11 2884 3 1562 5016 t
       +( a sort of ``)4 471(able\) people use)2 664 2 720 5136 t
       +10 CW f
       +(ed)1855 5136 w
       +10 R f
       +(subset'' of)1 431 1 2007 5136 t
       +10 CW f
       +(sam)2470 5136 w
       +10 R f
       +('s command language, and even ask why)6 1680 1 2650 5136 t
       +10 CW f
       +(sam)4362 5136 w
       +10 R f
       +('s command)1 498 1 4542 5136 t
       +(language is not exactly)3 942 1 720 5256 t
       +10 CW f
       +(ed)1695 5256 w
       +10 R f
       +( course, is that)3 604( reason, of)2 434('s. \(The)1 343 3 1815 5256 t
       +10 CW f
       +(sam)3230 5256 w
       +10 R f
       +('s model for text does not include new-)7 1630 1 3410 5256 t
       +( central to)2 399(lines, which are)2 631 2 720 5376 t
       +10 CW f
       +(ed)1775 5376 w
       +10 R f
       +( the text an array of newlines to the command language would be too)13 2759(. Making)1 386 2 1895 5376 t
       +( editors, such as)3 647( Some)1 281( mouse.)1 314(much of a break from the seamless model provided by the)10 2338 4 720 5496 t
       +10 CW f
       +(vi)4328 5496 w
       +10 R f
       +(, are willing to)3 592 1 4448 5496 t
       +( difficulty is that)3 706( The)1 219( though.\))1 375(make this break,)2 687 4 720 5616 t
       +10 CW f
       +(sam)2746 5616 w
       +10 R f
       +('s syntax is so close to)5 967 1 2926 5616 t
       +10 CW f
       +(ed)3932 5616 w
       +10 R f
       +('s that people believe it)4 988 1 4052 5616 t
       +10 I f
       +(should)720 5736 w
       +10 R f
       +( in hindsight, that making)4 1050( thought, with some justification)4 1318( I)1 88(be the same.)2 506 4 1017 5736 t
       +10 CW f
       +(sam)4010 5736 w
       +10 R f
       +(similar to)1 387 1 4221 5736 t
       +10 CW f
       +(ed)4639 5736 w
       +10 R f
       +(would)4790 5736 w
       +( and raised the users' expectations too)6 1573( I may have overstepped)4 1015( But)1 205(make it easier to learn and to accept.)7 1527 4 720 5856 t
       +( hard to decide which way to resolve this problem.)9 2019(much. It's)1 430 2 720 5976 t
       +(Finally, there is a tradeoff in)5 1187 1 970 6132 t
       +10 CW f
       +(sam)2192 6132 w
       +10 R f
       +(that was decided by the environment in which it runs:)9 2235 1 2407 6132 t
       +10 CW f
       +(sam)4677 6132 w
       +10 R f
       +(is a)1 147 1 4893 6132 t
       +( The)1 220( system there might instead be multiple single-file editors.)8 2444(multi-file editor, although in a different)5 1656 3 720 6252 t
       +( choice)1 298( the)1 159( If)1 128(decision was made primarily because starting a new program in a Blit is time-consuming.)13 3735 4 720 6372 t
       +( still choose the multi-file architecture, because it allows groups of)10 2700(could be made freely, however, I would)6 1620 2 720 6492 t
       +( is delightful)2 518( It)1 117( a unit; the usefulness of the multi-file commands is incontrovertible.)10 2822(files to be handled as)4 863 4 720 6612 t
       +(to have the source to an entire program available at your fingertips.)11 2683 1 720 6732 t
       +cleartomark
       +showpage
       +saveobj restore
       +%%EndPage: 26 26
       +%%Page: 27 27
       +/saveobj save def
       +mark
       +27 pagesetup
       +10 R f
       +(- 27 -)2 216 1 2772 480 t
       +10 B f
       +(Acknowledgements)720 840 w
       +10 R f
       +(Tom Cargill suggested the idea behind the)6 1699 1 720 996 t
       +10 CW f
       +(Rasp)2445 996 w
       +10 R f
       +( Wilson and Ken Thompson influ-)5 1384( Norman)1 379(data structure.)1 566 3 2711 996 t
       +( improved by comments from Al Aho, Jon Bentley, Chris)9 2392( paper was)2 448( This)1 239(enced the command language.)3 1241 4 720 1116 t
       +(Fraser, Gerard Holzmann, Brian Kernighan, Ted Kowalski, Doug McIlroy and Dennis Ritchie.)11 3787 1 720 1236 t
       +9 B f
       +(REFERENCES)720 1476 w
       +8 R f
       +( Pike, `The Blit: a multiplexed graphics terminal,')7 1588(1. R.)1 184 2 760 1648 t
       +8 I f
       +(AT&T Bell Labs. Tech. J.,)4 835 1 2552 1648 t
       +8 B f
       +(63)3407 1648 w
       +8 R f
       +(, \(8\), 1607-1631 \(1984\).)3 770 1 3487 1648 t
       +( Johnson,)1 302(2. L.)1 179 2 760 1784 t
       +8 I f
       +(MacWrite,)1261 1784 w
       +8 R f
       +(Apple Computer Inc., Cupertino, Calif. 1983.)5 1453 1 1618 1784 t
       +( Lampson, `Bravo Manual,' in)4 969(3. B.)1 184 2 760 1920 t
       +8 I f
       +(Alto User's Handbook,)2 738 1 1933 1920 t
       +8 R f
       +( 1979.)1 220(pp. 31-62, Xerox Palo Alto Research Center, Palo Alto, Calif.)9 1975 2 2691 1920 t
       +( Teitelman, `A tour through Cedar,')5 1138(4. W.)1 205 2 760 2056 t
       +8 I f
       +(IEEE Software,)1 497 1 2123 2056 t
       +8 B f
       +(1)2640 2056 w
       +8 R f
       +(\(2\), 44-73 \(1984\).)2 570 1 2700 2056 t
       +( Gutknecht, `Concepts of the text editor Lara,')7 1473(5. J.)1 161 2 760 2192 t
       +8 I f
       +(Comm. ACM,)1 439 1 2414 2192 t
       +8 B f
       +(28)2873 2192 w
       +8 R f
       +(, \(9\), 942-960 \(1985\).)3 690 1 2953 2192 t
       +( Telephone Laboratories,)2 797(6. Bell)1 243 2 760 2328 t
       +8 I f
       +(UNIX Programmer's Manual,)2 965 1 1820 2328 t
       +8 R f
       +(Holt, Rinehart and Winston, New York 1983.)6 1456 1 2805 2328 t
       +( W. Kernighan and R. Pike,)5 882(7. B.)1 184 2 760 2464 t
       +8 I f
       +(The Unix Programming Environment,)3 1216 1 1846 2464 t
       +8 R f
       +(Prentice-Hall, Englewood Cliffs, New Jersey 1984.)5 1637 1 3082 2464 t
       +(8.)760 2600 w
       +8 I f
       +( Programmer's Manual, Research Version, Ninth Edition, Volume 1,)8 2215(Unix Time-Sharing System)2 857 2 870 2600 t
       +8 R f
       +(AT&T Bell Laboratories, Murray)3 1076 1 3964 2600 t
       +(Hill, New Jersey 1986.)3 733 1 870 2700 t
       +(9.)760 2836 w
       +8 I f
       +( Distribution, Volumes 1 and 2C,)5 1174(Unix Time-Sharing System Programmer's Manual, 4.1 Berkeley Software)7 2508 2 870 2836 t
       +8 R f
       +(University of)1 445 1 4595 2836 t
       +(California, Berkeley, Calif. 1981.)3 1068 1 870 2936 t
       +( Pike, `Structural Regular Expressions,')4 1264(10. R.)1 224 2 720 3072 t
       +8 I f
       +(Proc. EUUG Spring Conf., Helsinki 1987,)5 1357 1 2229 3072 t
       +8 R f
       +(Eur. Unix User's Group, Buntingford, Herts,)5 1433 1 3607 3072 t
       +(UK 1987.)1 316 1 870 3172 t
       +( Goldberg,)1 341(11. A.)1 228 2 720 3308 t
       +8 I f
       +(Smalltalk-80 \261 The Interactive Programming Environment,)5 1891 1 1309 3308 t
       +8 R f
       +(Addison-Wesley, Reading, Mass. 1984.)3 1269 1 3220 3308 t
       +( Thompson, `Regular expression search algorithm,')5 1637(12. K.)1 228 2 720 3444 t
       +8 I f
       +(Comm. ACM,)1 439 1 2605 3444 t
       +8 B f
       +(11)3064 3444 w
       +8 R f
       +(, \(6\), 419-422 \(1968\).)3 690 1 3144 3444 t
       +( V. Aho, J. E. Hopcroft and J. D. Ullman,)9 1344(13. A.)1 228 2 720 3580 t
       +8 I f
       +( of Computer Algorithms,)3 826(The Design and Analysis)3 802 2 2314 3580 t
       +8 R f
       +(Addison-Wesley, Reading, Mass.)2 1075 1 3965 3580 t
       +(1974.)870 3680 w
       +( W. Kernighan and D. M. Ritchie,)6 1085(14. B.)1 224 2 720 3816 t
       +8 I f
       +(The C Programming Language,)3 1023 1 2049 3816 t
       +8 R f
       +(Prentice-Hall, Englewood Cliffs, New Jersey 1978.)5 1637 1 3092 3816 t
       +( M. Waite, `The cost of lexical analysis,')7 1297(15. W.)1 245 2 720 3952 t
       +8 I f
       +(Softw. Pract. Exp.,)2 599 1 2282 3952 t
       +8 B f
       +(16)2901 3952 w
       +8 R f
       +(, \(5\), 473-488 \(1986\).)3 690 1 2981 3952 t
       +( W. Fraser, `A generalized text editor,')6 1232(16. C.)1 224 2 720 4088 t
       +8 I f
       +(Comm. ACM,)1 439 1 2196 4088 t
       +8 B f
       +(23)2655 4088 w
       +8 R f
       +(, \(3\), 154-158 \(1980\).)3 690 1 2735 4088 t
       +( Pike, `Graphics in overlapping bitmap layers,')6 1493(17. R.)1 224 2 720 4224 t
       +8 I f
       +(ACM Trans. on Graph.,)3 765 1 2457 4224 t
       +8 B f
       +(2)3242 4224 w
       +8 R f
       +(, \(2\) 135-160 \(1983\).)3 670 1 3282 4224 t
       +( J. Guibas and J. Stolfi, `A language for bitmap manipulation,')10 1990(18. L.)1 219 2 720 4360 t
       +8 I f
       +(ACM Trans. on Graph.,)3 765 1 2949 4360 t
       +8 B f
       +(1)3734 4360 w
       +8 R f
       +(, \(3\), 191-214 \(1982\).)3 690 1 3774 4360 t
       +( Pike, B. Locanthi and J. Reiser, `Hardware/software trade-offs for bitmap graphics on the Blit,')14 3180(19. R.)1 224 2 720 4496 t
       +8 I f
       +(Softw. Pract. Exp.,)2 617 1 4153 4496 t
       +8 B f
       +(15)4799 4496 w
       +8 R f
       +(, \(2\),)1 161 1 4879 4496 t
       +(131-151 \(1985\).)1 518 1 870 4596 t
       +( A. Cargill, `The feel of Pi,')6 886(20. T.)1 219 2 720 4732 t
       +8 I f
       +(Winter USENIX Conference Proceedings, Denver 1986,)5 1791 1 1845 4732 t
       +8 R f
       +(62-71, USENIX Assoc., El Cerrito, CA.)5 1283 1 3656 4732 t
       +( J. Holzmann, `Tracing protocols,')4 1098(21. G.)1 228 2 720 4868 t
       +8 I f
       +(AT&T Tech. J.,)2 491 1 2066 4868 t
       +8 B f
       +(64)2577 4868 w
       +8 R f
       +(, \(10\), 2413-2434 \(1985\).)3 810 1 2657 4868 t
       +cleartomark
       +showpage
       +saveobj restore
       +%%EndPage: 27 27
       +%%Trailer
       +done
       +%%Pages: 27
       +%%DocumentFonts: Courier Times-Bold Times-Italic Times-Roman Times-Roman Symbol
 (DIR) diff --git a/doc/sam.tut.ms b/doc/sam.tut.ms
       @@ -0,0 +1,1776 @@
       +.de P1
       +.KS
       +.DS
       +.ft CW
       +.ta 5n 10n 15n 20n 25n 30n 35n 40n 45n 50n 55n 60n 65n 70n 75n 80n
       +..
       +.de P2
       +.ft 1
       +.DE
       +.KE
       +..
       +.de CW
       +.lg 0
       +\%\&\\$3\fB\\$1\fP\&\\$2
       +.lg
       +..
       +.de WC
       +.lg 0
       +\%\&\\$3\fI\\$1\fP\&\\$2
       +.lg
       +..
       +.TL
       +A tutorial for the
       +.CW sam
       +.B
       +command language
       +.AU
       +Rob Pike
       +.AI
       +.MH
       +.AB
       +.CW sam
       +is an interactive text editor with a command language that makes heavy use
       +of regular expressions.
       +Although the language is syntactically similar to
       +.CW ed (1),
       +the details are interestingly different.
       +This tutorial introduces the command language, but does not discuss
       +the screen and mouse interface.
       +With apologies to those unfamiliar with the Ninth Edition Blit software,
       +it is assumed that the similarity of
       +.CW sam
       +to
       +.CW mux (9)
       +at this level makes
       +.CW sam 's
       +mouse language easy to learn.
       +.PP
       +The
       +.CW sam
       +command language applies identically to two environments:
       +when running
       +.CW sam
       +on an ordinary terminal
       +(\f2via\f1\f1
       +.CW sam\ -d ),
       +and in the command window of a
       +.I downloaded
       +.CW sam ,
       +that is, one using the bitmap display and mouse.
       +.AE
       +.SH
       +Introduction
       +.PP
       +This tutorial describes the command language of
       +.CW sam ,
       +an interactive text editor that runs on Blits and
       +some computers with bitmap displays.
       +For most editing tasks, the mouse-based editing features
       +are sufficient, and they are easy to use and to learn.
       +.PP
       +The command language is often useful, however, particularly
       +when making global changes.
       +Unlike the commands in
       +.CW ed ,
       +which are necessary to make changes,
       +.CW sam
       +commands tend to be used
       +only for complicated or repetitive editing tasks.
       +It is in these more involved uses that
       +the differences between
       +.CW sam
       +and other text editors are most evident.
       +.PP
       +.CW sam 's
       +language makes it easy to do some things that other editors,
       +including programs like
       +.CW sed
       +and
       +.CW awk ,
       +do not handle gracefully, so this tutorial serves partly as a
       +lesson in
       +.CW sam 's
       +manner of manipulating text.
       +The examples below therefore concentrate entirely on the language,
       +assuming that facility with the use of the mouse in
       +.CW sam
       +is at worst easy to pick up.
       +In fact,
       +.CW sam
       +can be run without the mouse at all (not
       +.I downloaded ),
       +by specifying the
       +.CW -d
       +flag, and it is this domain that the tutorial
       +occupies; the command language in these modes
       +are identical.
       +.PP
       +A word to the Unix adept:
       +although
       +.CW sam
       +is syntactically very similar to
       +.CW ed ,
       +it is fundamentally and deliberately different in design and detailed semantics.
       +You might use knowledge of
       +.CW ed
       +to predict how the substitute command works,
       +but you'd only be right if you had used some understanding of
       +.CW sam 's
       +workings to influence your prediction.
       +Be particularly careful about idioms.
       +Idioms form in curious nooks of languages and depend on
       +undependable peculiarities.
       +.CW ed
       +idioms simply don't work in
       +.CW sam :
       +.CW 1,$s/a/b/
       +makes one substitution in the whole file, not one per line.
       +.CW sam
       +has its own idioms.
       +Much of the purpose of this tutorial is to publish them
       +and make fluency in
       +.CW sam
       +a matter of learning, not cunning.
       +.PP
       +The tutorial depends on familiarity with regular expressions, although
       +some experience with a more traditional Unix editor may be helpful.
       +To aid readers familiar with
       +.CW ed ,
       +I have pointed out in square brackets [] some of
       +the relevant differences between
       +.CW ed
       +and
       +.CW sam .
       +Read these comments only if you wish
       +to understand the differences; the lesson is about
       +.CW sam ,
       +not
       +.CW sam
       +.I vs.
       +.CW ed .
       +Another typographic convention is that output appears in
       +.CW "this font,
       +while typed input appears as
       +.WC "slanty text.
       +.PP
       +Nomenclature:
       +.CW sam
       +keeps a copy of the text it is editing.
       +This copy is called a
       +.I file .
       +To avoid confusion, I have called the permanent storage on disc a
       +.I
       +Unix file.
       +.R
       +.SH
       +Text
       +.PP
       +To get started, we need some text to play with.
       +Any text will do; try something from
       +James Gosling's Emacs manual:
       +.P1
       +$ \fIsam -d
       +a
       +This manual is organized in a rather haphazard manner.  The first
       +several sections were written hastily in an attempt to provide a
       +general introduction to the commands in Emacs and to try to show
       +the method in the madness that is the Emacs command structure.
       +\&.
       +.ft
       +.P2
       +.WC "sam -d
       +starts
       +.CW sam
       +running.
       +The
       +.CW a
       +command adds text until a line containing just a period, and sets the
       +.I
       +current text
       +.R
       +(also called
       +.I dot )
       +to what was typed \(em everything between the
       +.CW a
       +and the period.
       +.CW ed "" [
       +would leave dot set to only the last line.]
       +The
       +.CW p
       +command prints the current text:
       +.P1
       +.WC p
       +This manual is organized in a rather haphazard manner.  The first
       +several sections were written hastily in an attempt to provide a
       +general introduction to the commands in Emacs and to try to show
       +the method in the madness that is the Emacs command structure.
       +.P2
       +[Again,
       +.CW ed
       +would print only the last line.]
       +The
       +.CW a
       +command adds its text
       +.I after
       +dot; the
       +.CW i
       +command is like
       +.CW a,
       +but adds the text
       +.I before
       +dot.
       +.P1
       +.ft I
       +i
       +Introduction
       +\&.
       +p
       +.ft
       +Introduction
       +.P2
       +There is also a
       +.CW c
       +command that changes (replaces) the current text,
       +and
       +.CW d
       +that deletes it; these are illustrated below.
       +.PP
       +To see all the text, we can specify what text to print;
       +for the moment, suffice it to say that
       +.WC 0,$
       +specifies the entire file.
       +.CW ed "" [
       +users would probably type
       +.WC 1,$ ,
       +which in practice is the same thing, but see below.]
       +.P1
       +.WC 0,$p
       +Introduction
       +This manual is organized in a rather haphazard manner.  The first
       +several sections were written hastily in an attempt to provide a
       +general introduction to the commands in Emacs and to try to show
       +the method in the madness that is the Emacs command structure.
       +.P2
       +Except for the
       +.CW w
       +command described below,
       +.I all
       +commands,
       +including
       +.CW p ,
       +set dot to the text they touch.
       +Thus,
       +.CW a
       +and
       +.CW i
       +set dot to the new text,
       +.CW p
       +to the text printed, and so on.
       +Similarly, all commands
       +(except
       +.CW w )
       +by default operate on the current
       +text [unlike
       +.CW ed ,
       +for which some commands (such as
       +.CW g )
       +default to the entire file].
       +.PP
       +Things are not going to get very interesting until we can
       +set dot arbitrarily.
       +This is done by
       +.I addresses ,
       +which specify a piece of the file.
       +The address
       +.CW 1 ,
       +for example, sets dot to the first line of the file.
       +.P1
       +.WC 1p
       +Introduction
       +.WC c
       +.WC Preamble
       +.WC .
       +.P2
       +The
       +.CW c
       +command didn't need to specify dot; the
       +.CW p
       +left it on line one.
       +It's therefore easy to delete the first line utterly;
       +the last command left dot set to line one:
       +.P1
       +.WC d
       +.WC 1p
       +This manual is organized in a rather haphazard manner.  The first
       +.P2
       +(Line numbers change
       +to reflect changes to the file.)
       +.PP
       +The address \f(CW/\f2text\f(CW/\f1
       +sets dot to the first appearance of
       +.I text ,
       +after dot.
       +.CW ed "" [
       +matches the first line containing
       +.I text .]
       +If
       +.I text
       +is not found, the search restarts at the beginning of the file
       +and continues until dot.
       +.P1
       +.WC /Emacs/p
       +Emacs
       +.P2
       +It's difficult to indicate typographically, but in this example no newline appears
       +after
       +.CW Emacs :
       +the text to be printed is the string
       +.CW Emacs ', `
       +exactly.
       +(The final
       +.CW p
       +may be left off \(em it is the default command.
       +When downloaded, however, the default is instead to select the text,
       +to highlight it,
       +and to make it visible by moving the window on the file if necessary.
       +Thus,
       +.CW /Emacs/
       +indicates on the display the next occurrence of the text.)
       +.PP
       +Imagine we wanted to change the word
       +.CW haphazard
       +to
       +.CW thoughtless .
       +Obviously, what's needed is another
       +.CW c
       +command, but the method used so far to insert text includes a newline.
       +The syntax for including text without newlines is to surround the
       +text with slashes (which is the same as the syntax for
       +text searches, but what is going on should be clear from context).
       +The text must appear immediately after the
       +.CW c
       +(or
       +.CW a
       +or
       +.CW i ).
       +Given this, it is easy to make the required change:
       +.P1
       +.WC /haphazard/c/thoughtless/
       +1p
       +This manual is organized in a rather thoughtless manner.  The first
       +.P2
       +[Changes can always be done with a
       +.CW c
       +command, even if the text is smaller than a line].
       +You'll find that this way of providing text to commands is much
       +more common than is the multiple-lines syntax.
       +If you want to include a slash
       +.CW /
       +in the text, just precede it with a backslash
       +.CW \e ,
       +and use a backslash to protect a backslash itself.
       +.P1
       +.WC /Emacs/c/Emacs\e\e360/
       +.WC 4p
       +general introduction to the commands in Emacs\e360 and to try to show
       +.P2
       +We could also make this particular change by
       +.P1
       +.WC /Emacs/a/\e\e360/
       +.P2
       +.PP
       +This is as good a place as any to introduce the
       +.CW u
       +command, which undoes the last command.
       +A second
       +.CW u
       +will undo the penultimate command, and so on.
       +.P1
       +.WC u
       +.WC 4p
       +general introduction to the commands in Emacs and to try to show
       +.WC u
       +.WC 3p
       +This manual is organized in a rather haphazard manner.  The first
       +.P2
       +Undoing can only back up; there is no way to undo a previous
       +.CW u .
       +.SH
       +Addresses
       +.PP
       +We've seen the simplest forms of addresses, but there is more
       +to learn before we can get too much further.
       +An address selects a region in the file \(em a substring \(em
       +and therefore must define the beginning and the end of a region.
       +Thus, the address
       +.CW 13
       +selects from the beginning of line thirteen to the end of line thirteen, and
       +.CW /Emacs/
       +selects from the beginning of the word
       +.CW Emacs ' `
       +to the end.
       +.PP
       +Addresses may be combined with a comma:
       +.P1
       +13,15
       +.P2
       +selects lines thirteen through fifteen.  The definition of the comma
       +operator is to select from the beginning of the left hand address (the
       +beginning of line 13) to the end of the right hand address (the
       +end of line 15).
       +.PP
       +A few special simple addresses come in handy:
       +.CW .
       +(a period) represents dot, the current text,
       +.CW 0
       +(line zero) selects the null string at the beginning of the file, and
       +.CW $
       +selects the null string at the end of the file
       +[not the last line of the file].
       +Therefore,
       +.P1
       +0,13
       +.P2
       +selects from the beginning of the file to the end of line thirteen,
       +.P1
       +\&.,$
       +.P2
       +selects from the beginning of the current text to the end of the file, and
       +.P1
       +0,$
       +.P2
       +selects the whole file [that is, a single string containing the whole file,
       +not a list of all the lines in the file].
       +.PP
       +These are all
       +.I absolute
       +addresses: they refer to specific places in the file.
       +.CW sam
       +also has relative addresses, which depend
       +on the value of dot,
       +and in fact we have already seen one form:
       +.CW /Emacs/
       +finds the first occurrence of
       +.CW Emacs
       +searching forwards from dot.
       +Which occurrence of
       +.CW Emacs
       +it finds depends on the value of dot.
       +What if you wanted the first occurrence
       +.CW before
       +dot?  Just precede the pattern with a minus sign, which reverses the direction
       +of the search:
       +.P1
       +-/Emacs/
       +.P2
       +In fact, the complete syntax for forward searching is
       +.P1
       ++/Emacs/
       +.P2
       +but the plus sign is the default, and in practice is rarely used.
       +Here is an example that includes it for clarity:
       +.P1
       +0+/Emacs/
       +.P2
       +selects the first occurrence of
       +.CW Emacs
       +in the file; read it as ``go to line 0, then search forwards for
       +.CW Emacs .''
       +Since the
       +.CW +
       +is optional, this can be written
       +.CW 0/Emacs/ .
       +Similarly,
       +.P1
       +$-/Emacs/
       +.P2
       +finds the last occurrence in the file, so
       +.P1
       +0/Emacs/,$-/Emacs/
       +.P2
       +selects the text from the first to last
       +.CW Emacs ,
       +inclusive.
       +Slightly more interesting:
       +.P1
       +/Emacs/+/Emacs/
       +.P2
       +(there is an implicit
       +.CW .+
       +at the beginning) selects the second
       +.CW Emacs
       +following dot.
       +.PP
       +Line numbers may also be relative.
       +.P1
       +-2
       +.P2
       +selects the second previous line, and
       +.P1
       ++5
       +.P2
       +selects the fifth following line (here the plus sign is obligatory).
       +.PP
       +Since addresses may select (and dot may be) more than one line,
       +we need a definition of `previous' and `following:'
       +`previous' means
       +.I
       +before the beginning
       +.R
       +of dot, and `following'
       +means
       +.I
       +after the end
       +.R
       +of dot.
       +For example, if the file contains \fBA\fIAA\fBA\f1,
       +with dot set to the middle two
       +.CW A 's
       +(the slanting characters),
       +.CW -/A/
       +sets dot to the first
       +.CW A ,
       +and
       +.CW +/A/
       +sets dot to the last
       +.CW A .
       +Except under odd circumstances (such as when the only occurrence of the
       +text in the file is already the current text), the text selected by a
       +search will be disjoint from dot.
       +.PP
       +To select the
       +.CW "troff -ms
       +paragraph containing dot, however long it is, use
       +.P1
       +-/.PP/,/.PP/-1
       +.P2
       +which will include the
       +.CW .PP
       +that begins the paragraph, and exclude the one that ends it.
       +.PP
       +When typing relative line number addresses, the default number is
       +.CW 1 ,
       +so the above could be written slightly more simply:
       +.P1
       +-/.PP/,/.PP/-
       +.P2
       +.PP
       +What does the address
       +.CW +1-1
       +or the equivalent
       +.CW +-
       +mean?  It looks like it does nothing, but recall that dot need not be a
       +complete line of text.
       +.CW +1
       +selects the line after the end of the current text, and
       +.CW -1
       +selects the line before the beginning.  Therefore
       +.CW +1-1
       +selects the line before the line after the end of dot, that is,
       +the complete line containing the end of dot.
       +We can use this construction to expand a selection to include a complete line,
       +say the first line in the file containing
       +.CW Emacs :
       +.P1
       +.WC 0/Emacs/+-p
       +general introduction to the commands in Emacs and to try to show
       +.P2
       +The address
       +.CW +-
       +is an idiom.
       +.SH
       +Loops
       +.PP
       +Above, we changed one occurrence of
       +.CW Emacs
       +to
       +.CW Emacs\e360 ,
       +but if the name of the editor is really changing, it would be useful
       +to change
       +.I all
       +instances of the name in a single command.
       +.CW sam
       +provides a command,
       +.CW x
       +(extract), for just that job.
       +The syntax is
       +\f(CWx/\f2pattern\f(CW/\f2command\f1.
       +For each occurrence of the pattern in the selected text,
       +.CW x
       +sets dot to the occurrence and runs command.
       +For example, to change
       +.CW Emacs
       +to
       +.CW vi,
       +.P1
       +.WC 0,$x/Emacs/c/vi/
       +.WC 0,$p
       +This manual is organized in a rather haphazard manner.  The first
       +several sections were written hastily in an attempt to provide a
       +general introduction to the commands in vi and to try to show
       +the method in the madness that is the vi command structure.
       +.P2
       +This
       +works by subdividing the current text
       +.CW 0,$ "" (
       +\(em the whole file) into appearances of its textual argument
       +.CW Emacs ), (
       +and then running the command that follows
       +.CW c/vi/ ) (
       +with dot set to the text.
       +We can read this example as, ``find all occurrences of
       +.CW Emacs
       +in the file, and for each one,
       +set the current text to the occurrence and run the command
       +.CW c/vi/ ,
       +which will replace the current text by
       +.CW vi. ''
       +[This command is somewhat similar to
       +.CW ed 's
       +.CW g
       +command.  The differences will develop below, but note that the
       +default address, as always, is dot rather than the whole file.]
       +.PP
       +A single
       +.CW u
       +command is sufficient to undo an
       +.CW x
       +command, regardless of how many individual changes the
       +.CW x
       +makes.
       +.P1
       +.WC u
       +.WC 0,$p
       +This manual is organized in a rather haphazard manner.  The first
       +several sections were written hastily in an attempt to provide a
       +general introduction to the commands in Emacs and to try to show
       +the method in the madness that is the Emacs command structure.
       +.P2
       +.PP
       +Of course,
       +.CW c
       +is not the only command
       +.CW x
       +can run.  An
       +.CW a
       +command can be used to put proprietary markings on
       +.CW Emacs :
       +.P1
       +.WC 0,$x/Emacs/a/{TM}/
       +.WC /Emacs/+-p
       +general introduction to the commands in Emacs{TM} and to try to show
       +.P2
       +[There is no way to see the changes as they happen, as in
       +.CW ed 's
       +.CW g/Emacs/s//&{TM}/p ;
       +see the section on Multiple Changes, below.]
       +.PP
       +The
       +.CW p
       +command is also useful when driven by an
       +.CW x ,
       +but be careful that you say what you mean;
       +.P1
       +.WC 0,$x/Emacs/p
       +EmacsEmacs
       +.P2
       +since
       +.CW x
       +sets dot to the text in the slashes, printing only that text
       +is not going to be very
       +informative.  But the command that
       +.CW x
       +runs can contain addresses.  For example, if we want to print all
       +lines containing
       +.CW Emacs ,
       +just use
       +.CW +- :
       +.P1
       +.WC 0,$x/Emacs/+-p
       +general introduction to the commands in Emacs{TM} and to try to show
       +the method in the madness that is the Emacs{TM} command structure.
       +.P2
       +Finally, let's restore the state of the file with another
       +.CW x
       +command, and make use of a handy shorthand:
       +a comma in an address has its left side default to
       +.CW 0 ,
       +and its right side default to
       +.CW $ ,
       +so the easy-to-type address
       +.CW ,
       +refers to the whole file:
       +.P1
       +.WC ",x/Emacs/ /{TM}/d
       +.WC ,p
       +This manual is organized in a rather haphazard manner.  The first
       +several sections were written hastily in an attempt to provide a
       +general introduction to the commands in Emacs and to try to show
       +the method in the madness that is the Emacs command structure.
       +.P2
       +Notice what this
       +.CW x
       +does: for each occurrence of Emacs,
       +find the
       +.CW {TM}
       +that follows, and delete it.
       +.PP
       +The `text'
       +.CW sam
       +accepts
       +for searches in addresses and in
       +.CW x
       +commands is not simple text, but rather
       +.I regular\ expressions.
       +Unix has several distinct interpretations of regular expressions.
       +The form used by
       +.CW sam
       +is that of
       +.CW regexp (6),
       +including parentheses
       +.CW ()
       +for grouping and an `or' operator
       +.CW |
       +for matching strings in parallel.
       +.CW sam
       +also matches the character sequence
       +.CW \en
       +with a newline character.
       +Replacement text, such as used in the
       +.CW a
       +and
       +.CW c
       +commands, is still plain text, but the sequence
       +.CW \en
       +represents newline in that context, too.
       +.PP
       +Here is an example.  Say we wanted to double space the document, that is,
       +turn every newline into two newlines.
       +The following all do the job:
       +.P1
       +.WC ",x/\en/ a/\en/
       +.WC ",x/\en/ c/\en\en/
       +.WC ",x/$/ a/\en/
       +.WC ",x/^/ i/\en/
       +.P2
       +The last example is slightly different, because it puts a newline
       +.I before
       +each line; the other examples place it after.
       +The first two examples manipulate newlines directly
       +[something outside
       +.CW ed 's
       +ken]; the last two
       +use regular expressions:
       +.CW $
       +is the empty string at the end of a line, while
       +.CW ^
       +is the empty string at the beginning.
       +.PP
       +These solutions all have a possible drawback: if there is already a blank line
       +(that is, two consecutive newlines), they make it much larger (four
       +consecutive newlines).
       +A better method is to extend every group of newlines by one:
       +.P1
       +.WC ",x/\en+/ a/\en/
       +.P2
       +The regular expression operator
       +.CW +
       +means `one or more;'
       +.CW \en+
       +is identical to
       +.CW \en\en* .
       +Thus, this example
       +takes every sequence of newlines and adds another
       +to the end.
       +.PP
       +A more common example is indenting a block of text by a tab stop.
       +The following all work,
       +although the first is arguably the cleanest (the blank text in slashes is a tab):
       +.P1
       +.WC ",x/^/a/         /
       +.WC ",x/^/c/         /
       +.WC ",x/.*\en/i/         /
       +.P2
       +The last example uses the pattern (idiom, really)
       +.CW .*\en
       +to match lines:
       +.CW .*
       +matches the longest possible string of non-newline characters.
       +Taking initial tabs away is just as easy:
       +.P1
       +.WC ",x/^    /d
       +.P2
       +In these examples I have specified an address (the whole file), but
       +in practice commands like these are more likely to be run without
       +an address, using the value of dot set by selecting text with the mouse.
       +.SH
       +Conditionals
       +.PP
       +The
       +.CW x
       +command is a looping construct:
       +for each match of a regular expression,
       +it extracts (sets dot to) the match and runs a command.
       +.CW sam
       +also has a conditional,
       +.CW g :
       +\f(CWg/\f2pattern\f(CW/\f2command\f1
       +runs the command if dot contains a match of the pattern
       +.I
       +without changing the value of dot.
       +.R
       +The inverse,
       +.CW v ,
       +runs the command if dot does
       +.I not
       +contain a match of the pattern.
       +(The letters
       +.CW g
       +and
       +.CW v
       +are historical and have no mnemonic significance.  You might
       +think of
       +.CW g
       +as `guard.')
       +.CW ed "" [
       +users should read the above definitions very carefully; the
       +.CW g
       +command in
       +.CW sam
       +is fundamentally different from that in
       +.CW ed .]
       +Here is an example of the difference between
       +.CW x
       +and
       +.CW g:
       +.P1
       +,x/Emacs/c/vi/
       +.P2
       +changes each occurrence of the word
       +.CW Emacs
       +in the file to the word
       +.CW vi ,
       +but
       +.P1
       +,g/Emacs/c/vi/
       +.P2
       +changes the
       +.I "whole file
       +to
       +.CW vi
       +if there is the word
       +.CW Emacs
       +anywhere in the file.
       +.PP
       +Neither of these commands is particularly interesting in isolation,
       +but they are valuable when combined with
       +.CW x
       +and with themselves.
       +.SH
       +Composition
       +.PP
       +One way to think about the
       +.CW x
       +command is that, given a selection (a value of dot)
       +it iterates through interesting subselections (values of dot within).
       +In other words, it takes a piece of text and cuts it into smaller pieces.
       +But the text that it cuts up may already be a piece cut by a previous
       +.CW x
       +command or selected by a
       +.CW g .
       +.CW sam 's
       +most interesting property is the ability to define a sequence of commands
       +to perform a particular task.\(dg
       +.FS
       +\(dg
       +The obvious analogy with shell pipelines is only partially valid,
       +because the individual
       +.CW sam
       +commands are all working on the same text; it is only how the text is
       +sliced up that is changing.
       +.FE
       +A simple example is to change all occurrences of
       +.CW Emacs
       +to
       +.CW emacs ;
       +certainly the command
       +.P1
       +.WC ",x/Emacs/ c/emacs/
       +.P2
       +will work, but we can use an
       +.CW x
       +command to save retyping most of the word
       +.CW Emacs :
       +.P1
       +.WC ",x/Emacs/ x/E/ c/e/
       +.P2
       +(Blanks can be used
       +to separate commands on a line to make them easier to read.)
       +What this command does is find all occurrences of
       +.CW Emacs
       +.CW ,x/Emacs/ ), (
       +and then
       +.I
       +with dot set to that text,
       +.R
       +find all occurrences of the letter
       +.CW E
       +.CW x/E/ ), (
       +and then
       +.I
       +with dot set to that text,
       +.R
       +run the command
       +.CW c/e/
       +to change the character to lower case.
       +Note that the address for the command \(em the whole file, specified by a comma
       +\(em is only given to the leftmost
       +piece of the command; the rest of the pieces have dot set for them by
       +the execution of the pieces to their left.
       +.PP
       +As another simple example, consider a problem
       +solved above: printing all lines in the file containing the word
       +.CW Emacs:
       +.P1
       +.WC ",x/.*\en/ g/Emacs/p
       +general introduction to the commands in Emacs and to try to show
       +the method in the madness that is the Emacs command structure.
       +.P2
       +This command says to break the file into lines
       +.CW ,x/.*\en/ ), (
       +and for each line that contains the string
       +.CW Emacs
       +.CW g/Emacs/ ), (
       +run the command
       +.CW p
       +with dot set to the line (not the match of
       +.CW Emacs ),
       +which prints the line.
       +To save typing, because
       +.CW .*\en
       +is a common pattern in
       +.CW x
       +commands,
       +if the
       +.CW x
       +is followed immediately by a space, the pattern
       +.CW .*\en
       +is assumed.
       +Therefore, the above could be written more succinctly:
       +.P1
       +.WC ",x g/Emacs/p
       +.P2
       +The solution we used before was
       +.P1
       +.WC ,x/Emacs/+-p
       +.P2
       +which runs the command
       +.CW +-p
       +with dot set to each match of
       +.CW Emacs
       +in the file (recall that the idiom
       +.CW +-p
       +prints the line containing the end of dot).
       +.PP
       +The two commands usually produce the same result
       +(the
       +.CW +-p
       +form will print a line twice if it contains
       +.CW Emacs
       +twice).  Which is better?
       +.CW ,x/Emacs/+-p
       +is easier to type and will be much faster if the file is large and
       +there are few occurrences of the string, but it is really an odd special case.
       +.CW ",x/.*\en/ g/Emacs/p
       +is slower \(em it breaks each line out separately, then examines
       +it for a match \(em but is conceptually cleaner, and generalizes more easily.
       +For example, consider the following piece of the Emacs manual:
       +.P1
       +command name="append-to-file", key="[unbound]"
       +Takes the contents of the current buffer and appends it to the
       +named file. If the file doesn't exist, it will be created.
       +
       +command name="apropos", key="ESC-?"
       +Prompts for a keyword and then prints a list of those commands
       +whose short description contains that keyword.  For example,
       +if you forget which commands deal with windows, just type
       +"@b[ESC-?]@t[window]@b[ESC]".
       +
       +\&\f2and so on\f(CW
       +.P2
       +This text consists of groups of non-empty lines, with a simple format
       +for the text within each group.
       +Imagine that we wanted to find the description of the `apropos'
       +command.
       +The problem is to break the file into individual descriptions,
       +and then to find the description of `apropos' and to print it.
       +The solution is straightforward:
       +.P1
       +.WC ,x/(.+\en)+/\ g/command\ name="apropos"/p
       +command name="apropos", key="ESC-?"
       +Prompts for a keyword and then prints a list of those commands
       +whose short description contains that keyword.  For example,
       +if you forget which commands deal with windows, just type
       +"@b[ESC-?]@t[window]@b[ESC]".
       +.P2
       +The regular expression
       +.CW (.+\en)+
       +matches one or more lines with one or more characters each, that is,
       +the text between blank lines, so
       +.CW ,x/(.+\en)+/
       +extracts each description; then
       +.CW g/command\ name="apropos"/
       +selects the description for `apropos' and
       +.CW p
       +prints it.
       +.PP
       +Imagine that we had a C program containing the variable
       +.CW n ,
       +but we wanted to change it to
       +.CW num .
       +This command is a first cut:
       +.P1
       +.WC ",x/n/ c/num/
       +.P2
       +but is obviously flawed: it will change all
       +.CW n 's
       +in the file, not just the
       +.I identifier
       +.CW n .
       +A better solution is to use an
       +.CW x
       +command to extract the identifiers, and then use
       +.CW g
       +to find the
       +.CW n 's:
       +.P1
       +.WC ",x/[a-zA-Z_][a-zA-Z_0-9]*/ g/n/ v/../ c/num/
       +.P2
       +It looks awful, but it's fairly easy to understand when read
       +left to right.
       +A C identifier is an alphabetic or underscore followed by zero or more
       +alphanumerics or underscores, that is, matches of the regular expression
       +.CW [a-zA-Z_][a-zA-Z_0-9]* .
       +The
       +.CW g
       +command selects those identifiers containing
       +.CW n ,
       +and the
       +.CW v
       +is a trick: it rejects those identifiers containing more than one
       +character.  Hence the
       +.CW c/num/
       +applies only to free-standing
       +.CW n 's.
       +.PP
       +There is still a problem here:
       +we don't want to change
       +.CW n 's
       +that are part of the character constant
       +.CW \en .
       +There is a command
       +.CW y ,
       +complementary to
       +.CW x ,
       +that is just what we need:
       +\f(CWy/\f2pattern\f(CW/\f2command\f1
       +runs the command on the pieces of text
       +.I between
       +matches of the pattern;
       +if
       +.CW x
       +selects,
       +.CW y
       +rejects.
       +Here is the final command:
       +.P1
       +.WC ",y/\e\en/ x/[a-zA-Z_][a-zA-Z_0-9]*/ g/n/ v/../ c/num/
       +.P2
       +The
       +.CW y/\e\en/
       +(with backslash doubled to make it a literal character)
       +removes the two-character sequence
       +.CW \en
       +from consideration, so the rest of the command will not touch it.
       +There is more we could do here; for example, another
       +.CW y
       +could be prefixed to protect comments in the code.
       +I won't elaborate the example any further, but you should have
       +an idea of the way in which the looping and conditional commands
       +in
       +.CW sam
       +may be composed to do interesting things.
       +.SH
       +Grouping
       +.PP
       +There is another way to arrange commands.
       +By enclosing them in brace brackets
       +.CW {} ,
       +commands may be applied in parallel.
       +This example uses the
       +.CW =
       +command, which reports the line and character numbers of dot,
       +together with
       +.CW p ,
       +to report on appearances of
       +.CW Emacs
       +in our original file:
       +.P1
       +.WC ,p
       +This manual is organized in a rather haphazard manner.  The first
       +several sections were written hastily in an attempt to provide a
       +general introduction to the commands in Emacs and to try to show
       +the method in the madness that is the Emacs command structure.
       +.ft I
       +,x/Emacs/{
       +        =
       +        +-p
       +}
       +.ft
       +3; #171,#176
       +general introduction to the commands in Emacs and to try to show
       +4; #234,#239
       +the method in the madness that is the Emacs command structure.
       +.P2
       +(The number before the semicolon is the line number;
       +the numbers beginning with
       +.CW #
       +are character numbers.)
       +As a more interesting example, consider changing all occurrences of
       +.CW Emacs
       +to
       +.CW vi
       +and vice versa.  We can type
       +.P1
       +.ft I
       +,x/Emacs|vi/{
       +        g/Emacs/ c/vi/
       +        g/vi/ c/Emacs/
       +}
       +.ft
       +.P2
       +or even
       +.P1
       +.ft I
       +,x/[a-zA-Z]+/{
       +        g/Emacs/ v/....../ c/vi/
       +        g/vi/ v/.../ c/Emacs/
       +}
       +.ft
       +.P2
       +to make sure we don't change strings embedded in words.
       +.SH
       +Multiple Changes
       +.PP
       +You might wonder why, once
       +.CW Emacs
       +has been changed to
       +.CW vi
       +in the above example,
       +the second command in the braces doesn't put it back again.
       +The reason is that the commands are run in parallel:
       +within any top-level
       +.CW sam
       +command, all changes to the file refer to the state of the file
       +before any of the changes in that command are made.
       +After all the changes have been determined, they are all applied
       +simultaneously.
       +.PP
       +This means, as mentioned, that commands within a compound
       +command see the state of the file before any of the changes apply.
       +This method of evaluation makes some things easier (such as the exchange of
       +.CW Emacs
       +and
       +.CW vi ),
       +and some things harder.
       +For instance, it is impossible to use a
       +.CW p
       +command to print the changes as they happen,
       +because they haven't happened when the
       +.CW p
       +is executed.
       +An indirect ramification is that changes must occur in forward
       +order through the file,
       +and must not overlap.
       +.SH
       +Unix
       +.PP
       +.CW sam
       +has a few commands to connect to Unix processes.
       +The simplest is
       +.CW ! ,
       +which runs the command with input and output connected to the terminal.
       +.P1
       +.WC !date
       +Wed May 28 23:25:21 EDT 1986
       +!
       +.P2
       +(When downloaded, the input is connected to
       +.CW /dev/null
       +and only the first few lines of output are printed;
       +any overflow is stored in
       +.CW $HOME/sam.err .)
       +The final
       +.CW !
       +is a prompt to indicate when the command completes.
       +.PP
       +Slightly more interesting is
       +.CW > ,
       +which provides the current text as standard input to the Unix command:
       +.P1
       +.WC "1,2 >wc
       +      2       22      131
       +!
       +.P2
       +The complement of
       +.CW >
       +is, naturally,
       +.CW < :
       +it replaces the current text with the standard output of the Unix command:
       +.P1
       +.WC "1 <date
       +!
       +.WC 1p
       +Wed May 28 23:26:44 EDT 1986
       +.P2
       +The last command is
       +.CW | ,
       +which is a combination of
       +.CW <
       +and
       +.CW > :
       +the current text is provided as standard input to the Unix command,
       +and the Unix command's standard output is collected and used to
       +replace the original text.
       +For example,
       +.P1
       +.WC ",| sort
       +.P2
       +runs
       +.CW sort (1)
       +on the file, sorting the lines of the text lexicographically.
       +Note that
       +.CW < ,
       +.CW >
       +and
       +.CW |
       +are
       +.CW sam
       +commands, not Unix shell operators.
       +.PP
       +The next example converts all appearances of
       +.CW Emacs
       +to upper case using
       +.CW tr (1):
       +.P1
       +.WC ",x/Emacs/ | tr a-z A-Z
       +.P2
       +.CW tr
       +is run once for each occurrence of
       +.CW Emacs .
       +Of course, you could do this example more efficiently with a simple
       +.CW c
       +command, but here's a trickier one:
       +given a Unix mail box as input,
       +convert all the
       +.CW Subject
       +headers to distinct fortunes:
       +.P1
       +.WC ",x/^Subject:.*\en/ x/[^:]*\en/ < /usr/games/fortune
       +.P2
       +(The regular expression
       +.CW [^:]
       +refers to any character
       +.I except
       +.CW :
       +and newline; the negation operator
       +.CW ^
       +excludes newline from the list of characters.) 
       +Again,
       +.CW /usr/games/fortune
       +is run once for each
       +.CW Subject
       +line, so each
       +.CW Subject
       +line is changed to a different fortune.
       +.SH
       +A few other text commands
       +.PP
       +For completeness, I should mention three other commands that
       +manipulate text.  The
       +.CW m
       +command moves the current text to after the text specified by the
       +(obligatory) address after the command.
       +Thus
       +.P1
       +.WC "/Emacs/+- m 0
       +.P2
       +moves the next line containing
       +.CW Emacs
       +to the beginning of the file.
       +Similarly,
       +.CW t
       +(another historic character) copies the text:
       +.P1
       +.WC "/Emacs/+- t 0
       +.P2
       +would make, at the beginning of the file, a copy of the next line
       +containing
       +.CW Emacs .
       +.PP
       +The third command is more interesting: it makes substitutions.
       +Its syntax is
       +\f(CWs/\f2pattern\f(CW/\f2replacement\f(CW/\f1.
       +Within the current text, it finds the first occurrence of
       +the pattern and replaces it by the replacement text,
       +leaving dot set to the entire address of the substitution.
       +.P1
       +.WC 1p
       +This manual is organized in a rather haphazard manner.  The first
       +.WC s/haphazard/thoughtless/
       +.WC p
       +This manual is organized in a rather thoughtless manner.  The first
       +.P2
       +Occurrences of the character
       +.CW &
       +in the replacement text stand for the text matching the pattern.
       +.P1
       +.WC s/T/"&&&&"/
       +.WC p
       +"TTTT"his manual is organized in a rather thoughtless manner.  The first
       +.P2
       +There are two variants.  The first is that a number may be specified
       +after the
       +.CW s ,
       +to indicate which occurrence of the pattern to substitute; the default
       +is the first.
       +.P1
       +.WC s2/is/was/
       +.WC p
       +"TTTT"his manual was organized in a rather thoughtless manner.  The first
       +.P2
       +The second is that suffixing a
       +.CW g
       +(global) causes replacement of all occurrences, not just the first.
       +.P1
       +.WC s/[a-zA-Z]/x/g
       +.WC p
       +"xxxx"xxx xxxxxx xxx xxxxxxxxx xx x xxxxxx xxxxxxxxxxx xxxxxxx  xxx xxxxx
       +.P2
       +Notice that in all these examples
       +dot is left
       +set to the entire line.
       +.PP
       +[The substitute command is vital to
       +.CW ed,
       +because it is the only way to make changes within a line.
       +It is less valuable in
       +.CW sam ,
       +in which the concept of a line is much less important.
       +For example, many
       +.CW ed
       +substitution idioms are handled well by
       +.CW sam 's
       +basic commands. Consider the commands
       +.P1
       +s/good/bad/
       +s/good//
       +s/good/& bye/
       +.P2
       +which are equivalent in
       +.CW sam
       +to
       +.P1
       +/good/c/bad/
       +/good/d
       +/good/a/ bye/
       +.P2
       +and for which the context search is likely unnecessary because the desired
       +text is already dot.
       +Also, beware this
       +.CW ed
       +idiom:
       +.P1
       +1,$s/good/bad/
       +.P2
       +which changes the first
       +.CW good
       +on each line; the same command in
       +.CW sam
       +will only change the first one in the whole file.
       +The correct
       +.CW sam
       +version is
       +.P1
       +,x s/good/bad/
       +.P2
       +but what is more likely meant is
       +.P1
       +,x/good/ c/bad/
       +.P2
       +.CW sam
       +operates under different rules.]
       +.SH
       +Files
       +.PP
       +So far, we have only been working with a single file,
       +but
       +.CW sam
       +is a multi-file editor.
       +Only one file may be edited at a time, but
       +it is easy to change which file is the `current' file for editing.
       +To see how to do this, we need a
       +.CW sam
       +with a few files;
       +the easiest way to do this is to start it
       +with a list of Unix file names to edit.
       +.P1
       +$ \fIecho *.ms\f(CW
       +conquest.ms death.ms emacs.ms famine.ms slaughter.ms
       +$ \fIsam -d *.ms\f(CW
       + -. conquest.ms
       +.P2
       +(I'm sorry the Horsemen don't appear in liturgical order.)
       +The line printed by
       +.CW sam
       +is an indication that the Unix file
       +.CW conquest.ms
       +has been read, and is now the current file.
       +.CW sam
       +does not read the Unix file until
       +the associated
       +.CW sam
       +file becomes current.
       +.PP
       +The
       +.CW n
       +command prints the names of all the files:
       +.P1
       +.WC n
       + -. conquest.ms
       + -  death.ms
       + -  emacs.ms
       + -  famine.ms
       + -  slaughter.ms
       +.P2
       +This list is also available in the menu on mouse button 3.
       +The command
       +.CW f
       +tells the name of just the current file:
       +.P1
       +.WC f
       + -. conquest.ms
       +.P2
       +The characters to the left of the file name encode helpful information about
       +the file.
       +The minus sign becomes a plus sign if the file has a window open, and an
       +asterisk if more than one is open.
       +The period (another meaning of dot) identifies the current file.
       +The leading blank changes to an apostrophe if the file is different
       +from the contents of the associated Unix file, as far as
       +.CW sam
       +knows.
       +This becomes evident if we make a change.
       +.P1
       +.WC 1d
       +.WC f
       +\&'-. conquest.ms
       +.P2
       +If the file is restored by an undo command, the apostrophe disappears.
       +.P1
       +.WC u
       +.WC f
       + -. conquest.ms
       +.P2
       +The file name may be changed by providing a new name with the
       +.CW f
       +command:
       +.P1
       +.CW "f pestilence.ms
       +\&'-. pestilence.ms
       +.P2
       +.WC f
       +prints the new status of the file,
       +that is, it changes the name if one is provided, and prints the
       +name regardless.
       +A file name change may also be undone.
       +.P1
       +.WC u
       +.WC f
       + -. conquest.ms
       +.P2
       +.PP
       +When
       +.CW sam
       +is downloaded, the current file may be changed simply by selecting
       +the desired file from the menu (selecting the same file subsequently
       +cycles through the windows opened on the file).
       +Otherwise, the
       +.CW b
       +command can be used to choose the desired file:\(dg
       +.FS
       +\(dg A bug prevents the
       +.CW b
       +command from working when downloaded.
       +Because the menu is more convenient anyway, and
       +because the method
       +of choosing files from the command language is slated to change,
       +the bug hasn't been fixed.
       +.FE
       +.P1
       +.WC "b emacs.ms
       + -. emacs.ms
       +.P2
       +Again,
       +.CW sam
       +prints the name (actually, executes an implicit
       +.CW f
       +command) because the Unix file
       +.CW emacs.ms
       +is being read for the first time.
       +It is an error to ask for a file
       +.CW sam
       +doesn't know about, but the
       +.CW B
       +command will prime
       +.CW sam 's
       +menu with a new file, and make it current.
       +.P1
       +.WC "b flood.pic
       +?no such file `flood.pic'
       +.WC "B flood.pic
       + -. flood.pic
       +.WC n
       + -  conquest.ms
       + -  death.ms
       + -  emacs.ms
       + -  famine.ms
       + -. flood.pic
       + -  slaughter.ms
       +.P2
       +Both
       +.CW b
       +and
       +.CW B
       +will accept a list of file names.
       +.CW b
       +simply takes the first file in the list, but
       +.CW B
       +loads them all.
       +The list may be typed on one line \(em
       +.P1
       +.WC "B devil.tex satan.tex 666.tex emacs.tex
       +.P2
       +\(em or generated by a Unix command \(em
       +.P1
       +.WC "B <echo *.tex
       +.P2
       +The latter form requires a Unix command;
       +.CW sam
       +does not understand the shell file name metacharacters, so
       +.CW "B *.tex
       +attempts to load a single file named
       +.CW *.tex .
       +(The
       +.CW <
       +form is of course derived from
       +.CW sam 's
       +.CW <
       +command.)
       +.CW echo
       +is not the only useful command to run subservient to
       +.CW B ;
       +for example,
       +.P1
       +.WC "B <grep -l Emacs *
       +.P2
       +will load only those files containing the string
       +.CW Emacs .
       +Finally, a special case: a
       +.CW B
       +with no arguments creates an empty, nameless file within
       +.CW sam .
       +.PP
       +The complement of
       +.CW B
       +is
       +.CW D :
       +.P1
       +.WC "D devil.tex satan.tex 666.tex emacs.tex
       +.P2
       +eradicates the files from
       +.CW sam 's
       +memory (not from the Unix machine's disc).
       +.CW D
       +without any file names removes the current file from
       +.CW sam .
       +.PP
       +There are three other commands that relate the current file
       +to Unix files.
       +The
       +.CW w
       +command writes the file to disc;
       +without arguments, it writes the entire file to the Unix file associated
       +with the current file in
       +.CW sam
       +(it is the only command whose default address is not dot).
       +Of course, you can specify an address to be written,
       +and a different file name, with the obvious syntax:
       +.P1
       +.WC "1,2w /tmp/revelations
       +/tmp/revelations: #44
       +.P2
       +.CW sam
       +responds with the file name and the number of characters written to the file.
       +The
       +.CW write
       +command on the button 3 menu is identical in function to an unadorned
       +.CW w
       +command.
       +.PP
       +The other two commands,
       +.CW e
       +and
       +.CW r ,
       +read data from Unix files.
       +The
       +.CW e
       +command clears out the current file,
       +reads the data from the named file (or uses the current file's old name if
       +none is explicitly provided), and sets the file name.
       +It's much like a
       +.CW B
       +command, but puts the information in the current file instead of a new one.
       +.CW e
       +without any file name is therefore an easy way to refresh
       +.CW sam 's
       +copy of a Unix file.
       +[Unlike in
       +.CW ed ,
       +.CW e
       +doesn't complain if the file is modified.  The principle is not
       +to protect against things that can be undone if wrong.]
       +Since its job is to replace the whole text,
       +.CW e
       +never takes an address.
       +.PP
       +The
       +.CW r
       +command is like
       +.CW e ,
       +but it doesn't clear the file:
       +the text in the Unix file replaces dot, or the specified text if an
       +address is given.
       +.P1
       +.WC "r emacs.ms
       +.P2
       +has essentially the effect of
       +.P1
       +.WC "<cat emacs.ms
       +.P2
       +The commands
       +.CW r
       +and
       +.CW w
       +will set the name of the file if the current file has no name already defined;
       +.CW e
       +sets the name even if the file already has one.
       +.PP
       +There is a command, analogous to
       +.CW x ,
       +that iterates over files instead of pieces of text:
       +.CW X
       +(capital
       +.CW x ).
       +The syntax is easy; it's just like that of
       +.CW x
       +\(em \f(CWX/\f2pattern\f(CW/\f2command\f1.
       +(The complementary command is
       +.CW Y ,
       +analogous to
       +.CW y .)
       +The effect is to run the command in each file whose menu entry
       +(that is, whose line printed by an
       +.CW f
       +command) matches the pattern.
       +For example, since an apostrophe identifies modified files,
       +.P1
       +.WC "X/'/ w
       +.P2
       +writes the changed files out to disc.
       +Here is a longer example: find all uses of a particular variable
       +in the C source files:
       +.P1
       +.WC "X/\e.c$/ ,x/variable/+-p
       +.P2
       +We can use an
       +.CW f
       +command to identify which file the variable appears in:
       +.P1
       +.ft I
       +X/\e.c$/ ,g/variable/ {
       +        f
       +        ,x/variable/+-{
       +                =
       +                p
       +        }
       +}
       +.ft
       +.P2
       +Here, the
       +.CW g
       +command guarantees that only the names of files containing the variable
       +will be printed (but beware that
       +.CW sam
       +may confuse matters by printing the names of files it reads in during
       +the command).
       +The
       +.CW =
       +command shows where in the file the variable appears, and the
       +.CW p
       +command prints the line.
       +.PP
       +The
       +.CW D
       +command is handy as the target of an
       +.CW X .
       +This example deletes from the menu all C files that do not contain
       +a particular variable:
       +.P1
       +.WC "X/\e.c$/ ,v/variable/ D
       +.P2
       +If no pattern is provided for the
       +.CW X ,
       +the command (which defaults to
       +.CW f )
       +is run in all files, so
       +.P1
       +.WC "X D
       +.P2
       +cleans
       +.CW sam
       +up for a fresh start.
       +.PP
       +But rather than working any further, let's stop now:
       +.P1
       +.WC q
       +$
       +.P2
       +.fi
       +.PP
       +Some of the file manipulating commands can be undone:
       +undoing a
       +.CW f ,
       +.CW e ,
       +or
       +.CW r
       +restores the previous state of the file,
       +but
       +.CW w ,
       +.CW B
       +and
       +.CW D
       +are irrevocable.
       +And, of course, so is
       +.CW q .
 (DIR) diff --git a/doc/se.ps b/doc/se.ps
       @@ -0,0 +1,1487 @@
       +%!PS
       +%%Version: 3.3.2
       +%%DocumentFonts: (atend)
       +%%Pages: (atend)
       +%%EndComments
       +%
       +% Version 3.3.2 prologue for troff files.
       +%
       +
       +/#copies 1 store
       +/aspectratio 1 def
       +/formsperpage 1 def
       +/landscape false def
       +/linewidth .3 def
       +/magnification 1 def
       +/margin 0 def
       +/orientation 0 def
       +/resolution 720 def
       +/rotation 1 def
       +/xoffset 0 def
       +/yoffset 0 def
       +
       +/roundpage true def
       +/useclippath true def
       +/pagebbox [0 0 612 792] def
       +
       +/R  /Times-Roman def
       +/I  /Times-Italic def
       +/B  /Times-Bold def
       +/BI /Times-BoldItalic def
       +/H  /Helvetica def
       +/HI /Helvetica-Oblique def
       +/HB /Helvetica-Bold def
       +/HX /Helvetica-BoldOblique def
       +/CW /Courier def
       +/CO /Courier def
       +/CI /Courier-Oblique def
       +/CB /Courier-Bold def
       +/CX /Courier-BoldOblique def
       +/PA /Palatino-Roman def
       +/PI /Palatino-Italic def
       +/PB /Palatino-Bold def
       +/PX /Palatino-BoldItalic def
       +/Hr /Helvetica-Narrow def
       +/Hi /Helvetica-Narrow-Oblique def
       +/Hb /Helvetica-Narrow-Bold def
       +/Hx /Helvetica-Narrow-BoldOblique def
       +/KR /Bookman-Light def
       +/KI /Bookman-LightItalic def
       +/KB /Bookman-Demi def
       +/KX /Bookman-DemiItalic def
       +/AR /AvantGarde-Book def
       +/AI /AvantGarde-BookOblique def
       +/AB /AvantGarde-Demi def
       +/AX /AvantGarde-DemiOblique def
       +/NR /NewCenturySchlbk-Roman def
       +/NI /NewCenturySchlbk-Italic def
       +/NB /NewCenturySchlbk-Bold def
       +/NX /NewCenturySchlbk-BoldItalic def
       +/ZD /ZapfDingbats def
       +/ZI /ZapfChancery-MediumItalic def
       +/S  /S def
       +/S1 /S1 def
       +/GR /Symbol def
       +
       +/inch {72 mul} bind def
       +/min {2 copy gt {exch} if pop} bind def
       +
       +/show {show} bind def                % so later references don't bind
       +/widthshow {widthshow} bind def
       +/stringwidth {stringwidth} bind def
       +
       +/setup {
       +        counttomark 2 idiv {def} repeat pop
       +
       +        landscape {/orientation 90 orientation add def} if
       +        /scaling 72 resolution div def
       +        linewidth setlinewidth
       +        1 setlinecap
       +
       +        pagedimensions
       +        xcenter ycenter translate
       +        orientation rotation mul rotate
       +        width 2 div neg height 2 div translate
       +        xoffset inch yoffset inch neg translate
       +        margin 2 div dup neg translate
       +        magnification dup aspectratio mul scale
       +        scaling scaling scale
       +
       +        addmetrics
       +        0 0 moveto
       +} def
       +
       +/pagedimensions {
       +        useclippath userdict /gotpagebbox known not and {
       +                /pagebbox [clippath pathbbox newpath] def
       +                roundpage currentdict /roundpagebbox known and {roundpagebbox} if
       +        } if
       +        pagebbox aload pop
       +        4 -1 roll exch 4 1 roll 4 copy
       +        landscape {4 2 roll} if
       +        sub /width exch def
       +        sub /height exch def
       +        add 2 div /xcenter exch def
       +        add 2 div /ycenter exch def
       +        userdict /gotpagebbox true put
       +} def
       +
       +/addmetrics {
       +        /Symbol /S null Sdefs cf
       +        /Times-Roman /S1 StandardEncoding dup length array copy S1defs cf
       +} def
       +
       +/pagesetup {
       +        /page exch def
       +        currentdict /pagedict known currentdict page known and {
       +                page load pagedict exch get cvx exec
       +        } if
       +} def
       +
       +/decodingdefs [
       +        {counttomark 2 idiv {y moveto show} repeat}
       +        {neg /y exch def counttomark 2 idiv {y moveto show} repeat}
       +        {neg moveto {2 index stringwidth pop sub exch div 0 32 4 -1 roll widthshow} repeat}
       +        {neg moveto {spacewidth sub 0.0 32 4 -1 roll widthshow} repeat}
       +        {counttomark 2 idiv {y moveto show} repeat}
       +        {neg setfunnytext}
       +] def
       +
       +/setdecoding {/t decodingdefs 3 -1 roll get bind def} bind def
       +
       +/w {neg moveto show} bind def
       +/m {neg dup /y exch def moveto} bind def
       +/done {/lastpage where {pop lastpage} if} def
       +
       +/f {
       +        dup /font exch def findfont exch
       +        dup /ptsize exch def scaling div dup /size exch def scalefont setfont
       +        linewidth ptsize mul scaling 10 mul div setlinewidth
       +        /spacewidth ( ) stringwidth pop def
       +} bind def
       +
       +/changefont {
       +        /fontheight exch def
       +        /fontslant exch def
       +        currentfont [
       +                1 0
       +                fontheight ptsize div fontslant sin mul fontslant cos div
       +                fontheight ptsize div
       +                0 0
       +        ] makefont setfont
       +} bind def
       +
       +/sf {f} bind def
       +
       +/cf {
       +        dup length 2 idiv
       +        /entries exch def
       +        /chtab exch def
       +        /newencoding exch def
       +        /newfont exch def
       +
       +        findfont dup length 1 add dict
       +        /newdict exch def
       +        {1 index /FID ne {newdict 3 1 roll put}{pop pop} ifelse} forall
       +
       +        newencoding type /arraytype eq {newdict /Encoding newencoding put} if
       +
       +        newdict /Metrics entries dict put
       +        newdict /Metrics get
       +        begin
       +                chtab aload pop
       +                1 1 entries {pop def} for
       +                newfont newdict definefont pop
       +        end
       +} bind def
       +
       +%
       +% A few arrays used to adjust reference points and character widths in some
       +% of the printer resident fonts. If square roots are too high try changing
       +% the lines describing /radical and /radicalex to,
       +%
       +%        /radical        [0 -75 550 0]
       +%        /radicalex        [-50 -75 500 0]
       +%
       +% Move braceleftbt a bit - default PostScript character is off a bit.
       +%
       +
       +/Sdefs [
       +        /bracketlefttp                [201 500]
       +        /bracketleftbt                [201 500]
       +        /bracketrighttp                [-81 380]
       +        /bracketrightbt                [-83 380]
       +        /braceleftbt                [203 490]
       +        /bracketrightex                [220 -125 500 0]
       +        /radical                [0 0 550 0]
       +        /radicalex                [-50 0 500 0]
       +        /parenleftex                [-20 -170 0 0]
       +        /integral                [100 -50 500 0]
       +        /infinity                [10 -75 730 0]
       +] def
       +
       +/S1defs [
       +        /underscore                [0 80 500 0]
       +        /endash                        [7 90 650 0]
       +] def
       +%
       +% Tries to round clipping path dimensions, as stored in array pagebbox, so they
       +% match one of the known sizes in the papersizes array. Lower left coordinates
       +% are always set to 0.
       +%
       +
       +/roundpagebbox {
       +    7 dict begin
       +        /papersizes [8.5 inch 11 inch 14 inch 17 inch] def
       +
       +        /mappapersize {
       +                /val exch def
       +                /slop .5 inch def
       +                /diff slop def
       +                /j 0 def
       +                0 1 papersizes length 1 sub {
       +                        /i exch def
       +                        papersizes i get val sub abs
       +                        dup diff le {/diff exch def /j i def} {pop} ifelse
       +                } for
       +                diff slop lt {papersizes j get} {val} ifelse
       +        } def
       +
       +        pagebbox 0 0 put
       +        pagebbox 1 0 put
       +        pagebbox dup 2 get mappapersize 2 exch put
       +        pagebbox dup 3 get mappapersize 3 exch put
       +    end
       +} bind def
       +
       +%%EndProlog
       +%%BeginSetup
       +mark
       +/linewidth 0.5 def
       +/#copies 1 store
       +/landscape false def
       +/resolution 720 def
       +%
       +% Encoding vector and redefinition of findfont for the ISO Latin1 standard.
       +% The 18 characters missing from ROM based fonts on older printers are noted
       +% below.
       +%
       +
       +/ISOLatin1Encoding [
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /space
       +        /exclam
       +        /quotedbl
       +        /numbersign
       +        /dollar
       +        /percent
       +        /ampersand
       +        /quoteright
       +        /parenleft
       +        /parenright
       +        /asterisk
       +        /plus
       +        /comma
       +        /minus
       +        /period
       +        /slash
       +        /zero
       +        /one
       +        /two
       +        /three
       +        /four
       +        /five
       +        /six
       +        /seven
       +        /eight
       +        /nine
       +        /colon
       +        /semicolon
       +        /less
       +        /equal
       +        /greater
       +        /question
       +        /at
       +        /A
       +        /B
       +        /C
       +        /D
       +        /E
       +        /F
       +        /G
       +        /H
       +        /I
       +        /J
       +        /K
       +        /L
       +        /M
       +        /N
       +        /O
       +        /P
       +        /Q
       +        /R
       +        /S
       +        /T
       +        /U
       +        /V
       +        /W
       +        /X
       +        /Y
       +        /Z
       +        /bracketleft
       +        /backslash
       +        /bracketright
       +        /asciicircum
       +        /underscore
       +        /quoteleft
       +        /a
       +        /b
       +        /c
       +        /d
       +        /e
       +        /f
       +        /g
       +        /h
       +        /i
       +        /j
       +        /k
       +        /l
       +        /m
       +        /n
       +        /o
       +        /p
       +        /q
       +        /r
       +        /s
       +        /t
       +        /u
       +        /v
       +        /w
       +        /x
       +        /y
       +        /z
       +        /braceleft
       +        /bar
       +        /braceright
       +        /asciitilde
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /.notdef
       +        /dotlessi
       +        /grave
       +        /acute
       +        /circumflex
       +        /tilde
       +        /macron
       +        /breve
       +        /dotaccent
       +        /dieresis
       +        /.notdef
       +        /ring
       +        /cedilla
       +        /.notdef
       +        /hungarumlaut
       +        /ogonek
       +        /caron
       +        /space
       +        /exclamdown
       +        /cent
       +        /sterling
       +        /currency
       +        /yen
       +        /brokenbar                % missing
       +        /section
       +        /dieresis
       +        /copyright
       +        /ordfeminine
       +        /guillemotleft
       +        /logicalnot
       +        /hyphen
       +        /registered
       +        /macron
       +        /degree                        % missing
       +        /plusminus                % missing
       +        /twosuperior                % missing
       +        /threesuperior                % missing
       +        /acute
       +        /mu                        % missing
       +        /paragraph
       +        /periodcentered
       +        /cedilla
       +        /onesuperior                % missing
       +        /ordmasculine
       +        /guillemotright
       +        /onequarter                % missing
       +        /onehalf                % missing
       +        /threequarters                % missing
       +        /questiondown
       +        /Agrave
       +        /Aacute
       +        /Acircumflex
       +        /Atilde
       +        /Adieresis
       +        /Aring
       +        /AE
       +        /Ccedilla
       +        /Egrave
       +        /Eacute
       +        /Ecircumflex
       +        /Edieresis
       +        /Igrave
       +        /Iacute
       +        /Icircumflex
       +        /Idieresis
       +        /Eth                        % missing
       +        /Ntilde
       +        /Ograve
       +        /Oacute
       +        /Ocircumflex
       +        /Otilde
       +        /Odieresis
       +        /multiply                % missing
       +        /Oslash
       +        /Ugrave
       +        /Uacute
       +        /Ucircumflex
       +        /Udieresis
       +        /Yacute                        % missing
       +        /Thorn                        % missing
       +        /germandbls
       +        /agrave
       +        /aacute
       +        /acircumflex
       +        /atilde
       +        /adieresis
       +        /aring
       +        /ae
       +        /ccedilla
       +        /egrave
       +        /eacute
       +        /ecircumflex
       +        /edieresis
       +        /igrave
       +        /iacute
       +        /icircumflex
       +        /idieresis
       +        /eth                        % missing
       +        /ntilde
       +        /ograve
       +        /oacute
       +        /ocircumflex
       +        /otilde
       +        /odieresis
       +        /divide                        % missing
       +        /oslash
       +        /ugrave
       +        /uacute
       +        /ucircumflex
       +        /udieresis
       +        /yacute                        % missing
       +        /thorn                        % missing
       +        /ydieresis
       +] def
       +
       +/NewFontDirectory FontDirectory maxlength dict def
       +
       +%
       +% Apparently no guarantee findfont is defined in systemdict so the obvious
       +%
       +%        systemdict /findfont get exec
       +%
       +% can generate an error. So far the only exception is a VT600 (version 48.0).
       +%
       +
       +userdict /@RealFindfont known not {
       +        userdict begin
       +                /@RealFindfont systemdict begin /findfont load end def
       +        end
       +} if
       +
       +/findfont {
       +        dup NewFontDirectory exch known not {
       +                dup
       +                %dup systemdict /findfont get exec        % not always in systemdict
       +                dup userdict /@RealFindfont get exec
       +                dup /Encoding get StandardEncoding eq {
       +                        dup length dict begin
       +                                {1 index /FID ne {def}{pop pop} ifelse} forall
       +                                /Encoding ISOLatin1Encoding def
       +                                currentdict
       +                        end
       +                        /DummyFontName exch definefont
       +                } if
       +                NewFontDirectory 3 1 roll put
       +        } if
       +        NewFontDirectory exch get
       +} bind def
       +
       +setup
       +2 setdecoding
       +%%EndSetup
       +%%Page: 1 1
       +/saveobj save def
       +mark
       +1 pagesetup
       +12 B f
       +(Structural Regular Expressions)2 1622 1 2069 1230 t
       +10 I f
       +(Rob Pike)1 363 1 2698 1470 t
       +10 R f
       +(AT&T Bell Laboratories)2 993 1 2383 1650 t
       +(Murray Hill, New Jersey 07974)4 1267 1 2246 1770 t
       +10 I f
       +(ABSTRACT)2643 2150 w
       +10 R f
       +(The current)1 465 1 1330 2410 t
       +9 R f
       +(UNIX)1821 2410 w
       +10 R f
       +( the built\255in concept of a)5 999(\256 text processing tools are weakened by)6 1635 2 2046 2410 t
       +( describe the `shape' of files when the typical)8 1908( is a simple notation that can)6 1222(line. There)1 470 3 1080 2530 t
       +( regular)1 316( Using)1 298( is regular expressions.)3 942( notation)1 361( That)1 241(array\255of\255lines picture is inadequate.)3 1442 6 1080 2650 t
       +( files has interesting)3 841(expressions to describe the structure in addition to the contents of)10 2759 2 1080 2770 t
       +(applications, and yields elegant methods for dealing with some problems the current tools)12 3600 1 1080 2890 t
       +( are composed, the result is)5 1157( operations using these expressions)4 1464( When)1 303(handle clumsily.)1 676 4 1080 3010 t
       +(reminiscent of shell pipelines.)3 1199 1 1080 3130 t
       +10 B f
       +(The Peter\255On\255Silicon Problem)2 1299 1 720 3490 t
       +10 R f
       +( model,)1 301(In the traditional)2 666 2 970 3646 t
       +9 R f
       +(UNIX)1961 3646 w
       +10 R f
       +(text files are arrays of lines, and all the familiar tools)10 2120 1 2212 3646 t
       +10 S1 f
       +(\320)4358 3646 w
       +10 CW f
       +(grep)4484 3646 w
       +10 R f
       +(,)4724 3646 w
       +10 CW f
       +(sort)4775 3646 w
       +10 R f
       +(,)5015 3646 w
       +10 CW f
       +(awk)720 3766 w
       +10 R f
       +(, etc.)1 197 1 900 3766 t
       +10 S1 f
       +(\320)1128 3766 w
       +10 R f
       +( of)1 113( output)1 287( The)1 211(expect arrays of lines as input.)5 1244 4 1259 3766 t
       +10 CW f
       +(ls)3144 3766 w
       +10 R f
       +(\(regardless of options\) is a list of files, one)8 1746 1 3294 3766 t
       +(per line, that may be selected by tools such as)9 1825 1 720 3886 t
       +10 CW f
       +(grep)2570 3886 w
       +10 R f
       +(:)2810 3886 w
       +10 CW f
       +(ls \255l /usr/ken/bin | grep 'rws.*root')5 2220 1 1080 4066 t
       +10 R f
       +(\(I assume that the reader is familiar with the)8 1803 1 720 4246 t
       +9 R f
       +(UNIX)2551 4246 w
       +10 R f
       +( model is powerful, but it is also pervasive,)8 1769(tools.\) The)1 464 2 2807 4246 t
       +( Many)1 298(sometimes overly so.)2 877 2 720 4366 t
       +9 R f
       +(UNIX)1933 4366 w
       +10 R f
       +( more general, and more useful, if they could be)9 2041(programs would be)2 801 2 2198 4366 t
       +( example,)1 400( For)1 201(applied to arbitrarily structured input.)4 1549 3 720 4486 t
       +10 CW f
       +(diff)2907 4486 w
       +10 R f
       +( C)1 105(could in principle report differences at the)6 1751 2 3184 4486 t
       +( if the interesting quantum of information isn't a line, most of)11 2537( But)1 202( level.)1 251(function level instead of the line)5 1330 4 720 4606 t
       +(the tools \(including)2 804 1 720 4726 t
       +10 CW f
       +(diff)1562 4726 w
       +10 R f
       +( solution so the line\255)4 873( perverting the)2 608( Worse,)1 348(\) don't help, or at best do poorly.)7 1409 4 1802 4726 t
       +(oriented tools can implement it often obscures the original problem.)9 2714 1 720 4846 t
       +( consider the problem of turning)5 1320(To see how a line oriented view of text can introduce complication,)11 2750 2 970 5002 t
       +( input is an array of blank and non\255blank characters, like this:)11 2451( The)1 205(Peter into silicon.)2 703 3 720 5122 t
       +10 CW f
       +(#######)1320 5252 w
       +(#########)1260 5322 w
       +(#### #####)1 660 1 1200 5392 t
       +( #)1 180(#### ####)1 720 2 1140 5462 t
       +(#### #####)1 840 1 1140 5532 t
       +(#### ###)1 840 1 1080 5602 t
       +(######## #####)1 960 1 1080 5672 t
       +(#### #########)1 840 1 1080 5742 t
       +( ####)1 300( #)1 180(#### #)1 360 3 1080 5812 t
       +( ##)1 300( ###)1 300(## #)1 240 3 1080 5882 t
       +( ###)1 300(### #)1 480 2 1080 5952 t
       +(### ##)1 540 1 1080 6022 t
       +(## #)1 360 1 1140 6092 t
       +(# ####)1 480 1 1200 6162 t
       +(# #)1 180 1 1200 6232 t
       +(## # ##)2 660 1 1080 6302 t
       +10 R f
       +(The output is to be statements in a language for laying out integrated circuits:)13 3094 1 720 6482 t
       +10 CW f
       +(rect minx miny maxx maxy)4 1440 1 1080 6662 t
       +10 R f
       +( simplify the problem slightly,)4 1247( To)1 169(The statements encode where the non\255blank characters are in the input.)10 2904 3 720 6842 t
       +(the coordinate system has)3 1032 1 720 6962 t
       +10 I f
       +(x)1778 6962 w
       +10 R f
       +(positive to the right and)4 954 1 1848 6962 t
       +10 I f
       +(y)2828 6962 w
       +10 R f
       +( output need not be efficient in its)7 1346( The)1 206(positive down.)1 590 3 2898 6962 t
       +(use of rectangles.)2 723 1 720 7082 t
       +10 CW f
       +(Awk)1507 7082 w
       +10 R f
       +( which is a mixture of text processing and)8 1790(is the obvious language for the task,)6 1524 2 1726 7082 t
       +( the input is an array of lines, as)8 1345( Since)1 281(geometry, hence arithmetic.)2 1132 3 720 7202 t
       +10 CW f
       +(awk)3511 7202 w
       +10 R f
       +(expects, the job should be fairly)5 1316 1 3724 7202 t
       +cleartomark
       +showpage
       +saveobj restore
       +%%EndPage: 1 1
       +%%Page: 2 2
       +/saveobj save def
       +mark
       +2 pagesetup
       +10 R f
       +(\255 2 \255)2 166 1 2797 480 t
       +( is an)2 211( Here)1 243(easy, and in fact it is.)5 846 3 720 840 t
       +10 CW f
       +(awk)2045 840 w
       +10 R f
       +(program for the job:)3 807 1 2250 840 t
       +10 CW f
       +(BEGIN{)1080 1020 w
       +(y=1)1330 1140 w
       +(})1080 1260 w
       +(/^/{)1080 1380 w
       +(for\(x=1; x<=length\($0\); x++\))2 1680 1 1330 1500 t
       +(if\(substr\($0, x, 1\)=="#"\))2 1500 1 1580 1620 t
       +(print "rect", x, y, x+1, y+1)5 1680 1 1830 1740 t
       +(y++)1330 1860 w
       +(})1080 1980 w
       +10 R f
       +(Although it is certainly easy to write, there is something odd about this program: the line\255driven nature of)17 4320 1 720 2160 t
       +10 CW f
       +(awk)720 2280 w
       +10 R f
       +(results in only one obvious advantage)5 1512 1 926 2280 t
       +10 S1 f
       +(\320)2464 2280 w
       +10 R f
       +(the ease of tracking)3 781 1 2590 2280 t
       +10 CW f
       +(y)3397 2280 w
       +10 R f
       +( breaking out the pieces of)5 1056( task of)2 296(. The)1 231 3 3457 2280 t
       +( simple procedural code that does not use any advanced technology such as)12 3077(the line is left to explicit code,)6 1243 2 720 2400 t
       +( peculiarity becomes more evident if the problem is)8 2234( This)1 250( manipulation.)1 600(regular expressions for string)3 1236 4 720 2520 t
       +(rephrased to demand that each horizontal run of rectangles be folded into a single rectangle:)14 3669 1 720 2640 t
       +10 CW f
       +(BEGIN{)1080 2820 w
       +(y=1)1330 2940 w
       +(})1080 3060 w
       +(/^/{)1080 3180 w
       +(for\(x=1; x<=length\($0\); x++\))2 1680 1 1330 3300 t
       +(if\(substr\($0, x, 1\)=="#"\){)2 1560 1 1580 3420 t
       +(x0=x;)1830 3540 w
       +(while\(++x<=length\($0\) && substr\($0, x, 1\)=="#"\))4 2820 1 1830 3660 t
       +(;)2080 3780 w
       +(print "rect", x0, y, x, y+1)5 1620 1 1830 3900 t
       +(})1580 4020 w
       +(y++)1330 4140 w
       +(})1080 4260 w
       +10 R f
       +( In)1 133(Here a considerable amount of code is being spent to do a job a regular expression could do very simply.)19 4187 2 720 4440 t
       +(fact, the only regular expression in the program is)8 2044 1 720 4560 t
       +10 CW f
       +(^)2796 4560 w
       +10 R f
       +( ver\255)1 191( \(Newer)1 354( input.)1 262(, which is almost irrelevant to the)6 1377 4 2856 4560 t
       +(sions of)1 324 1 720 4680 t
       +10 CW f
       +(awk)1079 4680 w
       +10 R f
       +(have mechanisms to use regular expressions within actions, but even there the relationship)12 3746 1 1294 4680 t
       +(between the patterns that match text and the actions that manipulate the text is still too weak.\))16 3743 1 720 4800 t
       +10 CW f
       +(Awk's)970 4956 w
       +10 R f
       +(patterns)1302 4956 w
       +10 S1 f
       +(\320)1650 4956 w
       +10 R f
       +( in slashes)2 427(the text)1 304 2 1782 4956 t
       +10 CW f
       +(//)2546 4956 w
       +10 R f
       +(that select the input on which to run the actions, the pro\255)11 2341 1 2699 4956 t
       +( braces)1 280(grams in the)2 498 2 720 5076 t
       +10 CW f
       +({})1524 5076 w
       +10 S1 f
       +(\320)1670 5076 w
       +10 R f
       +( But)1 196(pass to the actions the entire line containing the text matched by the pattern.)13 3048 2 1796 5076 t
       +( that)1 176( Imagine)1 378( can only be a line.)5 759(much of the power of this idea is being wasted, since the matched text)13 2801 4 720 5196 t
       +10 CW f
       +(awk)4860 5196 w
       +10 R f
       +( so the patterns instead passed precisely the text they matched, with no implicit line bound\255)15 3761(were changed)1 559 2 720 5316 t
       +( first program could then be written:)6 1448(aries. Our)1 418 2 720 5436 t
       +cleartomark
       +showpage
       +saveobj restore
       +%%EndPage: 2 2
       +%%Page: 3 3
       +/saveobj save def
       +mark
       +3 pagesetup
       +10 R f
       +(\255 3 \255)2 166 1 2797 480 t
       +10 CW f
       +(BEGIN{)1080 900 w
       +(x=1)1330 1020 w
       +(y=1)1330 1140 w
       +(})1080 1260 w
       +(/ /{)1 240 1 1080 1380 t
       +(x++)1330 1500 w
       +(})1080 1620 w
       +(/#/{)1080 1740 w
       +(print "rect", x, x+1, y, y+1)5 1680 1 1330 1860 t
       +(x++)1330 1980 w
       +(})1080 2100 w
       +(/\\n/{)1080 2220 w
       +(x=1)1330 2340 w
       +(y++)1330 2460 w
       +(})1080 2580 w
       +10 R f
       +( regular expressions to break out complete strings of blanks and)10 2606(and the second version could use)5 1342 2 720 2760 t
       +10 CW f
       +(#)4699 2760 w
       +10 R f
       +('s sim\255)1 281 1 4759 2760 t
       +(ply:)720 2880 w
       +10 CW f
       +(BEGIN{)1080 3060 w
       +(x=1)1330 3180 w
       +(y=1)1330 3300 w
       +(})1080 3420 w
       +(/ +/{)1 300 1 1080 3540 t
       +(x+=length\($0\))1330 3660 w
       +(})1080 3780 w
       +(/#+/{)1080 3900 w
       +(print "rect", x, x+length\($0\), y, y+1)5 2220 1 1330 4020 t
       +(x+=length\($0\))1330 4140 w
       +(})1080 4260 w
       +(/\\n/{)1080 4380 w
       +(x=1)1330 4500 w
       +(y++)1330 4620 w
       +(})1080 4740 w
       +10 R f
       +( are)1 148(In these programs, regular expressions are being used to do more than just select the input, the way they)18 4172 2 720 4920 t
       +(used in all the traditional)4 1050 1 720 5040 t
       +9 R f
       +(UNIX)1806 5040 w
       +10 R f
       +( the expressions are doing a simple parsing \(or at least a)11 2375(tools. Instead,)1 596 2 2069 5040 t
       +( expressions are called)3 900( Such)1 250(breaking into lexical tokens\) of the input.)6 1651 3 720 5160 t
       +10 I f
       +(structural regular expressions)2 1213 1 3547 5160 t
       +10 R f
       +(or just)1 254 1 4786 5160 t
       +10 I f
       +(structural expressions.)1 911 1 720 5280 t
       +10 R f
       +( notably shorter than the originals, but they are conceptually simpler, because)11 3125(These programs are not)3 945 2 970 5436 t
       +( The)1 208(the structure of the input is expressed in the structure of the programs, rather than in procedural code.)17 4112 2 720 5556 t
       +( between the patterns and the actions: the patterns select portions of the input)13 3102(labor has been cleanly divided)4 1218 2 720 5676 t
       +( actions contain no code to disassemble the input.)8 1979( The)1 205(while the actions operate on them.)5 1370 3 720 5796 t
       +(The lexical analysis generator)3 1233 1 970 5952 t
       +10 CW f
       +(lex)2241 5952 w
       +10 R f
       +( but its)2 301(uses regular expressions to define the structure of text,)8 2280 2 2459 5952 t
       +( \(its output must be run through the C)8 1594(implementation is poor, and since it is not an interactive program)10 2726 2 720 6072 t
       +( conve\255)1 302( even ignoring issues of speed and)6 1400( But)1 200(compiler\) it has largely been forgotten as a day\255to\255day tool.)9 2418 4 720 6192 t
       +(nience,)720 6312 w
       +10 CW f
       +(lex)1041 6312 w
       +10 R f
       +( the next)2 364( As)1 171( structural expressions.)2 938(still misses out on one of the most important aspects of)10 2310 4 1257 6312 t
       +( be nested to describe the structure of a file recursively, with)11 2510(section illustrates, structural expressions can)4 1810 2 720 6432 t
       +(surprising results.)1 711 1 720 6552 t
       +10 B f
       +(Interactive Text Editing)2 1027 1 720 6792 t
       +10 R f
       +(It is ironic that)3 589 1 970 6948 t
       +9 R f
       +(UNIX)1583 6948 w
       +10 R f
       +( typ\255)1 188(files are uninterpreted byte streams, yet the style of programming that most)11 3018 2 1834 6948 t
       +(ifies)720 7068 w
       +9 R f
       +(UNIX)925 7068 w
       +10 R f
       +( imposed on files)3 713(has a fairly rigid structure)4 1071 2 1185 7068 t
       +10 S1 f
       +(\320)3003 7068 w
       +10 R f
       +( silent limits)2 514( \(The)1 247(arrays of not\255too\255long lines.)3 1142 3 3137 7068 t
       +( the)1 153( Although)1 434( line lengths by most tools can be frustrating.\))8 1883(placed on)1 390 4 720 7188 t
       +10 CW f
       +(awk)3611 7188 w
       +10 R f
       +(variant introduced above does)3 1218 1 3822 7188 t
       +(not exist, there is an interactive text editor,)7 1706 1 720 7308 t
       +10 CW f
       +(sam)2451 7308 w
       +10 R f
       +(, that treats its files as simple byte streams.)8 1710 1 2631 7308 t
       +cleartomark
       +showpage
       +saveobj restore
       +%%EndPage: 3 3
       +%%Page: 4 4
       +/saveobj save def
       +mark
       +4 pagesetup
       +10 R f
       +(\255 4 \255)2 166 1 2797 480 t
       +(The)970 840 w
       +10 CW f
       +(sam)1153 840 w
       +10 R f
       +(command language looks much like that of)6 1744 1 1361 840 t
       +10 CW f
       +(ed)3133 840 w
       +10 R f
       +(, but the details are different because)6 1483 1 3253 840 t
       +10 CW f
       +(sam)4764 840 w
       +10 R f
       +(is)4973 840 w
       +( example, the simple address)4 1151( For)1 189(not line\255oriented.)1 688 3 720 960 t
       +10 CW f
       +(/string/)1080 1140 w
       +10 R f
       +( there are short\255)3 646( Although)1 434( not the next line containing ``string''.)6 1565(matches the next occurrence of ``string'',)5 1675 4 720 1320 t
       +(hands to simplify common actions, the idea of a line must be stated explicitly in)14 3196 1 720 1440 t
       +10 CW f
       +(sam)3941 1440 w
       +10 R f
       +(.)4121 1440 w
       +10 CW f
       +(Sam)970 1596 w
       +10 R f
       +(has the same simple text addition and modification commands)8 2509 1 1177 1596 t
       +10 CW f
       +(ed)3713 1596 w
       +10 R f
       +(has:)3860 1596 w
       +10 CW f
       +(a)4048 1596 w
       +10 R f
       +(adds text after the cur\255)4 905 1 4135 1596 t
       +(rent location,)1 527 1 720 1716 t
       +10 CW f
       +(i)1272 1716 w
       +10 R f
       +(adds text before it,)3 743 1 1357 1716 t
       +10 CW f
       +(d)2125 1716 w
       +10 R f
       +(deletes it, and)2 552 1 2210 1716 t
       +10 CW f
       +(c)2787 1716 w
       +10 R f
       +(replaces it.)1 432 1 2872 1716 t
       +(Unlike in)1 376 1 970 1872 t
       +10 CW f
       +(ed)1372 1872 w
       +10 R f
       +(, the current location in)4 933 1 1492 1872 t
       +10 CW f
       +(sam)2451 1872 w
       +10 R f
       +( simplifies some)2 660( This)1 230( \(and usually isn't\) a line.)5 1031(need not be)2 462 4 2657 1872 t
       +( example,)1 397( For)1 198(operations considerably.)1 985 3 720 1992 t
       +10 CW f
       +(ed)2334 1992 w
       +10 R f
       +( a file.)2 268(has several ways to delete all occurrences of a string in)10 2284 2 2488 1992 t
       +(One method is)2 583 1 720 2112 t
       +10 CW f
       +(g/string/ s///g)1 900 1 1080 2292 t
       +10 R f
       +( substitute command is used to delete text within a line, while a delete command is)15 3369(It is symptomatic that a)4 951 2 720 2472 t
       +( deleted contains a newline, this technique doesn't work.)8 2271( if the string to be)5 718( Also,)1 266(used to delete whole lines.)4 1065 4 720 2592 t
       +( just an array of characters, but some characters are more equal than others.\))13 3310(\(A file is)2 395 2 720 2712 t
       +10 CW f
       +(Sam)4496 2712 w
       +10 R f
       +(is more)1 318 1 4722 2712 t
       +(forthright:)720 2832 w
       +10 CW f
       +(x/string/d)1080 3012 w
       +10 R f
       +(The)720 3192 w
       +10 CW f
       +(x)905 3192 w
       +10 R f
       +( runs the subsequent command)4 1256(\(`extract'\) command searches for each occurrence of the pattern, and)9 2789 2 995 3192 t
       +( that this is subtly different)5 1075( Note)1 244( \(not to the line containing the match\).)7 1532(with the current text set to the match)7 1469 4 720 3312 t
       +(from)720 3432 w
       +10 CW f
       +(ed)940 3432 w
       +10 R f
       +('s)1060 3432 w
       +10 CW f
       +(g)1159 3432 w
       +10 R f
       +(command:)1246 3432 w
       +10 CW f
       +(x)1695 3432 w
       +10 R f
       +(extracts the complete text for the command,)6 1767 1 1782 3432 t
       +10 CW f
       +(g)3576 3432 w
       +10 R f
       +( is also)2 282( There)1 284(merely selects lines.)2 811 3 3663 3432 t
       +(a complement to)2 666 1 720 3552 t
       +10 CW f
       +(x)1411 3552 w
       +10 R f
       +(, called)1 288 1 1471 3552 t
       +10 CW f
       +(y)1784 3552 w
       +10 R f
       +(, that extracts the pieces)4 956 1 1844 3552 t
       +10 I f
       +(between)2825 3552 w
       +10 R f
       +(the matches of the pattern.)4 1056 1 3177 3552 t
       +(The)970 3708 w
       +10 CW f
       +(x)1151 3708 w
       +10 R f
       +(command is a loop, and)4 956 1 1237 3708 t
       +10 CW f
       +(sam)2220 3708 w
       +10 R f
       +(has a corresponding conditional command, called)5 1990 1 2427 3708 t
       +10 CW f
       +(g)4444 3708 w
       +10 R f
       +(\(unrelated to)1 509 1 4531 3708 t
       +10 CW f
       +(ed)720 3828 w
       +10 R f
       +('s)840 3828 w
       +10 CW f
       +(g)937 3828 w
       +10 R f
       +(\):)997 3828 w
       +10 CW f
       +(g/pattern/command)1080 4008 w
       +10 R f
       +( that it does not loop, and it does not change)10 1783( Note)1 246( matches the pattern.)3 832(runs the command if the current text)6 1459 4 720 4188 t
       +( lines con\255)2 424( the command to print all)5 1033( Hence)1 309(the current text; it merely selects whether a command will run.)10 2554 4 720 4308 t
       +(taining a string is)3 692 1 720 4428 t
       +10 CW f
       +(x/.*\\n/ g/string/p)1 1080 1 1080 4608 t
       +10 S1 f
       +(\320)720 4788 w
       +10 R f
       +( reverse conditional is)3 891( The)1 209( contains the string.)3 795(extract all the lines, and print each one that)8 1740 4 848 4788 t
       +10 CW f
       +(v)4512 4788 w
       +10 R f
       +(, so to print)3 468 1 4572 4788 t
       +(all lines containing `rob' but not `robot':)6 1621 1 720 4908 t
       +10 CW f
       +(x/.*\\n/ g/rob/ v/robot/p)2 1440 1 1080 5088 t
       +10 R f
       +(A more dramatic example is to capitalize all occurrences of words `i':)11 2790 1 720 5268 t
       +10 CW f
       +(x/[A\255Za\255z]+/ g/i/ v/../ c/I/)3 1680 1 1080 5448 t
       +10 S1 f
       +(\320)720 5628 w
       +10 R f
       +( more characters, and change the)5 1316(extract all the words, find those that contain `i', reject those with two or)13 2878 2 846 5628 t
       +( people have overcome the dif\255)5 1253( Some)1 282(string to `I' \(borrowing a little syntax from the substitute command\).)10 2785 3 720 5748 t
       +( expressions,)1 530(ficulty of selecting words or identifiers using regular expressions by adding notation to the)13 3790 2 720 5868 t
       +( the precise definition of `identifier' is immutable in the implementation.)10 3006(which has the disadvantage that)4 1314 2 720 5988 t
       +(With)720 6108 w
       +10 CW f
       +(sam)945 6108 w
       +10 R f
       +(, the definition is part of the program and easy to change, although more long\255winded.)14 3442 1 1125 6108 t
       +(The program to capitalize `i's should be writable as)8 2057 1 970 6264 t
       +10 CW f
       +(x/[A\255Za\255z]+/ g/^i$/ c/I/)2 1440 1 1080 6444 t
       +10 R f
       +(That is, the definition of)4 977 1 720 6624 t
       +10 CW f
       +(^)1724 6624 w
       +10 R f
       +(and)1811 6624 w
       +10 CW f
       +($)1982 6624 w
       +10 R f
       +( compatibility and because of)4 1188( For)1 192( input.)1 259(should reflect the structure of the)5 1332 4 2069 6624 t
       +(some problems in the implementation, however,)5 1929 1 720 6744 t
       +10 CW f
       +(^)2674 6744 w
       +10 R f
       +(and)2759 6744 w
       +10 CW f
       +($)2928 6744 w
       +10 R f
       +(in)3013 6744 w
       +10 CW f
       +(sam)3116 6744 w
       +10 R f
       +(always match line boundaries.)3 1209 1 3321 6744 t
       +(In)970 6900 w
       +10 CW f
       +(ed)1078 6900 w
       +10 R f
       +( each global is still)4 754(, it would not be very useful to nest global commands because the `output' of)14 3088 2 1198 6900 t
       +( However,)1 445(a line.)1 249 2 720 7020 t
       +10 CW f
       +(sam)1444 7020 w
       +10 R f
       +( benefit comes from separating)4 1256( \(This)1 266('s extract commands can be nested effectively.)6 1894 3 1624 7020 t
       +( problem of changing all occurrences of the variable)8 2131( the)1 152( Consider)1 416(the notions of looping and matching.\))5 1530 4 720 7140 t
       +10 CW f
       +(n)4980 7140 w
       +10 R f
       +(in a C program to some other name, say)8 1595 1 720 7260 t
       +10 CW f
       +(num)2340 7260 w
       +10 R f
       +( method above will work)4 999(. The)1 230 2 2520 7260 t
       +10 S1 f
       +(\320)3774 7260 w
       +cleartomark
       +showpage
       +saveobj restore
       +%%EndPage: 4 4
       +%%Page: 5 5
       +/saveobj save def
       +mark
       +5 pagesetup
       +10 R f
       +(\255 5 \255)2 166 1 2797 480 t
       +10 CW f
       +(x/[a\255zA\255Z0\2559]+/ g/n/ v/../ c/num/)3 1980 1 1080 900 t
       +10 S1 f
       +(\320)720 1080 w
       +10 R f
       +( are places in C where the `identifier')7 1508(except that there)2 663 2 847 1080 t
       +10 CW f
       +(n)3046 1080 w
       +10 R f
       +(occurs but not as a variable, in particular as the)9 1906 1 3134 1080 t
       +(constant)720 1200 w
       +10 CW f
       +(\\n)1081 1200 w
       +10 R f
       +( cou\255)1 204( prevent incorrect changes, the command can be prefixed by a)10 2503( To)1 164(in characters or strings.)3 940 4 1229 1200 t
       +(ple of)1 230 1 720 1320 t
       +10 CW f
       +(y)975 1320 w
       +10 R f
       +(commands to weed out characters and strings:)6 1841 1 1060 1320 t
       +10 CW f
       +(y/".*"/ y/'.*'/ x/[a\255zA\255Z0\2559]+/ g/n/ v/../ c/num/)5 2940 1 1080 1500 t
       +10 R f
       +(This example illustrates the power of composing extractions and conditionals, but it is not artificial: it)15 4070 1 970 1716 t
       +(was encountered when editing a real program \(in fact,)8 2192 1 720 1836 t
       +10 CW f
       +(sam)2942 1836 w
       +10 R f
       +( with shell pipe\255)3 659( is an obvious analogy)4 914(\). There)1 345 3 3122 1836 t
       +(lines, but these command)3 1019 1 720 1956 t
       +10 I f
       +(chains)1765 1956 w
       +10 R f
       +(are subtly)1 393 1 2052 1956 t
       +10 S1 f
       +(\320)2472 1956 w
       +10 R f
       +(and importantly)1 638 1 2599 1956 t
       +10 S1 f
       +(\320)3264 1956 w
       +10 R f
       +( flows into)2 432( Data)1 240(different from pipelines.)2 977 3 3391 1956 t
       +( chains, the data flow is implicit:)6 1338( In)1 138( pipeline and emerges transformed from the right end.)8 2194(the left end of a)4 650 4 720 2076 t
       +( commands are operating on the same data \(except that the last element of the chain may modify the)18 4070(all the)1 250 2 720 2196 t
       +( is being)2 345( What)1 269( flows through the chain.)4 1008(text\); the complete operation is done in place; and no data actually)11 2698 4 720 2316 t
       +( in the)2 262(passed from link to link in the chain is a view of the data, until it looks right for the final command)21 4058 2 720 2436 t
       +( data stays the same, only the structure is modified.)9 2045(chain. The)1 446 2 720 2556 t
       +10 B f
       +(More than one line, and less than one line)8 1771 1 720 2796 t
       +10 R f
       +(The standard)1 532 1 970 2952 t
       +9 R f
       +(UNIX)1539 2952 w
       +10 R f
       +(tools have difficulty handling several lines at a time, if they can do so at all.)15 3237 1 1803 2952 t
       +10 CW f
       +(Grep)720 3072 w
       +10 R f
       +(,)960 3072 w
       +10 CW f
       +(sort)1022 3072 w
       +10 R f
       +(and)1299 3072 w
       +10 CW f
       +(diff)1480 3072 w
       +10 R f
       +( if they could operate on larger)6 1296(work on lines only, although it would be useful)8 1987 2 1757 3072 t
       +( as a)2 197(pieces, such)1 491 2 720 3192 t
       +10 CW f
       +(refer)1443 3192 w
       +10 R f
       +(database.)1778 3192 w
       +10 CW f
       +(awk)2206 3192 w
       +10 R f
       +(can be tricked into accepting multiple\255line records, but then the)9 2619 1 2421 3192 t
       +( sub\255pieces \(typically ordinary lines\) by explicit code.)7 2236(actions must break out the)4 1101 2 720 3312 t
       +10 CW f
       +(sed)4119 3312 w
       +10 R f
       +(has a unique and)3 704 1 4336 3312 t
       +(clumsy mechanism for manipulating multiple lines, which few have mastered.)9 3127 1 720 3432 t
       +( a)1 84( Consider)1 426(Structural expressions make it easy to specify multiple\255line actions.)8 2812 3 970 3588 t
       +10 CW f
       +(refer)4332 3588 w
       +10 R f
       +(database,)4672 3588 w
       +( percent sign and)3 685( line of a record begins with a)7 1210( Each)1 252(which has multi\255line records separated by blank lines.)7 2173 4 720 3708 t
       +( the line:)2 366(a character indicating the type of information on)7 1981 2 720 3828 t
       +10 CW f
       +(A)3100 3828 w
       +10 R f
       +(for author,)1 429 1 3193 3828 t
       +10 CW f
       +(T)3655 3828 w
       +10 R f
       +( with)1 211( Staying)1 364(for title, etc.)2 504 3 3748 3828 t
       +10 CW f
       +(sam)4860 3828 w
       +10 R f
       +(notation, the command to search a)5 1370 1 720 3948 t
       +10 CW f
       +(refer)2115 3948 w
       +10 R f
       +(database for all papers written by Bimmler is:)7 1828 1 2440 3948 t
       +10 CW f
       +(x/\(.+\\n\)+/ g/%A.*Bimmler/p)1 1560 1 1080 4128 t
       +10 S1 f
       +(\320)720 4308 w
       +10 R f
       +( set of lines containing `Bimm\255)5 1257(break the file into non\255empty sequences of non\255empty lines and print any)11 2937 2 846 4308 t
       +( be compatible with the other tools, a `)8 1572( \(To)1 198( after `%A'.)2 486(ler' on a line)3 522 4 720 4428 t
       +10 CW f
       +(.)3498 4428 w
       +10 R f
       +( Except)1 331(' does not match a newline.\))5 1151 2 3558 4428 t
       +(for the structural expression, this is a regular)7 1836 1 720 4548 t
       +10 CW f
       +(grep)2589 4548 w
       +10 R f
       +( that)1 184(operation, implying)1 797 2 2862 4548 t
       +10 CW f
       +(grep)3877 4548 w
       +10 R f
       +(could benefit from an)3 889 1 4151 4548 t
       +( `stream)1 339( the short term, however, a)5 1147( In)1 149(additional regular expression to define the structure of its input.)9 2685 4 720 4668 t
       +10 CW f
       +(sam)720 4788 w
       +10 R f
       +(,' analogous to)2 591 1 900 4788 t
       +10 CW f
       +(sed,)1516 4788 w
       +10 R f
       +(would be convenient, and is currently being implemented.)7 2322 1 1781 4788 t
       +( example, we can)3 713( For)1 196( search program.)2 681(The ability to compose expressions makes it easy to tune the)10 2480 4 970 4944 t
       +(select just the)2 544 1 720 5064 t
       +10 I f
       +(titles)1289 5064 w
       +10 R f
       +(of the papers written by Bimmler by applying another extraction:)9 2605 1 1509 5064 t
       +10 CW f
       +(x/\(.+\\n\)+/ g/%A.*Bimmler/ x/.*\\n/ g/%T/p)3 2400 1 1080 5244 t
       +10 R f
       +( into individual lines, then prints the lines con\255)8 1912(This program breaks the records with author Bimmler back)8 2408 2 720 5424 t
       +(taining)720 5544 w
       +10 CW f
       +(%T)1023 5544 w
       +10 R f
       +(.)1143 5544 w
       +( examples of multiple\255line components of files that may profitably be extracted,)11 3212(There are many other)3 858 2 970 5700 t
       +(such as C functions, messages in mail boxes, paragraphs in)9 2415 1 720 5820 t
       +10 CW f
       +(troff)3166 5820 w
       +10 R f
       +( records in on\255line telephone)4 1162(input and)1 381 2 3497 5820 t
       +( that, unlike in systems that define file structures)8 1948(books. Note)1 509 2 720 5940 t
       +10 I f
       +(a priori)1 310 1 3203 5940 t
       +10 R f
       +(, the structures are applied by the pro\255)7 1527 1 3513 5940 t
       +( sometimes a C)3 644( means the structure can change from application to application;)9 2658( This)1 239(gram, not the data.)3 779 4 720 6060 t
       +( and sometimes it is just a byte)7 1343(program is an array of functions, but sometimes it is an array of lines,)13 2977 2 720 6180 t
       +(stream.)720 6300 w
       +( determine the appearance of their input,)6 1627(If the standard commands admitted a structural expression to)8 2443 2 970 6456 t
       +( a version of)3 553(many currently annoying problems could become simple: imagine)7 2790 2 720 6576 t
       +10 CW f
       +(diff)4107 6576 w
       +10 R f
       +(that could print)2 649 1 4391 6576 t
       +( or functions instead of changed lines, or a)8 1710(changed sentences)1 740 2 720 6696 t
       +10 CW f
       +(sort)3197 6696 w
       +10 R f
       +(that could sort a)3 647 1 3464 6696 t
       +10 CW f
       +(refer)4138 6696 w
       +10 R f
       +(database. The)1 575 1 4465 6696 t
       +(case of)1 281 1 720 6816 t
       +10 CW f
       +(sort)1028 6816 w
       +10 R f
       +( the input records be described by a struc\255)8 1676(is particularly interesting: not only can the shape of)8 2069 2 1295 6816 t
       +( current bewildering maze of options to control the)8 2062( The)1 209(tural expression, but also the shape of the sort key.)9 2049 3 720 6936 t
       +( be largely replaced by a structural expression to extract the key from the record, with)15 3434(sort could in principle)3 886 2 720 7056 t
       +(multiple expressions to define multiple keys.)5 1794 1 720 7176 t
       +cleartomark
       +showpage
       +saveobj restore
       +%%EndPage: 5 5
       +%%Page: 6 6
       +/saveobj save def
       +mark
       +6 pagesetup
       +10 R f
       +(\255 6 \255)2 166 1 2797 480 t
       +10 B f
       +(The)720 840 w
       +10 CW f
       +(awk)912 840 w
       +10 B f
       +(of the future?)2 582 1 1117 840 t
       +10 R f
       +(It is entertaining to imagine a version of)7 1622 1 970 996 t
       +10 CW f
       +(awk)2621 996 w
       +10 R f
       +( as discussed)2 524( First,)1 263(that applies these ideas throughout.)4 1423 3 2830 996 t
       +( For)1 199( to the actions would be defined, rather than merely selected, by the patterns.)13 3196(earlier, the text passed)3 925 3 720 1116 t
       +(example,)720 1236 w
       +10 CW f
       +(/#+/ { print })3 840 1 1080 1416 t
       +10 R f
       +(would print only)2 667 1 720 1596 t
       +10 CW f
       +(#)1412 1596 w
       +10 R f
       +(characters; conventional)1 972 1 1497 1596 t
       +10 CW f
       +(awk)2494 1596 w
       +10 R f
       +(would instead print every line containing)5 1640 1 2699 1596 t
       +10 CW f
       +(#)4364 1596 w
       +10 R f
       +(characters.)4449 1596 w
       +( of using the restrictive idea of a)7 1317( Instead)1 342( parsed.)1 314(Next, the expressions would define how the input is)8 2097 4 970 1752 t
       +( instance, in)2 496( For)1 197( demarcate fields.)2 722(field separator, the iterations implied by closures in the expression can)10 2905 4 720 1872 t
       +(the program)1 485 1 720 1992 t
       +10 CW f
       +(/\(.+\\n\)+/ {)1 660 1 1080 2172 t
       +10 I f
       +(action)1800 2172 w
       +10 CW f
       +(})2110 2172 w
       +10 R f
       +( lines, but the outermost closure \(the)6 1481(the action sees groups of)4 999 2 720 2352 t
       +10 CW f
       +(+)3229 2352 w
       +10 R f
       +(operator\) examines, and hence can extract,)5 1722 1 3318 2352 t
       +(the individual lines.)2 802 1 720 2472 t
       +10 CW f
       +(ed)1577 2472 w
       +10 R f
       +( We)1 192( back\255referencing operators.)2 1128(uses parentheses to define sub\255expressions for its)6 1993 3 1727 2472 t
       +( to define the `fields' in)5 980(can modify this idea)3 834 2 720 2592 t
       +10 CW f
       +(awk)2567 2592 w
       +10 R f
       +(, so)1 147 1 2747 2592 t
       +10 CW f
       +($1)2927 2592 w
       +10 R f
       +(defines the first element of the closure \(the first)8 1960 1 3080 2592 t
       +(line\),)720 2712 w
       +10 CW f
       +($2)961 2712 w
       +10 R f
       +( arrays, so the)3 575( interestingly, the closures could generate indices for)7 2163( More)1 274(the second, and so on.)4 914 4 1114 2712 t
       +( say,)1 191(fields would be called,)3 925 2 720 2832 t
       +10 CW f
       +(input[1])1869 2832 w
       +10 R f
       +(and so on, perhaps with the unadorned identifier)7 1986 1 2382 2832 t
       +10 CW f
       +(input)4401 2832 w
       +10 R f
       +(holding)4734 2832 w
       +( generate multi\255dimensional)2 1163( has the advantage that nested closures can)7 1858( This)1 250(the original intact string.)3 1049 4 720 2952 t
       +( is some subtlety involving the relationship between)7 2192( \(There)1 331(arrays, which is notationally clean.)4 1456 3 720 3072 t
       +10 CW f
       +(input)4740 3072 w
       +10 R f
       +(indices and the order of the closures in the pattern, but the details are not important here.\))16 3571 1 720 3192 t
       +(Finally, as in)2 524 1 970 3348 t
       +10 CW f
       +(sam)1521 3348 w
       +10 R f
       +( expressions would be applicable to the output of structural expressions;)10 2910(, structural)1 429 2 1701 3348 t
       +( following program computes)3 1185( The)1 205( actions.)1 333(that is, we would be able to nest structural expressions inside the)11 2597 4 720 3468 t
       +(how many pages of articles Bimmler has written:)7 1967 1 720 3588 t
       +10 CW f
       +( break into records)3 1140(/\(.+\\n\)+/{ #)1 900 2 1080 3768 t
       +( is Bimmler author? \(see text\))5 1800( #)1 300(input ~ /%A.*Bimmler/{)2 1320 3 1330 3888 t
       +( extract page numbers)3 1260(/%P.*\([0\2559]+\)\255\([0\2559]+\)/{ #)1 1740 2 1580 4008 t
       +(pages+=input[2]\255input[1]+1)1830 4128 w
       +(})1580 4248 w
       +(})1330 4368 w
       +(})1080 4488 w
       +(END{)1080 4608 w
       +(print pages)1 660 1 1330 4728 t
       +(})1080 4848 w
       +10 R f
       +(Real)720 5028 w
       +10 CW f
       +(awk)935 5028 w
       +10 R f
       +( \(that is, regular expressions\) only like)6 1582(uses patterns)1 520 2 1147 5028 t
       +10 CW f
       +(sam)3282 5028 w
       +10 R f
       +('s)3462 5028 w
       +10 CW f
       +(g)3567 5028 w
       +10 R f
       +(command, but our)2 746 1 3660 5028 t
       +10 CW f
       +(awk)4439 5028 w
       +10 R f
       +('s patterns)1 421 1 4619 5028 t
       +(are)720 5148 w
       +10 CW f
       +(x)871 5148 w
       +10 R f
       +( is why in the pro\255)5 750( This)1 232( we need both to exploit structural expressions well.)8 2110(expressions. Obviously,)1 987 4 961 5148 t
       +(gram above the test for whether)5 1276 1 720 5268 t
       +10 CW f
       +(input)2024 5268 w
       +10 R f
       +(contains a paper by Bimmler must be written as an explicit pattern)11 2688 1 2352 5268 t
       +( separated by a dash, which is how)7 1393( innermost pattern searches for lines containing two numbers)8 2451(match. The)1 476 3 720 5388 t
       +10 CW f
       +(refer)720 5508 w
       +10 R f
       +(stores the starting and ending pages of the article.)8 1977 1 1045 5508 t
       +( real)1 178( The)1 209(This is a contrived example, of course, but it illustrates the basic ideas.)12 2875 3 970 5664 t
       +10 CW f
       +(awk)4261 5664 w
       +10 R f
       +(suffers from a)2 569 1 4471 5664 t
       +( making the parsing actions of the)6 1366( would be improved by)4 939( It)1 114(mismatch between the patterns and the actions.)6 1901 4 720 5784 t
       +( lan\255)1 185( A)1 127( pattern\255matching abilities available in the actions.)6 2047(patterns visible in the actions, and by having the)8 1961 4 720 5904 t
       +(guage with regular expressions should not base its text manipulation on a)11 2928 1 720 6024 t
       +10 CW f
       +(substr)3673 6024 w
       +10 R f
       +(function.)4058 6024 w
       +10 B f
       +(Comments)720 6264 w
       +10 R f
       +( is a powerful and convenient, if unfa\255)7 1545(The use of regular expressions to describe the structure of files)10 2525 2 970 6420 t
       +( current)1 308(miliar, way to address a number of difficulties the)8 2010 2 720 6540 t
       +9 R f
       +(UNIX)3062 6540 w
       +10 R f
       +( is obviously around this)4 988( There)1 283(tools share.)1 456 3 3313 6540 t
       +( all.)1 172(new notation a number of interesting problems, and I am not pretending to have addressed them)15 4148 2 720 6660 t
       +( the possibilities,)2 678(Rather, I have skipped enthusiastically from example to example to indicate the breadth of)13 3642 2 720 6780 t
       +( these ideas, and perhaps to)5 1117( hope is to encourage others to think about)8 1730( My)1 193(not the depth of the difficulties.)5 1280 4 720 6900 t
       +(apply them to old tools as well as new ones.)9 1760 1 720 7020 t
       +cleartomark
       +showpage
       +saveobj restore
       +%%EndPage: 6 6
       +%%Page: 7 7
       +/saveobj save def
       +mark
       +7 pagesetup
       +10 R f
       +(\255 7 \255)2 166 1 2797 480 t
       +10 B f
       +(Acknowledgements)720 840 w
       +10 R f
       +( some of their ideas)4 806(John Linderman, Chris Van Wyk, Tom Duff and Norman Wilson will recognize)11 3264 2 970 996 t
       +( hope I have not misrepresented them.)6 1522( I)1 83(in these notes.)2 569 3 720 1116 t
       +cleartomark
       +showpage
       +saveobj restore
       +%%EndPage: 7 7
       +%%Trailer
       +done
       +%%Pages: 7
       +%%DocumentFonts: Times-Roman Times-Bold Times-Italic Times-Roman Courier
 (DIR) diff --git a/include/frame.h b/include/frame.h
       @@ -0,0 +1,72 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +
       +typedef struct Frbox Frbox;
       +typedef struct Frame Frame;
       +
       +struct Frbox
       +{
       +        long                wid;                /* in pixels */
       +        long                nrune;                /* <0 ==> negate and treat as break char */
       +        union{
       +                uchar        *ptr;
       +                struct{
       +                        short        bc;        /* break char */
       +                        short        minwid;
       +                } b;
       +        } a;
       +};
       +
       +struct Frame
       +{
       +        XftFont                *font;                /* of chars in the frame */
       +        Bitmap                *b;                /* on which frame appears */
       +        Rectangle        r;                /* in which text appears */
       +        Rectangle        entire;                /* of full frame */
       +        Frbox                *box;
       +        ulong                p0, p1;                /* selection */
       +        short                left;                /* left edge of text */
       +        ushort                nbox, nalloc;
       +        ushort                maxtab;                /* max size of tab, in pixels */
       +        ushort                nchars;                /* # runes in frame */
       +        ushort                nlines;                /* # lines with text */
       +        ushort                maxlines;        /* total # lines in frame */
       +        ushort                lastlinefull;        /* last line fills frame */
       +        ushort                modified;        /* changed since frselect() */
       +};
       +
       +ulong        frcharofpt(Frame*, Point);
       +Point        frptofchar(Frame*, ulong);
       +int        frdelete(Frame*, ulong, ulong);
       +void        frinsert(Frame*, Rune*, Rune*, ulong);
       +void        frselect(Frame*, Mouse*);
       +void        frselectp(Frame*, Fcode);
       +void        frselectf(Frame*, Point, Point, Fcode);
       +void        frinit(Frame*, Rectangle, XftFont*, Bitmap*);
       +void        frsetrects(Frame*, Rectangle, Bitmap*);
       +void        frclear(Frame*);
       +void        frgetmouse(void);
       +
       +uchar        *_frallocstr(unsigned);
       +void        _frinsure(Frame*, int, unsigned);
       +Point        _frdraw(Frame*, Point);
       +void        _frgrowbox(Frame*, int);
       +void        _frfreebox(Frame*, int, int);
       +void        _frmergebox(Frame*, int);
       +void        _frdelbox(Frame*, int, int);
       +void        _frsplitbox(Frame*, int, int);
       +int        _frfindbox(Frame*, int, ulong, ulong);
       +void        _frclosebox(Frame*, int, int);
       +int        _frcanfit(Frame*, Point, Frbox*);
       +void        _frcklinewrap(Frame*, Point*, Frbox*);
       +void        _frcklinewrap0(Frame*, Point*, Frbox*);
       +void        _fradvance(Frame*, Point*, Frbox*);
       +int        _frnewwid(Frame*, Point, Frbox*);
       +void        _frclean(Frame*, Point, int, int);
       +void        _frredraw(Frame*, Point);
       +void        _fraddbox(Frame*, int, int);
       +Point        _frptofcharptb(Frame*, ulong, Point, int);
       +Point        _frptofcharnb(Frame*, ulong, int);
       +int        _frstrlen(Frame*, int);
       +
       +#define        NRUNE(b)        ((b)->nrune<0? 1 : (b)->nrune)
       +#define        NBYTE(b)        strlen((char*)(b)->a.ptr)
 (DIR) diff --git a/include/libc.h b/include/libc.h
       @@ -0,0 +1,54 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +
       +        /* Plan 9 C library interface */
       +
       +
       +#define        sprint                                sprintf
       +#define        dup(a,b)                        dup2(a,b)
       +#define        seek(a,b,c)                        lseek(a,b,c)
       +#define        create(name, mode, perm)        creat(name, perm)
       +#define        exec(a,b)                        execv(a,b)
       +#define        USED(a)
       +#define SET(a)
       +
       +#define        _exits(v)                        if (v!=0) _exit(1); else _exit(0)
       +
       +enum
       +{
       +        OREAD        =        0,                /* open for read */
       +        OWRITE        =        1,                /* open for write */
       +        ORDWR        =        2,                /* open for read/write */
       +        ERRLEN        =        64                /* length of error message */
       +};
       +
       +enum
       +{
       +        UTFmax                = 3,                /* maximum bytes per rune */
       +        Runesync        = 0x80,                /* cannot represent part of a utf sequence (<) */
       +        Runeself        = 0x80,                /* rune and utf sequences are the same (<) */
       +        Runeerror        = 0x80                /* decoding error in utf */
       +};
       +
       +/*
       + * new rune routines
       + */
       +extern        int        runetochar(char*, Rune*);
       +extern        int        chartorune(Rune*, char*);
       +extern        int        runelen(long);
       +extern        int        fullrune(char*, int);
       +
       +/*
       + * rune routines from converted str routines
       + */
       +extern        int        utflen(char*);                /* was countrune */
       +extern        char*        utfrune(char*, long);
       +extern        char*        utfrrune(char*, long);
       +extern        char*        utfutf(char*, char*);
       +/*
       + *        Miscellaneous functions
       + */
       +extern        void        fprint(int, char *, ...);
       +extern        int        notify (void(*)(void *, char *));
       +extern        int        errstr(char *);
       +extern        char*        getuser(void);
       +extern        void        exits(char*);
 (DIR) diff --git a/include/libg.h b/include/libg.h
       @@ -0,0 +1,225 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#ifndef _LIBG_H
       +#define _LIBG_H
       +
       +#ifndef _LIBXG_EXTENSION
       +    This header file is not defined in pure ANSI/POSIX
       +#endif
       +/*
       + *  Like Plan9's libg.h, but suitable for inclusion on non-Plan9 machines
       + */
       +
       +#define Cursor xCursor
       +#include <X11/Xft/Xft.h>
       +#undef Cursor
       +
       +enum{ EMAXMSG = 128+8192 };        /* max event size */
       +
       +/*
       + * Types
       + */
       +
       +typedef        struct        Bitmap                Bitmap;
       +typedef struct        Point                Point;
       +typedef struct        Rectangle         Rectangle;
       +typedef struct        Cursor                Cursor;
       +typedef struct        Mouse                Mouse;
       +typedef struct        Menu                Menu;
       +typedef struct        Event                Event;
       +typedef struct        RGB                RGB;
       +
       +struct        Point
       +{
       +        int        x;
       +        int        y;
       +};
       +
       +struct Rectangle
       +{
       +        Point min;
       +        Point max;
       +};
       +
       +struct        Bitmap
       +{
       +        Rectangle r;                /* rectangle in data area, local coords */
       +        Rectangle clipr;        /* clipping region */
       +        int        ldepth;
       +        int        id;                /* as known by the X server */
       +        Bitmap        *cache;                /* zero; distinguishes bitmap from layer */
       +        int        flag;                /* flag used by X implementation of libg */
       +};
       +
       +struct        Mouse
       +{
       +        int                buttons; /* bit array: LMR=124 */
       +        Point                xy;
       +        unsigned long        msec;
       +};
       +
       +struct        Cursor
       +{
       +        Point                offset;
       +        unsigned char        clr[2*16];
       +        unsigned char        set[2*16];
       +        int                id;        /* init to zero; used by library */
       +};
       +
       +struct Menu
       +{
       +        char        **item;
       +        char        *(*gen)(int);
       +        int        lasthit;
       +};
       +
       +struct        Event
       +{
       +        int                kbdc;
       +        Mouse                mouse;
       +        int                n;                /* number of characters in mesage */
       +        unsigned char        data[EMAXMSG];        /* message from an arbitrary file descriptor */
       +};
       +
       +struct RGB
       +{
       +        unsigned long        red;
       +        unsigned long        green;
       +        unsigned long        blue;
       +};
       +
       +/*
       + * Codes for bitblt etc.
       + *
       + *               D
       + *             0   1
       + *         ---------
       + *         0 | 1 | 2 |
       + *     S   |---|---|
       + *          1 | 4 | 8 |
       + *         ---------
       + *
       + *        Usually used as D|S; DorS is so tracebacks are readable.
       + */
       +typedef
       +enum        Fcode
       +{
       +        Zero                = 0x0,
       +        DnorS                = 0x1,
       +        DandnotS        = 0x2,
       +        notS                = 0x3,
       +        notDandS        = 0x4,
       +        notD                = 0x5,
       +        DxorS                = 0x6,
       +        DnandS                = 0x7,
       +        DandS                = 0x8,
       +        DxnorS                = 0x9,
       +        D                = 0xA,
       +        DornotS                = 0xB,
       +        S                = 0xC,
       +        notDorS                = 0xD,
       +        DorS                = 0xE,
       +        F                = 0xF
       +} Fcode;
       +
       +/*
       + * Miscellany
       + */
       +
       +typedef void         (*Errfunc)(char *);
       +
       +extern Point         add(Point, Point);
       +extern Point         sub(Point, Point);
       +extern Point         mul(Point, int);
       +extern Point         divpt(Point, int);
       +extern Rectangle rsubp(Rectangle, Point);
       +extern Rectangle raddp(Rectangle, Point);
       +extern Rectangle inset(Rectangle, int);
       +extern Rectangle rmul(Rectangle, int);
       +extern Rectangle rdiv(Rectangle, int);
       +extern Rectangle rshift(Rectangle, int);
       +extern Rectangle rcanon(Rectangle);
       +extern Bitmap*         balloc(Rectangle, int);
       +extern void         bfree(Bitmap*);
       +extern int         rectclip(Rectangle*, Rectangle);
       +extern void         xtbinit(Errfunc, char*, int*, char**, char**);
       +extern void         bclose(void);
       +extern void         berror(char*);
       +extern void         bitblt(Bitmap*, Point, Bitmap*, Rectangle, Fcode);
       +extern void         copymasked(Bitmap*, Point, Bitmap*, Bitmap*, Rectangle);
       +extern int         bitbltclip(void*);
       +extern Point         string(Bitmap*, Point, XftFont*, char*, Fcode);
       +extern void         segment(Bitmap*, Point, Point, int, Fcode);
       +extern void         point(Bitmap*, Point, int, Fcode);
       +extern void         arc(Bitmap*, Point, Point, Point, int, Fcode);
       +extern void         circle(Bitmap*, Point, int, int, Fcode);
       +extern void         disc(Bitmap*, Point, int, int, Fcode);
       +extern void         ellipse(Bitmap*, Point, int, int, int, Fcode);
       +extern void         polysegment(Bitmap *, int, Point *, int, Fcode);
       +extern long         strwidth(XftFont*, char*);
       +extern Point         strsize(XftFont*, char*);
       +extern long         charwidth(XftFont*, Rune);
       +extern void         texture(Bitmap*, Rectangle, Bitmap*, Fcode);
       +extern void         wrbitmap(Bitmap*, int, int, unsigned char*);
       +extern void         rdbitmap(Bitmap*, int, int, unsigned char*);
       +extern void         wrbitmapfile(int, Bitmap*);
       +extern Bitmap*         rdbitmapfile(int);
       +extern int         ptinrect(Point, Rectangle);
       +extern int         rectXrect(Rectangle, Rectangle);
       +extern int         eqpt(Point, Point);
       +extern int         eqrect(Rectangle, Rectangle);
       +extern void         border(Bitmap*, Rectangle, int, Fcode);
       +extern void         cursorswitch(Cursor*);
       +extern void         cursorset(Point);
       +extern Rectangle bscreenrect(Rectangle*);
       +extern void         bflush(void);
       +extern int         clipline(Rectangle, Point*, Point*);
       +extern int         clipr(Bitmap*, Rectangle);
       +extern int         scrpix(int*,int*);
       +
       +extern void         einit(unsigned long);
       +extern unsigned long estart(unsigned long, int, int);
       +extern unsigned long etimer(unsigned long, long);
       +extern unsigned long event(Event*);
       +extern unsigned long eread(unsigned long, Event*);
       +extern Mouse         emouse(void);
       +extern int         ekbd(void);
       +extern int         ecanread(unsigned long);
       +extern int         ecanmouse(void);
       +extern int         ecankbd(void);
       +extern void         ereshaped(Rectangle);        /* supplied by user */
       +extern void         eflush(unsigned long);
       +extern int         menuhit(int, Mouse*, Menu*);
       +extern Rectangle getrect(int, Mouse*);
       +extern unsigned long rgbpix(Bitmap*, RGB);
       +extern void         rdcolmap(Bitmap*, RGB*);
       +extern void         wrcolmap(Bitmap*, RGB*);
       +
       +/* Extra functions supplied by libXg */
       +extern int        snarfswap(char*, int, char**);
       +extern int        scrollfwdbut(void);
       +
       +enum{
       +        Emouse                = 1,
       +        Ekeyboard        = 2
       +};
       +
       +extern Point         Pt(int, int);
       +extern Rectangle Rect(int, int, int, int);
       +extern Rectangle Rpt(Point, Point);
       +
       +
       +#define        Dx(r)        ((r).max.x-(r).min.x)
       +#define        Dy(r)        ((r).max.y-(r).min.y)
       +
       +
       +extern        Bitmap        screen;
       +extern        XftFont        *font;
       +extern  XftColor fontcolor;
       +extern  XftColor bgcolor;
       +
       +#define        BGSHORT(p)                (((p)[0]<<0) | ((p)[1]<<8))
       +#define        BGLONG(p)                ((BGSHORT(p)<<0) | (BGSHORT(p+2)<<16))
       +#define        BPSHORT(p, v)                ((p)[0]=(v), (p)[1]=((v)>>8))
       +#define        BPLONG(p, v)                (BPSHORT(p, (v)), BPSHORT(p+2, (v)>>16))
       +
       +#endif
 (DIR) diff --git a/include/regexp.h b/include/regexp.h
       @@ -0,0 +1,65 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +
       +typedef struct Resub                Resub;
       +typedef struct Reclass                Reclass;
       +typedef struct Reinst                Reinst;
       +typedef struct Reprog                Reprog;
       +
       +/*
       + *        Sub expression matches
       + */
       +struct Resub{
       +        union
       +        {
       +                char *sp;
       +                Rune *rsp;
       +        }s;
       +        union
       +        {
       +                char *ep;
       +                Rune *rep;
       +        }e;
       +};
       +
       +/*
       + *        character class, each pair of rune's defines a range
       + */
       +struct Reclass{
       +        Rune        *end;
       +        Rune        spans[64];
       +};
       +
       +/*
       + *        Machine instructions
       + */
       +struct Reinst{
       +        int        type;
       +        union        {
       +                Reclass        *cp;                /* class pointer */
       +                Rune        r;                /* character */
       +                int        subid;                /* sub-expression id for RBRA and LBRA */
       +                Reinst        *right;                /* right child of OR */
       +        }u1;
       +        union {        /* regexp relies on these two being in the same union */
       +                Reinst *left;                /* left child of OR */
       +                Reinst *next;                /* next instruction for CAT & LBRA */
       +        }u2;
       +};
       +
       +/*
       + *        Reprogram definition
       + */
       +struct Reprog{
       +        Reinst        *startinst;        /* start pc */
       +        Reclass        class[16];        /* .data */
       +        Reinst        firstinst[5];        /* .text */
       +};
       +
       +extern Reprog        *regcomp(char*);
       +extern Reprog        *regcomplit(char*);
       +extern Reprog        *regcompnl(char*);
       +extern void        regerror(char*);
       +extern int        regexec(Reprog*, char*, Resub*, int);
       +extern void        regsub(char*, char*, Resub*, int);
       +extern int        rregexec(Reprog*, Rune*, Resub*, int);
       +extern void        rregsub(Rune*, Rune*, Resub*, int);
 (DIR) diff --git a/include/u.h b/include/u.h
       @@ -0,0 +1,117 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <stdlib.h>
       +#include <string.h>
       +#include <sys/types.h>
       +#include <setjmp.h>
       +#include <stdio.h>
       +#include <unistd.h>
       +#include <fcntl.h>
       +#include <stdint.h>
       +
       +typedef unsigned short      ushort;
       +typedef unsigned long       ulong;
       +typedef unsigned char                uchar;
       +typedef        unsigned short                Rune;
       +
       +        /* System configuration parameters */
       +
       +#ifdef        SYSVR3
       +#include        <malloc.h>
       +typedef        unsigned short        ushort;
       +typedef unsigned long        ulong;
       +#define        remove(v)                        unlink(v)
       +#define        WEXITSTATUS(s)                        (((s)>>8)&0xFF)
       +extern        char *getenv(char*);
       +extern        char *getlogin(void);
       +extern        char *strerror(int);
       +extern        void *memmove(void*, const void*, size_t);
       +#define        NEEDMEMMOVE
       +#define        NEEDSTRERROR
       +#define        NEEDVARARG
       +#endif        /* SYSVR3 */
       +
       +#ifdef        IRIX5
       +typedef        unsigned short        ushort;
       +typedef unsigned long        ulong;
       +#endif        /* IRIX5 */
       +
       +#ifdef        IRIX
       +extern        void *memmove(void*, const void*, size_t);
       +#define        NEEDMEMMOVE
       +#endif        /* IRIX */
       +
       +#ifdef        UMIPS
       +typedef unsigned long        ulong;
       +typedef        unsigned short        ushort;
       +#define        const                        /* mips compiler doesn't support const */
       +extern        char *strerror(int);
       +extern        void *memmove(void*, const void*, size_t);
       +#define        NEEDMEMMOVE
       +#define        NEEDSTRERROR
       +#define        NEEDVARARG
       +#define        NOFIFO                        /* turn off exstart in samterm/unix.c */
       +#endif        /* UMIPS */
       +
       +#ifdef        SUNOS
       +typedef        unsigned short        ushort;
       +typedef unsigned long        ulong;
       +extern        char *strerror(int);
       +extern        void *memmove(void*, const void*, size_t);
       +extern        void *memcpy(void*, const void*, size_t);
       +#define        NEEDMEMMOVE
       +#define        NEEDSTRERROR
       +#endif        /* SUNOS */
       +
       +#ifdef        SOLARIS
       +typedef        unsigned short        ushort;
       +typedef unsigned long        ulong;
       +#endif
       +
       +#ifdef        AIX
       +typedef        unsigned short        ushort;
       +typedef unsigned long        ulong;
       +#endif        /* AIX */
       +
       +#ifdef        OSF1
       +typedef        unsigned short        ushort;
       +typedef unsigned long        ulong;
       +extern        void *memmove(void*, const void*, size_t);
       +#endif        /* OSF1 */
       +
       +#ifdef  HPUX
       +typedef        unsigned short        ushort;
       +typedef unsigned long        ulong;
       +#define        NEEDSTRERROR
       +#endif  /* HPUX */
       +
       +#ifdef  APOLLO
       +typedef        unsigned short        ushort;
       +typedef unsigned long        ulong;
       +#endif  /* APOLLO */
       +
       +#ifdef  CONVEX
       +typedef unsigned long        ulong;
       +#endif  /* CONVEX */
       +
       +#ifdef  DYNIX
       +#define        SIG_ERR                BADSIG
       +#define        NEEDMEMMOVE
       +#define        remove(v)                        unlink(v)
       +#define        WEXITSTATUS(s)                        (((s)>>8)&0xFF)
       +#define        NEEDMEMMOVE
       +#define        NOFIFO                        /* turn off exstart in samterm/unix.c */
       +#endif  /* DYNIX */
       +
       +#ifdef        PTX
       +typedef        unsigned short        ushort;
       +typedef unsigned long        ulong;
       +#endif        /* PTX */
       +
       +#ifdef        BSDi
       +typedef unsigned long   ulong;
       +#endif        /* BSDi */
       +
       +#ifdef        v10
       +typedef        unsigned short        ushort;
       +typedef unsigned long        ulong;
       +#endif
 (DIR) diff --git a/libXg/Gwin.h b/libXg/Gwin.h
       @@ -0,0 +1,43 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#ifndef GWIN_H
       +#define GWIN_H
       +
       +/* New resource names */
       +
       +#define XtNscrollForwardR "scrollForwardR"
       +#define XtCScrollForwardR "ScrollForwardR"
       +#define XtNreshaped "reshaped"
       +#define XtCReshaped "Reshaped"
       +#define XtNgotchar "gotchar"
       +#define XtCGotchar "Gotchar"
       +#define XtNgotmouse "gotmouse"
       +#define XtCGotmouse "Gotmouse"
       +#define XtNp9font   "p9font"
       +#define XtCP9font   "P9font"
       +#define XtNcomposeMod   "composeMod"
       +#define XtCComposeMod   "ComposeMod"
       +
       +/* External reference to the class record pointer */
       +extern WidgetClass gwinWidgetClass;
       +
       +/* Type definition for gwin widgets */
       +typedef struct _GwinRec *GwinWidget;
       +
       +/* Type definition for gwin resources */
       +typedef struct {
       +                int buttons;
       +                struct {
       +                        int x;
       +                        int y;
       +                } xy;
       +                unsigned long msec;
       +        } Gwinmouse;
       +
       +typedef void (*Reshapefunc)(int, int, int, int);
       +typedef void (*Charfunc)(int);
       +typedef void (*Mousefunc)(Gwinmouse*);
       +
       +/* Method declarations */
       +extern String GwinSelectionSwap(Widget, String);
       +
       +#endif /* GWIN_H */
 (DIR) diff --git a/libXg/GwinP.h b/libXg/GwinP.h
       @@ -0,0 +1,45 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#ifndef GWINP_H
       +#define GWINP_H
       +
       +#include "Gwin.h"
       +
       +/* Gwin is derived from Core */
       +
       +/* Gwin instance part */
       +typedef struct {
       +        /* New resource fields */
       +        Pixel                foreground;
       +        Boolean                forwardr;        /* does right button scroll forward? */
       +        Reshapefunc        reshaped;        /* Notify app of reshape */
       +        Charfunc        gotchar;        /* Notify app of char arrival */
       +        Mousefunc        gotmouse;        /* Notify app of mouse change */
       +        String                selection;        /* Current selection */
       +        int                compose;
       +} GwinPart;
       +
       +/* Full instance record */
       +typedef struct _GwinRec {
       +        CorePart        core;
       +        GwinPart        gwin;
       +} GwinRec;
       +
       +/* New type for class methods */
       +typedef String (*SelSwapProc)(Widget, String);
       +
       +/* Class part */
       +typedef struct {
       +        SelSwapProc        select_swap;
       +        XtPointer        extension;
       +} GwinClassPart;
       +
       +/* Full class record */
       +typedef struct _GwinClassRec {
       +        CoreClassPart        core_class;
       +        GwinClassPart        gwin_class;
       +} GwinClassRec, *GwinWidgetClass;
       +
       +/* External definition for class record */
       +extern GwinClassRec gwinClassRec;
       +
       +#endif /* GWINP_H */
 (DIR) diff --git a/libXg/Makefile b/libXg/Makefile
       @@ -0,0 +1,58 @@
       +#        Copyright (c) 1998 Lucent Technologies - All rights reserved.
       +#
       +#        Prototype Makefile for libXg
       +#
       +#        define operating system.  ONE of:
       +#                -DIRIX -DSUNOS -DUMIPS -DSYSVR3 -DAIX -DOSF1
       +#                -DHPUX -DAPOLLO -DCONVEX -DDYNIX
       +#        
       +#        Additionally, -D_POSIX_SOURCE (or its equivalent) may be specified
       +#        if your compiler supports posix-compatible compilation
       +include ../config.mk
       +
       +OS=-DIRIX5 
       +
       +#        add -Iincludedir for any include directories that need to be searched
       +INCS=-I../include -I$(FREETYPEINC)
       +
       +#        set this if your X libraries are in different locations
       +#        or if you need extra libraries to load with X11 applications
       +XLIBS=-lXt
       +
       +#        add name of library orderer - use ":" if none
       +RANLIB=:
       +
       +#        add name of librarian
       +AR=ar
       +
       +#        the name of the library
       +LIB=libXg.a
       +
       +CFLAGS=$(OS) -D_LIBXG_EXTENSION $(INCS)
       +CC=cc
       +
       +OBJS=        arc.o arith.o balloc.o bitblt.o bitbltclip.o border.o bscreenrect.o\
       +        circle.o clipline.o clipr.o copymasked.o cursorset.o cursorswitch.o\
       +        disc.o ellipse.o font.o gcs.o getrect.o gwin.o ldconvert.o latin1.o\
       +        menuhit.o point.o polysegment.o rdbitmap.o rdbitmapfile.o\
       +        rectclip.o rune.o segment.o string.o strwidth.o texture.o\
       +        wrbitmap.o wrbitmapfile.o xtbinit.o
       +
       +all install:        $(LIB)
       +compile:        $(LIB)
       +test:        $(LIB) test.o
       +        $(CC) -o $@ $? $(LIB) $(XLIBS) -lm
       +        echo try running test
       +clean:
       +        rm -f *.o test *.a
       +
       +nuke:        clean
       +        rm -f $(LIB)
       +
       +$(LIB):        $(OBJS)
       +        $(AR) rv $(LIB) $(OBJS)
       +        $(RANLIB) $(LIB)
       +
       +$(LIB)(%.o): %.o
       +
       +$(OBJS):        ../include/libg.h libgint.h ../include/libc.h
 (DIR) diff --git a/libXg/arc.c b/libXg/arc.c
       @@ -0,0 +1,45 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include "libgint.h"
       +#include <math.h>
       +
       +#define rad2deg(x) 180*((x)/3.1415926535897932384626433832795028841971693993751)
       +
       +void
       +arc(Bitmap *b, Point p0, Point p1, Point p2, int v, Fcode f)
       +{
       +        unsigned int d;
       +        int x, y, r, start, end, delta;
       +        GC g;
       +
       +        p1.x -= p0.x;
       +        p1.y -= p0.y;
       +        p2.x -= p0.x;
       +        p2.y -= p0.y;
       +        r = (int)sqrt((double)(p1.x*p1.x + p1.y*p1.y));
       +        start = (int)(64*rad2deg(atan2(-p2.y, p2.x)));
       +        end = (int)(64*rad2deg(atan2(-p1.y, p1.x)));
       +        if(start < 0)
       +                start += 64*360;
       +        if(end < 0)
       +                end += 64*360;
       +        delta = end - start;
       +        if(delta < 0)
       +                delta += 64*360;
       +        x = p0.x - r;
       +        y = p0.y - r;
       +        if(b->flag&SHIFT){
       +                x -= b->r.min.x;
       +                y -= b->r.min.y;
       +        }
       +        d = 2*r;
       +        g = _getfillgc(f, b, v);
       +        /*
       +         * delta is positive, so this draws counterclockwise arc
       +         * from start to start+delta
       +         */
       +        XDrawArc(_dpy, (Drawable)b->id, g, x, y, d, d, start, delta);
       +}
       +
 (DIR) diff --git a/libXg/arith.c b/libXg/arith.c
       @@ -0,0 +1,191 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +
       +Point
       +add(Point a, Point b)
       +{
       +        a.x += b.x;
       +        a.y += b.y;
       +        return a;
       +}
       +
       +Point
       +sub(Point a, Point b)
       +{
       +        a.x -= b.x;
       +        a.y -= b.y;
       +        return a;
       +}
       +
       +Rectangle
       +inset(Rectangle r, int n)
       +{
       +        r.min.x += n;
       +        r.min.y += n;
       +        r.max.x -= n;
       +        r.max.y -= n;
       +        return r;
       +}
       +
       +Point
       +divpt(Point a, int b)
       +{
       +        a.x /= b;
       +        a.y /= b;
       +        return a;
       +}
       +
       +Point
       +mul(Point a, int b)
       +{
       +        a.x *= b;
       +        a.y *= b;
       +        return a;
       +}
       +
       +Rectangle
       +rsubp(Rectangle r, Point p)
       +{
       +        r.min.x -= p.x;
       +        r.min.y -= p.y;
       +        r.max.x -= p.x;
       +        r.max.y -= p.y;
       +        return r;
       +}
       +
       +Rectangle
       +raddp(Rectangle r, Point p)
       +{
       +        r.min.x += p.x;
       +        r.min.y += p.y;
       +        r.max.x += p.x;
       +        r.max.y += p.y;
       +        return r;
       +}
       +
       +Rectangle
       +rmul(Rectangle r, int a)
       +{
       +        if (a != 1) {
       +                r.min.x *= a;
       +                r.min.y *= a;
       +                r.max.x *= a;
       +                r.max.y *= a;
       +        }
       +        return r;
       +}
       +
       +Rectangle
       +rdiv(Rectangle r, int a)
       +{
       +        if (a != 1) {
       +                r.min.x /= a;
       +                r.min.y /= a;
       +                r.max.x /= a;
       +                r.max.y /= a;
       +        }
       +        return r;
       +}
       +
       +Rectangle
       +rshift(Rectangle r, int a)
       +{
       +        if (a > 0) {
       +                r.min.x <<= a;
       +                r.min.y <<= a;
       +                r.max.x <<= a;
       +                r.max.y <<= a;
       +        }
       +        else if (a < 0) {
       +                a = -a;
       +                r.min.x >>= a;
       +                r.min.y >>= a;
       +                r.max.x >>= a;
       +                r.max.y >>= a;
       +        }
       +        return r;
       +}
       +
       +eqpt(Point p, Point q)
       +{
       +        return p.x==q.x && p.y==q.y;
       +}
       +
       +eqrect(Rectangle r, Rectangle s)
       +{
       +        return r.min.x==s.min.x && r.max.x==s.max.x &&
       +               r.min.y==s.min.y && r.max.y==s.max.y;
       +}
       +
       +rectXrect(Rectangle r, Rectangle s)
       +{
       +        return r.min.x<s.max.x && s.min.x<r.max.x &&
       +               r.min.y<s.max.y && s.min.y<r.max.y;
       +}
       +
       +int
       +rectinrect(Rectangle r, Rectangle s)
       +{
       +        /* !ptinrect(r.min, s) in line for speed */
       +        if(!(r.min.x>=s.min.x && r.min.x<s.max.x &&
       +                r.min.y>=s.min.y && r.min.y<s.max.y))
       +                        return 0;
       +        return r.max.x<=s.max.x && r.max.y<=s.max.y;
       +}
       +
       +ptinrect(Point p, Rectangle r)
       +{
       +        return p.x>=r.min.x && p.x<r.max.x &&
       +               p.y>=r.min.y && p.y<r.max.y;
       +}
       +
       +Rectangle
       +rcanon(Rectangle r)
       +{
       +        int t;
       +        if (r.max.x < r.min.x) {
       +                t = r.min.x;
       +                r.min.x = r.max.x;
       +                r.max.x = t;
       +        }
       +        if (r.max.y < r.min.y) {
       +                t = r.min.y;
       +                r.min.y = r.max.y;
       +                r.max.y = t;
       +        }
       +        return r;
       +}
       +
       +Rectangle
       +Rect(int x1, int y1, int x2, int y2)
       +{
       +        Rectangle r;
       +
       +        r.min.x = x1;
       +        r.min.y = y1;
       +        r.max.x = x2;
       +        r.max.y = y2;
       +        return r;
       +}
       +
       +Rectangle
       +Rpt(Point p1, Point p2)
       +{
       +        Rectangle r;
       +
       +        r.min = p1;
       +        r.max = p2;
       +        return r;
       +}
       +
       +Point
       +Pt(int x, int y)
       +{
       +        Point p;
       +
       +        p.x = x;
       +        p.y = y;
       +        return p;
       +}
 (DIR) diff --git a/libXg/balloc.c b/libXg/balloc.c
       @@ -0,0 +1,60 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include "libgint.h"
       +
       +Bitmap*
       +balloc(Rectangle r, int ldepth)
       +{
       +        Bitmap *b;
       +
       +        b = _balloc(r, ldepth);
       +        bitblt(b, r.min, b, r, Zero);
       +        return b;
       +}
       +
       +Bitmap*
       +_balloc(Rectangle r, int ldepth)
       +{
       +        int id;
       +        Bitmap *b;
       +        int ld;
       +        Rectangle rx;
       +
       +        b = (Bitmap *)malloc(sizeof(Bitmap));
       +        if(b == 0)
       +                berror("balloc malloc");
       +        if (ldepth == 0)
       +                ld = 0;
       +        else
       +                ld = screen.ldepth;
       +        rx = r;
       +        if (Dx(rx) == 0)
       +                rx.max.x++;
       +        if (Dy(rx) == 0)
       +                rx.max.y++;
       +        id = (int) XCreatePixmap(_dpy, (Drawable)screen.id,
       +                        Dx(rx), Dy(rx), _ld2d[ld]);
       +        b->ldepth = ldepth;
       +        b->r = r;
       +        b->clipr = r;
       +        b->id = id;
       +        b->cache = 0;
       +        if(ldepth == 0)
       +                b->flag = DP1|BL1;
       +        else
       +                b->flag = screen.flag&BL1;
       +        if(r.min.x==0 && r.min.y ==0)
       +                b->flag |= ZORG;
       +        else
       +                b->flag |= SHIFT;
       +        return b;
       +}
       +
       +void
       +bfree(Bitmap *b)
       +{
       +        XFreePixmap(_dpy, (Pixmap)b->id);
       +        free(b);
       +}
 (DIR) diff --git a/libXg/bitblt.c b/libXg/bitblt.c
       @@ -0,0 +1,59 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include "libgint.h"
       +
       +void
       +bitblt(Bitmap *d, Point p, Bitmap *s, Rectangle r, Fcode f)
       +{
       +        int sx, sy, dx, dy, bfunc;
       +        GC g;
       +        unsigned long plane;
       +        Bitmap *btmp;
       +
       +        if(Dx(r)<=0 || Dy(r)<=0)
       +                return;
       +        sx = r.min.x;
       +        sy = r.min.y;
       +        if(s->flag&SHIFT){
       +                sx -= s->r.min.x;
       +                sy -= s->r.min.y;
       +        }
       +        dx = p.x;
       +        dy = p.y;
       +        if(d->flag&SHIFT){
       +                dx -= d->r.min.x;
       +                dy -= d->r.min.y;
       +        }
       +        g = _getcopygc(f, d, s, &bfunc);
       +        if(bfunc == UseCopyArea)
       +                XCopyArea(_dpy, (Drawable)s->id, (Drawable)d->id, g,
       +                        sx, sy, Dx(r), Dy(r), dx, dy);
       +        else if(bfunc == UseFillRectangle){
       +                XFillRectangle(_dpy, (Drawable)d->id, g,
       +                        dx, dy, Dx(r), Dy(r));
       +        }else{
       +                /* bfunc == UseCopyPlane */
       +                plane = _ld2dmask[s->ldepth];
       +                plane &= ~(plane>>1);
       +                if(0/*f == S*/)
       +                        XCopyPlane(_dpy, (Drawable)s->id, (Drawable)d->id, g,
       +                                sx, sy, Dx(r), Dy(r), dx, dy, plane);
       +                else {
       +                        /*
       +                         * CopyPlane can only do func code S,
       +                         * so copy src rect into a bitmap with the same depth
       +                         * as the dest, then do the bitblt from the tmp.
       +                         * This won't recurse again because we only get
       +                         * UseCopyPlane with differing bitmap depths
       +                         */
       +                        btmp = _balloc(Rect(0,0,Dx(r),Dy(r)), d->ldepth);
       +                        XCopyPlane(_dpy, (Drawable)s->id, (Drawable)btmp->id, g,
       +                                sx, sy, Dx(r), Dy(r), 0, 0, plane);
       +                        bitblt(d, p, btmp, btmp->r, f);
       +                        bfree(btmp);
       +                }
       +        }
       +        XFlush(_dpy);
       +}
 (DIR) diff --git a/libXg/bitbltclip.c b/libXg/bitbltclip.c
       @@ -0,0 +1,68 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include "libgint.h"
       +
       +int
       +bitbltclip(void *vp)
       +{
       +        int dx, dy;
       +        int i;
       +        struct bbcarg{
       +                Bitmap *dm;
       +                Point p;
       +                Bitmap *sm;
       +                Rectangle r;
       +                Fcode f;
       +        }*bp;
       +
       +        bp = (struct bbcarg *)vp;
       +        dx = Dx(bp->r);
       +        dy = Dy(bp->r);
       +        if(bp->p.x < bp->dm->clipr.min.x){
       +                i = bp->dm->clipr.min.x-bp->p.x;
       +                bp->r.min.x += i;
       +                bp->p.x += i;
       +                dx -= i;
       +        }
       +        if(bp->p.y < bp->dm->clipr.min.y){
       +                i = bp->dm->clipr.min.y-bp->p.y;
       +                bp->r.min.y += i;
       +                bp->p.y += i;
       +                dy -= i;
       +        }
       +        if(bp->p.x+dx > bp->dm->clipr.max.x){
       +                i = bp->p.x+dx-bp->dm->clipr.max.x;
       +                bp->r.max.x -= i;
       +                dx -= i;
       +        }
       +        if(bp->p.y+dy > bp->dm->clipr.max.y){
       +                i = bp->p.y+dy-bp->dm->clipr.max.y;
       +                bp->r.max.y -= i;
       +                dy -= i;
       +        }
       +        if(bp->r.min.x < bp->sm->clipr.min.x){
       +                i = bp->sm->clipr.min.x-bp->r.min.x;
       +                bp->p.x += i;
       +                bp->r.min.x += i;
       +                dx -= i;
       +        }
       +        if(bp->r.min.y < bp->sm->clipr.min.y){
       +                i = bp->sm->clipr.min.y-bp->r.min.y;
       +                bp->p.y += i;
       +                bp->r.min.y += i;
       +                dy -= i;
       +        }
       +        if(bp->r.max.x > bp->sm->clipr.max.x){
       +                i = bp->r.max.x-bp->sm->clipr.max.x;
       +                bp->r.max.x -= i;
       +                dx -= i;
       +        }
       +        if(bp->r.max.y > bp->sm->clipr.max.y){
       +                i = bp->r.max.y-bp->sm->clipr.max.y;
       +                bp->r.max.y -= i;
       +                dy -= i;
       +        }
       +        return dx>0 && dy>0;
       +}
 (DIR) diff --git a/libXg/border.c b/libXg/border.c
       @@ -0,0 +1,28 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +
       +void
       +border(Bitmap *l, Rectangle r, int i, Fcode c)
       +{
       +        if(i > 0){
       +                bitblt(l, r.min,
       +                        l, Rect(r.min.x, r.min.y, r.max.x, r.min.y+i), c);
       +                bitblt(l, Pt(r.min.x, r.max.y-i),
       +                        l, Rect(r.min.x, r.max.y-i, r.max.x, r.max.y), c);
       +                bitblt(l, Pt(r.min.x, r.min.y+i),
       +                        l, Rect(r.min.x, r.min.y+i, r.min.x+i, r.max.y-i), c);
       +                bitblt(l, Pt(r.max.x-i, r.min.y+i),
       +                        l, Rect(r.max.x-i, r.min.y+i, r.max.x, r.max.y-i), c);
       +        }else if(i < 0){
       +                bitblt(l, Pt(r.min.x, r.min.y+i),
       +                        l, Rect(r.min.x, r.min.y+i, r.max.x, r.min.y), c);
       +                bitblt(l, Pt(r.min.x, r.max.y),
       +                        l, Rect(r.min.x, r.max.y, r.max.x, r.max.y-i), c);
       +                bitblt(l, Pt(r.min.x+i, r.min.y+i),
       +                        l, Rect(r.min.x+i, r.min.y+i, r.min.x, r.max.y-i), c);
       +                bitblt(l, Pt(r.max.x, r.min.y+i),
       +                        l, Rect(r.max.x, r.min.y+i, r.max.x-i, r.max.y-i), c);
       +        }
       +}
 (DIR) diff --git a/libXg/bscreenrect.c b/libXg/bscreenrect.c
       @@ -0,0 +1,18 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include "libgint.h"
       +
       +/*
       + * The screen data structure should always be up to date
       + * (Not true in the Plan 9 library, which is why this
       + * function exists).
       + */
       +Rectangle
       +bscreenrect(Rectangle *clipr)
       +{
       +        if(clipr)
       +                *clipr = screen.clipr;
       +        return screen.r;
       +}
 (DIR) diff --git a/libXg/circle.c b/libXg/circle.c
       @@ -0,0 +1,23 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include "libgint.h"
       +
       +void
       +circle(Bitmap *b, Point p, int r, int v, Fcode f)
       +{
       +        unsigned int d;
       +        int x, y;
       +        GC g;
       +
       +        x = p.x - r;
       +        y = p.y - r;
       +        if (b->flag&SHIFT){
       +                x -= b->r.min.x;
       +                y -= b->r.min.y;
       +        }
       +        d = 2*r;
       +        g = _getfillgc(f, b, v);
       +        XDrawArc(_dpy, (Drawable)b->id, g, x, y, d, d, 0, 23040/* 360 deg */);
       +}
 (DIR) diff --git a/libXg/clipline.c b/libXg/clipline.c
       @@ -0,0 +1,225 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include "libgint.h"
       +
       +typedef struct Linedesc
       +{
       +        int        x0;
       +        int        y0;
       +        char        xmajor;
       +        char        slopeneg;
       +        long        dminor;
       +        long        dmajor;
       +} Linedesc;
       +
       +int         _clipline(Rectangle, Point*, Point*, Linedesc*);
       +
       +#define        XYswap(p)        t=(p)->x, (p)->x=(p)->y, (p)->y=t
       +#define        Swap(x, y)        t=x, x=y, y=t
       +
       +static long
       +lfloor(long x, long y)        /* first integer <= x/y */
       +{
       +        if(y <= 0){
       +                if(y == 0)
       +                        return x;
       +                y = -y;
       +                x = -x;
       +        }
       +        if(x < 0){        /* be careful; C div. is undefined */
       +                x = -x;
       +                x += y-1;
       +                return -(x/y);
       +        }
       +        return x/y;
       +}
       +
       +static long
       +lceil(long x, long y)        /* first integer >= x/y */
       +{
       +        if(y <= 0){
       +                if(y == 0)
       +                        return x;
       +                y = -y;
       +                x = -x;
       +        }
       +        if(x < 0){
       +                x = -x;
       +                return -(x/y);
       +        }
       +        x += y-1;
       +        return x/y;
       +}
       +
       +int
       +_gminor(long x, Linedesc *l)
       +{
       +        long y;
       +
       +        y = 2*(x-l->x0)*l->dminor + l->dmajor;
       +        y = lfloor(y, 2*l->dmajor) + l->y0;
       +        return l->slopeneg? -y : y;
       +}
       +
       +int
       +_gmajor(long y, Linedesc *l)
       +{
       +        long x;
       +
       +        x = 2*((l->slopeneg? -y : y)-l->y0)*l->dmajor - l->dminor;
       +        x = lceil(x, 2*l->dminor) + l->x0;
       +        if(l->dminor)
       +                while(_gminor(x-1, l) == y)
       +                        x--;
       +        return x;
       +}
       +
       +void
       +gsetline(Point *pp0, Point *pp1, Linedesc *l)
       +{
       +        long dx, dy, t;
       +        Point endpt;
       +        int swapped;
       +        Point p0, p1;
       +
       +        swapped = 0;
       +        p0 = *pp0;
       +        p1 = *pp1;
       +        l->xmajor = 1;
       +        l->slopeneg = 0;
       +        dx = p1.x - p0.x;
       +        dy = p1.y - p0.y;
       +        if(abs(dy) > abs(dx)){        /* Steep */
       +                l->xmajor = 0;
       +                XYswap(&p0);
       +                XYswap(&p1);
       +                Swap(dx, dy);
       +        }
       +        if(dx < 0){
       +                swapped++;
       +                Swap(p0.x, p1.x);
       +                Swap(p0.y, p1.y);
       +                dx = -dx;
       +                dy = -dy;
       +        }
       +        if(dy < 0){
       +                l->slopeneg = 1;
       +                dy = -dy;
       +                p0.y = -p0.y;
       +        }
       +        l->dminor = dy;
       +        l->dmajor = dx;
       +        l->x0 = p0.x;
       +        l->y0 = p0.y;
       +        p1.x = swapped? p0.x+1 : p1.x-1;
       +        p1.y = _gminor(p1.x, l);
       +        if(l->xmajor == 0){
       +                XYswap(&p0);
       +                XYswap(&p1);
       +        }
       +        if(pp0->x > pp1->x){
       +                *pp1 = *pp0;
       +                *pp0 = p1;
       +        }else
       +                *pp1 = p1;
       +}
       +/*
       + * Modified clip-to-rectangle algorithm
       + *        works in bitmaps
       + *        Everything in SCREEN coordinates.
       + *
       + *        Newman & Sproull 124 (1st edition)
       + */
       +
       +static
       +code(Point *p, Rectangle *r)
       +{
       +        return( (p->x<r->min.x? 1 : p->x>=r->max.x? 2 : 0) |
       +                (p->y<r->min.y? 4 : p->y>=r->max.y? 8 : 0));
       +}
       +
       +int
       +clipline(Rectangle r, Point *p0, Point *p1)
       +{
       +        Linedesc l;
       +
       +        return _clipline(r, p0, p1, &l);
       +}
       +
       +int
       +_clipline(Rectangle r, Point *p0, Point *p1, Linedesc *l)
       +{
       +        int c0, c1, n;
       +        long t, ret;
       +        Point temp;
       +        int swapped;
       +
       +        if(p0->x==p1->x && p0->y==p1->y)
       +                return 0;
       +        gsetline(p0, p1, l);
       +        /* line is now closed */
       +        if(l->xmajor == 0){
       +                XYswap(p0);
       +                XYswap(p1);
       +                XYswap(&r.min);
       +                XYswap(&r.max);
       +        }
       +        c0 = code(p0, &r);
       +        c1 = code(p1, &r);
       +        ret = 1;
       +        swapped = 0;
       +        n = 0;
       +        while(c0 | c1){
       +                if(c0 & c1){        /* no point of line in r */
       +                        ret = 0;
       +                        goto Return;
       +                }
       +                if(++n > 10){        /* horrible points; overflow etc. etc. */
       +                        ret = 0;
       +                        goto Return;
       +                }
       +                if(c0 == 0){        /* swap points */
       +                        temp = *p0;
       +                        *p0 = *p1;
       +                        *p1 = temp;
       +                        Swap(c0, c1);
       +                        swapped ^= 1;
       +                }
       +                if(c0 == 0)
       +                        break;
       +                if(c0 & 1){                /* push towards left edge */
       +                        p0->x = r.min.x;
       +                        p0->y = _gminor(p0->x, l);
       +                }else if(c0 & 2){        /* push towards right edge */
       +                        p0->x = r.max.x-1;
       +                        p0->y = _gminor(p0->x, l);
       +                }else if(c0 & 4){        /* push towards top edge */
       +                        p0->y = r.min.y;
       +                        if(l->slopeneg)
       +                                p0->x = _gmajor(p0->y-1, l)-1;
       +                        else
       +                                p0->x = _gmajor(p0->y, l);
       +                }else if(c0 & 8){        /* push towards bottom edge */
       +                        p0->y = r.max.y-1;
       +                        if(l->slopeneg)
       +                                p0->x = _gmajor(p0->y, l);
       +                        else
       +                                p0->x = _gmajor(p0->y+1, l)-1;
       +                }
       +                c0 = code(p0, &r);
       +        }
       +
       +    Return:
       +        if(l->xmajor == 0){
       +                XYswap(p0);
       +                XYswap(p1);
       +        }
       +        if(swapped){
       +                temp = *p0;
       +                *p0 = *p1;
       +                *p1 = temp;
       +        }
       +        return ret;
       +}
 (DIR) diff --git a/libXg/clipr.c b/libXg/clipr.c
       @@ -0,0 +1,21 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include "libgint.h"
       +
       +int
       +clipr(Bitmap *d, Rectangle r)
       +{
       +        if(rectclip(&r, d->r) == 0)
       +                return 0;
       +        d->clipr = r;
       +        if(r.min.x != d->r.min.x ||
       +           r.min.y != d->r.min.y ||
       +           r.max.x != d->r.max.x ||
       +           r.max.y != d->r.max.y)
       +                d->flag |= CLIP;
       +        else
       +                d->flag &= ~CLIP;
       +        return 1;
       +}
 (DIR) diff --git a/libXg/copymasked.c b/libXg/copymasked.c
       @@ -0,0 +1,49 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include "libgint.h"
       +
       +/*
       + * m should be a 1-bit-deep bitmap with origin (0,0) and the
       + * same extents as r.  s should have the same depth as d.
       + * Rectangle r of s is copied to d wherever corresponding
       + * bits of m are 1
       + */
       +void
       +copymasked(Bitmap *d, Point p, Bitmap *s, Bitmap *m, Rectangle r)
       +{
       +        int sx, sy, dx, dy;
       +        XGCValues gcv;
       +        GC g;
       +
       +        if(Dx(r)<=0 || Dy(r)<=0)
       +                return;
       +        sx = r.min.x;
       +        sy = r.min.y;
       +        if(s->flag&SHIFT){
       +                sx -= s->r.min.x;
       +                sy -= s->r.min.y;
       +        }
       +        dx = p.x;
       +        dy = p.y;
       +        if(d->flag&SHIFT){
       +                dx -= d->r.min.x;
       +                dy -= d->r.min.y;
       +        }
       +        gcv.fill_style = FillStippled;
       +        gcv.stipple = (Pixmap)m->id;
       +        gcv.function = GXclear;
       +        gcv.ts_x_origin = dx;
       +        gcv.ts_y_origin = dy;
       +        gcv.fill_style = FillStippled;
       +        g = _getgc(d, GCFunction|GCStipple|GCTileStipXOrigin
       +                |GCTileStipYOrigin|GCFillStyle, &gcv);
       +        XFillRectangle(_dpy, (Drawable)d->id, g,
       +                        dx, dy, Dx(r), Dy(r));
       +        gcv.function = GXor;
       +        gcv.fill_style = FillSolid;
       +        g = _getgc(d, GCFunction|GCFillStyle, &gcv);
       +        XCopyArea(_dpy, (Drawable)s->id, (Drawable)d->id, g,
       +                        sx, sy, Dx(r), Dy(r), dx, dy);
       +}
 (DIR) diff --git a/libXg/cursorset.c b/libXg/cursorset.c
       @@ -0,0 +1,16 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include "libgint.h"
       +
       +/*
       + * Only allow cursor to move within screen Bitmap
       + */
       +void
       +cursorset(Point p)
       +{
       +        /* motion will be relative to window origin */
       +        p = sub(p, screen.r.min);
       +        XWarpPointer(_dpy, None, (Window)screen.id, 0, 0, 0, 0, p.x, p.y);
       +}
 (DIR) diff --git a/libXg/cursorswitch.c b/libXg/cursorswitch.c
       @@ -0,0 +1,66 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include "libgint.h"
       +
       +/*
       + * Use the id field in Cursor to hold the X id corresponding
       + * to the cursor, so that it doesn't have to be recreated on
       + * each cursorswitch.  This doesn't quite match the semantics
       + * of Plan9 libg, since the user could create a cursor (say
       + * with malloc) with garbage in the id field; or the user
       + * could change the contents of the other fields and we
       + * wouldn't know about it.  Neither of these happen in
       + * existing uses of libg.
       + */
       +static Cursor arrow =
       +{
       +        {-1, -1},
       +        {0xFF, 0xE0, 0xFF, 0xE0, 0xFF, 0xC0, 0xFF, 0x00,
       +         0xFF, 0x00, 0xFF, 0x80, 0xFF, 0xC0, 0xFF, 0xE0,
       +         0xE7, 0xF0, 0xE3, 0xF8, 0xC1, 0xFC, 0x00, 0xFE,
       +         0x00, 0x7F, 0x00, 0x3E, 0x00, 0x1C, 0x00, 0x08,
       +        },
       +        {0x00, 0x00, 0x7F, 0xC0, 0x7F, 0x00, 0x7C, 0x00,
       +         0x7E, 0x00, 0x7F, 0x00, 0x6F, 0x80, 0x67, 0xC0,
       +         0x43, 0xE0, 0x41, 0xF0, 0x00, 0xF8, 0x00, 0x7C,
       +         0x00, 0x3E, 0x00, 0x1C, 0x00, 0x08, 0x00, 0x00,
       +        }
       +};
       +
       +static Bitmap *bsrc, *bmask;
       +static Rectangle crect = { 0, 0, 16, 16 };
       +
       +void
       +cursorswitch(Cursor *c)
       +{
       +        if(c == 0)
       +                c = &arrow;
       +        if(c->id == 0){
       +                if(bsrc == 0){
       +                        bsrc = balloc(crect, 0);
       +                        bmask = balloc(crect, 0);
       +                }
       +                /*
       +                 * Cursor should have fg where "set" is 1,
       +                 * and bg where "clr" is 1 and "set" is 0,
       +                 * and should leave places alone where "set" and "clr" are both 0
       +                 */
       +                wrbitmap(bsrc, 0, 16, c->set);
       +#ifdef CURSORBUG
       +                /*
       +                 * Some X servers (e.g., Sun X-on-news for some color
       +                 * monitors) don't do XCreatePixmapCursor properly:
       +                 * only the mask gets displayed, all black
       +                 */
       +                wrbitmap(bmask, 0, 16, c->set);
       +#else
       +                wrbitmap(bmask, 0, 16, c->clr);
       +                bitblt(bmask, Pt(0,0), bsrc, crect, S|D);
       +#endif
       +                c->id = (int) XCreatePixmapCursor(_dpy, (Pixmap)bsrc->id, (Pixmap)bmask->id,
       +                        &_fgcolor, &_bgcolor, -c->offset.x, -c->offset.y);
       +        }
       +        XDefineCursor(_dpy, (Window)screen.id, (xCursor)c->id);
       +}
 (DIR) diff --git a/libXg/disc.c b/libXg/disc.c
       @@ -0,0 +1,23 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include "libgint.h"
       +
       +void
       +disc(Bitmap *b, Point p, int r, int v, Fcode f)
       +{
       +        unsigned int d;
       +        int x, y;
       +        GC g;
       +
       +        x = p.x - r;
       +        y = p.y - r;
       +        if (b->flag&SHIFT){
       +                x -= b->r.min.x;
       +                y -= b->r.min.y;
       +        }
       +        d = 2*r;
       +        g = _getfillgc(f, b, v);
       +        XFillArc(_dpy, (Drawable)b->id, g, x, y, d, d, 0, 23040/* 360 deg */);
       +}
 (DIR) diff --git a/libXg/ellipse.c b/libXg/ellipse.c
       @@ -0,0 +1,23 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include "libgint.h"
       +
       +/* e(x,y) = b*b*x*x + a*a*y*y - a*a*b*b */
       +
       +void
       +ellipse(Bitmap *bp, Point p, int a, int b, int v, Fcode f)
       +{
       +        int x, y;
       +        GC g;
       +
       +        x = p.x - a;
       +        y = p.y - b;
       +        if (bp->flag&SHIFT){
       +                x -= bp->r.min.x;
       +                y -= bp->r.min.y;
       +        }
       +        g = _getfillgc(f, bp, v);
       +        XDrawArc(_dpy, (Drawable)bp->id, g, x, y, 2*a, 2*b, 0, 23040/* 360 deg */);
       +}
 (DIR) diff --git a/libXg/font.c b/libXg/font.c
       @@ -0,0 +1,18 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include "libgint.h"
       +
       +#define        PJW        0        /* use NUL==pjw for invisible characters */
       +
       +long
       +charwidth(XftFont *f, Rune r)
       +{
       +    
       +    char chars[UTFmax + 1] = {0};
       +
       +    runetochar(chars, &r);
       +    return strwidth(f, chars);
       +}
       +
 (DIR) diff --git a/libXg/gcs.c b/libXg/gcs.c
       @@ -0,0 +1,401 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include "libgint.h"
       +
       +/*
       + * Libg applications are written assuming that black is ~0
       + * and white is 0.  Some screens use the reverse convention.
       + * We get the effect the application desired by seeing what
       + * happens if both the source and dest are converted to the
       + * black==~0 convention, and then converting the dest back
       + * to whatever convention it uses.
       + *
       + * Offscreen bitmaps of depth 1 use the black==~0 convention.
       + *
       + * Bitmaps of depth > 1 are probably in color.  Libg operations that
       + * would produce a 1 should produce the foreground color, and
       + * libg operations that would produce a 0 should produce the background
       + * color.  Operations that use bitmaps of depth > 1 as source
       + * should interpret the foreground pixel as "black" (1) and the
       + * background pixel as "white" (0).  It is hard to make this work,
       + * but the important cases are Fcodes Zero, F, S, and S^D, so
       + * we make sure that those work.  When a fill value is given for
       + * a bitmap of depth > 1, assume ~0 means foreground, but otherwise
       + * take any other value literally (assume it came from rgbpix).
       + * This may be wrong for the case of 0, but libg programmers
       + * usually use Fcode Zero instead of passing 0 with Fcode S.
       + *
       + * We assume there are at most two depths of bitmaps: depth 1
       + * and depth of the screen.
       + */
       +
       +/*
       + * gx func code corresponding to libg func code when both
       + * source and dest use 1 for black.  This is a straight translation.
       + */
       +static int gx[16] = {
       +        GXclear,                /* Zero */
       +        GXnor,                        /* DnorS */
       +        GXandInverted,                /* DandnotS */
       +        GXcopyInverted,                /* notS */
       +        GXandReverse,                /* notDandS */
       +        GXinvert,                /* notD */
       +        GXxor,                        /* DxorS */
       +        GXnand,                        /* DnandS */
       +        GXand,                        /* DandS */
       +        GXequiv,                /* DxnorS */
       +        GXnoop,                        /* D */
       +        GXorInverted,                /* DornotS */
       +        GXcopy,                        /* S */
       +        GXorReverse,                /* notDorS */
       +        GXor,                        /* DorS */
       +        GXset,                        /* F */
       +};
       +
       +/*
       + * gx func code corresponding to libg func code when 0 means black
       + * in dst and 1 means black in src. These means the table has op'
       + * where dst <- dst op' src == not ( not(dst)  op  src ).
       + * The comment on each line is op, in Fcode terms.
       + */
       +static int d0s1gx[16] = {
       +        GXset,                        /* Zero */
       +        GXorReverse,                /* DnorS */
       +        GXor,                        /* DandnotS */
       +        GXcopy,                        /* notS */
       +        GXnand,                        /* notDandS */
       +        GXinvert,                /* notD */
       +        GXxor,                        /* DxorS */
       +        GXandReverse,                /* DnandS */
       +        GXorInverted,                /* DandS */
       +        GXequiv,                /* DxnorS */
       +        GXnoop,                        /* D */
       +        GXand,                        /* DornotS */
       +        GXcopyInverted,                /* S */
       +        GXnor,                        /* notDorS */
       +        GXandInverted,                /* DorS */
       +        GXclear,                /* F */
       +};
       +/*
       + * gx func code corresponding to libg func code when 1 means black
       + * in dst and 0 means black in src. These means the table has op'
       + * where dst <- dst op' src == dst  op  not(src) )
       + * The comment on each line is op, in Fcode terms.
       + */
       +static int d1s0gx[16] = {
       +        GXclear,                /* Zero */
       +        GXandReverse,                /* DnorS */
       +        GXand,                        /* DandnotS */
       +        GXcopy,                        /* notS */
       +        GXnor,                        /* notDandS */
       +        GXinvert,                /* notD */
       +        GXequiv,                /* DxorS */
       +        GXorReverse,                /* DnandS */
       +        GXandInverted,                /* DandS */
       +        GXxor,                        /* DxnorS */
       +        GXnoop,                        /* D */
       +        GXor,                        /* DornotS */
       +        GXcopyInverted,                /* S */
       +        GXnand,                        /* notDorS */
       +        GXorInverted,                /* DorS */
       +        GXset,                        /* F */
       +};
       +
       +/*
       + * gx func code corresponding to libg func code when 0 means black
       + * in both the src and the dst. These means the table has op'
       + * where dst <- dst op' src == not (not(dst)  op  not(src)) )
       + * The comment on each line is op, in Fcode terms.
       + */
       +static int d0s0gx[16] = {
       +        GXset,                        /* Zero */
       +        GXnand,                        /* DnorS */
       +        GXorInverted,                /* DandnotS */
       +        GXcopyInverted,                /* notS */
       +        GXorReverse,                /* notDandS */
       +        GXinvert,                /* notD */
       +        GXequiv,                /* DxorS */
       +        GXnor,                        /* DnandS */
       +        GXor,                        /* DandS */
       +        GXxor,                        /* DxnorS */
       +        GXnoop,                        /* D */
       +        GXandInverted,                /* DornotS */
       +        GXcopy,                        /* S */
       +        GXandReverse,                /* notDorS */
       +        GXand,                        /* DorS */
       +        GXclear,                /* F */
       +};
       +
       +/*
       + * 1 for those Fcodes that are degenerate (don't involve src)
       + */
       +static int degengc[16] = {
       +        1,                        /* Zero */
       +        0,                        /* DnorS */
       +        0,                        /* DandnotS */
       +        0,                        /* notS */
       +        0,                        /* notDandS */
       +        1,                        /* notD */
       +        0,                        /* DxorS */
       +        0,                        /* DnandS */
       +        0,                        /* DandS */
       +        0,                        /* DxnorS */
       +        1,                        /* D */
       +        0,                        /* DornotS */
       +        0,                        /* S */
       +        0,                        /* notDorS */
       +        0,                        /* DorS */
       +        1,                        /* F */
       +};
       +
       +/*
       + * GCs are all for same screen, and depth is either 1 or screen depth.
       + * Return a GC for the depth of b, with values as specified by gcv.
       + *
       + * Also, set (or unset) the clip rectangle if necessary.
       + * (This implementation should be improved if setting a clip rectangle is not rare).
       + */
       +GC
       +_getgc(Bitmap *b, unsigned long gcvm, XGCValues *pgcv)
       +{
       +        static GC gc0, gcn;
       +        static clipset = 0;
       +        GC g;
       +        XRectangle xr;
       +
       +        g = (b->ldepth==0)? gc0 : gcn;
       +        if(!g){
       +                g = XCreateGC(_dpy, (Drawable)b->id, gcvm, pgcv);
       +                if(b->ldepth==0)
       +                        gc0 = g;
       +                else
       +                        gcn = g;
       +        } else
       +                XChangeGC(_dpy, g, gcvm, pgcv);
       +        if(b->flag&CLIP){
       +                xr.x = b->clipr.min.x;
       +                xr.y = b->clipr.min.y;
       +                xr.width = Dx(b->clipr);
       +                xr.height = Dy(b->clipr);
       +                if(b->flag&SHIFT){
       +                        xr.x -= b->r.min.x;
       +                        xr.y -= b->r.min.y;
       +                }
       +                XSetClipRectangles(_dpy, g, 0, 0, &xr, 1, YXBanded);
       +                clipset = 1;
       +        }else if(clipset){
       +                pgcv->clip_mask = None;
       +                XChangeGC(_dpy, g, GCClipMask, pgcv);
       +                clipset = 0;
       +        }
       +        return g;
       +}
       +
       +/*
       + * Return a GC that will fill bitmap b using a pixel value v and Fcode f.
       + * Pixel value v is according to libg convention, so 0 means
       + * white (or background) and ~0 means black (or foreground).
       + */
       +GC
       +_getfillgc(Fcode f, Bitmap *b, unsigned long val)
       +{
       +        int xf, m;
       +        unsigned long v, fg, bg, spix, vmax;
       +        XGCValues gcv;
       +
       +        f &= F;
       +        vmax = _ld2dmask[b->ldepth];
       +        v = val & vmax;
       +        spix = v;
       +        xf = GXcopy;
       +        m = b->flag;
       +        if(m & DP1){
       +                xf = (m&BL1)? gx[f] : d0s1gx[f];
       +        }else{
       +                fg = _fgpixel;
       +                bg = _bgpixel;
       +                switch(f){
       +                case Zero:
       +            labZero:
       +                        spix = bg;
       +                        break;
       +                case F:
       +            labF:
       +                        spix = fg;
       +                        break;
       +                case D:
       +            labD:
       +                        xf = GXnoop;
       +                        break;
       +                case notD:
       +            labnotD:
       +                        xf = GXxor;
       +                        spix = fg^bg;
       +                        break;
       +                case S:
       +                        if(val == ~0)
       +                                spix = fg;
       +                        else
       +                                spix = v;
       +                        break;
       +                case notS:
       +                        if(val == ~0)
       +                                spix = bg;
       +                        else
       +                                spix = v;
       +                        break;
       +                case DxorS:
       +                        xf = GXxor;
       +                        if(val == ~0)
       +                                spix = fg^bg;
       +                        else
       +                                spix = v;
       +                        break;
       +                case DxnorS:
       +                        xf = GXxor;
       +                        if(val == 0)
       +                                spix = fg^bg;
       +                        else
       +                                spix = v;
       +                        break;
       +                default:
       +                        /* hard to do anything other than v==0 or v==~0 case */
       +                        if(v < vmax-v){
       +                                /* v is closer to 0 than vmax */
       +                                switch(f&~S){
       +                                case D&~S:        goto labD;
       +                                case notD&~S:        goto labnotD;
       +                                case Zero&~S:        goto labZero;
       +                                case F&~S:        goto labF;
       +                                }
       +                        }else{
       +                                /* v is closer to vmax than 0 */
       +                                switch(f&S){
       +                                case D&S:        goto labD;
       +                                case notD&S:        goto labnotD;
       +                                case Zero&S:        goto labZero;
       +                                case F&S:        goto labF;
       +                                }
       +                        }
       +                        
       +                }
       +        }
       +        gcv.foreground = spix;
       +        gcv.function = xf;
       +        return _getgc(b, GCForeground|GCFunction, &gcv);
       +}
       +
       +/*
       + * Return a GC to be used to copy an area from bitmap sb to
       + * bitmap db.  Sometimes the calling function shouldn't use
       + * XCopyArea, but instead should use XCopyPlane or XFillRectangle.
       + * The *bltfunc arg is set to one of UseCopyArea, UseCopyPlane,
       + * UseFillRectangle.
       + */
       +GC
       +_getcopygc(Fcode f, Bitmap *db, Bitmap *sb, int *bltfunc)
       +{
       +        unsigned long spix, bg, fg, df, sf;
       +        int xf, c;
       +        XGCValues gcv;
       +        unsigned long gcvm;
       +
       +        f &= F;
       +        gcvm = 0;
       +        df = db->flag;
       +        if(degengc[f]){
       +                *bltfunc = UseFillRectangle;
       +                if(df&SCR || !(df&DP1)){
       +                        fg = _fgpixel;
       +                        bg = _bgpixel;
       +                }else{
       +                        /* must be DP1 and BL1 */
       +                        fg = 1;
       +                        bg = 0;
       +                }
       +                switch(f){
       +                case Zero:
       +                        xf = GXcopy;
       +                        spix = bg;
       +                        break;
       +                case F:
       +                        xf = GXcopy;
       +                        spix = fg;
       +                        break;
       +                case D:
       +                        xf = GXnoop;
       +                        spix = fg;
       +                        break;
       +                case notD:
       +                        xf = GXxor;
       +                        spix = fg^bg;
       +                        break;
       +                }
       +                gcv.function = xf;
       +                gcv.foreground = spix;
       +                gcvm = GCFunction|GCForeground;
       +        }else{
       +                /* src is involved in f */
       +
       +#define code(f1,f2) ((((f1)&(DP1|BL1))<<2)|((f2)&(DP1|BL1)))
       +
       +                sf = sb->flag;
       +                c = code(df,sf);
       +                *bltfunc = UseCopyArea;
       +                switch(code(df,sf)){
       +                case code(DP1|BL1,DP1|BL1):
       +                case code(BL1,BL1):
       +                        xf = gx[f];
       +                        break;
       +                case code(DP1|BL1,DP1):
       +                        xf = d1s0gx[f];
       +                        break;
       +                case code(DP1,DP1|BL1):
       +                        xf = d0s1gx[f];
       +                        break;
       +                case code(DP1,DP1):
       +                case code(0,0):
       +                        xf = d0s0gx[f];
       +                        break;
       +                default:
       +                        /*
       +                         * One bitmap has depth 1, the other has screen depth.
       +                         * We know the bitmap must have BL1.
       +                         * CopyPlane must be used; it won't really work
       +                         * for more than fcode==S.
       +                         */
       +
       +                        *bltfunc = UseCopyPlane;
       +                        xf = GXcopy;
       +                        switch(c){
       +
       +                        case code(0,DP1|BL1):
       +                        case code(BL1,DP1|BL1):
       +                                fg = _fgpixel;
       +                                bg = _bgpixel;
       +                                break;
       +                        case code(DP1|BL1,0):
       +                                fg = 0;
       +                                bg = 1;
       +                                break;
       +                        case code(DP1|BL1,BL1):
       +                                fg = 1;
       +                                bg = 0;
       +                                break;
       +                        default:
       +                                berror("bad combination of copy bitmaps");
       +                        }
       +                        gcv.foreground = fg;
       +                        gcv.background = bg;
       +                        gcvm |= GCForeground|GCBackground;
       +                }
       +                gcv.function = xf;
       +                gcvm |= GCFunction;
       +        
       +#undef code
       +        }
       +
       +        return _getgc(db, gcvm, &gcv);
       +}
 (DIR) diff --git a/libXg/getrect.c b/libXg/getrect.c
       @@ -0,0 +1,73 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include "libgint.h"
       +
       +static Cursor sweep={
       +        {-7, -7},
       +        {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x07,
       +         0xE0, 0x07, 0xE0, 0x07, 0xE3, 0xF7, 0xE3, 0xF7,
       +         0xE3, 0xE7, 0xE3, 0xF7, 0xE3, 0xFF, 0xE3, 0x7F,
       +         0xE0, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,},
       +        {0x00, 0x00, 0x7F, 0xFE, 0x40, 0x02, 0x40, 0x02,
       +         0x40, 0x02, 0x40, 0x02, 0x40, 0x02, 0x41, 0xE2,
       +         0x41, 0xC2, 0x41, 0xE2, 0x41, 0x72, 0x40, 0x38,
       +         0x40, 0x1C, 0x40, 0x0E, 0x7F, 0xE6, 0x00, 0x00,}
       +};
       +
       +static void
       +grabcursor(void)
       +{
       +        /* Grab X server with an limp wrist. */
       +        while (XGrabPointer(_dpy, screen.id, False,
       +                        ButtonPressMask|ButtonReleaseMask|
       +                        ButtonMotionMask|StructureNotifyMask,
       +                GrabModeAsync, GrabModeAsync, None, None, CurrentTime)
       +                        != GrabSuccess)
       +                sleep(2);
       +        /* Grab the keyboard too */
       +        XSetInputFocus(_dpy, screen.id, RevertToParent, CurrentTime);
       +}
       +
       +static void
       +ungrabcursor(void)
       +{
       +        XUngrabPointer(_dpy, CurrentTime);
       +}
       +
       +Rectangle
       +getrect(int but, Mouse *m){
       +        Rectangle r, rc;
       +
       +        but = 1<<(but-1);
       +        cursorswitch(&sweep);
       +        while(m->buttons)
       +                *m = emouse();
       +        grabcursor();
       +        while(!(m->buttons & but)){
       +                *m = emouse();
       +                if(m->buttons & (7^but))
       +                        goto Return;
       +        }
       +        r.min = m->xy;
       +        r.max = m->xy;
       +        do{
       +                rc = rcanon(r);
       +                border(&screen, rc, 2, F&~D);
       +                *m = emouse();
       +                border(&screen, rc, 2, F&~D);
       +                r.max = m->xy;
       +        }while(m->buttons & but);
       +
       +    Return:
       +        cursorswitch((Cursor *)0);
       +        if(m->buttons & (7^but)){
       +                rc.min.x = rc.max.x = 0;
       +                rc.min.y = rc.max.y = 0;
       +                while(m->buttons)
       +                        *m = emouse();
       +        }
       +        ungrabcursor();
       +        return rc;
       +}
 (DIR) diff --git a/libXg/gwin.c b/libXg/gwin.c
       @@ -0,0 +1,466 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <stdio.h>
       +#if        defined(v10) || defined(HPUX)
       +typedef        char*        caddr_t;
       +#endif
       +#include <X11/IntrinsicP.h>
       +#include <X11/StringDefs.h>
       +#include <X11/Xatom.h>
       +#include <X11/keysym.h>
       +
       +#ifndef XtSpecificationRelease
       +#define R3
       +#define XtPointer caddr_t
       +#define XtOffsetOf(s_type,field) XtOffset(s_type*,field)
       +#define XtExposeCompressMultiple TRUE
       +#endif
       +
       +#include "GwinP.h"
       +
       +/* Forward declarations */
       +static void Realize(Widget, XtValueMask *, XSetWindowAttributes *);
       +static void Resize(Widget);
       +static void Redraw(Widget, XEvent *, Region);
       +static void Mappingaction(Widget, XEvent *, String *, Cardinal*);
       +static void Keyaction(Widget, XEvent *, String *, Cardinal*);
       +static void Mouseaction(Widget, XEvent *, String *, Cardinal*);
       +static String SelectSwap(Widget, String);
       +
       +/* Data */
       +
       +#define Offset(field) XtOffsetOf(GwinRec, gwin.field)
       +
       +static XtResource resources[] = {
       +        {XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel),
       +                Offset(foreground), XtRString, (XtPointer)XtDefaultForeground},
       +        {XtNscrollForwardR, XtCScrollForwardR, XtRBoolean, sizeof(Boolean),
       +                Offset(forwardr), XtRImmediate, (XtPointer)TRUE},
       +        {XtNreshaped, XtCReshaped, XtRFunction, sizeof(Reshapefunc),
       +                Offset(reshaped), XtRFunction, (XtPointer) NULL},
       +        {XtNgotchar, XtCGotchar, XtRFunction, sizeof(Charfunc),
       +                Offset(gotchar), XtRFunction, (XtPointer) NULL},
       +        {XtNgotmouse, XtCGotmouse, XtRFunction, sizeof(Mousefunc),
       +                Offset(gotmouse), XtRFunction, (XtPointer) NULL},
       +        {XtNselection, XtCSelection, XtRString, sizeof(String),
       +                Offset(selection), XtRString, (XtPointer) NULL},
       +        {XtNcomposeMod, XtCComposeMod, XtRInt, sizeof(int),
       +                Offset(compose), XtRImmediate, (XtPointer) 0}
       +};
       +#undef Offset
       +
       +static XtActionsRec actions[] = {
       +        {"key", Keyaction},
       +        {"mouse", Mouseaction},
       +        {"mapping", Mappingaction}
       +};
       +
       +static char tms[] =
       +        "<Key> : key() \n\
       +        <Motion> : mouse() \n\
       +        <BtnDown> : mouse() \n\
       +        <BtnUp> : mouse() \n\
       +        <Mapping> : mapping() \n";
       +
       +/* Class record declaration */
       +
       +GwinClassRec gwinClassRec = {
       +  /* Core class part */
       +   {
       +    /* superclass         */    (WidgetClass)&widgetClassRec,
       +    /* class_name         */    "Gwin",
       +    /* widget_size        */    sizeof(GwinRec),
       +    /* class_initialize   */    NULL,
       +    /* class_part_initialize*/  NULL,
       +    /* class_inited       */    FALSE,
       +    /* initialize         */    NULL,
       +    /* initialize_hook    */    NULL,
       +    /* realize            */    Realize,
       +    /* actions            */    actions,
       +    /* num_actions        */    XtNumber(actions),
       +    /* resources          */    resources,
       +    /* num_resources      */    XtNumber(resources),
       +    /* xrm_class          */    NULLQUARK,
       +    /* compress_motion    */    TRUE,
       +    /* compress_exposure  */    XtExposeCompressMultiple,
       +    /* compress_enterleave*/    TRUE,
       +    /* visible_interest   */    FALSE,
       +    /* destroy            */    NULL,
       +    /* resize             */    Resize,
       +    /* expose             */    Redraw,
       +    /* set_values         */    NULL,
       +    /* set_values_hook    */    NULL,
       +    /* set_values_almost  */    XtInheritSetValuesAlmost,
       +    /* get_values_hook    */    NULL,
       +    /* accept_focus       */    XtInheritAcceptFocus,
       +    /* version            */    XtVersion,
       +    /* callback_offsets   */    NULL,
       +    /* tm_table           */    tms,
       +    /* query_geometry       */  XtInheritQueryGeometry,
       +    /* display_accelerator  */  NULL,
       +    /* extension            */  NULL
       +   },
       +  /* Gwin class part */
       +   {
       +    /* select_swap          */    SelectSwap,
       +   }
       +};
       +
       +/* Class record pointer */
       +WidgetClass gwinWidgetClass = (WidgetClass) &gwinClassRec;
       +
       +static XModifierKeymap *modmap;
       +static int keypermod;
       +
       +static void
       +Realize(Widget w, XtValueMask *valueMask, XSetWindowAttributes *attrs)
       +{
       +        XtValueMask                mask;
       +
       +        *valueMask |= CWBackingStore;
       +        attrs->backing_store = Always;
       +
       +        XtCreateWindow(w, InputOutput, (Visual *)0, *valueMask, attrs);
       +        XtSetKeyboardFocus(w->core.parent, w);
       +        if (modmap = XGetModifierMapping(XtDisplay(w)))
       +                keypermod = modmap->max_keypermod;
       +
       +        Resize(w);
       +}
       +
       +static void
       +Resize(Widget w)
       +{
       +        if(XtIsRealized(w))
       +                (*(XtClass(w)->core_class.expose))(w, (XEvent *)NULL, (Region)NULL);
       +}
       +
       +static void
       +Redraw(Widget w, XEvent *e, Region r)
       +{
       +        Reshapefunc f;
       +
       +        f = ((GwinWidget)w)->gwin.reshaped;
       +        if(f)
       +                (*f)(w->core.x, w->core.y,
       +                        w->core.x+w->core.width, w->core.y+w->core.height);
       +}
       +
       +static void
       +Mappingaction(Widget w, XEvent *e, String *p, Cardinal *np)
       +{
       +        if (modmap)
       +                XFreeModifiermap(modmap);
       +        modmap = XGetModifierMapping(e->xany.display);
       +        if (modmap)
       +                keypermod = modmap->max_keypermod;
       +}
       +
       +#define STUFFCOMPOSE() \
       +                                f = ((GwinWidget)w)->gwin.gotchar; \
       +                                if (f) \
       +                                        for (c = 0; c < composing; c++) \
       +                                                (*f)(compose[c])
       +
       +static void
       +Keyaction(Widget w, XEvent *e, String *p, Cardinal *np)
       +{
       +        static unsigned char compose[5];
       +        static int composing = -2;
       +
       +        int c, minmod;
       +        KeySym k, mk;
       +        Charfunc f;
       +        Modifiers md;
       +
       +        /*
       +         * I tried using XtGetActionKeysym, but it didn't seem to
       +         * do case conversion properly
       +         * (at least, with Xterminal servers and R4 intrinsics)
       +         */
       +        if(e->xany.type != KeyPress)
       +                return;
       +        XtTranslateKeycode(e->xany.display, (KeyCode)e->xkey.keycode,
       +                        e->xkey.state, &md, &k);
       +        /*
       +         * The following song and dance is so we can have our chosen
       +         * modifier key behave like a compose key, i.e, press and release
       +         * and then type the compose sequence, like Plan 9.  We have
       +         * to find out which key is the compose key first 'though.
       +         */
       +        if (IsModifierKey(k) && ((GwinWidget)w)->gwin.compose
       +                        && composing == -2 && modmap) {
       +                minmod = (((GwinWidget)w)->gwin.compose+2)*keypermod;
       +                for (c = minmod; c < minmod+keypermod; c++) {
       +                        XtTranslateKeycode(e->xany.display,
       +                                        modmap->modifiermap[c],
       +                                        e->xkey.state, &md, &mk);
       +                        if (k == mk) {
       +                                composing = -1;
       +                                break;
       +                        }
       +                }
       +                return;
       +        }
       +        /* Handle Multi_key separately, since it isn't a modifier */
       +        if(k == XK_Multi_key) {
       +                composing = -1;
       +                return;
       +        }
       +        if(k == NoSymbol)
       +                return;
       +        if(k&0xFF00){
       +                switch(k){
       +                case XK_BackSpace:
       +                case XK_Tab:
       +                case XK_Escape:
       +                case XK_Delete:
       +                case XK_KP_0:
       +                case XK_KP_1:
       +                case XK_KP_2:
       +                case XK_KP_3:
       +                case XK_KP_4:
       +                case XK_KP_5:
       +                case XK_KP_6:
       +                case XK_KP_7:
       +                case XK_KP_8:
       +                case XK_KP_9:
       +                case XK_KP_Divide:
       +                case XK_KP_Multiply:
       +                case XK_KP_Subtract:
       +                case XK_KP_Add:
       +                case XK_KP_Decimal:
       +                        k &= 0x7F;
       +                        break;
       +                case XK_Linefeed:
       +                        k = '\r';
       +                        break;
       +                case XK_KP_Enter:
       +                case XK_Return:
       +                        k = '\n';
       +                        break;
       +                case XK_Left:
       +                case XK_Down:
       +                case XK_Right:
       +                case XK_Next:
       +                        k = 0x80; /* VIEW -- "Scroll" */
       +                        break;
       +                case XK_Up:
       +                case XK_Prior:
       +                        k = 0x81; /* PREVIEW -- "Scroll back" */
       +                        break;
       +                default:
       +                        return;        /* not ISO-1 or tty control */
       +                }
       +        }
       +        /* Compensate for servers that call a minus a hyphen */
       +        if(k == XK_hyphen)
       +                k = XK_minus;
       +        /* Do control mapping ourselves if translator doesn't */
       +        if((e->xkey.state&ControlMask) && !(md&ControlMask))
       +                k &= 0x9f;
       +        if(k == NoSymbol)
       +                return;
       +        /* Check to see if we are in a composition sequence */
       +        if (!((GwinWidget)w)->gwin.compose && (e->xkey.state & Mod1Mask)
       +                        && composing == -2)
       +                composing = -1;
       +        if (composing > -2) {
       +                compose[++composing] = k;
       +                if ((*compose == 'X') && (composing > 0)) {
       +                        if ((k < '0') || (k > 'f') ||
       +                                        ((k > '9') && (k < 'a'))) {
       +                                STUFFCOMPOSE();
       +                                c = (unsigned short)k;
       +                                composing = -2;
       +                        } else if (composing == 4) {
       +                                c = (int)unicode(compose);
       +                                if (c == -1) {
       +                                        STUFFCOMPOSE();
       +                                        c = (unsigned short)compose[4];
       +                                }
       +                                composing = -2;
       +                        }
       +                } else if (composing == 1) {
       +                        c = (int)latin1(compose);
       +                        if (c == -1) {
       +                                STUFFCOMPOSE();
       +                                c = (unsigned short)compose[1];
       +                        }
       +                        composing = -2;
       +                }
       +        } else {
       +                if (composing >= 0) {
       +                        composing++;
       +                        STUFFCOMPOSE();
       +                }
       +                c = (unsigned short)k;
       +                composing = -2;
       +        }
       +
       +        if (composing >= -1)
       +                return;
       +
       +        f = ((GwinWidget)w)->gwin.gotchar;
       +        if(f)
       +                (*f)(c);
       +}
       +
       +static void
       +Mouseaction(Widget w, XEvent *e, String *p, Cardinal *np)
       +{
       +        int s;
       +        XButtonEvent *be;
       +        XMotionEvent *me;
       +        Gwinmouse m;
       +        Mousefunc f;
       +
       +        switch(e->type){
       +        case ButtonPress:
       +                be = (XButtonEvent *)e;
       +                m.xy.x = be->x;
       +                m.xy.y = be->y;
       +                m.msec = be->time;
       +                s = be->state;        /* the previous state */
       +                switch(be->button){
       +                case 1:        s |= Button1Mask; break;
       +                case 2:        s |= Button2Mask; break;
       +                case 3:        s |= Button3Mask; break;
       +                case 4:        s |= Button4Mask; break;
       +                }
       +                break;
       +        case ButtonRelease:
       +                be = (XButtonEvent *)e;
       +                m.xy.x = be->x;
       +                m.xy.y = be->y;
       +                m.msec = be->time;
       +                s = be->state;
       +                switch(be->button){
       +                case 1:        s &= ~Button1Mask; break;
       +                case 2:        s &= ~Button2Mask; break;
       +                case 3:        s &= ~Button3Mask; break;
       +                case 4:        s &= ~Button4Mask; break;
       +                }
       +                break;
       +        case MotionNotify:
       +                me = (XMotionEvent *)e;
       +                s = me->state;
       +                m.xy.x = me->x;
       +                m.xy.y = me->y;
       +                m.msec = me->time;
       +                break;
       +        default:
       +                return;
       +        }
       +        m.buttons = 0;
       +        if(s & Button1Mask) m.buttons |= 1;
       +        if(s & Button2Mask) m.buttons |= 2;
       +        if(s & Button3Mask) m.buttons |= 4;
       +        if(s & Button4Mask) m.buttons |= 8;
       +        f = ((GwinWidget)w)->gwin.gotmouse;
       +        if(f)
       +                (*f)(&m);
       +}
       +
       +static void
       +SelCallback(Widget w, XtPointer cldata, Atom *sel, Atom *seltype,
       +        XtPointer val, unsigned long *len, int *fmt)
       +{
       +        String s;
       +        int n;
       +        GwinWidget gw = (GwinWidget)w;
       +
       +        if(gw->gwin.selection)
       +                XtFree(gw->gwin.selection);
       +        if(*seltype != XA_STRING)
       +                n = 0;
       +        else
       +                n = (*len) * (*fmt/8);
       +        s = (String)XtMalloc(n+1);
       +        if(n > 0)
       +                memcpy(s, (char *)val, n);
       +        s[n] = 0;
       +        gw->gwin.selection = s;
       +        XtFree(val);
       +}
       +
       +static Boolean
       +SendSel(Widget w, Atom *sel, Atom *target, Atom *rtype, XtPointer *ans,
       +                unsigned long *anslen, int *ansfmt)
       +{
       +        GwinWidget gw = (GwinWidget)w;
       +        static Atom targets = 0;
       +        XrmValue src, dst;
       +        char *s;
       +
       +        if(*target == XA_STRING){
       +                s = gw->gwin.selection;
       +                if(!s)
       +                        s = "";
       +                *rtype = XA_STRING;
       +                *ans = (XtPointer) XtNewString(s);
       +                *anslen = strlen(*ans);
       +                *ansfmt = 8;
       +                return TRUE;
       +        }
       +#ifndef R3
       +        if(targets == 0){
       +                src.addr = "TARGETS";
       +                src.size = strlen(src.addr)+1;
       +                dst.size = sizeof(Atom);
       +                dst.addr = (XtPointer) &targets;
       +                XtConvertAndStore(w, XtRString, &src, XtRAtom, &dst);
       +        }
       +        if(*target == targets){
       +                *rtype = XA_ATOM;
       +                *ans = (XtPointer) XtNew(Atom);
       +                *(Atom*) *ans = XA_STRING;
       +                *anslen = 1;
       +                *ansfmt = 32;
       +                return TRUE;
       +        }
       +#endif
       +        return FALSE;
       +}
       +
       +static String
       +SelectSwap(Widget w, String s)
       +{
       +        GwinWidget gw;
       +        String ans;
       +
       +        gw = (GwinWidget)w;
       +        if(gw->gwin.selection){
       +                XtFree(gw->gwin.selection);
       +                gw->gwin.selection = 0;
       +        }
       +#ifdef R3
       +        XtGetSelectionValue(w, XA_PRIMARY, XA_STRING, SelCallback, 0,
       +                        CurrentTime);
       +#else
       +        XtGetSelectionValue(w, XA_PRIMARY, XA_STRING, SelCallback, 0,
       +                        XtLastTimestampProcessed(XtDisplay(w)));
       +#endif
       +        while(gw->gwin.selection == 0)
       +                XtAppProcessEvent(XtWidgetToApplicationContext(w) , XtIMAll);
       +        ans = gw->gwin.selection;
       +        gw->gwin.selection = XtMalloc(strlen(s)+1);
       +        strcpy(gw->gwin.selection, s);
       +#ifdef R3
       +        XtOwnSelection(w, XA_PRIMARY, CurrentTime, SendSel, NULL, NULL);
       +#else
       +        XtOwnSelection(w, XA_PRIMARY, XtLastTimestampProcessed(XtDisplay(w)),
       +                        SendSel, NULL, NULL);
       +#endif
       +        return ans;
       +}
       +
       +/* The returned answer should be free()ed when no longer needed */
       +String
       +GwinSelectionSwap(Widget w, String s)
       +{
       +        XtCheckSubclass(w, gwinWidgetClass, NULL);
       +        return (*((GwinWidgetClass) XtClass(w))->gwin_class.select_swap)(w, s);
       +}
       +
 (DIR) diff --git a/libXg/latin1.c b/libXg/latin1.c
       @@ -0,0 +1,313 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +/* Changes copyright 2014-2014 Rob King. */
       +
       +#include <stdio.h>
       +#include <stdlib.h>
       +#include <string.h>
       +
       +#define MAPPING_MAX 65535
       +
       +struct latin
       +{
       +        unsigned short        l;
       +        unsigned char        c[2];
       +};
       +
       +struct latin latintab[] = {
       +        0x00a1,        '!','!',        /* spanish initial ! */
       +        0x00a2,        'c','$',        /* cent */
       +        0x00a3,        'l','$',        /* pound sterling */
       +        0x00a4,        'g','$',        /* general currency */
       +        0x00a5,        'y','$',        /* yen */
       +        0x00a6,        '|','|',        /* broken vertical bar */
       +        0x00a7,        'S','S',        /* section symbol */
       +        0x00a8,        '\"','\"',        /* dieresis */
       +        0x00a9,        'c','O',        /* copyright */
       +        0x00aa,        's','a',        /* super a, feminine ordinal */
       +        0x00ab,        '<','<',        /* left angle quotation */
       +        0x00ac,        'n','o',        /* not sign, hooked overbar */
       +        0x00ad,        '-','-',        /* soft hyphen */
       +        0x00ae,        'r','O',        /* registered trademark */
       +        0x00af,        '_','_',        /* macron */
       +        0x00b0,        'd','e',        /* degree */
       +        0x00b1,        '+','-',        /* plus-minus */
       +        0x00b2,        's','2',        /* sup 2 */
       +        0x00b3,        's','3',        /* sup 3 */
       +        0x00b4,        '\'','\'',        /* acute accent */
       +        0x00b5,        'm','i',        /* micron */
       +        0x00b6,        'p','g',        /* paragraph (pilcrow) */
       +        0x00b7,        '.','.',        /* centered . */
       +        0x00b8,        ',',',',        /* cedilla */
       +        0x00b9,        's','1',        /* sup 1 */
       +        0x00ba,        's','o',        /* super o, masculine ordinal */
       +        0x00bb,        '>','>',        /* right angle quotation */
       +        0x00bc,        '1','4',        /* 1/4 */
       +        0x00bd,        '1','2',        /* 1/2 */
       +        0x00be,        '3','4',        /* 3/4 */
       +        0x00bf,        '?','?',        /* spanish initial ? */
       +        0x00c0,        '`','A',        /* A grave */
       +        0x00c1,        '\'','A',        /* A acute */
       +        0x00c2,        '^','A',        /* A circumflex */
       +        0x00c3,        '~','A',        /* A tilde */
       +        0x00c4,        '\"','A',        /* A dieresis */
       +        0x00c5,        'o','A',        /* A circle */
       +        0x00c6,        'A','E',        /* AE ligature */
       +        0x00c7,        ',','C',        /* C cedilla */
       +        0x00c8,        '`','E',        /* E grave */
       +        0x00c9,        '\'','E',        /* E acute */
       +        0x00ca,        '^','E',        /* E circumflex */
       +        0x00cb,        '\"','E',        /* E dieresis */
       +        0x00cc,        '`','I',        /* I grave */
       +        0x00cd,        '\'','I',        /* I acute */
       +        0x00ce,        '^','I',        /* I circumflex */
       +        0x00cf,        '\"','I',        /* I dieresis */
       +        0x00d0,        'D','-',        /* Eth */
       +        0x00d1,        '~','N',        /* N tilde */
       +        0x00d2,        '`','O',        /* O grave */
       +        0x00d3,        '\'','O',        /* O acute */
       +        0x00d4,        '^','O',        /* O circumflex */
       +        0x00d5,        '~','O',        /* O tilde */
       +        0x00d6,        '\"','O',        /* O dieresis */
       +        0x00d7,        'm','u',        /* times sign */
       +        0x00d8,        '/','O',        /* O slash */
       +        0x00d9,        '`','U',        /* U grave */
       +        0x00da,        '\'','U',        /* U acute */
       +        0x00db,        '^','U',        /* U circumflex */
       +        0x00dc,        '\"','U',        /* U dieresis */
       +        0x00dd,        '\'','Y',        /* Y acute */
       +        0x00de,        '|','P',        /* Thorn */
       +        0x00df,        's','s',        /* sharp s */
       +        0x00e0,        '`','a',        /* a grave */
       +        0x00e1,        '\'','a',        /* a acute */
       +        0x00e2,        '^','a',        /* a circumflex */
       +        0x00e3,        '~','a',        /* a tilde */
       +        0x00e4,        '\"','a',        /* a dieresis */
       +        0x00e5,        'o','a',        /* a circle */
       +        0x00e6,        'a','e',        /* ae ligature */
       +        0x00e7,        ',','c',        /* c cedilla */
       +        0x00e8,        '`','e',        /* e grave */
       +        0x00e9,        '\'','e',        /* e acute */
       +        0x00ea,        '^','e',        /* e circumflex */
       +        0x00eb,        '\"','e',        /* e dieresis */
       +        0x00ec,        '`','i',        /* i grave */
       +        0x00ed,        '\'','i',        /* i acute */
       +        0x00ee,        '^','i',        /* i circumflex */
       +        0x00ef,        '\"','i',        /* i dieresis */
       +        0x00f0,        'd','-',        /* eth */
       +        0x00f1,        '~','n',        /* n tilde */
       +        0x00f2,        '`','o',        /* o grave */
       +        0x00f3,        '\'','o',        /* o acute */
       +        0x00f4,        '^','o',        /* o circumflex */
       +        0x00f5,        '~','o',        /* o tilde */
       +        0x00f6,        '\"','o',        /* o dieresis */
       +        0x00f7,        '-',':',        /* divide sign */
       +        0x00f8,        '/','o',        /* o slash */
       +        0x00f9,        '`','u',        /* u grave */
       +        0x00fa,        '\'','u',        /* u acute */
       +        0x00fb,        '^','u',        /* u circumflex */
       +        0x00fc,        '\"','u',        /* u dieresis */
       +        0x00fd,        '\'','y',        /* y acute */
       +        0x00fe,        '|','p',        /* thorn */
       +        0x00ff,        '\"','y',        /* y dieresis */
       +        0x2654,        'w','k',        /* chess white king */
       +        0x2655,        'w','q',        /* chess white queen */
       +        0x2656,        'w','r',        /* chess white rook */
       +        0x2657,        'w','b',        /* chess white bishop */
       +        0x2658,        'w','n',        /* chess white knight */
       +        0x2659,        'w','p',        /* chess white pawn */
       +        0x265a,        'b','k',        /* chess black king */
       +        0x265b,        'b','q',        /* chess black queen */
       +        0x265c,        'b','r',        /* chess black rook */
       +        0x265d,        'b','b',        /* chess black bishop */
       +        0x265e,        'b','n',        /* chess black knight */
       +        0x265f,        'b','p',        /* chess black pawn */
       +        0x03b1,        '*','a',        /* alpha */
       +        0x03b2,        '*','b',        /* beta */
       +        0x03b3,        '*','g',        /* gamma */
       +        0x03b4,        '*','d',        /* delta */
       +        0x03b5,        '*','e',        /* epsilon */
       +        0x03b6,        '*','z',        /* zeta */
       +        0x03b7,        '*','y',        /* eta */
       +        0x03b8,        '*','h',        /* theta */
       +        0x03b9,        '*','i',        /* iota */
       +        0x03ba,        '*','k',        /* kappa */
       +        0x03bb,        '*','l',        /* lambda */
       +        0x03bc,        '*','m',        /* mu */
       +        0x03bd,        '*','n',        /* nu */
       +        0x03be,        '*','c',        /* xsi */
       +        0x03bf,        '*','o',        /* omicron */
       +        0x03c0,        '*','p',        /* pi */
       +        0x03c1,        '*','r',        /* rho */
       +        0x03c2,        't','s',        /* terminal sigma */
       +        0x03c3,        '*','s',        /* sigma */
       +        0x03c4,        '*','t',        /* tau */
       +        0x03c5,        '*','u',        /* upsilon */
       +        0x03c6,        '*','f',        /* phi */
       +        0x03c7,        '*','x',        /* chi */
       +        0x03c8,        '*','q',        /* psi */
       +        0x03c9,        '*','w',        /* omega */        
       +        0x0391,        '*','A',        /* Alpha */
       +        0x0392,        '*','B',        /* Beta */
       +        0x0393,        '*','G',        /* Gamma */
       +        0x0394,        '*','D',        /* Delta */
       +        0x0395,        '*','E',        /* Epsilon */
       +        0x0396,        '*','Z',        /* Zeta */
       +        0x0397,        '*','Y',        /* Eta */
       +        0x0398,        '*','H',        /* Theta */
       +        0x0399,        '*','I',        /* Iota */
       +        0x039a,        '*','K',        /* Kappa */
       +        0x039b,        '*','L',        /* Lambda */
       +        0x039c,        '*','M',        /* Mu */
       +        0x039d,        '*','N',        /* Nu */
       +        0x039e,        '*','C',        /* Xsi */
       +        0x039f,        '*','O',        /* Omicron */
       +        0x03a0,        '*','P',        /* Pi */
       +        0x03a1,        '*','R',        /* Rho */
       +        0x03a3,        '*','S',        /* Sigma */
       +        0x03a4,        '*','T',        /* Tau */
       +        0x03a5,        '*','U',        /* Upsilon */
       +        0x03a6,        '*','F',        /* Phi */
       +        0x03a7,        '*','X',        /* Chi */
       +        0x03a8,        '*','Q',        /* Psi */
       +        0x03a9,        '*','W',        /* Omega */
       +        0x2190,        '<','-',        /* left arrow */
       +        0x2191,        'u','a',        /* up arrow */
       +        0x2192,        '-','>',        /* right arrow */
       +        0x2193,        'd','a',        /* down arrow */
       +        0x2194,        'a','b',        /* arrow both */
       +        0x21d0,        'V','=',        /* left double-line arrow */
       +        0x21d2,        '=','V',        /* right double-line arrow */
       +        0x2200,        'f','a',        /* forall */
       +        0x2203,        't','e',        /* there exists */
       +        0x2202,        'p','d',        /* partial differential */
       +        0x2205,        'e','s',        /* empty set */
       +        0x2206,        'D','e',        /* delta */
       +        0x2207,        'g','r',        /* gradient */
       +        0x2208,        'm','o',        /* element of */
       +        0x2209,        '!','m',        /* not element of */
       +        0x220d,        's','t',        /* such that */
       +        0x2217,        '*','*',        /* math asterisk */
       +        0x2219,        'b','u',        /* bullet */
       +        0x221a,        's','r',        /* radical */
       +        0x221d,        'p','t',        /* proportional */
       +        0x221e,        'i','f',        /* infinity */
       +        0x2220,        'a','n',        /* angle */
       +        0x2227,        'l','&',        /* logical and */
       +        0x2228,        'l','|',        /* logical or */
       +        0x2229,        'c','a',        /* intersection */
       +        0x222a,        'c','u',        /* union */
       +        0x222b,        'i','s',        /* integral */
       +        0x2234,        't','f',        /* therefore */
       +        0x2243,        '~','=',        /* asymptotically equal */
       +        0x2245,        'c','g',        /* congruent */
       +        0x2248,        '~','~',        /* almost equal */
       +        0x2260,        '!','=',        /* not equal */
       +        0x2261,        '=','=',        /* equivalent */
       +        0x2266,        '<','=',        /* less than or equal */
       +        0x2267,        '>','=',        /* greater than or equal */
       +        0x2282,        's','b',        /* proper subset */
       +        0x2283,        's','p',        /* proper superset */
       +        0x2284,        '!','b',        /* not subset */
       +        0x2286,        'i','b',        /* reflexive subset */
       +        0x2287,        'i','p',        /* reflexive superset */
       +        0x2295,        'O','+',        /* circle plus */
       +        0x2296,        'O','-',        /* circle minus */
       +        0x2297,        'O','x',        /* circle multiply */
       +        0x22a2,        't','u',        /* turnstile */
       +        0x22a8,        'T','u',        /* valid */
       +        0x22c4,        'l','z',        /* lozenge */
       +        0x22ef,        'e','l',        /* ellipses */
       +         0x2639, ':','(',        /* saddy */
       +         0x263a, ':',')',        /* white-face smiley */
       +         0x263b, ';',')',        /* dark-face smiley */
       +        0,        0,
       +};
       +
       +struct latin *mappings = NULL;
       +
       +void
       +freelatin(void)
       +{
       +        free(mappings);
       +}
       +
       +void
       +initlatin(void)
       +{
       +        FILE *keyboard = NULL;
       +        if (getenv("HOME"))
       +        {
       +                char path[1024] = {0};
       +                snprintf(path, 1023, "%s/.keyboard", getenv("HOME"));
       +                keyboard = fopen(path, "r");
       +        }
       +
       +        if (!keyboard)
       +        {
       +                mappings = latintab;
       +                return;
       +        }
       +
       +        mappings = calloc(MAPPING_MAX + 1, sizeof(struct latin));
       +        if (!mappings)
       +        {
       +                mappings = latintab;
       +                fclose(keyboard);
       +                return;
       +        }
       +
       +        int j = 0;
       +        while (j < MAPPING_MAX)
       +        {
       +                int count = fscanf(keyboard, " %c%c %hx%*[^\n]\n", &(mappings[j].c[0]), &(mappings[j].c[1]), &(mappings[j].l));
       +                if (count == 3)
       +                {
       +                        j++;
       +
       +                }
       +                else if (count == EOF)
       +                {
       +                        memset(&(mappings[j]), 0, sizeof(struct latin));
       +                        break;
       +                }
       +                else
       +                {
       +                        memset(&(mappings[j]), 0, sizeof(struct latin));
       +                }
       +        }
       +
       +        fclose(keyboard);
       +        atexit(freelatin);
       +}
       +
       +long
       +latin1(unsigned char *k)
       +{
       +        struct latin *l;
       +
       +        for(l=mappings; l->l; l++)
       +                if(k[0]==l->c[0] && k[1]==l->c[1])
       +                        return l->l;
       +        return -1;
       +}
       +
       +long
       +unicode(unsigned char *k)
       +{
       +        long i, c;
       +
       +        k++;        /* skip 'X' */
       +        c = 0;
       +        for(i=0; i<4; i++,k++){
       +                c <<= 4;
       +                if('0'<=*k && *k<='9')
       +                        c += *k-'0';
       +                else if('a'<=*k && *k<='f')
       +                        c += 10 + *k-'a';
       +                else if('A'<=*k && *k<='F')
       +                        c += 10 + *k-'A';
       +                else
       +                        return -1;
       +        }
       +        return c;
       +}
 (DIR) diff --git a/libXg/ldconvert.c b/libXg/ldconvert.c
       @@ -0,0 +1,55 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include "libgint.h"
       +
       +void
       +_ldconvert(char *in, int inld, char *out, int outld, int w, int h)
       +{
       +        int        a, b, i, j, i1, j1, j2, mask;
       +        int        ind, inl, outd, outl;
       +        int        hh, ww;
       +        char        *p, *q;
       +
       +        i1 = 8 >> inld;
       +        j1 = 8 >> outld;
       +        ind = 1 << inld;
       +        outd = 1 << outld;
       +        inl = ((w << inld) + 7)/8;
       +        outl = ((w << outld) + 7)/8;
       +        b = 0;
       +
       +        if (ind > outd) {
       +                mask = 256 - (256 >> outd);
       +                for (hh = 0; hh < h; hh++, in += inl, out += outl)
       +                        for (p = in, q = out, ww = 0; ww < w; ww++) {
       +                                for (j = j1; j > 0; ) {
       +                                        a = *p++;
       +                                        for (i = i1; i > 0; i--, j--) {
       +                                                b |= a & mask;
       +                                                a <<= ind;
       +                                                b <<= outd;
       +                                        }
       +                                }
       +                                *q++ = (b >> 8);
       +                        }
       +        } else {
       +                j2 = 1 << (outld - inld);
       +                mask = 256 - (256 >> ind);
       +                for (hh = 0; hh < h; hh++, in += inl, out += outl)
       +                        for (p = in, q = out, ww = 0; ww < w; ww++) {
       +                                a = *p++;
       +                                for (i = i1; i > 0; ) {
       +                                        for (j = j1; j > 0; j--, i--) {
       +                                                b |= a & mask;
       +                                                a <<= ind;
       +                                                b <<= outd;
       +                                        }
       +                                        for (j = j2; j > 0; j--)
       +                                                b |= (b << ind);
       +                                        *q++ = (b >> 8);
       +                                }
       +                        }
       +        }
       +}
 (DIR) diff --git a/libXg/libgint.h b/libXg/libgint.h
       @@ -0,0 +1,93 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +/* internal libg implementation file - include after libg */
       +
       +/*
       + * include defs of standard library routines, if possible,
       + * and string routines
       + */
       +#ifdef _POSIX_SOURCE
       +#include <stdlib.h>
       +#include <string.h>
       +#endif /* _POSIX_SOURCE */
       +
       +/*
       + * use defines to rename X11 types Cursor, Font, Event
       + */
       +
       +#define Cursor xCursor
       +#define Font xFont
       +#define Event xEvent
       +
       +#if        defined(v10) || defined(HPUX)
       +typedef        char*        caddr_t;
       +#endif
       +
       +#include <X11/Xlib.h>
       +#include <X11/Xatom.h>
       +#include <X11/Xutil.h>
       +#include <X11/Xft/Xft.h>
       +
       +#undef Cursor
       +#undef Font
       +#undef Event
       +
       +/* Return a GCs for solid filling/strings/etc., segments/points, and tiling */
       +extern GC        _getfillgc(Fcode, Bitmap*, unsigned long);
       +extern GC        _getcopygc(Fcode, Bitmap*, Bitmap*, int*);
       +extern GC        _getgc(Bitmap*, unsigned long, XGCValues *);
       +
       +/* convert between different bitmap depths */
       +extern void        _ldconvert(char *, int, char *, int, int, int);
       +
       +/* balloc without zero init (which uses a gc!) */
       +extern Bitmap        *_balloc(Rectangle, int);
       +
       +/* X Display for this application's connection */
       +extern Display        *_dpy;
       +
       +/* screen depth foreground and background for this application */
       +extern unsigned long        _fgpixel, _bgpixel;
       +extern XColor                _fgcolor, _bgcolor;
       +
       +/* indexed by log depth (0 <= ld <= 5), to give depth and planemask */
       +extern int                _ld2d[];
       +extern unsigned long        _ld2dmask[];
       +
       +/* libg.h defines:
       + *   extern Bitmap screen;   -- Bitmap for application Window after xbinit()
       + *   extern Font *font;      -- Font for application default font after xbinit()
       + */
       +
       +/*
       + * Conventions:
       + *   The .id field of a Bitmap is an X Pixmap unless the Bitmap is screen,
       + *   in which case it is a Window.
       + *   The .id field of a Cursor is set to the X xCursor the first time the
       + *   cursor is used.
       + *   The .id field of a Font is set to the X xFont.
       + *
       + *   Coordinate conventions: libg bitmaps can have non (0,0) origins,
       + *   but not X Pixmaps, so we have to subtract the min point of a Bitmap
       + *   from coords in the Bitmap before using the point in the corresponding Pixmap.
       + *   The screen Bitmap, however, contains the rectangle in X coords of the
       + *   widget in which the application is started, relative to the window.
       + *   The origin may or may not be (0,0), but in any case, coordinates should
       + *   NOT be translated before using in X calls on the Window.
       + */
       +
       +/* values for bitmap flag field (see _getcopygc if change first two vals) */
       +enum {
       +        DP1=        0x1,        /* depth == 1 (ldepth == 0) */
       +        BL1=        0x2,        /* black == 1 model */
       +        SCR=        0x4,        /* on screen */
       +        ZORG=        0x8,        /* r.min == Pt(0,0) */
       +        SHIFT= 0x20,        /* !SCR & !ZORG */
       +        CLIP=  0x40        /* r != clipr */
       +};
       +
       +/* values for return bltfunc arg of _getcopygc */
       +enum {
       +        UseCopyArea,
       +        UseCopyPlane,
       +        UseFillRectangle
       +};
 (DIR) diff --git a/libXg/menuhit.c b/libXg/menuhit.c
       @@ -0,0 +1,236 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include "libgint.h"
       +
       +enum
       +{
       +        Margin = 3,                /* outside to text */
       +        Border = 2,                /* outside to selection boxes */
       +        Blackborder = 1,        /* width of outlining border */
       +        Vspacing = 1,                /* extra spacing between lines of text */
       +        Maxunscroll = 25,        /* maximum #entries before scrolling turns on */
       +        Nscroll = 20,                /* number entries in scrolling part */
       +        Scrollwid = 14,                /* width of scroll bar */
       +        Gap = 4                        /* between text and scroll bar */
       +};
       +
       +static        Bitmap        *menutxt;
       +
       +static        uchar menutxtbits[] = {
       +        0x22, 0x22, 0x88, 0x88, 0x22, 0x22, 0x88, 0x88,
       +        0x22, 0x22, 0x88, 0x88, 0x22, 0x22, 0x88, 0x88,
       +        0x22, 0x22, 0x88, 0x88, 0x22, 0x22, 0x88, 0x88,
       +        0x22, 0x22, 0x88, 0x88, 0x22, 0x22, 0x88, 0x88,
       +};
       +
       +/*
       + * r is a rectangle holding the text elements.
       + * return the rectangle, including its black edge, holding element i.
       + */
       +static Rectangle
       +menurect(Rectangle r, int i)
       +{
       +        if(i < 0)
       +                return Rect(0, 0, 0, 0);
       +        r.min.y += (font->height+Vspacing)*i;
       +        r.max.y = r.min.y+font->height+Vspacing;
       +        return inset(r, Border-Margin);
       +}
       +
       +/*
       + * r is a rectangle holding the text elements.
       + * return the element number containing p.
       + */
       +static int
       +menusel(Rectangle r, Point p)
       +{
       +        if(!ptinrect(p, r))
       +                return -1;
       +        return (p.y-r.min.y)/(font->height+Vspacing);
       +}
       +
       +/*
       + * menur is a rectangle holding all the highlightable text elements.
       + * track mouse while inside the box, return what's selected when button
       + * is raised, -1 as soon as it leaves box.
       + * invariant: nothing is highlighted on entry or exit.
       + */
       +static int
       +menuscan(int but, Mouse *m, Rectangle menur, int lasti)
       +{
       +        int i;
       +        Rectangle r;
       +
       +        r = menurect(menur, lasti);
       +        bitblt(&screen, r.min, &screen, r, F&~D);
       +        *m = emouse();
       +        while(m->buttons & (1<<(but-1))){
       +                *m = emouse();
       +                i = menusel(menur, m->xy);
       +                if(i == lasti)
       +                        continue;
       +                bitblt(&screen, r.min, &screen, r, F&~D);
       +                if(i == -1)
       +                        return i;
       +                r = menurect(menur, i);
       +                bitblt(&screen, r.min, &screen, r, F&~D);
       +                lasti = i;
       +        }
       +        return lasti;
       +}
       +
       +void
       +menupaint(Menu *menu, Rectangle textr, int off, int nitemdrawn)
       +{
       +        int i;
       +        Point pt;
       +        Rectangle r;
       +        char *item;
       +
       +        r = inset(textr, Border-Margin);
       +        bitblt(&screen, r.min, &screen, r, 0);
       +        pt = Pt(textr.min.x+textr.max.x, textr.min.y);
       +        for(i = 0; i<nitemdrawn; i++, pt.y += font->height+Vspacing){
       +                item = menu->item? menu->item[i+off] : (*menu->gen)(i+off);
       +                string(&screen,
       +                        Pt((pt.x-strwidth(font, item))/2, pt.y),
       +                        font, item, S);
       +        }
       +}
       +
       +static void
       +menuscrollpaint(Rectangle scrollr, int off, int nitem, int nitemdrawn)
       +{
       +        Rectangle r;
       +
       +        bitblt(&screen, scrollr.min, &screen, scrollr, 0);
       +        r.min.x = scrollr.min.x;
       +        r.max.x = scrollr.max.x;
       +        r.min.y = scrollr.min.y + (Dy(scrollr)*off)/nitem;
       +        r.max.y = scrollr.min.y + (Dy(scrollr)*(off+nitemdrawn))/nitem;
       +        if(r.max.y < r.min.y+2)
       +                r.max.y = r.min.y+2;
       +        border(&screen, r, 1, F);
       +        if(menutxt == 0){
       +                menutxt = balloc(Rect(0, 0, 16, 16), 0);
       +                if(menutxt)
       +                        wrbitmap(menutxt, 0, 16, menutxtbits);
       +        }
       +        if(menutxt)
       +                texture(&screen, inset(r, 1), menutxt, S);
       +}
       +
       +int
       +menuhit(int but, Mouse *m, Menu *menu)
       +{
       +        int i, nitem, nitemdrawn, maxwid, lasti, off, noff, wid, screenitem;
       +        int scrolling;
       +        Rectangle r, menur, sc, textr, scrollr;
       +        Bitmap *b;
       +        Point pt;
       +        char *item;
       +
       +        sc = screen.clipr;
       +        clipr(&screen, screen.r);
       +        maxwid = 0;
       +        for(nitem = 0;
       +            item = menu->item? menu->item[nitem] : (*menu->gen)(nitem);
       +            nitem++){
       +                i = strwidth(font, item);
       +                if(i > maxwid)
       +                        maxwid = i;
       +        }
       +        if(menu->lasthit<0 || menu->lasthit>=nitem)
       +                menu->lasthit = 0;
       +        screenitem = (Dy(screen.r)-10)/(font->height+Vspacing);
       +        if(nitem>Maxunscroll || nitem>screenitem){
       +                scrolling = 1;
       +                nitemdrawn = Nscroll;
       +                if(nitemdrawn > screenitem)
       +                        nitemdrawn = screenitem;
       +                wid = maxwid + Gap + Scrollwid;
       +                off = menu->lasthit - nitemdrawn/2;
       +                if(off < 0)
       +                        off = 0;
       +                if(off > nitem-nitemdrawn)
       +                        off = nitem-nitemdrawn;
       +                lasti = menu->lasthit-off;
       +        }else{
       +                scrolling = 0;
       +                nitemdrawn = nitem;
       +                wid = maxwid;
       +                off = 0;
       +                lasti = menu->lasthit;
       +        }
       +        r = inset(Rect(0, 0, wid, nitemdrawn*(font->height+Vspacing)), -Margin);
       +        r = rsubp(r, Pt(wid/2, lasti*(font->height+Vspacing)+font->height/2));
       +        r = raddp(r, m->xy);
       +        pt = Pt(0, 0);
       +        if(r.max.x>screen.r.max.x)
       +                pt.x = screen.r.max.x-r.max.x;
       +        if(r.max.y>screen.r.max.y)
       +                pt.y = screen.r.max.y-r.max.y;
       +        if(r.min.x<screen.r.min.x)
       +                pt.x = screen.r.min.x-r.min.x;
       +        if(r.min.y<screen.r.min.y)
       +                pt.y = screen.r.min.y-r.min.y;
       +        menur = raddp(r, pt);
       +        textr.max.x = menur.max.x-Margin;
       +        textr.min.x = textr.max.x-maxwid;
       +        textr.min.y = menur.min.y+Margin;
       +        textr.max.y = textr.min.y + nitemdrawn*(font->height+Vspacing);
       +        if(scrolling){
       +                scrollr = inset(menur, Border);
       +                scrollr.max.x = scrollr.min.x+Scrollwid;
       +        }else
       +                scrollr = Rect(0, 0, 0, 0);
       +
       +        b = balloc(menur, screen.ldepth);
       +        if(b == 0)
       +                b = &screen;
       +        bitblt(b, menur.min, &screen, menur, S);
       +        bitblt(&screen, menur.min, &screen, menur, 0);
       +        border(&screen, menur, Blackborder, F);
       +        r = menurect(textr, lasti);
       +        cursorset(divpt(add(r.min, r.max), 2));
       +        menupaint(menu, textr, off, nitemdrawn);
       +        if(scrolling)
       +                menuscrollpaint(scrollr, off, nitem, nitemdrawn);
       +        r = menurect(textr, lasti);
       +        cursorset(divpt(add(r.min, r.max), 2));
       +        menupaint(menu, textr, off, nitemdrawn);
       +        if(scrolling)
       +                menuscrollpaint(scrollr, off, nitem, nitemdrawn);
       +        while(m->buttons & (1<<(but-1))){
       +                lasti = menuscan(but, m, textr, lasti);
       +                if(lasti >= 0)
       +                        break;
       +                while(!ptinrect(m->xy, textr) && (m->buttons & (1<<(but-1)))){
       +                        if(scrolling && ptinrect(m->xy, scrollr)){
       +                                noff = ((m->xy.y-scrollr.min.y)*nitem)/Dy(scrollr);
       +                                noff -= nitemdrawn/2;
       +                                if(noff < 0)
       +                                        noff = 0;
       +                                if(noff > nitem-nitemdrawn)
       +                                        noff = nitem-nitemdrawn;
       +                                if(noff != off){
       +                                        off = noff;
       +                                        menupaint(menu, textr, off, nitemdrawn);
       +                                        menuscrollpaint(scrollr, off, nitem, nitemdrawn);
       +                                }
       +                        }
       +                        *m = emouse();
       +                }
       +        }
       +        bitblt(&screen, menur.min, b, menur, S);
       +        if(b != &screen)
       +                bfree(b);
       +        clipr(&screen, sc);
       +        if(lasti >= 0){
       +                menu->lasthit = lasti+off;
       +                return menu->lasthit;
       +        }
       +        return -1;
       +}
 (DIR) diff --git a/libXg/point.c b/libXg/point.c
       @@ -0,0 +1,21 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include "libgint.h"
       +
       +void
       +point(Bitmap *b, Point p, int v, Fcode f)
       +{
       +        int x, y;
       +        GC g;
       +
       +        x = p.x;
       +        y = p.y;
       +        if(b->flag&SHIFT){
       +                x -= b->r.min.x;
       +                y -= b->r.min.y;
       +        }
       +        g = _getfillgc(f, b, v);
       +        XDrawPoint(_dpy, (Drawable)b->id, g, x, y);
       +}
 (DIR) diff --git a/libXg/polysegment.c b/libXg/polysegment.c
       @@ -0,0 +1,27 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include "libgint.h"
       +
       +void
       +polysegment(Bitmap *d, int n, Point *pp, int v, Fcode f)
       +{
       +        XPoint *xp;
       +        int i;
       +        GC g;
       +
       +        if (!(xp = (XPoint *)calloc(n, sizeof(XPoint))))
       +                berror("polysegment: could not allocate XPoints");
       +        for (i = 0; i < n; i++, pp++)
       +                if(d->flag&SHIFT){
       +                        xp[i].x = pp->x - d->r.min.x;
       +                        xp[i].y = pp->y - d->r.min.y;
       +                } else {
       +                        xp[i].x = pp->x;
       +                        xp[i].y = pp->y;
       +                }
       +        g = _getfillgc(f, d, v);
       +        XDrawLines(_dpy, (Drawable)d->id, g, xp, n, CoordModeOrigin);
       +        free(xp);
       +}
 (DIR) diff --git a/libXg/rdbitmap.c b/libXg/rdbitmap.c
       @@ -0,0 +1,63 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include "libgint.h"
       +
       +void
       +rdbitmap(Bitmap *b, int miny, int maxy, unsigned char *data)
       +{
       +        XImage *gim, *eim;
       +        int x, y, w, h, pix, l, offset, px;
       +        int inld, outld;
       +        char *tdata;
       +
       +        /*
       +         * The XGetImage returned image may be wrong in a number of ways:
       +         * wrong bit order, byte order, bit pad, scanline pad,
       +         * and constant shift.
       +         * So use a SLOW loop, for now
       +         */
       +        w = Dx(b->r);
       +        h = maxy - miny;
       +        outld = b->ldepth;
       +        inld = (b->ldepth == 0) ? 0 : screen.ldepth;
       +        gim = XGetImage(_dpy, (Drawable)b->id, 0, miny - b->r.min.y,
       +                        w, h, ~0, ZPixmap);
       +        px = 1<<(3-outld);        /* pixels per byte */
       +        /* set l to number of bytes of data per scan line */
       +        if(b->r.min.x >= 0)
       +                offset = b->r.min.x % px;
       +        else
       +                offset = px - b->r.min.x % px;
       +        l = (-b->r.min.x+px-1)/px;
       +        if(b->r.max.x >= 0)
       +                l += (b->r.max.x+px-1)/px;
       +        else
       +                l -= b->r.max.x/px;
       +        l *= h;
       +        if(l <= 0)
       +                return;
       +        tdata = (char *)malloc(l);
       +        if (tdata == (char *) 0)
       +                berror("rdbitmap malloc");
       +        eim = XCreateImage(_dpy, 0, 1 << inld, ZPixmap, 0, tdata,
       +                        w+offset, h, 8, 0);
       +        eim->bitmap_pad = 8;
       +        eim->bitmap_bit_order = MSBFirst;
       +        eim->byte_order = MSBFirst;
       +
       +        for(y = 0; y < h; y++)
       +                for(x = 0; x < w; x++) {
       +                        pix = XGetPixel(gim, x, y);
       +                        XPutPixel(eim, x+offset, y, pix);
       +                }
       +
       +        if (inld == outld)
       +                memcpy((char *)data, tdata, l);
       +        else
       +                _ldconvert(tdata, inld, (char*)data, outld, w, h);
       +
       +        XDestroyImage(gim);
       +        XDestroyImage(eim);
       +}
 (DIR) diff --git a/libXg/rdbitmapfile.c b/libXg/rdbitmapfile.c
       @@ -0,0 +1,65 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include "libgint.h"
       +
       +#define CHUNK 6000
       +
       +Bitmap*
       +rdbitmapfile(int fd)
       +{
       +        char hdr[5*12+1];
       +        unsigned char *data;
       +        long dy, px;
       +        unsigned long l, t, n;
       +        long miny, maxy;
       +        Rectangle r;
       +        int ld;
       +        Bitmap *b;
       +
       +        if(read(fd, hdr, 5*12)!=5*12)
       +                berror("rdbitmapfile read");
       +        ld = atoi(hdr+0*12);
       +        r.min.x = atoi(hdr+1*12);
       +        r.min.y = atoi(hdr+2*12);
       +        r.max.x = atoi(hdr+3*12);
       +        r.max.y = atoi(hdr+4*12);
       +        if(ld<0 || ld>1)
       +                berror("rdbitmapfile ldepth");
       +        if(r.min.x>r.max.x || r.min.y>r.max.y)
       +                berror("rdbitmapfile rectangle");
       +
       +        miny = r.min.y;
       +        maxy = r.max.y;
       +        px = 1<<(3-ld);        /* pixels per byte */
       +        /* set l to number of bytes of data per scan line */
       +        if(r.min.x >= 0)
       +                l = (r.max.x+px-1)/px - r.min.x/px;
       +        else{        /* make positive before divide */
       +                t = (-r.min.x)+px-1;
       +                t = (t/px)*px;
       +                l = (t+r.max.x+px-1)/px;
       +        }
       +        b = balloc(r, ld);
       +        if(b == 0)
       +                return 0;
       +        data = (unsigned char *)malloc(CHUNK);
       +        if(data == 0)
       +                berror("rdbitmapfile malloc");
       +        while(maxy > miny){
       +                dy = maxy - miny;
       +                if(dy*l > CHUNK)
       +                        dy = CHUNK/l;
       +                n = dy*l;
       +                if(read(fd, data, n) != n){
       +                        free(data);
       +                        bfree(b);
       +                        berror("rdbitmapfile read");
       +                }
       +                wrbitmap(b, miny, miny+dy, data);
       +                miny += dy;
       +        }
       +        free(data);
       +        return b;
       +}
 (DIR) diff --git a/libXg/rectclip.c b/libXg/rectclip.c
       @@ -0,0 +1,25 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +
       +rectclip(Rectangle *rp, Rectangle b)                /* first by reference, second by value */
       +{
       +        Rectangle *bp = &b;
       +        /*
       +         * Expand rectXrect() in line for speed
       +         */
       +        if((rp->min.x<bp->max.x && bp->min.x<rp->max.x &&
       +            rp->min.y<bp->max.y && bp->min.y<rp->max.y)==0)
       +                return 0;
       +        /* They must overlap */
       +        if(rp->min.x < bp->min.x)
       +                rp->min.x = bp->min.x;
       +        if(rp->min.y < bp->min.y)
       +                rp->min.y = bp->min.y;
       +        if(rp->max.x > bp->max.x)
       +                rp->max.x = bp->max.x;
       +        if(rp->max.y > bp->max.y)
       +                rp->max.y = bp->max.y;
       +        return 1;
       +}
 (DIR) diff --git a/libXg/rune.c b/libXg/rune.c
       @@ -0,0 +1,213 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include        <u.h>
       +#include        <libc.h>
       +#include <string.h>
       +
       +enum
       +{
       +        Bit1        = 7,
       +        Bitx        = 6,
       +        Bit2        = 5,
       +        Bit3        = 4,
       +        Bit4        = 3,
       +
       +        T1        = ((1<<(Bit1+1))-1) ^ 0xFF,        /* 0000 0000 */
       +        Tx        = ((1<<(Bitx+1))-1) ^ 0xFF,        /* 1000 0000 */
       +        T2        = ((1<<(Bit2+1))-1) ^ 0xFF,        /* 1100 0000 */
       +        T3        = ((1<<(Bit3+1))-1) ^ 0xFF,        /* 1110 0000 */
       +        T4        = ((1<<(Bit4+1))-1) ^ 0xFF,        /* 1111 0000 */
       +
       +        Rune1        = (1<<(Bit1+0*Bitx))-1,                /* 0000 0000 0111 1111 */
       +        Rune2        = (1<<(Bit2+1*Bitx))-1,                /* 0000 0111 1111 1111 */
       +        Rune3        = (1<<(Bit3+2*Bitx))-1,                /* 1111 1111 1111 1111 */
       +
       +        Maskx        = (1<<Bitx)-1,                        /* 0011 1111 */
       +        Testx        = Maskx ^ 0xFF,                        /* 1100 0000 */
       +
       +        Bad        = Runeerror
       +};
       +
       +int
       +chartorune(Rune *rune, char *str)
       +{
       +        int c, c1, c2;
       +        long l;
       +
       +        /*
       +         * one character sequence
       +         *        00000-0007F => T1
       +         */
       +        c = *(uchar*)str;
       +        if(c < Tx) {
       +                *rune = c;
       +                return 1;
       +        }
       +
       +        /*
       +         * two character sequence
       +         *        0080-07FF => T2 Tx
       +         */
       +        c1 = *(uchar*)(str+1) ^ Tx;
       +        if(c1 & Testx)
       +                goto bad;
       +        if(c < T3) {
       +                if(c < T2)
       +                        goto bad;
       +                l = ((c << Bitx) | c1) & Rune2;
       +                if(l <= Rune1)
       +                        goto bad;
       +                *rune = l;
       +                return 2;
       +        }
       +
       +        /*
       +         * three character sequence
       +         *        0800-FFFF => T3 Tx Tx
       +         */
       +        c2 = *(uchar*)(str+2) ^ Tx;
       +        if(c2 & Testx)
       +                goto bad;
       +        if(c < T4) {
       +                l = ((((c << Bitx) | c1) << Bitx) | c2) & Rune3;
       +                if(l <= Rune2)
       +                        goto bad;
       +                *rune = l;
       +                return 3;
       +        }
       +
       +        /*
       +         * bad decoding
       +         */
       +bad:
       +        *rune = Bad;
       +        return 1;
       +}
       +
       +int
       +runetochar(char *str, Rune *rune)
       +{
       +        long c;
       +
       +        /*
       +         * one character sequence
       +         *        00000-0007F => 00-7F
       +         */
       +        c = *rune;
       +        if(c <= Rune1) {
       +                str[0] = c;
       +                return 1;
       +        }
       +
       +        /*
       +         * two character sequence
       +         *        0080-07FF => T2 Tx
       +         */
       +        if(c <= Rune2) {
       +                str[0] = T2 | (c >> 1*Bitx);
       +                str[1] = Tx | (c & Maskx);
       +                return 2;
       +        }
       +
       +        /*
       +         * three character sequence
       +         *        0800-FFFF => T3 Tx Tx
       +         */
       +        str[0] = T3 |  (c >> 2*Bitx);
       +        str[1] = Tx | ((c >> 1*Bitx) & Maskx);
       +        str[2] = Tx |  (c & Maskx);
       +        return 3;
       +}
       +
       +int
       +runelen(long c)
       +{
       +        Rune rune;
       +        char str[10];
       +
       +        rune = c;
       +        return runetochar(str, &rune);
       +}
       +
       +int
       +runenlen(Rune *r, int nrune)
       +{
       +        int nb, c;
       +
       +        nb = 0;
       +        while(nrune--) {
       +                c = *r++;
       +                if(c <= Rune1)
       +                        nb++;
       +                else
       +                if(c <= Rune2)
       +                        nb += 2;
       +                else
       +                        nb += 3;
       +        }
       +        return nb;
       +}
       +
       +int
       +fullrune(char *str, int n)
       +{
       +        int c;
       +
       +        if(n > 0) {
       +                c = *(uchar*)str;
       +                if(c < Tx)
       +                        return 1;
       +                if(n > 1)
       +                        if(c < T3 || n > 2)
       +                                return 1;
       +        }
       +        return 0;
       +}
       +
       +char*
       +utfrune(char *s, long c)
       +{
       +        long c1;
       +        Rune r;
       +        int n;
       +
       +        if(c < Runesync)                /* not part of utf sequence */
       +                return strchr(s, c);
       +
       +        for(;;) {
       +                c1 = *(uchar*)s;
       +                if(c1 < Runeself) {        /* one byte rune */
       +                        if(c1 == 0)
       +                                return 0;
       +                        if(c1 == c)
       +                                return s;
       +                        s++;
       +                        continue;
       +                }
       +                n = chartorune(&r, s);
       +                if(r == c)
       +                        return s;
       +                s += n;
       +        }
       +        return 0;
       +}
       +
       +int
       +utflen(char *s)
       +{
       +        int c;
       +        long n;
       +        Rune rune;
       +
       +        n = 0;
       +        for(;;) {
       +                c = *(uchar*)s;
       +                if(c < Runeself) {
       +                        if(c == 0)
       +                                return n;
       +                        s++;
       +                } else
       +                        s += chartorune(&rune, s);
       +                n++;
       +        }
       +        return 0;
       +}
 (DIR) diff --git a/libXg/segment.c b/libXg/segment.c
       @@ -0,0 +1,25 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include "libgint.h"
       +
       +void
       +segment(Bitmap *d, Point p1, Point p2, int v, Fcode f)
       +{
       +        int x1, y1, x2, y2;
       +        GC g;
       +
       +        x1 = p1.x;
       +        y1 = p1.y;
       +        x2 = p2.x;
       +        y2 = p2.y;
       +        if(d->flag&SHIFT){
       +                x1 -= d->r.min.x;
       +                y1 -= d->r.min.y;
       +                x2 -= d->r.min.x;
       +                y2 -= d->r.min.y;
       +        }
       +        g = _getfillgc(f, d, v);
       +        XDrawLine(_dpy, (Drawable)d->id, g, x1, y1, x2, y2);
       +}
 (DIR) diff --git a/libXg/string.c b/libXg/string.c
       @@ -0,0 +1,38 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <string.h>
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include "libgint.h"
       +
       +enum        { Max = 128 };
       +
       +Point
       +string(Bitmap *b, Point p, XftFont *ft, char *s, Fcode f)
       +{
       +        size_t     length  = strlen(s);
       +        XGlyphInfo extents = {0};
       +        int        x       = p.x;
       +        int        y       = p.y;
       +
       +        XftTextExtentsUtf8(_dpy, ft, s, length, &extents);
       +
       +        x = p.x;
       +        y = p.y;
       +        if (b->flag & SHIFT){
       +                x -= b->r.min.x;
       +                y -= b->r.min.y;
       +        }
       +        y += ft->ascent;
       +
       +        XftDraw *drawable = XftDrawCreate(_dpy, (Drawable)(b->id), DefaultVisual(_dpy, DefaultScreen(_dpy)), DefaultColormap(_dpy, DefaultScreen(_dpy)));
       +
       +        XftDrawStringUtf8(drawable, &fontcolor, ft, x, y, s, length);
       +        XftDrawDestroy(drawable);
       +
       +        x += extents.xOff;
       +
       +        p.x = (b->flag & SHIFT) ? x + b->r.min.x : x;
       +        p.x = x + b->r.min.x;
       +        return p;
       +}
 (DIR) diff --git a/libXg/strwidth.c b/libXg/strwidth.c
       @@ -0,0 +1,23 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include "libgint.h"
       +
       +long
       +strwidth(XftFont *f, char *s)
       +{
       +    XGlyphInfo extents = {0};
       +    XftTextExtentsUtf8(_dpy, f, s, strlen(s), &extents);
       +
       +    return extents.xOff;
       +}
       +
       +Point
       +strsize(XftFont *f, char *s)
       +{
       +    XGlyphInfo extents = {0};
       +    XftTextExtentsUtf8(_dpy, f, s, strlen(s), &extents);
       +
       +        return Pt(strwidth(f, s), extents.yOff);
       +}
 (DIR) diff --git a/libXg/test.c b/libXg/test.c
       @@ -0,0 +1,237 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <libc.h>
       +#ifdef __STDC__
       +#include <stdlib.h>
       +#endif
       +#include <libg.h>
       +#include <stdio.h>
       +
       +void cont(char *);
       +void putstring(char *);
       +void colorinit(void);
       +void printcolmap(void);
       +void invertcolmap(void);
       +
       +unsigned char arrowset[] =
       +        {0x00, 0x00, 0x7F, 0xC0, 0x7F, 0x00, 0x7C, 0x00,
       +         0x7E, 0x00, 0x7F, 0x00, 0x6F, 0x80, 0x67, 0xC0,
       +         0x43, 0xE0, 0x41, 0xF0, 0x00, 0xF8, 0x00, 0x7C,
       +         0x00, 0x3E, 0x00, 0x1C, 0x00, 0x08, 0x00, 0x00};
       +
       +char *colors[] = { "Black", "Red", "Green", "Yellow",
       +                "Cyan", "Magenta", "Blue", "White" };
       +RGB colordefs[] = {
       +        { 0,0,0 },                                        /* black */
       +        {0xFFFFFFFF,        0x00000000,        0x00000000},        /* red */
       +        {0x00000000,        0xFFFFFFFF,        0x00000000},        /* green */
       +        {0xFFFFFFFF,        0xFFFFFFFF,        0x00000000},        /* yellow */
       +        {0x00000000,        0xFFFFFFFF,        0xFFFFFFFF},        /* cyan */
       +        {0xFFFFFFFF,        0x00000000,        0xFFFFFFFF},        /* magenta */
       +        {0x00000000,        0x00000000,        0xFFFFFFFF},        /* blue */
       +        {0xFFFFFFFF,        0xFFFFFFFF,        0xFFFFFFFF},        /* white */
       +};
       +#define Ncol (sizeof(colordefs)/sizeof(colordefs[0]))
       +unsigned long rgbval[Ncol];
       +Bitmap *rgbbitmap[Ncol];
       +
       +main(int argc, char **argv)
       +{
       +        Point p1,p2,p3;
       +        Mouse m;
       +        int r,rx,ry;
       +        int n, i;
       +        char *m3gen(int);
       +        static Menu menu3 = { (char **) 0, m3gen, 0 };
       +        char *p, buf[200];
       +        Bitmap *bm, *bm2;
       +        RGB cmap[256];
       +
       +        xtbinit(0,0,&argc,argv,0);
       +        einit(Ekeyboard|Emouse);
       +        p1 = add(screen.r.min, Pt(15,15));
       +        p2 = sub(screen.r.max, Pt(15,15));
       +        p3 = divpt(add(p1,p2),2);
       +        fprintf(stderr, "segment(&screen, (%d,%d), (%d,%d), ~0, S)\n",
       +                p1.x,p1.y,p2.x,p2.y);
       +        segment(&screen, p1, p2, ~0, S);
       +        cont("point");
       +        fprintf(stderr, "point(&screen, (%d,%d), ~0, S)\n", p1.x,p1.y);
       +        point(&screen, p1, ~0, S);
       +        cont("circle");
       +        rx = p3.x - p1.x;
       +        ry = p3.y - p1.y;
       +        r = (rx < ry)? rx : ry;
       +        fprintf(stderr, "circle(&screen, (%d,%d), %d, ~0, S)\n",
       +                p3.x,p3.y,r);
       +        circle(&screen, p3, r, ~0, S);
       +        cont("disc");
       +        fprintf(stderr, "disc(&screen, (%d,%d), %d, ~0, S)\n",
       +                p3.x,p3.y,r);
       +        disc(&screen, p3, r, ~0, S);
       +        cont("clipped disc");
       +        fprintf(stderr, "clipr(&screen, ((%d,%d)(%d,%d))\n",
       +                p1.x+30, p1.y+5, p3.x-30, p3.y-5);
       +        clipr(&screen, Rect(p1.x+30, p1.y+5, p3.x-30, p3.y-5));
       +        fprintf(stderr, "disc(&screen, (%d,%d), %d, ~0, S)\n",
       +                p3.x,p3.y,r);
       +        disc(&screen, p3, r, ~0, S);
       +        clipr(&screen, screen.r);
       +        cont("ellipse");
       +        fprintf(stderr, "ellipse(&screen, (%d,%d), %d, %d, ~0, S)\n",
       +                p3.x,p3.y,r,r/2);
       +        ellipse(&screen, p3, r, r/2, ~0, S);
       +        cont("arc");
       +        fprintf(stderr, "arc(&screen, (%d,%d), (%d,%d), (%d,%d), ~0, S)\n",
       +                p3.x,p3.y, p3.x+r,p3.y, p3.x+r/2,p3.x-(int)(r*.866));
       +        arc(&screen, p3, Pt(p3.x+r,p3.y), Pt(p3.x+r/2,p3.x-(int)(r*.866)), ~0, S);
       +        if(screen.ldepth > 1){
       +                cont("color");
       +                colorinit();
       +                p3 = p1;
       +                rx *= 2;
       +                ry *= 2;
       +                for(i = 0; i<Ncol; i++) {
       +                        texture(&screen, Rpt(p3,add(p3,Pt(rx,ry/Ncol))),
       +                                rgbbitmap[i], S);
       +                        string(&screen, add(p3,Pt(15,15)), font, colors[i], DxorS);
       +                        p3.y += ry/Ncol;
       +                }
       +                printcolmap();
       +                cont("invert colmap");
       +                invertcolmap();
       +                printcolmap();
       +                p3 = p1;
       +                for(i = 0; i<Ncol; i++) {
       +                        texture(&screen, Rpt(p3,add(p3,Pt(rx,ry/Ncol))),
       +                                rgbbitmap[i], S);
       +                        string(&screen, add(p3,Pt(15,15)), font, colors[i], DxorS);
       +                        p3.y += ry/Ncol;
       +                }
       +                cont("restore colmap");
       +                invertcolmap();
       +        }
       +        cont("wrbitmap, border, and bitblt(S)");
       +        bm = balloc(Rect(0,0,16,16), 0);
       +        fprintf(stderr, "border (%d,%d,%d,%d), -2, F)\n",
       +                p1.x, p1.y, p1.x+16, p1.y+16);
       +        border(&screen, Rpt(p1, add(p1,Pt(16,16))), -2, F);
       +        wrbitmap(bm, 0, 16, arrowset);
       +        fprintf(stderr, "bitblt(&screen, (%d,%d), bm, (0,0,16,16), S)\n",
       +                p1.x,p1.y);
       +        bitblt(&screen, p1, bm, Rect(0,0,16,16), S);
       +        cont("mouse track (button 1)");
       +        do{
       +                m = emouse();
       +        } while(!(m.buttons&1));
       +        fprintf(stderr,"test tracking\n");
       +        while(m.buttons&1){
       +                point(&screen, m.xy, ~0, S);
       +                m = emouse();
       +        }
       +        cursorswitch(0);
       +        cont("menuhit (button 3)");
       +        do {
       +                do{
       +                        m = emouse();
       +                } while(!(m.buttons&4));
       +                n = menuhit(3, &m, &menu3);
       +                fprintf(stderr, "button %d\n", n);
       +        } while (n != 0);
       +        cont("keyboard (end with \\n)");
       +        fprintf(stderr, "type something\n");
       +        for (p = buf; (*p = ekbd()) != '\n' && *p != '\r'; p++) {
       +                fprintf(stderr, "%c", *p);
       +                if (*p == '\b')
       +                        p -= 2;
       +                if (p < buf-1)
       +                        p = buf-1;
       +                p[1] = 0;
       +                 putstring(buf);
       +        }
       +        cont("done");
       +        exit(0);
       +}
       +
       +void colorinit(void)        /* set up color definitions */
       +{
       +        int i;
       +
       +        for (i = 0; i < Ncol; i++) {
       +                rgbval[i] = rgbpix(&screen, colordefs[i]);
       +                rgbbitmap[i] = balloc(Rect(0,0,1,1), screen.ldepth);
       +                point(rgbbitmap[i], Pt(0,0), rgbval[i], S);
       +        }
       +}
       +
       +void printcolmap(void)
       +{
       +        int i, n;
       +        RGB cmap[256];
       +
       +        rdcolmap(&screen, cmap);
       +        n = 1 << (1 << screen.ldepth);
       +        fprintf(stderr, "colormap, %d entries\n", n);
       +        for(i = 0; i < n; i++)
       +                fprintf(stderr, "%d:\t%.8x\t%.8x\t%.8x\n",
       +                        i, cmap[i].red, cmap[i].green, cmap[i].blue);
       +}
       +
       +void invertcolmap(void)
       +{
       +        int i, n;
       +        RGB cmap[256];
       +
       +        rdcolmap(&screen, cmap);
       +        n = 1 << (1 << screen.ldepth);
       +        for(i = 0; i < n; i++) {
       +                cmap[i].red = ~cmap[i].red;
       +                cmap[i].green = ~cmap[i].green;
       +                cmap[i].blue = ~cmap[i].blue;
       +        }
       +        wrcolmap(&screen, cmap);
       +}
       +
       +void
       +putstring(char *buf)
       +{
       +        Point p;
       +        static int jmax = 0, l;
       +
       +        p = add(screen.r.min, Pt(20,20));
       +        bitblt(&screen, p, &screen, Rect(p.x, p.y, p.x+jmax, p.y+font->height), Zero);
       +        string(&screen, p, font, buf, F);
       +        if ((l = strwidth(font, buf)) > jmax)
       +                jmax = l;
       +}
       +
       +void
       +cont(char *msg)
       +{
       +        Event ev;
       +        Point mp;
       +
       +        while(event(&ev) != Ekeyboard)
       +                continue;
       +        bitblt(&screen, Pt(0,0), &screen, screen.r, Zero);
       +        mp = add(screen.r.min, Pt(20,20));
       +        string(&screen, mp, font, msg, S);
       +        while(event(&ev) != Ekeyboard)
       +                continue;
       +        bitblt(&screen, Pt(0,0), &screen, screen.r, Zero);
       +}
       +
       +char *
       +m3gen(int n)
       +{
       +        static char *m3[] ={ "quit", "thing1", "thing2" };
       +
       +        if (n < 0 || n > 2)
       +                return 0;
       +        else 
       +                return m3[n];
       +}
       +
       +void
       +ereshaped(Rectangle r)
       +{
       +}
 (DIR) diff --git a/libXg/texture.c b/libXg/texture.c
       @@ -0,0 +1,42 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include "libgint.h"
       +
       +void
       +texture(Bitmap *d, Rectangle r, Bitmap *s, Fcode f)
       +{
       +        int x, y, w, h, bfunc;
       +        GC g;
       +
       +        x = r.min.x;
       +        y = r.min.y;
       +        if(d->flag&SHIFT){
       +                x -= d->r.min.x;
       +                y -= d->r.min.y;
       +        }
       +        g = _getcopygc(f, d, s, &bfunc);
       +        if(d->flag&SHIFT){
       +                XSetTSOrigin(_dpy, g, -d->r.min.x, -d->r.min.y);
       +        }else
       +                XSetTSOrigin(_dpy, g, 0, 0);
       +        w = Dx(r);
       +        h = Dy(r);
       +        if(bfunc == UseFillRectangle){
       +                /* source isn't involved at all */
       +                XFillRectangle(_dpy, (Drawable)d->id, g, x, y, w, h);
       +        }else if(bfunc == UseCopyArea){
       +                XSetTile(_dpy, g, (Drawable)s->id);
       +                XSetFillStyle(_dpy, g, FillTiled);
       +                XFillRectangle(_dpy, (Drawable)d->id, g, x, y, w, h);
       +                XSetFillStyle(_dpy, g, FillSolid);
       +        }else{
       +                if(s->ldepth != 0)
       +                        berror("unsupported texture");
       +                XSetStipple(_dpy, g, (Drawable)s->id);
       +                XSetFillStyle(_dpy, g, FillOpaqueStippled);
       +                XFillRectangle(_dpy, (Drawable)d->id, g, x, y, w, h);
       +                XSetFillStyle(_dpy, g, FillSolid);
       +        }
       +}
 (DIR) diff --git a/libXg/wrbitmap.c b/libXg/wrbitmap.c
       @@ -0,0 +1,55 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include "libgint.h"
       +#include <X11/Intrinsic.h>
       +#ifndef XtSpecificationRelease
       +#define R3
       +#endif
       +
       +#include <stdio.h>
       +void
       +wrbitmap(Bitmap *b, int miny, int maxy, unsigned char *data)
       +{
       +        XImage *im;
       +        int w, h, inld, outld, l, offset, px;
       +        GC g;
       +        char *tdata;
       +
       +        w = Dx(b->r);
       +        h = maxy - miny;
       +        inld = b->ldepth;
       +        outld = (b->ldepth == 0) ? 0 : screen.ldepth;
       +        px = 1<<(3-outld);        /* pixels per byte */
       +        /* set l to number of bytes of data per scan line */
       +        if(b->r.min.x >= 0)
       +                offset = b->r.min.x % px;
       +        else
       +                offset = px - b->r.min.x % px;
       +        l = (-b->r.min.x+px-1)/px;
       +        if(b->r.max.x >= 0)
       +                l += (b->r.max.x+px-1)/px;
       +        else
       +                l -= b->r.max.x/px;
       +        l *= h;
       +
       +        tdata = (char *)malloc(l);
       +        if (tdata == (char *) 0)
       +                        berror("wrbitmap malloc");
       +        if (inld == outld)
       +                memcpy((void*)tdata, (void*)data, l);
       +        else
       +                _ldconvert((char*)data, inld, tdata, outld, w, h);
       +
       +        im = XCreateImage(_dpy, 0, 1 << outld, ZPixmap, 0, tdata, w, h, 8, 0);
       +
       +        /* Botched interface to XCreateImage doesn't let you set these: */
       +        im->bitmap_bit_order = MSBFirst;
       +        im->byte_order = MSBFirst;
       +
       +        g = _getfillgc(S, b, ~0);
       +        XSetBackground(_dpy, g, b->flag&DP1 ? 0 : _bgpixel);
       +        XPutImage(_dpy, (Drawable)b->id, g, im, offset, 0, 0, miny - b->r.min.y, w-offset, h);
       +        XDestroyImage(im);
       +}
 (DIR) diff --git a/libXg/wrbitmapfile.c b/libXg/wrbitmapfile.c
       @@ -0,0 +1,50 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include "libgint.h"
       +
       +#define        CHUNK        4096
       +
       +void
       +wrbitmapfile(int fd, Bitmap *b)
       +{
       +        char hdr[5*12+1];
       +        unsigned char *data;
       +        long dy, px;
       +        unsigned long l, t, n;
       +        long miny, maxy;
       +
       +        sprint(hdr, "%11d %11d %11d %11d %11d ",
       +                b->ldepth, b->r.min.x, b->r.min.y, b->r.max.x, b->r.max.y);
       +        if(write(fd, hdr, 5*12) != 5*12)
       +                berror("wrbitmapfile write");
       +
       +        px = 1<<(3-b->ldepth);        /* pixels per byte */
       +        /* set l to number of bytes of data per scan line */
       +        if(b->r.min.x >= 0)
       +                l = (b->r.max.x+px-1)/px - b->r.min.x/px;
       +        else{        /* make positive before divide */
       +                t = (-b->r.min.x)+px-1;
       +                t = (t/px)*px;
       +                l = (t+b->r.max.x+px-1)/px;
       +        }
       +        miny = b->r.min.y;
       +        maxy = b->r.max.y;
       +        data = (unsigned char *)malloc(CHUNK);
       +        if(data == 0)
       +                berror("wrbitmapfile malloc");
       +        while(maxy > miny){
       +                dy = maxy - miny;
       +                if(dy*l > CHUNK)
       +                        dy = CHUNK/l;
       +                rdbitmap(b, miny, miny+dy, data);
       +                n = dy*l;
       +                if(write(fd, data, n) != n){
       +                        free(data);
       +                        berror("wrbitmapfile write");
       +                }
       +                miny += dy;
       +        }
       +        free(data);
       +}
 (DIR) diff --git a/libXg/xtbinit.c b/libXg/xtbinit.c
       @@ -0,0 +1,830 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include <stdio.h>
       +#include "libgint.h"
       +
       +#define COMPRESSMOUSE
       +
       +#define Cursor xCursor
       +#define Font xFont
       +#define Event xEvent
       +
       +#include <X11/Intrinsic.h>
       +#include <X11/StringDefs.h>
       +#include <X11/Shell.h>
       +#include "Gwin.h"
       +
       +#ifndef XtSpecificationRelease
       +#define R3
       +#define XtAppInitialize(a,b,c,d,e,f,g,h,i) XtInitialize(0,b,c,d,e,f)
       +#define XtConvertAndStore(a,b,c,d,e) (XtConvert(a,b,c,d,e),1)
       +#define XtAppPending(a) XtPending()
       +#define XtAppProcessEvent(a,b) XtProcessEvent(b)
       +#define XtAppAddTimeOut(a,b,c,d) XtAddTimeOut(b,c,d)
       +#define XtAppAddInput(a,b,c,d,e) XtAddInput(b,c,d,e)
       +#define XtPointer caddr_t
       +#endif
       +
       +#undef Cursor
       +#undef Font
       +#undef Event
       +
       +/* libg globals */
       +Bitmap        screen;
       +XftFont        *font;
       +XftColor fontcolor;
       +XftColor bgcolor;
       +
       +/* implementation globals */
       +Display                *_dpy;
       +Widget                _toplevel;
       +unsigned long        _fgpixel, _bgpixel;
       +XColor                _fgcolor, _bgcolor;
       +int                _ld2d[6] = { 1, 2, 4, 8, 16, 24 };
       +unsigned long        _ld2dmask[6] = { 0x1, 0x3, 0xF, 0xFF, 0xFFFF, 0x00FFFFFF };
       +Colormap        _libg_cmap;
       +int                _cmap_installed;
       +
       +/* xbinit implementation globals */
       +#ifndef R3
       +static XtAppContext app;
       +#endif
       +static Widget widg;
       +static int exposed = 0;
       +static Atom wm_take_focus;
       +static Mouse lastmouse;
       +
       +typedef struct Ebuf {
       +    struct Ebuf        *next;
       +    int                n;
       +    unsigned char        buf[2];
       +} Ebuf;
       +
       +typedef struct Esrc {
       +    int        inuse;
       +    int        size;
       +    int        count;
       +    Ebuf        *head;
       +    Ebuf        *tail;
       +} Esrc;
       +
       +#define        MAXINPUT        1024                /* number of queued input events */
       +#define MAXSRC                 10
       +
       +static Esrc        esrc[MAXSRC];
       +static int        nsrc;
       +
       +
       +static int einitcalled = 0;
       +static int Smouse = -1;
       +static int Skeyboard = -1;
       +static int Stimer = -1;
       +
       +
       +static void        reshaped(int, int, int, int);
       +static void        gotchar(int);
       +static void        gotmouse(Gwinmouse *);
       +static int        ilog2(int);
       +static void        pixtocolor(Pixel, XColor *);
       +static Ebuf        *ebread(Esrc *);
       +static Ebuf        *ebadd(Esrc *);
       +static void        focinit(Widget);
       +static void        wmproto(Widget, XEvent *, String *, Cardinal *);
       +static void        waitevent(void);
       +void initlatin();
       +
       +static Errfunc        onerr;
       +
       +String _fallbacks[] = {
       +    "*gwin.width: 400",
       +    "*gwin.height: 400",
       +    NULL
       +};
       +
       +#ifndef R3
       +static char *shelltrans = 
       +    "<ClientMessage> WM_PROTOCOLS : WMProtocolAction()";
       +static XtActionsRec wmpactions[] = {
       +    {"WMProtocolAction", wmproto}
       +};
       +#endif
       +
       +    /* too many X options */
       +static XrmOptionDescRec optable[] = {
       +};
       +
       +
       +
       +
       +void
       +xtbinit(Errfunc f, char *class, int *pargc, char **argv, char **fallbacks)
       +{
       +    int n;
       +    unsigned int depth;
       +    Arg args[20];
       +    char *p;
       +    XSetWindowAttributes attr;
       +    int compose;
       +
       +    initlatin();
       +
       +    if(!class && argv[0]){
       +            p = strrchr(argv[0], '/');
       +            if(p)
       +                    class = XtNewString(p+1);
       +            else
       +                    class = XtNewString(argv[0]);
       +            if(class[0] >= 'a' && class[0] <= 'z')
       +                    class[0] += 'A' - 'a';
       +    }
       +    onerr = f;
       +    if (!fallbacks)
       +            fallbacks = _fallbacks;
       +    n = 0;
       +    XtSetArg(args[n], XtNinput, TRUE);                n++;
       +
       +
       +    if (*pargc >= 3 && strcmp(argv[1], "-r") == 0)
       +    {
       +        char name[512] = {0};
       +        snprintf(name, 511, "samterm on %s", argv[2]);
       +        XtSetArg(args[n], XtNtitle, XtNewString(name)); n++;
       +        XtSetArg(args[n], XtNiconName, XtNewString(name)); n++;
       +    }
       +    else
       +    {
       +        XtSetArg(args[n], XtNtitle, XtNewString("samterm on localhost")); n++;                XtSetArg(args[n], XtNiconName, XtNewString("samterm on localhost")); n++;
       +    }
       +    _toplevel = XtAppInitialize(&app, class,
       +                    optable, sizeof(optable)/sizeof(optable[0]),
       +                    pargc, argv, fallbacks, args, n);
       +
       +
       +    n = 0;
       +    XtSetArg(args[n], XtNreshaped, reshaped);        n++;
       +    XtSetArg(args[n], XtNgotchar, gotchar);                n++;
       +    XtSetArg(args[n], XtNgotmouse, gotmouse);        n++;
       +    widg = XtCreateManagedWidget("gwin", gwinWidgetClass, _toplevel, args, n);
       +
       +    _dpy = XtDisplay(widg);
       +    XAllocNamedColor(_dpy, DefaultColormap(_dpy, DefaultScreen(_dpy)), getenv("FOREGROUND") ? getenv("FOREGROUND") : "#000000", &_fgcolor, &_fgcolor);
       +    XAllocNamedColor(_dpy, DefaultColormap(_dpy, DefaultScreen(_dpy)), getenv("BACKGROUND") ? getenv("BACKGROUND") : "#ffffff", &_bgcolor, &_bgcolor);
       +
       +    n = 0;
       +    XtSetArg(args[n], XtNdepth, &depth);                n++; 
       +    XtSetArg(args[n], XtNcomposeMod, &compose);        n++;
       +    XtGetValues(widg, args, n);
       +
       +    if (compose < 0 || compose > 5) {
       +            n = 0;
       +            XtSetArg(args[n], XtNcomposeMod, 0);        n++;
       +            XtSetValues(widg, args, n);
       +    }
       +
       +    font = XftFontOpenName(_dpy, DefaultScreen(_dpy), getenv("FONT") ? getenv("FONT") : "Mono");
       +    screen.id = 0;
       +    XtRealizeWidget(_toplevel);
       +
       +    pid_t pid = getpid();
       +    XChangeProperty(_dpy, XtWindow(_toplevel), XInternAtom(_dpy, "_NET_WM_PID", False), XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&pid, 1);
       +
       +    _fgpixel = _fgcolor.pixel;
       +    _bgpixel = _bgcolor.pixel;
       +
       +    XRenderColor xrcolor = {0};
       +    xrcolor.red = _fgcolor.red;
       +    xrcolor.green = _fgcolor.green;
       +    xrcolor.blue = _fgcolor.blue;
       +    xrcolor.alpha = 65535;
       +    XftColorAllocValue(_dpy, DefaultVisual(_dpy, DefaultScreen(_dpy)), DefaultColormap(_dpy, DefaultScreen(_dpy)), &xrcolor, &fontcolor);
       +
       +    xrcolor.red = _bgcolor.red;
       +    xrcolor.green = _bgcolor.green;
       +    xrcolor.blue = _bgcolor.blue;
       +    XftColorAllocValue(_dpy, DefaultVisual(_dpy, DefaultScreen(_dpy)), DefaultColormap(_dpy, DefaultScreen(_dpy)), &xrcolor, &bgcolor);
       +
       +    screen.id = (int) XtWindow(widg);
       +    screen.ldepth = ilog2(depth);
       +    screen.flag = SCR;
       +    if(_fgpixel != 0)
       +            screen.flag |= BL1;
       +    if(depth == 1)
       +            screen.flag |= DP1;
       +    /* leave screen rect at all zeros until reshaped() sets it */
       +    while(!exposed) {
       +            XFlush(_dpy);
       +            XtAppProcessEvent(app, XtIMXEvent);
       +    }
       +    XFlush(_dpy);
       +    focinit(_toplevel);
       +}
       +
       +static void
       +focinit(Widget w)
       +{
       +#ifndef R3
       +    XrmValue src, dst;
       +
       +    src.addr = "WM_TAKE_FOCUS";
       +    src.size = strlen((char *)src.addr)+1;
       +    dst.addr = (XtPointer) &wm_take_focus;
       +    dst.size = sizeof(Atom);
       +    XtConvertAndStore(w, XtRString, &src, XtRAtom, &dst);
       +    XSetWMProtocols(XtDisplay(w), XtWindow(w), &wm_take_focus, 1);
       +    XtAppAddActions(app, wmpactions, XtNumber(wmpactions));
       +    XtAugmentTranslations(w, XtParseTranslationTable(shelltrans));
       +#endif
       +}
       +
       +#ifndef R3
       +static void
       +wmproto(Widget w, XEvent *e , String *p, Cardinal *np)
       +{
       +    Time t;
       +
       +    if(e->type == ClientMessage &&
       +          (Atom)(e->xclient.data.l[0]) == wm_take_focus) {
       +            t = (Time) e->xclient.data.l[1];
       +            XtCallAcceptFocus(widg, &t);
       +    }
       +}
       +#endif
       +
       +static void
       +reshaped(int minx, int miny, int maxx, int maxy)
       +{
       +    Ebuf *eb;
       +    Mouse m;
       +
       +    screen.r = Rect(minx, miny, maxx, maxy);
       +    screen.clipr = screen.r;
       +    if (screen.id) {
       +            exposed = 1;
       +            ereshaped(screen.r);
       +    }
       +    if(einitcalled){
       +            /*
       +             * Cause a mouse event, so programs like sam
       +             * will get out of eread and REALLY do the reshape
       +             */
       +            eb = ebadd(&esrc[Smouse]);
       +            if (eb == 0)
       +                    berror("eballoc can't malloc");
       +            memcpy((void*)eb->buf, (void*)&lastmouse, sizeof lastmouse);
       +            esrc[Smouse].count++;
       +    }
       +}
       +
       +static void
       +gotchar(int c)
       +{
       +    Ebuf *eb;
       +
       +    if(!einitcalled || Skeyboard == -1)
       +            return;
       +    eb = ebadd(&esrc[Skeyboard]);
       +    if (eb == 0)
       +            berror("eballoc can't malloc");
       +    BPSHORT(eb->buf, (unsigned short)(c & 0xffff));
       +    esrc[Skeyboard].count++;
       +}
       +
       +static void
       +gotmouse(Gwinmouse *gm)
       +{
       +    Ebuf *eb;
       +    Mouse m;
       +
       +    if(!einitcalled || Smouse == -1)
       +            return;
       +    m.buttons = gm->buttons;
       +    m.xy.x = gm->xy.x;
       +    m.xy.y = gm->xy.y;
       +    m.msec = gm->msec;
       +    lastmouse = m;
       +    eb = ebadd(&esrc[Smouse]);
       +    if (eb == 0)
       +            berror("eballoc can't malloc");
       +    memcpy((void*)eb->buf, (void*)&m, sizeof m);
       +    esrc[Smouse].count++;
       +}
       +
       +static void
       +gotinput(XtPointer cldata, int *pfd, XtInputId *id)
       +{
       +    Ebuf *eb, *lasttail, *newe;
       +    Esrc *es;
       +    int n;
       +
       +    if(!einitcalled)
       +            return;
       +    es = (Esrc *)cldata;
       +    if (es->count >= MAXINPUT)
       +            return;
       +    lasttail = es->tail;
       +    eb = ebadd(es);
       +    if (eb == 0)
       +            return;
       +    if(es->size){
       +            n = read(*pfd, (char *)eb->buf, es->size);
       +            if (n < 0)
       +                    n = 0;
       +            if(n < es->size) {
       +                    newe = realloc(eb, sizeof(Ebuf)+n);
       +                    newe->n = n;
       +                    if (es->head == eb)
       +                            es->head = newe;
       +                    else
       +                            lasttail->next = newe;
       +                    es->tail = newe;
       +            }
       +    }
       +    es->count++;
       +}
       +
       +static void
       +gottimeout(XtPointer cldata, XtIntervalId *id)
       +{
       +    if(!einitcalled || Stimer == -1)
       +            return;
       +    /*
       +     * Don't queue up timeouts, because there's
       +     * too big a danger that they might pile up
       +     * too quickly.
       +     */
       +    esrc[Stimer].head = (Ebuf *)1;
       +    esrc[Stimer].count = 1;
       +    XtAppAddTimeOut(app, (long)cldata, gottimeout, cldata);
       +}
       +
       +static int
       +ilog2(int n)
       +{
       +    int i, v;
       +
       +    for(i=0, v=1; i < 6; i++, v<<=1)
       +            if(n <= v)
       +                    break;
       +    return i;
       +}
       +
       +static void
       +pixtocolor(Pixel p, XColor *pc)
       +{
       +#ifdef R3
       +    Colormap cmap;
       +    Arg args[2];
       +    int n;
       +
       +    n = 0;
       +    XtSetArg(args[n], XtNcolormap, &cmap);        n++;
       +    XtGetValues(_toplevel, args, n);
       +    pc->pixel = p;
       +    XQueryColor(_dpy, cmap, pc);
       +#else
       +    XrmValue xvf, xvt;
       +
       +    xvf.size = sizeof(Pixel);
       +    xvf.addr = (XtPointer)&p;
       +    xvt.size = sizeof(XColor);
       +    xvt.addr = (XtPointer)pc;
       +    if(!XtConvertAndStore(_toplevel, XtRPixel, &xvf, XtRColor, &xvt))
       +            pc->pixel = p;        /* maybe that's enough */
       +#endif
       +}
       +
       +unsigned long
       +rgbpix(Bitmap *b, RGB col)
       +{
       +    XColor c;
       +    Colormap cmap;
       +    Arg args[2];
       +    int n, depth, dr, dg, db;
       +    RGB map[256], *m;
       +    unsigned long d, max, pixel;
       +
       +    if (!_cmap_installed) {
       +            n = 0;
       +            XtSetArg(args[n], XtNcolormap, &cmap);        n++;
       +            XtGetValues(_toplevel, args, n);
       +            c.red = col.red>>16;
       +            c.green = col.green>>16;
       +            c.blue = col.blue>>16;
       +            c.flags = DoRed|DoGreen|DoBlue;
       +            if(XAllocColor(_dpy, cmap, &c))
       +                    return (unsigned long)(c.pixel);
       +    }
       +    depth = _ld2d[screen.ldepth];
       +    rdcolmap(&screen, map);
       +    max = -1;
       +    for (n = 0, m = map; n < (1 << depth); n++, m++)
       +    {
       +            dr = m->red - col.red;
       +            dg = m->green - col.green;
       +            db = m->blue - col.blue;
       +            d = dr*dr+dg*dg+db*db;
       +            if (d < max || max == -1)
       +            {
       +                    max = d;
       +                    pixel = n;
       +            }
       +    }
       +    return pixel;
       +}
       +
       +void
       +rdcolmap(Bitmap *b, RGB *map)
       +{
       +    XColor cols[256];
       +    int i, n, depth;
       +    Colormap cmap;
       +    Arg args[2];
       +
       +    if (_cmap_installed) {
       +            cmap = _libg_cmap;
       +    } else {
       +            i = 0;
       +            XtSetArg(args[i], XtNcolormap, &cmap);        i++;
       +            XtGetValues(_toplevel, args, i);
       +    }
       +
       +    depth = _ld2d[screen.ldepth];
       +    n = 1 << depth;
       +    if (depth == 1) {
       +            map[0].red = map[0].green = map[0].blue = ~0;
       +            map[1].red = map[1].green = map[1].blue = 0;
       +    }
       +    else {
       +            if (n > 256) {
       +                    berror("rdcolmap bitmap too deep");
       +                    return;
       +            }
       +            for (i = 0; i < n; i++)
       +                    cols[i].pixel = i;
       +            XQueryColors(_dpy, cmap, cols, n);
       +            for (i = 0; i < n; i++) {
       +                    map[i].red = (cols[i].red << 16) | cols[i].red;
       +                    map[i].green = (cols[i].green << 16) | cols[i].green;
       +                    map[i].blue = (cols[i].blue << 16) | cols[i].blue;
       +            }
       +    }
       +}
       +
       +void
       +wrcolmap(Bitmap *b, RGB *map)
       +{
       +    int i, n, depth;
       +    Screen *scr;
       +    XColor cols[256];
       +    Arg args[2];
       +    XVisualInfo vi;
       +    Window w;
       +
       +    scr = XtScreen(_toplevel);
       +    depth = _ld2d[screen.ldepth];
       +    n = 1 << depth;
       +    if (n > 256) {
       +            berror("wrcolmap bitmap too deep");
       +            return;
       +    } else if (depth > 1) {
       +            for (i = 0; i < n; i++) {
       +                    cols[i].red = map[i].red >> 16;
       +                    cols[i].green = map[i].green >> 16;
       +                    cols[i].blue = map[i].blue >> 16;
       +                    cols[i].pixel = i;
       +                    cols[i].flags = DoRed|DoGreen|DoBlue;
       +            }
       +            if (!XMatchVisualInfo(_dpy, XScreenNumberOfScreen(scr),
       +                                    depth, PseudoColor, &vi)) {
       +                    berror("wrcolmap can't get visual");
       +                    return;
       +            }
       +            w = XtWindow(_toplevel);
       +            _libg_cmap = XCreateColormap(_dpy, w, vi.visual, AllocAll);
       +            XStoreColors(_dpy, _libg_cmap, cols, n);
       +
       +            i = 0;
       +            XtSetArg(args[i], XtNcolormap, _libg_cmap);        i++;
       +            XtSetValues(_toplevel, args, i);
       +            _cmap_installed = 1;
       +    }
       +}
       +
       +int
       +scrollfwdbut(void)
       +{
       +    Arg arg;
       +    Boolean v;
       +    String s;
       +
       +    XtSetArg(arg, XtNscrollForwardR, &v);
       +    XtGetValues(widg, &arg, 1);
       +    return v ? 3 : 1;
       +}
       +
       +void
       +einit(unsigned long keys)
       +{
       +    /*
       +     * Make sure Smouse = ilog2(Emouse) and Skeyboard == ilog2(Ekeyboard)
       +     */
       +    nsrc = 0;
       +    if(keys&Emouse){
       +            Smouse = 0;
       +            esrc[Smouse].inuse = 1;
       +            esrc[Smouse].size = sizeof(Mouse);
       +            esrc[Smouse].count = 0;
       +            nsrc = Smouse+1;
       +    }
       +    if(keys&Ekeyboard){
       +            Skeyboard = 1;
       +            esrc[Skeyboard].inuse = 1;
       +            esrc[Skeyboard].size = 1;
       +            esrc[Skeyboard].count = 0;
       +            if(Skeyboard >= nsrc)
       +                    nsrc = Skeyboard+1;
       +    }
       +    einitcalled = 1;
       +}
       +
       +unsigned long
       +estart(unsigned long key, int fd, int n)
       +{
       +    int i;
       +
       +    if(fd < 0)
       +            berror("bad fd to estart");
       +    if(n <= 0 || n > EMAXMSG)
       +            n = EMAXMSG;
       +    for(i=0; i<MAXSRC; i++)
       +            if((key & ~(1<<i)) == 0 && !esrc[i].inuse){
       +                    if(nsrc <= i)
       +                            nsrc = i+1;
       +                    esrc[i].inuse = 1;
       +                    esrc[i].size = n;
       +                    esrc[i].count = 0;
       +                    XtAppAddInput(app, fd, (XtPointer)XtInputReadMask,
       +                            gotinput, (XtPointer) &esrc[i]);
       +                    return 1<<i;
       +            }
       +    return 0;
       +}
       +
       +unsigned long
       +etimer(unsigned long key, long n)
       +{
       +    int i;
       +
       +    if(Stimer != -1)
       +            berror("timer started twice");
       +    if(n <= 0)
       +            n = 1000;
       +    for(i=0; i<MAXSRC; i++)
       +            if((key & ~(1<<i)) == 0 && !esrc[i].inuse){
       +                    if(nsrc <= i)
       +                            nsrc = i+1;
       +                    esrc[i].inuse = 1;
       +                    esrc[i].size = 0;
       +                    esrc[i].count = 0;
       +                    XtAppAddTimeOut(app, n, gottimeout, (XtPointer)n);
       +                    Stimer = i;
       +                    return 1<<i;
       +            }
       +    return 0;
       +}
       +
       +unsigned long
       +event(Event *e)
       +{
       +    return eread(~0L, e);
       +}
       +
       +unsigned long
       +eread(unsigned long keys, Event *e)
       +{
       +    Ebuf *eb;
       +    int i;
       +
       +    if(keys == 0)
       +            return 0;
       +            /* Give Priority to X events */
       +    if (XtAppPending(app) & XtIMXEvent)
       +            XtAppProcessEvent(app, XtIMXEvent);
       +
       +    for(;;){
       +            for(i=0; i<nsrc; i++)
       +                    if((keys & (1<<i)) && esrc[i].head){
       +                            if(i == Smouse)
       +                                    e->mouse = emouse();
       +                            else if(i == Skeyboard)
       +                                    e->kbdc = ekbd();
       +                            else if(i == Stimer) {
       +                                    esrc[i].head = 0;
       +                                    esrc[i].count = 0;
       +                            } else {
       +                                    eb = ebread(&esrc[i]);
       +                                    e->n = eb->n;
       +                                    if(e->n > 0)
       +                                            memcpy((void*)e->data, (void*)eb->buf, e->n);
       +                                    free(eb);
       +                            }
       +                            return 1<<i;
       +                    }
       +            waitevent();
       +    }
       +}
       +
       +void
       +eflush(unsigned long keys)
       +{
       +    int i;
       +    Ebuf *eb, *enext;
       +
       +    if(keys == 0)
       +            return;
       +
       +    for(i=0; i<nsrc; i++)
       +            if((keys & (1<<i))){
       +                    for (eb = esrc[i].head; eb; eb = enext) {
       +                            enext = eb->next;
       +                            free(eb);
       +                    }
       +                    esrc[i].count = 0;
       +                    esrc[i].head = 0;
       +                    esrc[i].tail = 0;
       +            }
       +}
       +
       +Mouse
       +emouse(void)
       +{
       +    Mouse m;
       +    Ebuf *eb;
       +
       +    if(!esrc[Smouse].inuse)
       +            berror("mouse events not selected");
       +    eb = ebread(&esrc[Smouse]);
       +    memcpy((void*)&m, (void*)eb->buf, sizeof(Mouse));
       +    free(eb);
       +    return m;
       +}
       +
       +int
       +ekbd(void)
       +{
       +    Ebuf *eb;
       +    int c;
       +
       +    if(!esrc[Skeyboard].inuse)
       +            berror("keyboard events not selected");
       +    eb = ebread(&esrc[Skeyboard]);
       +    c = BGSHORT(eb->buf);
       +    free(eb);
       +    return c;
       +}
       +
       +int
       +ecanread(unsigned long keys)
       +{
       +    int i;
       +
       +    for(;;){
       +            for(i=0; i<nsrc; i++){
       +                    if((keys & (1<<i)) && esrc[i].head)
       +                            return 1<<i;
       +            }
       +            if(XtAppPending(app))
       +                    waitevent();
       +            else
       +                    return 0;
       +    }
       +}
       +
       +int
       +ecanmouse(void)
       +{
       +    if(Smouse == -1)
       +            berror("mouse events not selected");
       +    return ecanread(Emouse);
       +}
       +
       +int
       +ecankbd(void)
       +{
       +    if(Skeyboard == -1)
       +            berror("keyboard events not selected");
       +    return ecanread(Ekeyboard);
       +}
       +
       +static Ebuf*
       +ebread(Esrc *s)
       +{
       +    Ebuf *eb;
       +
       +    while(s->head == 0)
       +            waitevent();
       +    eb = s->head;
       +#ifdef COMPRESSMOUSE
       +    if(s == &esrc[Smouse]) {
       +            while(eb->next) {
       +                    s->head = eb->next;
       +                    s->count--;
       +                    free(eb);
       +                    eb = s->head;
       +            }
       +    }
       +#endif
       +    s->head = s->head->next;
       +    if(s->head == 0) {
       +            s->tail = 0;
       +            s->count = 0;
       +    } else
       +            s->count--;
       +    return eb;
       +}
       +
       +static Ebuf*
       +ebadd(Esrc *s)
       +{
       +    Ebuf *eb;
       +    int m;
       +
       +    m = sizeof(Ebuf);
       +    if(s->size > 1)
       +            m += (s->size-1);        /* overestimate, because of alignment */
       +    eb = (Ebuf *)malloc(m);
       +    if(eb) {
       +            eb->next = 0;
       +            eb->n = s->size;
       +            if(s->tail){
       +                    s->tail->next = eb;
       +                    s->tail = eb;
       +            }else
       +                    s->head = s->tail = eb;
       +    }
       +    return eb;
       +}
       +
       +void
       +berror(char *s)
       +{
       +    if(onerr)
       +            (*onerr)(s);
       +    else{
       +            fprintf(stderr, "libg error: %s:\n", s);
       +            exit(1);
       +    }
       +}
       +
       +void
       +bflush(void)
       +{
       +    while(XtAppPending(app) & XtIMXEvent)
       +            waitevent();
       +}
       +
       +static void
       +waitevent(void)
       +{
       +    XFlush(_dpy);
       +    if (XtAppPending(app) & XtIMXEvent)
       +            XtAppProcessEvent(app, XtIMXEvent);
       +    else
       +            XtAppProcessEvent(app, XtIMAll);
       +}
       +            
       +int
       +snarfswap(char *s, int n, char **t)
       +{
       +    *t = GwinSelectionSwap(widg, s);
       +    if (*t)
       +            return strlen(*t);
       +    return 0;
       +}
       +
       +int scrpix(int *w, int *h)
       +{
       +    if (w)
       +            *w = WidthOfScreen(XtScreen(_toplevel));
       +    if (h)
       +            *h = HeightOfScreen(XtScreen(_toplevel));
       +    return 1;
       +}
       +
       +#ifdef DEBUG
       +/* for debugging */
       +printgc(char *msg, GC g)
       +{
       +    XGCValues v;
       +
       +    XGetGCValues(_dpy, g, GCFunction|GCForeground|GCBackground|GCFont|
       +                    GCTile|GCFillStyle|GCStipple, &v);
       +    fprintf(stderr, "%s: gc %x\n", msg, g);
       +    fprintf(stderr, "  fg %d bg %d func %d fillstyle %d font %x tile %x stipple %x\n",
       +            v.foreground, v.background, v.function, v.fill_style,
       +            v.font, v.tile, v.stipple);
       +}
       +#endif
       +
 (DIR) diff --git a/libframe/Makefile b/libframe/Makefile
       @@ -0,0 +1,47 @@
       +#        Copyright (c) 1998 Lucent Technologies - All rights reserved.
       +#
       +#        Prototype Makefile for libframe
       +#
       +#        define operating system.  ONE of:
       +#                -DIRIX -DSUNOS -DUMIPS -DSYSVR3 -DAIX -DOSF1
       +#                -DHPUX -DAPOLLO -DCONVEX -DDYNIX
       +#        
       +#        Additionally, -D_POSIX_SOURCE (or its equivalent) may be specified
       +#        if your compiler supports posix-compatible compilation
       +include ../config.mk
       +
       +OS=-DIRIX5 
       +
       +#        add -Iincludedir for any include directories that need to be searched
       +#        for posix header files (for UMIPS, add -I/usr/include/posix)
       +INCS=-I../include -I$(FREETYPEINC)
       +
       +#        add name of library orderer - use ":" if none exists
       +RANLIB=:
       +
       +#        add name of library
       +AR=ar
       +
       +CFLAGS=-c $(OS) $(INCS) -D_LIBXG_EXTENSION
       +
       +LIB=libframe.a
       +CC=cc
       +
       +OBJ=frbox.o frdelete.o frdraw.o frinit.o frinsert.o frptofchar.o\
       +        frselect.o frstr.o frutil.o misc.o
       +
       +all:        $(LIB)
       +
       +$(LIB):        $(OBJ)
       +        $(AR) rv $(LIB) $(OBJ)
       +        $(RANLIB) $(LIB)
       +
       +clean:
       +        rm -f *.o *.a
       +
       +nuke:        clean
       +        rm -f $(LIB)
       +
       +install:        $(LIB)
       +
       +$(OBJ):        ../include/u.h ../include/libc.h ../include/frame.h
 (DIR) diff --git a/libframe/frbox.c b/libframe/frbox.c
       @@ -0,0 +1,156 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include <frame.h>
       +
       +#define        SLOP        25
       +
       +void
       +_fraddbox(Frame *f, int bn, int n)        /* add n boxes after bn, shift the rest up,
       +                                 * box[bn+n]==box[bn] */
       +{
       +        int i;
       +
       +        if(bn > f->nbox)
       +                berror("_fraddbox");
       +        if(f->nbox+n > f->nalloc)
       +                _frgrowbox(f, n+SLOP);
       +        for(i=f->nbox; --i>=bn; )
       +                f->box[i+n] = f->box[i];
       +        f->nbox+=n;
       +}
       +
       +void
       +_frclosebox(Frame *f, int n0, int n1)        /* inclusive */
       +{
       +        int i;
       +
       +        if(n0>=f->nbox || n1>=f->nbox || n1<n0)
       +                berror("_frclosebox");
       +        n1++;
       +        for(i=n1; i<f->nbox; i++)
       +                f->box[i-(n1-n0)] = f->box[i];
       +        f->nbox -= n1-n0;
       +}
       +
       +void
       +_frdelbox(Frame *f, int n0, int n1)        /* inclusive */
       +{
       +        if(n0>=f->nbox || n1>=f->nbox || n1<n0)
       +                berror("_frdelbox");
       +        _frfreebox(f, n0, n1);
       +        _frclosebox(f, n0, n1);
       +}
       +
       +void
       +_frfreebox(Frame *f, int n0, int n1)        /* inclusive */
       +{
       +        int i;
       +
       +        if(n1<n0)
       +                return;
       +        if(n0>=f->nbox || n1>=f->nbox)
       +                berror("_frfreebox");
       +        n1++;
       +        for(i=n0; i<n1; i++)
       +                if(f->box[i].nrune >= 0)
       +                        free(f->box[i].a.ptr);
       +}
       +
       +void
       +_frgrowbox(Frame *f, int delta)
       +{
       +        f->nalloc += delta;
       +        f->box = realloc(f->box, f->nalloc*sizeof(Frbox));
       +        if(f->box == 0)
       +                berror("_frgrowbox");
       +}
       +
       +static
       +void
       +dupbox(Frame *f, int bn)
       +{
       +        uchar *p;
       +
       +        if(f->box[bn].nrune < 0)
       +                berror("dupbox");
       +        _fraddbox(f, bn, 1);
       +        if(f->box[bn].nrune >= 0){
       +                p = _frallocstr(NBYTE(&f->box[bn])+1);
       +                strcpy((char*)p, (char*)f->box[bn].a.ptr);
       +                f->box[bn+1].a.ptr = p;
       +        }
       +}
       +
       +static
       +uchar*
       +runeindex(uchar *p, int n)
       +{
       +        int i, w;
       +        Rune rune;
       +
       +        for(i=0; i<n; i++,p+=w)
       +                if(*p < Runeself)
       +                        w = 1;
       +                else{
       +                        w = chartorune(&rune, (char*)p);
       +                        USED(rune);
       +                }
       +        return p;
       +}
       +
       +static
       +void
       +truncatebox(Frame *f, Frbox *b, int n)        /* drop last n chars; no allocation done */
       +{
       +        if(b->nrune<0 || b->nrune<n)
       +                berror("truncatebox");
       +        b->nrune -= n;
       +        runeindex(b->a.ptr, b->nrune)[0] = 0;
       +        b->wid = strwidth(f->font, (char *)b->a.ptr);
       +}
       +
       +static
       +void
       +chopbox(Frame *f, Frbox *b, int n)        /* drop first n chars; no allocation done */
       +{
       +        if(b->nrune<0 || b->nrune<n)
       +                berror("chopbox");
       +        strcpy((char*)b->a.ptr, (char*)runeindex(b->a.ptr, n));
       +        b->nrune -= n;
       +        b->wid = strwidth(f->font, (char *)b->a.ptr);
       +}
       +
       +void
       +_frsplitbox(Frame *f, int bn, int n)
       +{
       +        dupbox(f, bn);
       +        truncatebox(f, &f->box[bn], f->box[bn].nrune-n);
       +        chopbox(f, &f->box[bn+1], n);
       +}
       +
       +void
       +_frmergebox(Frame *f, int bn)                /* merge bn and bn+1 */
       +{
       +        Frbox *b;
       +
       +        b = &f->box[bn];
       +        _frinsure(f, bn, NBYTE(&b[0])+NBYTE(&b[1])+1);
       +        strcpy((char*)runeindex(b[0].a.ptr, b[0].nrune), (char*)b[1].a.ptr);
       +        b[0].wid += b[1].wid;
       +        b[0].nrune += b[1].nrune;
       +        _frdelbox(f, bn+1, bn+1);
       +}
       +
       +int
       +_frfindbox(Frame *f, int bn, ulong p, ulong q)        /* find box containing q and put q on a box boundary */
       +{
       +        Frbox *b;
       +
       +        for(b = &f->box[bn]; bn<f->nbox && p+NRUNE(b)<=q; bn++, b++)
       +                p += NRUNE(b);
       +        if(p != q)
       +                _frsplitbox(f, bn++, (int)(q-p));
       +        return bn;
       +}
 (DIR) diff --git a/libframe/frdelete.c b/libframe/frdelete.c
       @@ -0,0 +1,104 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libg.h>
       +#include <frame.h>
       +
       +int
       +frdelete(Frame *f, ulong p0, ulong p1)
       +{
       +        Point pt0, pt1, ppt0;
       +        Frbox *b;
       +        int n0, n1, n;
       +        Rectangle r;
       +        int nn0;
       +
       +        if(p0>=f->nchars || p0==p1 || f->b==0)
       +                return 0;
       +        if(p1 > f->nchars)
       +                p1 = f->nchars;
       +        n0 = _frfindbox(f, 0, (unsigned long)0, p0);
       +        n1 = _frfindbox(f, n0, p0, p1);
       +        pt0 = _frptofcharnb(f, p0, n0);
       +        pt1 = frptofchar(f, p1);
       +        if(f->p0!=p0 || f->p1!=p1)        /* likely they ARE equal */
       +                frselectp(f, F&~D);        /* can do better some day */
       +        frselectf(f, pt0, pt1, 0);
       +        if(n0 == f->nbox)
       +                berror("off end in frdelete");
       +        nn0 = n0;
       +        ppt0 = pt0;
       +        _frfreebox(f, n0, n1-1);
       +        f->modified = 1;
       +
       +        /*
       +         * Invariants:
       +         *  pt0 points to beginning, pt1 points to end
       +         *  n0 is box containing beginning of stuff being deleted
       +         *  n1, b are box containing beginning of stuff to be kept after deletion
       +         *  region between pt0 and pt1 is clear
       +         */
       +        b = &f->box[n1];
       +        while(pt1.x!=pt0.x && n1<f->nbox){
       +                _frcklinewrap0(f, &pt0, b);
       +                _frcklinewrap(f, &pt1, b);
       +                if(b->nrune > 0){
       +                        n = _frcanfit(f, pt0, b);
       +                        if(n==0)
       +                                berror("_frcanfit==0");
       +                        if(n != b->nrune){
       +                                _frsplitbox(f, n1, n);
       +                                b = &f->box[n1];
       +                        }
       +                        r.min = pt1;
       +                        r.max = pt1;
       +                        r.max.x += b->wid;
       +                        r.max.y += f->font->height;
       +                        bitblt(f->b, pt0, f->b, r, S);
       +                        if(pt0.y == pt1.y)
       +                                r.min.x = r.max.x-(pt1.x-pt0.x);
       +                        bitblt(f->b, r.min, f->b, r, 0);
       +                }
       +                _fradvance(f, &pt1, b);
       +                pt0.x += _frnewwid(f, pt0, b);
       +                f->box[n0++] = f->box[n1++];
       +                b++;
       +        }
       +        if(pt1.y != pt0.y){
       +                Point pt2;
       +
       +                pt2 = _frptofcharptb(f, 32767, pt1, n1);
       +                if(pt2.y > f->r.max.y)
       +                        berror("frptofchar in frdelete");
       +                if(n1 < f->nbox){
       +                        int q0, q1, q2;
       +
       +                        q0 = pt0.y+f->font->height;
       +                        q1 = pt1.y+f->font->height;
       +                        q2 = pt2.y+f->font->height;
       +                        bitblt(f->b, pt0, f->b, Rect(pt1.x, pt1.y, f->r.max.x, q1), S);
       +                        bitblt(f->b, Pt(f->r.min.x, q0), f->b, Rect(f->r.min.x, q1, f->r.max.x, q2), S);
       +                        frselectf(f, Pt(pt2.x, pt2.y-(pt1.y-pt0.y)), pt2, 0);
       +                }else
       +                        frselectf(f, pt0, pt2, 0);
       +        }
       +        _frclosebox(f, n0, n1-1);
       +        if(nn0>0 && f->box[nn0-1].nrune>=0 && ppt0.x-f->box[nn0-1].wid>=(int)f->left){
       +                --nn0;
       +                ppt0.x -= f->box[nn0].wid;
       +        }
       +        _frclean(f, ppt0, nn0, n0<f->nbox-1? n0+1 : n0);
       +        if(f->p1 > p1)
       +                f->p1 -= p1-p0;
       +        else if(f->p1 > p0)
       +                f->p1 = p0;
       +        if(f->p0 > p1)
       +                f->p0 -= p1-p0;
       +        else if(f->p0 > p0)
       +                f->p0 = p0;
       +        frselectp(f, F&~D);
       +        f->nchars -= p1-p0;
       +        pt0 = frptofchar(f, f->nchars);
       +        n = f->nlines;
       +        f->nlines = (pt0.y-f->r.min.y)/f->font->height+(pt0.x>f->left);
       +        return n - f->nlines;
       +}
 (DIR) diff --git a/libframe/frdraw.c b/libframe/frdraw.c
       @@ -0,0 +1,59 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include <frame.h>
       +
       +void
       +_frredraw(Frame *f, Point pt)
       +{
       +        Frbox *b;
       +        int nb;
       +        for(nb=0,b=f->box; nb<f->nbox; nb++, b++){
       +                _frcklinewrap(f, &pt, b);
       +                if(b->nrune >= 0)
       +                        string(f->b, pt, f->font, (char *)b->a.ptr, S^D);
       +                pt.x += b->wid;
       +        }
       +}
       +
       +Point
       +_frdraw(Frame *f, Point pt)
       +{
       +        Frbox *b;
       +        int nb, n;
       +
       +        for(b=f->box,nb=0; nb<f->nbox; nb++, b++){
       +                _frcklinewrap0(f, &pt, b);
       +                if(pt.y == f->r.max.y){
       +                        f->nchars -= _frstrlen(f, nb);
       +                        _frdelbox(f, nb, f->nbox-1);
       +                        break;
       +                }
       +                if(b->nrune > 0){
       +                        n = _frcanfit(f, pt, b);
       +                        if(n == 0)
       +                                berror("draw: _frcanfit==0");
       +                        if(n != b->nrune){
       +                                _frsplitbox(f, nb, n);
       +                                b = &f->box[nb];
       +                        }
       +                        pt.x += b->wid;
       +                }else{
       +                        if(b->a.b.bc == '\n')
       +                                pt.x = f->left, pt.y+=f->font->height;
       +                        else
       +                                pt.x += _frnewwid(f, pt, b);
       +                }
       +        }
       +        return pt;
       +}
       +int
       +_frstrlen(Frame *f, int nb)
       +{
       +        int n;
       +
       +        for(n=0; nb<f->nbox; nb++)
       +                n += NRUNE(&f->box[nb]);
       +        return n;
       +}
 (DIR) diff --git a/libframe/frinit.c b/libframe/frinit.c
       @@ -0,0 +1,42 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include <frame.h>
       +
       +void
       +frinit(Frame *f, Rectangle r, XftFont *ft, Bitmap *b)
       +{
       +        f->font = ft;
       +        f->maxtab = 8*charwidth(ft, '0');
       +        f->nbox = 0;
       +        f->nalloc = 0;
       +        f->nchars = 0;
       +        f->nlines = 0;
       +        f->p0 = 0;
       +        f->p1 = 0;
       +        f->box = 0;
       +        f->lastlinefull = 0;
       +        frsetrects(f, r, b);
       +}
       +
       +void
       +frsetrects(Frame *f, Rectangle r, Bitmap *b)
       +{
       +        f->b = b;
       +        f->entire = r;
       +        f->r = r;
       +        f->r.max.y -= (r.max.y-r.min.y)%f->font->height;
       +        f->left = r.min.x+1;
       +        f->maxlines = (r.max.y-r.min.y)/f->font->height;
       +}
       +
       +void
       +frclear(Frame *f)
       +{
       +        if(f->nbox)
       +                _frdelbox(f, 0, f->nbox-1);
       +        if(f->box)
       +                free(f->box);
       +        f->box = 0;
       +}
 (DIR) diff --git a/libframe/frinsert.c b/libframe/frinsert.c
       @@ -0,0 +1,247 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include <frame.h>
       +
       +#define        DELTA        25
       +#define        TMPSIZE        256
       +static Frame                frame;
       +
       +static
       +Point
       +bxscan(Frame *f, Rune *sp, Rune *ep, Point *ppt)
       +{
       +        int w, c, nb, delta, nl, nr, rw;
       +        Frbox *b;
       +        char *s, tmp[TMPSIZE+3];        /* +3 for rune overflow */
       +        uchar *p;
       +
       +        frame.r = f->r;
       +        frame.b = f->b;
       +        frame.font = f->font;
       +        frame.maxtab = f->maxtab;
       +        frame.left = f->left;
       +        frame.nbox = 0;
       +        frame.nchars = 0;
       +        delta = DELTA;
       +        nl = 0;
       +        for(nb=0; sp<ep && nl<=f->maxlines; nb++,frame.nbox++){
       +                if(nb == frame.nalloc){
       +                        _frgrowbox(&frame, delta);
       +                        if(delta < 10000)
       +                                delta *= 2;
       +                }
       +                b = &frame.box[nb];
       +                c = *sp;
       +                if(c=='\t' || c=='\n'){
       +                        b->a.b.bc = c;
       +                        b->wid = 5000;
       +                        b->a.b.minwid = (c=='\n')? 0 : charwidth(frame.font, ' ');
       +                        b->nrune = -1;
       +                        if(c=='\n')
       +                                nl++;
       +                        frame.nchars++;
       +                        sp++;
       +                }else{
       +                        s = tmp;
       +                        nr = 0;
       +                        w = 0;
       +                        while(sp < ep){
       +                                c = *sp;
       +                                if(c=='\t' || c=='\n')
       +                                        break;
       +                                rw = runetochar(s, sp);
       +                                if(s+rw >= tmp+TMPSIZE)
       +                                        break;
       +                                w += charwidth(frame.font, c);
       +                                sp++;
       +                                s += rw;
       +                                nr++;
       +                        }
       +                        *s++ = 0;
       +                        p = _frallocstr(s-tmp);
       +                        b = &frame.box[nb];
       +                        b->a.ptr = p;
       +                        memmove(p, tmp, s-tmp);
       +                        b->wid = w;
       +                        b->nrune = nr;
       +                        frame.nchars += nr;
       +                }
       +        }
       +        _frcklinewrap0(f, ppt, &frame.box[0]);
       +        return _frdraw(&frame, *ppt);
       +}
       +
       +static
       +void
       +chopframe(Frame *f, Point pt, ulong p, int bn)
       +{
       +        Frbox *b;
       +
       +        for(b = &f->box[bn]; ; b++){
       +                if(b >= &f->box[f->nbox])
       +                        berror("endofframe");
       +                _frcklinewrap(f, &pt, b);
       +                if(pt.y >= f->r.max.y)
       +                        break;
       +                p += NRUNE(b);
       +                _fradvance(f, &pt, b);
       +        }
       +        f->nchars = p;
       +        f->nlines = f->maxlines;
       +        if(b<&f->box[f->nbox])                                /* BUG */
       +                _frdelbox(f, (int)(b-f->box), f->nbox-1);
       +}
       +
       +void
       +frinsert(Frame *f, Rune *sp, Rune *ep, ulong p0)
       +{
       +        Point pt0, pt1, ppt0, ppt1, pt;
       +        Frbox *b;
       +        int n, n0, nn0, y;
       +        Rectangle r;
       +        static struct{
       +                Point pt0, pt1;
       +        }*pts;
       +        static int nalloc=0;
       +        int npts;
       +
       +        if(p0>f->nchars || sp==ep || f->b==0)
       +                return;
       +        n0 = _frfindbox(f, 0, 0, p0);
       +        nn0 = n0;
       +        pt0 = _frptofcharnb(f, p0, n0);
       +        ppt0 = pt0;
       +        pt1 = bxscan(f, sp, ep, &ppt0);
       +        ppt1 = pt1;
       +        if(n0 < f->nbox){
       +                _frcklinewrap(f, &pt0, b = &f->box[n0]);        /* for frselectf() */
       +                _frcklinewrap0(f, &ppt1, b);
       +        }
       +        f->modified = 1;
       +        /*
       +         * ppt0 and ppt1 are start and end of insertion as they will appear when
       +         * insertion is complete. pt0 is current location of insertion position
       +         * (p0); pt1 is terminal point (without line wrap) of insertion.
       +         */
       +        if(p0==f->p0 && p0==f->p1)                /* quite likely */
       +                frselectf(f, pt0, pt0, F&~D);
       +        else
       +                frselectp(f, F&~D);
       +        /*
       +         * Find point where old and new x's line up
       +         * Invariants:
       +         *        pt0 is where the next box (b, n0) is now
       +         *        pt1 is where it will be after then insertion
       +         * If pt1 goes off the rectangle, we can toss everything from there on
       +         */
       +        for(b = &f->box[n0],npts=0;
       +             pt1.x!=pt0.x && pt1.y!=f->r.max.y && n0<f->nbox; b++,n0++,npts++){
       +                _frcklinewrap(f, &pt0, b);
       +                _frcklinewrap0(f, &pt1, b);
       +                if(b->nrune > 0){
       +                        n = _frcanfit(f, pt1, b);
       +                        if(n == 0)
       +                                berror("_frcanfit==0");
       +                        if(n != b->nrune){
       +                                _frsplitbox(f, n0, n);
       +                                b = &f->box[n0];
       +                        }
       +                }
       +                if(npts == nalloc){
       +                        pts = realloc(pts, (npts+DELTA)*sizeof(pts[0]));
       +                        nalloc += DELTA;
       +                        b = &f->box[n0];
       +                }
       +                pts[npts].pt0 = pt0;
       +                pts[npts].pt1 = pt1;
       +                /* has a text box overflowed off the frame? */
       +                if(pt1.y == f->r.max.y)
       +                        break;
       +                _fradvance(f, &pt0, b);
       +                pt1.x += _frnewwid(f, pt1, b);
       +        }
       +        if(pt1.y > f->r.max.y)
       +                berror("frinsert pt1 too far");
       +        if(pt1.y==f->r.max.y && n0<f->nbox){
       +                f->nchars -= _frstrlen(f, n0);
       +                _frdelbox(f, n0, f->nbox-1);
       +        }
       +        if(n0 == f->nbox)
       +                f->nlines = (pt1.y-f->r.min.y)/f->font->height+(pt1.x>f->left);
       +        else if(pt1.y!=pt0.y){
       +                int q0, q1;
       +
       +                y = f->r.max.y;
       +                q0 = pt0.y+f->font->height;
       +                q1 = pt1.y+f->font->height;
       +                f->nlines += (q1-q0)/f->font->height;
       +                if(f->nlines > f->maxlines)
       +                        chopframe(f, ppt1, p0, nn0);
       +                if(pt1.y < y){
       +                        r = f->r;
       +                        r.min.y = q0;
       +                        r.max.y = y-(q1-q0);
       +                        if(q1 < y)
       +                                bitblt(f->b, Pt(f->r.min.x, q1), f->b, r, S);
       +                        r.min = pt0;
       +                        r.max.y = q0;
       +                        bitblt(f->b, pt1, f->b, r, S);
       +                }
       +        }
       +        /*
       +         * Move the old stuff down to make room.  The loop will move the stuff
       +         * between the insertion and the point where the x's lined up.
       +         * The bitblts above moved everything down after the point they lined up.
       +         */
       +        for((y=pt1.y==f->r.max.y?pt1.y:0),b = &f->box[n0-1]; --npts>=0; --b){
       +                pt = pts[npts].pt1;
       +                if(b->nrune > 0){
       +                        r.min = pts[npts].pt0;
       +                        r.max = r.min;
       +                        r.max.x += b->wid;
       +                        r.max.y += f->font->height;
       +                        bitblt(f->b, pt, f->b, r, S);
       +                        if(pt.y < y){        /* clear bit hanging off right */
       +                                r.min = pt;
       +                                r.max = pt;
       +                                r.min.x += b->wid;
       +                                r.max.x = f->r.max.x;
       +                                r.max.y += f->font->height;
       +                                bitblt(f->b, r.min, f->b, r, 0);
       +                        }
       +                        y = pt.y;
       +                }else{
       +                        r.min = pt;
       +                        r.max = pt;
       +                        r.max.x += b->wid;
       +                        r.max.y += f->font->height;
       +                        if(r.max.x >= f->r.max.x)
       +                                r.max.x = f->r.max.x;
       +                        bitblt(f->b, r.min, f->b, r, 0);
       +                        y = (pt.x == f->left)? pt.y : 0;
       +                }
       +        }
       +        frselectf(f, ppt0, ppt1, 0);
       +        _frredraw(&frame, ppt0);
       +        _fraddbox(f, nn0, frame.nbox);
       +        for(n=0; n<frame.nbox; n++)
       +                f->box[nn0+n] = frame.box[n];
       +        if(nn0>0 && f->box[nn0-1].nrune>=0 && ppt0.x-f->box[nn0-1].wid>=(int)f->left){
       +                --nn0;
       +                ppt0.x -= f->box[nn0].wid;
       +        }
       +        n0 += frame.nbox;
       +        _frclean(f, ppt0, nn0, n0<f->nbox-1? n0+1 : n0);
       +        f->nchars += frame.nchars;
       +        if(f->p0 >= p0)
       +                f->p0 += frame.nchars;
       +        if(f->p0 > f->nchars)
       +                f->p0 = f->nchars;
       +        if(f->p1 >= p0)
       +                f->p1 += frame.nchars;
       +        if(f->p1 > f->nchars)
       +                f->p1 = f->nchars;
       +        frselectp(f, F&~D);
       +}
 (DIR) diff --git a/libframe/frptofchar.c b/libframe/frptofchar.c
       @@ -0,0 +1,116 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include <frame.h>
       +
       +Point
       +_frptofcharptb(Frame *f, ulong p, Point pt, int bn)
       +{
       +        uchar *s;
       +        Frbox *b;
       +        int w, l;
       +        Rune r;
       +
       +        for(b = &f->box[bn]; bn<f->nbox; bn++,b++){
       +                _frcklinewrap(f, &pt, b);
       +                if(p < (l=NRUNE(b))){
       +                        if(b->nrune > 0)
       +                                for(s=b->a.ptr; p>0; s+=w, p--){
       +                                        if((r = *s) < Runeself)
       +                                                w = 1;
       +                                        else
       +                                                w = chartorune(&r, (char*)s);
       +                                        pt.x += charwidth(f->font, r);
       +                                        if(r==0 || pt.x>f->r.max.x)
       +                                                berror("frptofchar");
       +                                }
       +                        break;
       +                }
       +                p -= l;
       +                _fradvance(f, &pt, b);
       +        }
       +        return pt;
       +}
       +
       +Point
       +frptofchar(Frame *f, ulong p)
       +{
       +        return _frptofcharptb(f, p, Pt(f->left, f->r.min.y), 0);
       +}
       +
       +Point
       +_frptofcharnb(Frame *f, ulong p, int nb)        /* doesn't do final _fradvance to next line */
       +{
       +        Point pt;
       +        int nbox;
       +
       +        nbox = f->nbox;
       +        f->nbox = nb;
       +        pt = _frptofcharptb(f, p, Pt(f->left, f->r.min.y), 0);
       +        f->nbox = nbox;
       +        return pt;
       +}
       +
       +static
       +Point
       +_frgrid(Frame *f, Point p)
       +{
       +        p.y -= f->r.min.y;
       +        p.y -= p.y%f->font->height;
       +        p.y += f->r.min.y;
       +        if(p.x > f->r.max.x)
       +                p.x = f->r.max.x;
       +        return p;
       +}
       +
       +ulong
       +frcharofpt(Frame *f, Point pt)
       +{
       +        Point qt;
       +        int w, bn;
       +        uchar *s;
       +        Frbox *b;
       +        ulong p;
       +        Rune r;
       +
       +        pt = _frgrid(f, pt);
       +        qt.x = f->left;
       +        qt.y = f->r.min.y;
       +        for(b=f->box,bn=0,p=0; bn<f->nbox && qt.y<pt.y; bn++,b++){
       +                _frcklinewrap(f, &qt, b);
       +                if(qt.y >= pt.y)
       +                        break;
       +                _fradvance(f, &qt, b);
       +                p += NRUNE(b);
       +        }
       +        for(; bn<f->nbox && qt.x<=pt.x; bn++,b++){
       +                _frcklinewrap(f, &qt, b);
       +                if(qt.y > pt.y)
       +                        break;
       +                if(qt.x+b->wid > pt.x){
       +                        if(b->nrune < 0)
       +                                _fradvance(f, &qt, b);
       +                        else{
       +                                s = b->a.ptr;
       +                                for(;;){
       +                                        if((r = *s) < Runeself)
       +                                                w = 1;
       +                                        else
       +                                                w = chartorune(&r, (char*)s);
       +                                        if(r == 0)
       +                                                berror("end of string in frcharofpt");
       +                                        s += w;
       +                                        qt.x += charwidth(f->font, r);
       +                                        if(qt.x > pt.x)
       +                                                break;
       +                                        p++;
       +                                }
       +                        }
       +                }else{
       +                        p += NRUNE(b);
       +                        _fradvance(f, &qt, b);
       +                }
       +        }
       +        return p;
       +}
 (DIR) diff --git a/libframe/frselect.c b/libframe/frselect.c
       @@ -0,0 +1,94 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include <frame.h>
       +
       +void
       +frselect(Frame *f, Mouse *m)        /* when called, button 1 is down */
       +{
       +        ulong p0, p1, q;
       +        Point mp, pt0, pt1, qt;
       +
       +        mp = m->xy;
       +
       +    Again:
       +        f->modified = 0;
       +        frselectp(f, F&~D);
       +        p0 = p1 = frcharofpt(f, mp);
       +        pt0 = frptofchar(f, p0);
       +        pt1 = frptofchar(f, p1);
       +        frselectf(f, pt0, pt1, F&~D);
       +        do{
       +                if(f->modified)        /* special hack so 8½ can frselect in parallel */
       +                        goto Again;
       +                q = frcharofpt(f, m->xy);
       +                if(p1 != q){
       +                        if(p0 == p1)
       +                                frselectf(f, pt0, pt1, F&~D);
       +                        qt = frptofchar(f, q);
       +                        if(p1 < q)
       +                                frselectf(f, pt1, qt, F&~D);
       +                        else
       +                                frselectf(f, qt, pt1, F&~D);
       +                        p1 = q;
       +                        pt1 = qt;
       +                        if(p0 == p1)
       +                                frselectf(f, pt0, pt1, F&~D);
       +                }
       +                f->modified = 0;
       +                if(p0 < p1)
       +                        f->p0 = p0, f->p1 = p1;
       +                else
       +                        f->p0 = p1, f->p1 = p0;
       +                frgetmouse();
       +        }while(m->buttons & 1);
       +}
       +/* it is assumed p0<=p1 and both were generated by frptofchar() */
       +void
       +frselectf(Frame *f, Point p0, Point p1, Fcode c)
       +{
       +        int n;
       +        Point q0, q1;
       +
       +        if(p0.x == f->left)
       +                p0.x = f->r.min.x;
       +        if(p1.x == f->left)
       +                p1.x = f->r.min.x;
       +        q0 = p0;
       +        q1 = p1;
       +        q0.y += f->font->height;
       +        q1.y += f->font->height;
       +        n = (p1.y-p0.y)/f->font->height;
       +        if(f->b == 0)
       +                berror("frselectf b==0");
       +        if(p0.y == f->r.max.y)
       +                return;
       +        if(n == 0){
       +                if(p0.x == p1.x)
       +                        if(p0.x == f->r.min.x)
       +                                q1.x++;
       +                        else
       +                                p0.x--;
       +                bitblt(f->b, p0, f->b, Rpt(p0, q1), c);
       +        }else{
       +                if(p0.x >= f->r.max.x)
       +                        p0.x = f->r.max.x-1;
       +                bitblt(f->b, p0, f->b, Rect(p0.x, p0.y, f->r.max.x, q0.y), c);
       +                if(n > 1)
       +                        bitblt(f->b, Pt(f->r.min.x, q0.y),
       +                                f->b, Rect(f->r.min.x, q0.y, f->r.max.x, p1.y), c);
       +                bitblt(f->b, Pt(f->r.min.x, p1.y),
       +                                f->b, Rect(f->r.min.x, p1.y, q1.x, q1.y), c);
       +        }
       +}
       +
       +void
       +frselectp(Frame *f, Fcode c)
       +{
       +        Point pt0, pt1;
       +
       +        pt0 = frptofchar(f, f->p0);
       +        pt1 = (f->p0==f->p1)? pt0 : frptofchar(f, f->p1);
       +        frselectf(f, pt0, pt1, c);
       +}
 (DIR) diff --git a/libframe/frstr.c b/libframe/frstr.c
       @@ -0,0 +1,41 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include <frame.h>
       +
       +/*
       + * The code here and elsewhere requires that strings not be gcalloc()ed
       + */
       +
       +#define        CHUNK        16
       +#define        ROUNDUP(n)        ((n+CHUNK)&~(CHUNK-1))
       +
       +uchar *
       +_frallocstr(unsigned n)
       +{
       +        uchar *p;
       +
       +        p = malloc(ROUNDUP(n));
       +        if(p == 0)
       +                berror("out of memory");
       +        return p;
       +}
       +
       +void
       +_frinsure(Frame *f, int bn, unsigned n)
       +{
       +        Frbox *b;
       +        uchar *p;
       +
       +        b = &f->box[bn];
       +        if(b->nrune < 0)
       +                berror("_frinsure");
       +        if(ROUNDUP(b->nrune) > n)        /* > guarantees room for terminal NUL */
       +                return;
       +        p = _frallocstr(n);
       +        b = &f->box[bn];
       +        memmove(p, b->a.ptr, NBYTE(b)+1);
       +        free(b->a.ptr);
       +        b->a.ptr = p;
       +}
 (DIR) diff --git a/libframe/frutil.c b/libframe/frutil.c
       @@ -0,0 +1,107 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include <frame.h>
       +
       +int
       +_frcanfit(Frame *f, Point pt, Frbox *b)
       +{
       +        int left, w, nr;
       +        uchar *p;
       +        Rune r;
       +
       +        left = f->r.max.x-pt.x;
       +        if(b->nrune < 0)
       +                return b->a.b.minwid <= left;
       +        if(left >= b->wid)
       +                return b->nrune;
       +        for(nr=0,p=b->a.ptr; *p; p+=w,nr++){
       +                r = *p;
       +                if(r < Runeself)
       +                        w = 1;
       +                else
       +                        w = chartorune(&r, (char*)p);
       +                left -= charwidth(f->font, r);
       +                if(left < 0)
       +                        return nr;
       +        }
       +        berror("_frcanfit can't");
       +        return 0;
       +}
       +
       +void
       +_frcklinewrap(Frame *f, Point *p, Frbox *b)
       +{
       +        if((b->nrune<0? b->a.b.minwid : b->wid) > f->r.max.x-p->x){
       +                p->x = f->left;
       +                p->y += f->font->height;
       +        }
       +}
       +
       +void
       +_frcklinewrap0(Frame *f, Point *p, Frbox *b)
       +{
       +        if(_frcanfit(f, *p, b) == 0){
       +                p->x = f->left;
       +                p->y += f->font->height;
       +        }
       +}
       +
       +void
       +_fradvance(Frame *f, Point *p, Frbox *b)
       +{
       +        if(b->nrune<0 && b->a.b.bc=='\n'){
       +                p->x = f->left;
       +                p->y += f->font->height;
       +        }else
       +                p->x += b->wid;
       +}
       +
       +int
       +_frnewwid(Frame *f, Point pt, Frbox *b)
       +{
       +        int c, x;
       +
       +        c = f->r.max.x;
       +        x = pt.x;
       +        if(b->nrune >= 0)
       +                return b->wid;
       +        if(b->a.b.bc == '\t'){
       +                if(x+b->a.b.minwid > c)
       +                        x = pt.x = f->left;
       +                x += f->maxtab;
       +                x -= (x-f->left)%f->maxtab;
       +                if(x-pt.x<b->a.b.minwid || x>c)
       +                        x = pt.x+b->a.b.minwid;
       +                b->wid = x-pt.x;
       +        }
       +        return b->wid;
       +}
       +
       +void
       +_frclean(Frame *f, Point pt, int n0, int n1)        /* look for mergeable boxes */
       +{
       +        Frbox *b;
       +        int nb, c;
       +
       +        c = f->r.max.x;
       +        for(nb=n0; nb<n1-1; nb++){
       +                b = &f->box[nb];
       +                _frcklinewrap(f, &pt, b);
       +                while(b[0].nrune>=0 && nb<n1-1 && b[1].nrune>=0 && pt.x+b[0].wid+b[1].wid<c){
       +                        _frmergebox(f, nb);
       +                        n1--;
       +                        b = &f->box[nb];
       +                }
       +                _fradvance(f, &pt, &f->box[nb]);
       +        }
       +        for(; nb<f->nbox; nb++){
       +                b = &f->box[nb];
       +                _frcklinewrap(f, &pt, b);
       +                _fradvance(f, &pt, &f->box[nb]);
       +        }
       +        f->lastlinefull = 0;
       +        if(pt.y >= f->r.max.y)
       +                f->lastlinefull = 1;
       +}
 (DIR) diff --git a/libframe/misc.c b/libframe/misc.c
       @@ -0,0 +1,93 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include        <u.h>
       +#include        <libc.h>
       +#include        <pwd.h>
       +#ifdef        NEEDVARARG
       +#include        <varargs.h>
       +#else
       +#include        <stdarg.h>
       +#endif
       +#include <errno.h>
       +
       +void
       +fprint(int fd, char *z, ...)
       +{
       +        va_list args;
       +        char buf[2048];                        /* pick reasonable blocksize */
       +
       +        va_start(args, z);
       +        vsprintf(buf, z, args);
       +        write(fd, buf, strlen(buf));
       +        va_end(args);
       +}
       +
       +int errstr(char *buf)
       +{
       +
       +        strncpy(buf, strerror(errno), ERRLEN);
       +        return 1;
       +}
       +
       +char*
       +getuser(void)
       +{
       +        struct passwd *p;
       +
       +        static char *user = 0;
       +
       +        if (!user) {
       +                p = getpwuid(getuid());
       +                if (p && p->pw_name) {
       +                        user = malloc(strlen(p->pw_name)+1);
       +                        if (user)
       +                                strcpy(user, p->pw_name);
       +                }
       +        }
       +        if(!user)
       +                user = "unknown";
       +        return user;
       +}
       +
       +#ifdef NEEDSTRERROR
       +char *
       +strerror(int n)
       +{
       +        extern char *sys_errlist[];
       +        return sys_errlist[n];
       +}
       +#endif /* NEEDSTRERROR */
       +
       +#ifdef NEEDMEMMOVE
       +/*
       + * memcpy is probably fast, but may not work with overlap
       + */
       +void*
       +memmove(void *a1, const void *a2, size_t n)
       +{
       +        char *s1;
       +        const char *s2;
       +
       +        s1 = a1;
       +        s2 = a2;
       +        if(s1 > s2)
       +                goto back;
       +        if(s1 + n <= s2)
       +                return memcpy(a1, a2, n);
       +        while(n > 0) {
       +                *s1++ = *s2++;
       +                n--;
       +        }
       +        return a1;
       +
       +back:
       +        s2 += n;
       +        if(s2 <= s1)
       +                return memcpy(a1, a2, n);
       +        s1 += n;
       +        while(n > 0) {
       +                *--s1 = *--s2;
       +                n--;
       +        }
       +        return a1;
       +}
       +#endif /* NEEDMEMMOVE */
 (DIR) diff --git a/rsam/Makefile b/rsam/Makefile
       @@ -0,0 +1,18 @@
       +# Copyright (C) 2013-2015 Rob King <jking@deadpixi.com
       +# This file may be redistributed and modified for any purpose.
       +# No warranty is expressed or implied; use at your own risk.
       +
       +include ../config.mk
       +
       +LDFLAGS=
       +CFLAGS=-DSAMBIN=\"$(BINDIR)/sam\"
       +
       +all: rsam
       +
       +rsam: rsam.o
       +
       +clean:
       +        rm -f *.o rsam
       +
       +install: rsam
       +        cp rsam $(BINDIR)
 (DIR) diff --git a/rsam/rsam.c b/rsam/rsam.c
       @@ -0,0 +1,147 @@
       +/* Copyright 2013-2015 Rob King <jking@deadpixi.com>
       + * This file may be freely redistributed in source or binary form with or without modification.
       + * No warranty is expressed or implied; use at your own risk.
       + */
       +
       +#define _POSIX_C_SOURCE 200112L
       +#include <fcntl.h>
       +#include <limits.h>
       +#include <stdio.h>
       +#include <stdlib.h>
       +#include <string.h>
       +#include <sys/select.h>
       +#include <sys/stat.h>
       +#include <sys/types.h>
       +#include <unistd.h>
       +
       +#define PARENT_READ        readpipe[0]
       +#define CHILD_WRITE        readpipe[1]
       +#define CHILD_READ        writepipe[0]
       +#define PARENT_WRITE        writepipe[1]
       +#define MAX(x, y)        ((x) > (y) ? (x) : (y))
       +
       +char *fifopath = NULL;
       +
       +void
       +cleanup(void)
       +{
       +        if (fifopath)
       +        {
       +                unlink(fifopath);
       +                free(fifopath);
       +        }
       +}
       +
       +int
       +main(int argc, char **argv)
       +{
       +        const char *home         = getenv("HOME") ? getenv("HOME") : "/tmp";
       +        long        pathmax      = pathconf(home, _PC_PATH_MAX) != -1 ? pathconf(home, _PC_PATH_MAX) : PATH_MAX;
       +        int         writepipe[2] = {-1};
       +        int         readpipe[2]  = {-1};
       +
       +        fifopath = calloc(pathmax, sizeof(char));
       +        if (fifopath == NULL)
       +        {
       +                perror("fifopath");
       +                return EXIT_FAILURE;
       +        }
       +
       +        if (pipe(writepipe) != 0 || pipe(readpipe) != 0)
       +        {
       +                perror("pipe");
       +                return EXIT_FAILURE;
       +        }
       +
       +        snprintf(fifopath, pathmax, "%s/.sam.fifo", home);
       +        unlink(fifopath);
       +        if (mkfifo(fifopath, 0600) != 0)
       +        {
       +                perror("mkfifo");
       +                return EXIT_FAILURE;
       +        }
       +
       +        fifopath = fifopath;
       +        atexit(cleanup);
       +
       +        int fifofd = open(fifopath, O_RDWR);
       +        if (fifofd < 0)
       +        {
       +                perror("open");
       +                return EXIT_FAILURE;
       +        }
       +
       +        pid_t child = fork();
       +        if (child == 0)
       +        {
       +                close(PARENT_WRITE);
       +                close(PARENT_READ);
       +
       +                dup2(CHILD_READ,  STDIN_FILENO);  close(CHILD_READ);
       +                dup2(CHILD_WRITE, STDOUT_FILENO); close(CHILD_WRITE);
       +
       +                execl(SAMBIN, "sam", "-R", NULL);
       +                return EXIT_FAILURE;
       +        }
       +        else if (child < 0)
       +        {
       +                perror("fork");
       +                return EXIT_FAILURE;
       +        }
       +
       +        close(CHILD_READ);
       +        close(CHILD_WRITE);
       +
       +        fd_set readfds;
       +        fd_set writefds;
       +
       +        FD_ZERO(&readfds);
       +        FD_SET(STDIN_FILENO, &readfds);
       +        FD_SET(fifofd, &readfds);
       +        FD_SET(PARENT_READ, &readfds);
       +
       +        while (select(MAX(STDIN_FILENO, MAX(PARENT_READ, fifofd)) + 1, &readfds, NULL, NULL, NULL) >= 0)
       +        {
       +                ssize_t count = 0;
       +                char        buf[8192];
       +
       +                if (FD_ISSET(STDIN_FILENO, &readfds))
       +                {
       +                        count = read(STDIN_FILENO, buf, 8192);
       +                        if (count <= 0)
       +                        {
       +                                exit(EXIT_SUCCESS);
       +                        }
       +                        write(PARENT_WRITE, buf, count);
       +                }
       +
       +                if (FD_ISSET(fifofd, &readfds))
       +                {
       +                        memset(buf, 0, 256);
       +                        count = read(fifofd, buf, 253);
       +                        if (count <= 0)
       +                        {
       +                                exit(EXIT_SUCCESS);
       +                        }
       +                        write(STDOUT_FILENO, "\x19\xff\x00", 3);
       +                        write(STDOUT_FILENO, buf, 255);
       +                }
       +
       +                if (FD_ISSET(PARENT_READ, &readfds))
       +                {
       +                        count = read(PARENT_READ, buf, 8192);
       +                        if (count <= 0)
       +                        {
       +                exit(EXIT_SUCCESS);
       +            }
       +                        write(STDOUT_FILENO, buf, count);
       +                }
       +
       +                FD_ZERO(&readfds);
       +                FD_SET(STDIN_FILENO, &readfds);
       +                FD_SET(fifofd, &readfds);
       +                FD_SET(PARENT_READ, &readfds);
       +        }
       +
       +        return EXIT_SUCCESS;
       +}
 (DIR) diff --git a/sam/B.rc b/sam/B.rc
       @@ -0,0 +1,51 @@
       +#!/bin/rc
       +
       +files=()
       +line=''
       +
       +if (~ $#* 0) {
       +        echo 'usage: B [-nnn] files...' >[1=2]
       +        exit 1
       +}
       +
       +dir=`{/bin/pwd}
       +
       +if (~ $#USER 0)
       +        USER=$LOGNAME
       +pipe=/tmp/.sam.$USER
       +
       +switch($DISPLAY) {
       +        case *:[0-9]*.[0-9]*
       +                        pipe=$pipe.$DISPLAY
       +                        if (! test -r $pipe)
       +                                pipe=`{echo $pipe | sed 's/\.[0-9]*$//'}
       +        case *:[0-9]*
       +                        pipe=$pipe.$DISPLAY
       +                        if (! test -r $pipe)
       +                                pipe=$pipe.0
       +        case ""
       +        case *        
       +                        pipe=$pipe.$DISPLAY
       +}
       +
       +if (! test -r $pipe) {
       +        echo `{basename $0}^': No pipe "'$pipe'" to sam.' >[1=2]
       +        exit 1
       +}
       +
       +for (i) {
       +        switch($i) {
       +        case /*
       +                files = ( $files $i )
       +        case -*
       +                line = `{echo $i | sed 's/.//'}
       +        case *
       +                files = ( $files $dir/$i )
       +        }
       +}
       +
       +if (! ~ $#files 0)
       +        echo B $files >> $pipe
       +
       +if (! ~ $line '')
       +        echo $line >> $pipe
 (DIR) diff --git a/sam/B.sh b/sam/B.sh
       @@ -0,0 +1,56 @@
       +#!/bin/sh
       +
       +files=
       +line="none"
       +
       +if [ $# = 0 ]; then
       +        echo 'usage: B [-nnnn] files...'  1>&2
       +        exit 1
       +fi
       +
       +dir=`/bin/pwd`
       +if [ "$USER" = "" ]; then
       +        USER=$LOGNAME
       +fi
       +pipe=/tmp/.sam.$USER
       +
       +case "$DISPLAY" in
       +        *:[0-9]*\.[0-9]*)
       +                        pipe=$pipe.$DISPLAY
       +                        if [ ! -r $pipe ]; then
       +                                pipe=`echo $pipe | sed 's/\.[0-9]*$//'`
       +                        fi
       +                        ;;
       +        *:[0-9]*)
       +                        pipe=$pipe.$DISPLAY
       +                        if [ ! -r $pipe ]; then
       +                                pipe=$pipe.0
       +                        fi
       +                        ;;
       +        "")
       +                        ;;
       +        *)                pipe=$pipe.$DISPLAY
       +                        ;;
       +esac
       +if [ ! -r $pipe ]; then
       +        echo `basename $0`": No pipe \""$pipe"\" to sam." 1>&2
       +        exit 1
       +fi
       +
       +for i in $*
       +do
       +        case "$i" in
       +                /*)        files="$files $i"
       +                        ;;
       +                -*)        line=`echo $i | sed 's/.//'`
       +                        ;;
       +                *)        files="$files $dir/$i"
       +                        ;;
       +        esac
       +done
       +
       +echo "B $files" >> $pipe
       +if [ $line != "none" ]; then
       +        echo $line >> $pipe
       +fi
       +
 (DIR) diff --git a/sam/Makefile b/sam/Makefile
       @@ -0,0 +1,89 @@
       +#        Copyright (c) 1998 Lucent Technologies - All rights reserved.
       +#
       +#        Prototype Makefile for sam
       +#
       +#        define operating system.  ONE of:
       +#                -DIRIX -DSUNOS -DUMIPS -DSYSVR3 -DAIX -DOSF1
       +#                -DHPUX -DAPOLLO -DCONVEX -DDYNIX
       +#
       +#        -DIRIX is the default and should actually work on any modern system.
       +#        Additionally, -D_POSIX_SOURCE (or its equivalent) may be specified
       +#        if your compiler supports posix-compatible compilation.
       +#
       +include ../config.mk
       +
       +#        If your system has 64-bit addresses, add -DUSE64BITS to $(OS).
       +OS=-DIRIX5 -DUSE64BITS=$(USE64BITS)
       +
       +#        add -Iincludedir for any include directories that need to be searched
       +#        for posix header files (for UMIPS, add -I/usr/include/posix)
       +INCS=-I../include
       +
       +#        Set the name of the environment variable containing the user's home directory
       +HOMEDIR=HOME
       +
       +#        RSAMNAME and TERMNAME contain the names of the files containing the
       +#        sam and samterm executables, respectively.  SAMDIR is the directory
       +#        where sam is to be installed.  SAMSAVEDIR is the name of the directory
       +#        where the samsave file restoration script is stored.
       +RSAMNAME=sam
       +TERMNAME=$(BINDIR)/samterm
       +SAMDIR=$(BINDIR)
       +SAMSAVEDIR=$(BINDIR)
       +
       +#        Set TMP to a good place for tmp files (with lots of room)
       +TMP=$(TMPDIR)
       +
       +#        Set SHELLNAME and SHELLPATH to the name of a shell and the pathname
       +#        of its executable
       +SHELLNAME=sh
       +SHELLPATH=/bin/sh
       +
       +#        Set RXNAME and RXPATHNAME to the name of the remote execution command
       +#        and the pathname of its executable
       +RXNAME=ssh
       +RXPATHNAME=/usr/bin/ssh
       +
       +#   Set RXSAMNAME to the name of the command to run on the remote host.
       +RXSAMNAME=/usr/local/bin/rsam
       +
       +SAMSAVE=/bin/sh\\n$(SAMSAVEDIR)/samsave
       +
       +CFLAGS=$(OS) -D_LIBXG_EXTENSION $(INCS)
       +
       +SYSFLAGS=  -DHOMEDIR=\"$(HOMEDIR)\" -DRSAMNAME=\"$(RSAMNAME)\" \
       +                -DTERMNAME=\"$(TERMNAME)\" -DTMP=\"$(TMP)\" \
       +                -DSHELLNAME=\"$(SHELLNAME)\" -DSHELLPATH=\"$(SHELLPATH)\" \
       +                -DRXNAME=\"$(RXNAME)\" -DRXPATHNAME=\"$(RXPATHNAME)\" \
       +                -DRXSAMNAME=\"$(RXSAMNAME)\" -DSAMSAVE=\"$(SAMSAVE)\"
       +
       +LIB=../libframe/libframe.a ../libXg/libXg.a
       +CC=cc $(SYSFLAGS)
       +
       +OBJ=sam.o address.o buffer.o cmd.o disc.o error.o file.o io.o \
       +        list.o mesg.o moveto.o multi.o rasp.o regexp.o shell.o \
       +        string.o sys.o unix.o xec.o
       +
       +all:    sam
       +
       +sam:        $(OBJ) $(LIB)
       +        $(CC)  -o sam $(OBJ) $(LIB)
       +
       +clean:
       +        rm -f *.o core sam
       +
       +nuke:        clean
       +        rm -f sam
       +
       +install:        sam
       +        cp sam $(SAMDIR)/$(RSAMNAME)
       +        cp samsave $(SAMSAVEDIR)/samsave
       +        chmod +x $(SAMSAVEDIR)/samsave
       +
       +$(OBJ):        sam.h ../include/u.h ../include/libc.h errors.h mesg.h
       +
       +cmd.o:        parse.h
       +xec.o:        parse.h
       +
       +unix.o:        sam.h ../include/u.h ../include/libc.h errors.h mesg.h
       +        $(CC) -c $(CFLAGS) $(SYSFLAGS) unix.c
 (DIR) diff --git a/sam/address.c b/sam/address.c
       @@ -0,0 +1,241 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include "sam.h"
       +#include "parse.h"
       +
       +Address        addr;
       +String        lastpat;
       +int        patset;
       +File        *menu;
       +
       +File        *matchfile(String*);
       +Address        charaddr(Posn, Address, int);
       +
       +Address
       +address(Addr *ap, Address a, int sign)
       +{
       +        File *f = a.f;
       +        Address a1, a2;
       +
       +        do{
       +                switch(ap->type){
       +                case 'l':
       +                case '#':
       +                        a = (*(ap->type=='#'?charaddr:lineaddr))(ap->num, a, sign);
       +                        break;
       +
       +                case '.':
       +                        a = f->dot;
       +                        break;
       +
       +                case '$':
       +                        a.r.p1 = a.r.p2 = f->nrunes;
       +                        break;
       +
       +                case '\'':
       +                        a.r = f->mark;
       +                        break;
       +
       +                case '?':
       +                        sign = -sign;
       +                        if(sign == 0)
       +                                sign = -1;
       +                        /* fall through */
       +                case '/':
       +                        nextmatch(f, ap->are, sign>=0? a.r.p2 : a.r.p1, sign);
       +                        a.r = sel.p[0];
       +                        break;
       +
       +                case '"':
       +                        a = matchfile(ap->are)->dot;
       +                        f = a.f;
       +                        if(f->state == Unread)
       +                                load(f);
       +                        break;
       +
       +                case '*':
       +                        a.r.p1 = 0, a.r.p2 = f->nrunes;
       +                        return a;
       +
       +                case ',':
       +                case ';':
       +                        if(ap->left)
       +                                a1 = address(ap->left, a, 0);
       +                        else
       +                                a1.f = a.f, a1.r.p1 = a1.r.p2 = 0;
       +                        if(ap->type == ';'){
       +                                f = a1.f;
       +                                f->dot = a = a1;
       +                        }
       +                        if(ap->next)
       +                                a2 = address(ap->next, a, 0);
       +                        else
       +                                a2.f = a.f, a2.r.p1 = a2.r.p2 = f->nrunes;
       +                        if(a1.f != a2.f)
       +                                error(Eorder);
       +                        a.f = a1.f, a.r.p1 = a1.r.p1, a.r.p2 = a2.r.p2;
       +                        if(a.r.p2 < a.r.p1)
       +                                error(Eorder);
       +                        return a;
       +
       +                case '+':
       +                case '-':
       +                        sign = 1;
       +                        if(ap->type == '-')
       +                                sign = -1;
       +                        if(ap->next==0 || ap->next->type=='+' || ap->next->type=='-')
       +                                a = lineaddr(1L, a, sign);
       +                        break;
       +                default:
       +                        panic("address");
       +                        return a;
       +                }
       +        }while(ap = ap->next);        /* assign = */
       +        return a;
       +}
       +
       +void
       +nextmatch(File *f, String *r, Posn p, int sign)
       +{
       +        compile(r);
       +        if(sign >= 0){
       +                if(!execute(f, p, INFINITY))
       +                        error(Esearch);
       +                if(sel.p[0].p1==sel.p[0].p2 && sel.p[0].p1==p){
       +                        if(++p>f->nrunes)
       +                                p = 0;
       +                        if(!execute(f, p, INFINITY))
       +                                panic("address");
       +                }
       +        }else{
       +                if(!bexecute(f, p))
       +                        error(Esearch);
       +                if(sel.p[0].p1==sel.p[0].p2 && sel.p[0].p2==p){
       +                        if(--p<0)
       +                                p = f->nrunes;
       +                        if(!bexecute(f, p))
       +                                panic("address");
       +                }
       +        }
       +}
       +
       +File *
       +matchfile(String *r)
       +{
       +        File *f;
       +        File *match = 0;
       +        int i;
       +
       +        for(i = 0; i<file.nused; i++){
       +                f = file.filepptr[i];
       +                if(f == cmd)
       +                        continue;
       +                if(filematch(f, r)){
       +                        if(match)
       +                                error(Emanyfiles);
       +                        match = f;
       +                }
       +        }
       +        if(!match)
       +                error(Efsearch);
       +        return match;
       +}
       +
       +int
       +filematch(File *f, String *r)
       +{
       +        char *c, buf[STRSIZE+100];
       +        String *t;
       +
       +        c = Strtoc(&f->name);
       +        sprint(buf, "%c%c%c %s\n", " '"[f->state==Dirty],
       +                "-+"[f->rasp!=0], " ."[f==curfile], c);
       +        free(c);
       +        t = tmpcstr(buf);
       +        Strduplstr(&genstr, t);
       +        freetmpstr(t);
       +        /* A little dirty... */
       +        if(menu == 0)
       +                (menu=Fopen())->state=Clean;
       +        Bdelete(menu->buf, 0, menu->buf->nrunes);
       +        Binsert(menu->buf, &genstr, 0);
       +        menu->nrunes = menu->buf->nrunes;
       +        compile(r);
       +        return execute(menu, 0, menu->nrunes);
       +}
       +
       +Address
       +charaddr(Posn l, Address addr, int sign)
       +{
       +        if(sign == 0)
       +                addr.r.p1 = addr.r.p2 = l;
       +        else if(sign < 0)
       +                addr.r.p2 = addr.r.p1-=l;
       +        else if(sign > 0)
       +                addr.r.p1 = addr.r.p2+=l;
       +        if(addr.r.p1<0 || addr.r.p2>addr.f->nrunes)
       +                error(Erange);
       +        return addr;
       +}
       +
       +Address
       +lineaddr(Posn l, Address addr, int sign)
       +{
       +        int n;
       +        int c;
       +        File *f = addr.f;
       +        Address a;
       +
       +        SET(c);
       +        a.f = f;
       +        if(sign >= 0){
       +                if(l == 0){
       +                        if(sign==0 || addr.r.p2==0){
       +                                a.r.p1 = a.r.p2 = 0;
       +                                return a;
       +                        }
       +                        a.r.p1 = addr.r.p2;
       +                        Fgetcset(f, addr.r.p2-1);
       +                }else{
       +                        if(sign==0 || addr.r.p2==0){
       +                                Fgetcset(f, (Posn)0);
       +                                n = 1;
       +                        }else{
       +                                Fgetcset(f, addr.r.p2-1);
       +                                n = Fgetc(f)=='\n';
       +                        }
       +                        for(; n<l; ){
       +                                c = Fgetc(f);
       +                                if(c == -1)
       +                                        error(Erange);
       +                                else if(c == '\n')
       +                                        n++;
       +                        }
       +                        a.r.p1 = f->getcp;
       +                }
       +                do; while((c=Fgetc(f))!='\n' && c!=-1);
       +                a.r.p2 = f->getcp;
       +        }else{
       +                Fbgetcset(f, addr.r.p1);
       +                if(l == 0)
       +                        a.r.p2 = addr.r.p1;
       +                else{
       +                        for(n = 0; n<l; ){        /* always runs once */
       +                                c = Fbgetc(f);
       +                                if(c == '\n')
       +                                        n++;
       +                                else if(c == -1){
       +                                        if(++n != l)
       +                                                error(Erange);
       +                                }
       +                        }
       +                        a.r.p2 = f->getcp;
       +                        if(c == '\n')
       +                                a.r.p2++;        /* lines start after a newline */
       +                }
       +                do; while((c=Fbgetc(f))!='\n' && c!=-1);
       +                a.r.p1 = f->getcp;
       +                if(c == '\n')
       +                        a.r.p1++;        /* lines start after a newline */
       +        }
       +        return a;
       +}
 (DIR) diff --git a/sam/buffer.c b/sam/buffer.c
       @@ -0,0 +1,179 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include "sam.h"
       +
       +int        incache(Buffer*, Posn, Posn);
       +
       +Buffer *
       +Bopen(Discdesc *dd)
       +{
       +        Buffer *b;
       +
       +        b = emalloc(sizeof(Buffer));
       +        b->disc = Dopen(dd);
       +        Strinit(&b->cache);
       +        return b;
       +}
       +
       +void
       +Bterm(Buffer *b)
       +{
       +        Dclose(b->disc);
       +        Strclose(&b->cache);
       +        free(b);
       +}
       +
       +int
       +Bread(Buffer *b, Rune *addr, int n, Posn p0)
       +{
       +        int m;
       +
       +        if(b->c2>b->disc->nrunes || b->c1>b->disc->nrunes)
       +                panic("bread cache");
       +        if(p0 < 0)
       +                panic("Bread p0<0");
       +        if(p0+n > b->nrunes){
       +                n = b->nrunes-p0;
       +                if(n < 0)
       +                        panic("Bread<0");
       +        }
       +        if(!incache(b, p0, p0+n)){
       +                Bflush(b);
       +                if(n>=BLOCKSIZE/2)
       +                        return Dread(b->disc, addr, n, p0);
       +                else{
       +                        Posn minp;
       +                        if(b->nrunes-p0>BLOCKSIZE/2)
       +                                m = BLOCKSIZE/2;
       +                        else
       +                                m = b->nrunes-p0;
       +                        if(m<n)
       +                                m = n;
       +                        minp = p0-BLOCKSIZE/2;
       +                        if(minp<0)
       +                                minp = 0;
       +                        m += p0-minp;
       +                        Strinsure(&b->cache, m);
       +                        if(Dread(b->disc, b->cache.s, m, minp)!=m)
       +                                panic("Bread");
       +                        b->cache.n = m;
       +                        b->c1 = minp;
       +                        b->c2 = minp+m;
       +                        b->dirty = FALSE;
       +                }
       +        }
       +        memmove(addr, &b->cache.s[p0-b->c1], n*RUNESIZE);
       +        return n;
       +}
       +
       +void
       +Binsert(Buffer *b, String *s, Posn p0)
       +{
       +        if(b->c2>b->disc->nrunes || b->c1>b->disc->nrunes)
       +                panic("binsert cache");
       +        if(p0<0)
       +                panic("Binsert p0<0");
       +        if(s->n == 0)
       +                return;
       +        if(incache(b, p0, p0) && b->cache.n+s->n<=STRSIZE){
       +                Strinsert(&b->cache, s, p0-b->c1);
       +                b->dirty = TRUE;
       +                if(b->cache.n > BLOCKSIZE*2){
       +                        b->nrunes += s->n;
       +                        Bflush(b);
       +                        /* try to leave some cache around p0 */
       +                        if(p0 >= b->c1+BLOCKSIZE){
       +                                /* first BLOCKSIZE can go */
       +                                Strdelete(&b->cache, 0, BLOCKSIZE);
       +                                b->c1 += BLOCKSIZE;
       +                        }else if(p0 <= b->c2-BLOCKSIZE){
       +                                /* last BLOCKSIZE can go */
       +                                b->cache.n -= BLOCKSIZE;
       +                                b->c2 -= BLOCKSIZE;
       +                        }else{
       +                                /* too hard; negate the cache and pick up next time */
       +                                Strzero(&b->cache);
       +                                b->c1 = b->c2 = 0;
       +                        }
       +                        return;
       +                }
       +        }else{
       +                Bflush(b);
       +                if(s->n >= BLOCKSIZE/2){
       +                        b->cache.n = 0;
       +                        b->c1 = b->c2 = 0;
       +                        Dinsert(b->disc, s->s, s->n, p0);
       +                }else{
       +                        int m;
       +                        Posn minp;
       +                        if(b->nrunes-p0 > BLOCKSIZE/2)
       +                                m = BLOCKSIZE/2;
       +                        else
       +                                m = b->nrunes-p0;
       +                        minp = p0-BLOCKSIZE/2;
       +                        if(minp < 0)
       +                                minp = 0;
       +                        m += p0-minp;
       +                        Strinsure(&b->cache, m);
       +                        if(Dread(b->disc, b->cache.s, m, minp)!=m)
       +                                panic("Bread");
       +                        b->cache.n = m;
       +                        b->c1 = minp;
       +                        b->c2 = minp+m;
       +                        Strinsert(&b->cache, s, p0-b->c1);
       +                        b->dirty = TRUE;
       +                }
       +        }
       +        b->nrunes += s->n;
       +}
       +
       +void
       +Bdelete(Buffer *b, Posn p1, Posn p2)
       +{
       +        if(p1<0 || p2<0)
       +                panic("Bdelete p<0");
       +        if(b->c2>b->disc->nrunes || b->c1>b->disc->nrunes)
       +                panic("bdelete cache");
       +        if(p1 == p2)
       +                return;
       +        if(incache(b, p1, p2)){
       +                Strdelete(&b->cache, p1-b->c1, p2-b->c1);
       +                b->dirty = TRUE;
       +        }else{
       +                Bflush(b);
       +                Ddelete(b->disc, p1, p2);
       +                b->cache.n = 0;
       +                b->c1 = b->c2 = 0;
       +        }
       +        b->nrunes -= p2-p1;
       +}
       +
       +void
       +Bflush(Buffer *b)
       +{
       +        if(b->dirty){
       +                Dreplace(b->disc, b->c1, b->c2, b->cache.s, b->cache.n);
       +                b->c2 = b->c1+b->cache.n;
       +                b->dirty = FALSE;
       +                if(b->nrunes != b->disc->nrunes)
       +                        panic("Bflush");
       +        }
       +}
       +
       +void
       +Bclean(Buffer *b)
       +{
       +        if(b->dirty){
       +                Bflush(b);
       +                b->c1 = b->c2 = 0;
       +                Strzero(&b->cache);
       +        }
       +}
       +
       +/*int hits, misses; /**/
       +
       +int
       +incache(Buffer *b, Posn p1, Posn p2)
       +{
       +        /*if(b->c1<=p1 && p2<=b->c1+b->cache.n)hits++; else misses++;/**/
       +        return b->c1<=p1 && p2<=b->c1+b->cache.n;
       +}
 (DIR) diff --git a/sam/cmd.c b/sam/cmd.c
       @@ -0,0 +1,591 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include "sam.h"
       +#include "parse.h"
       +
       +static char        linex[]="\n";
       +static char        wordx[]=" \t\n";
       +struct cmdtab cmdtab[]={
       +/*        cmdc        text        regexp        addr        defcmd        defaddr        count        token         fn        */
       +        '\n',        0,        0,        0,        0,        aDot,        0,        0,        nl_cmd,
       +        'a',        1,        0,        0,        0,        aDot,        0,        0,        a_cmd,
       +        'b',        0,        0,        0,        0,        aNo,        0,        linex,        b_cmd,
       +        'B',        0,        0,        0,        0,        aNo,        0,        linex,        b_cmd,
       +        'c',        1,        0,        0,        0,        aDot,        0,        0,        c_cmd,
       +        'd',        0,        0,        0,        0,        aDot,        0,        0,        d_cmd,
       +        'D',        0,        0,        0,        0,        aNo,        0,        linex,        D_cmd,
       +        'e',        0,        0,        0,        0,        aNo,        0,        wordx,        e_cmd,
       +        'f',        0,        0,        0,        0,        aNo,        0,        wordx,        f_cmd,
       +        'g',        0,        1,        0,        'p',        aDot,        0,        0,        g_cmd,
       +        'i',        1,        0,        0,        0,        aDot,        0,        0,        i_cmd,
       +        'k',        0,        0,        0,        0,        aDot,        0,        0,        k_cmd,
       +        'm',        0,        0,        1,        0,        aDot,        0,        0,        m_cmd,
       +        'n',        0,        0,        0,        0,        aNo,        0,        0,        n_cmd,
       +        'p',        0,        0,        0,        0,        aDot,        0,        0,        p_cmd,
       +        'q',        0,        0,        0,        0,        aNo,        0,        0,        q_cmd,
       +        'r',        0,        0,        0,        0,        aDot,        0,        wordx,        e_cmd,
       +        's',        0,        1,        0,        0,        aDot,        1,        0,        s_cmd,
       +        't',        0,        0,        1,        0,        aDot,        0,        0,        m_cmd,
       +        'u',        0,        0,        0,        0,        aNo,        1,        0,        u_cmd,
       +        'v',        0,        1,        0,        'p',        aDot,        0,        0,        g_cmd,
       +        'w',        0,        0,        0,        0,        aAll,        0,        wordx,        w_cmd,
       +        'x',        0,        1,        0,        'p',        aDot,        0,        0,        x_cmd,
       +        'y',        0,        1,        0,        'p',        aDot,        0,        0,        x_cmd,
       +        'X',        0,        1,        0,        'f',        aNo,        0,        0,        X_cmd,
       +        'Y',        0,        1,        0,        'f',        aNo,        0,        0,        X_cmd,
       +        '!',        0,        0,        0,        0,        aNo,        0,        linex,        plan9_cmd,
       +        '>',        0,        0,        0,        0,        aDot,        0,        linex,        plan9_cmd,
       +        '<',        0,        0,        0,        0,        aDot,        0,        linex,        plan9_cmd,
       +        '|',        0,        0,        0,        0,        aDot,        0,        linex,        plan9_cmd,
       +        '=',        0,        0,        0,        0,        aDot,        0,        linex,        eq_cmd,
       +        'c'|0x100,0,        0,        0,        0,        aNo,        0,        wordx,        cd_cmd,
       +        0,        0,        0,        0,        0,        0,        0,        0,
       +};
       +Cmd        *parsecmd(int);
       +Addr        *compoundaddr(void);
       +Addr        *simpleaddr(void);
       +void        freecmd(void);
       +void        okdelim(int);
       +
       +Rune        line[BLOCKSIZE];
       +Rune        termline[BLOCKSIZE];
       +Rune        *linep = line;
       +Rune        *terminp = termline;
       +Rune        *termoutp = termline;
       +List        cmdlist;
       +List        addrlist;
       +List        relist;
       +List        stringlist;
       +int        eof;
       +
       +void
       +resetcmd(void)
       +{
       +        linep = line;
       +        *linep = 0;
       +        terminp = termoutp = termline;
       +        freecmd();
       +}
       +
       +int
       +inputc(void)
       +{
       +        int n, nbuf;
       +        char buf[3];
       +        Rune r;
       +
       +    Again:
       +        nbuf = 0;
       +        if(downloaded){
       +                while(termoutp == terminp){
       +                        cmdupdate();
       +                        if(patset)
       +                                tellpat();
       +                        while(termlocked > 0){
       +                                outT0(Hunlock);
       +                                termlocked--;
       +                        }
       +                        if(rcv() == 0)
       +                                return -1;
       +                }
       +                r = *termoutp++;
       +                if(termoutp == terminp)
       +                        terminp = termoutp = termline;
       +        }else{
       +                   do{
       +                        n = read(0, buf+nbuf, 1);
       +                        if(n <= 0)
       +                                return -1;
       +                        nbuf += n;
       +                }while(!fullrune(buf, nbuf));
       +                chartorune(&r, buf);
       +        }
       +        if(r == 0){
       +                warn(Wnulls);
       +                goto Again;
       +        }
       +        return r;
       +}
       +
       +int
       +inputline(void)
       +{
       +        int i, c;
       +
       +        linep = line;
       +        i = 0;
       +        do{
       +                if((c = inputc())<=0)
       +                        return -1;
       +                if(i == (sizeof line)/RUNESIZE-1)
       +                        error(Etoolong);
       +        }while((line[i++]=c) != '\n');
       +        line[i] = 0;
       +        return 1;
       +}
       +
       +int
       +getch(void)
       +{
       +        if(eof)
       +                return -1;
       +        if(*linep==0 && inputline()<0){
       +                eof = TRUE;
       +                return -1;
       +        }
       +        return *linep++;
       +}
       +
       +int
       +nextc(void)
       +{
       +        if(*linep == 0)
       +                return -1;
       +        return *linep;
       +}
       +
       +void
       +ungetch(void)
       +{
       +        if(--linep < line)
       +                panic("ungetch");
       +}
       +
       +Posn
       +getnum(void)
       +{
       +        Posn n=0;
       +        int c;
       +
       +        if((c=nextc())<'0' || '9'<c)        /* no number defaults to 1 */
       +                return 1;
       +        while('0'<=(c=getch()) && c<='9')
       +                n = n*10 + (c-'0');
       +        ungetch();
       +        return n;
       +}
       +
       +int
       +skipbl(void)
       +{
       +        int c;
       +        do
       +                c = getch();
       +        while(c==' ' || c=='\t');
       +        if(c >= 0)
       +                ungetch();
       +        return c;
       +}
       +
       +void
       +termcommand(void)
       +{
       +        Posn p;
       +
       +        Fgetcset(cmd, cmdpt);
       +        for(p=cmdpt; p<cmd->nrunes; p++){
       +                if(terminp >= &termline[BLOCKSIZE]){
       +                        cmdpt = cmd->nrunes;
       +                        error(Etoolong);
       +                }
       +                *terminp++ = Fgetc(cmd);
       +        }
       +        cmdpt = cmd->nrunes;
       +}
       +
       +void
       +cmdloop(void)
       +{
       +        Cmd *cmdp;
       +        File *ocurfile;
       +        int loaded;
       +
       +        for(;;){
       +                if(!downloaded && curfile && curfile->state==Unread)
       +                        load(curfile);
       +                if((cmdp = parsecmd(0))==0){
       +                        if(downloaded){
       +                                rescue();
       +                                exits("eof");
       +                        }
       +                        break;
       +                }
       +                ocurfile = curfile;
       +                loaded = curfile && curfile->state!=Unread;
       +                if(cmdexec(curfile, cmdp) == 0)
       +                        break;
       +                freecmd();
       +                cmdupdate();
       +                update();
       +                if(downloaded && curfile &&
       +                    (ocurfile!=curfile || (!loaded && curfile->state!=Unread)))
       +                        outTs(Hcurrent, curfile->tag);
       +                        /* don't allow type ahead on files that aren't bound */
       +                if(downloaded && curfile && curfile->rasp == 0)
       +                        terminp = termoutp;
       +        }
       +}
       +
       +Cmd *
       +newcmd(void){
       +        Cmd *p;
       +
       +        p = emalloc(sizeof(Cmd));
       +        inslist(&cmdlist, cmdlist.nused, (long)p);
       +        return p;
       +}
       +
       +Addr*
       +newaddr(void)
       +{
       +        Addr *p;
       +
       +        p = emalloc(sizeof(Addr));
       +        inslist(&addrlist, addrlist.nused, (long)p);
       +        return p;
       +}
       +
       +String*
       +newre(void)
       +{
       +        String *p;
       +
       +        p = emalloc(sizeof(String));
       +        inslist(&relist, relist.nused, (long)p);
       +        Strinit(p);
       +        return p;
       +}
       +
       +String*
       +newstring(void)
       +{
       +        String *p;
       +
       +        p = emalloc(sizeof(String));
       +        inslist(&stringlist, stringlist.nused, (long)p);
       +        Strinit(p);
       +        return p;
       +}
       +
       +void
       +freecmd(void)
       +{
       +        int i;
       +
       +        while(cmdlist.nused > 0)
       +                free(cmdlist.ucharpptr[--cmdlist.nused]);
       +        while(addrlist.nused > 0)
       +                free(addrlist.ucharpptr[--addrlist.nused]);
       +        while(relist.nused > 0){
       +                i = --relist.nused;
       +                Strclose(relist.stringpptr[i]);
       +                free(relist.stringpptr[i]);
       +        }
       +        while(stringlist.nused>0){
       +                i = --stringlist.nused;
       +                Strclose(stringlist.stringpptr[i]);
       +                free(stringlist.stringpptr[i]);
       +        }
       +}
       +
       +int
       +lookup(int c)
       +{
       +        int i;
       +
       +        for(i=0; cmdtab[i].cmdc; i++)
       +                if(cmdtab[i].cmdc == c)
       +                        return i;
       +        return -1;
       +}
       +
       +void
       +okdelim(int c)
       +{
       +        if(c=='\\' || ('a'<=c && c<='z')
       +        || ('A'<=c && c<='Z') || ('0'<=c && c<='9'))
       +                error_c(Edelim, c);
       +}
       +
       +void
       +atnl(void)
       +{
       +        skipbl();
       +        if(getch() != '\n')
       +                error(Enewline);
       +}
       +
       +void
       +getrhs(String *s, int delim, int cmd)
       +{
       +        int c;
       +
       +        while((c = getch())>0 && c!=delim && c!='\n'){
       +                if(c == '\\'){
       +                        if((c=getch()) <= 0)
       +                                error(Ebadrhs);
       +                        if(c == '\n'){
       +                                ungetch();
       +                                c='\\';
       +                        }else if(c == 'n')
       +                                c='\n';
       +                        else if(c!=delim && (cmd=='s' || c!='\\'))        /* s does its own */
       +                                Straddc(s, '\\');
       +                }
       +                Straddc(s, c);
       +        }
       +        ungetch();        /* let client read whether delimeter, '\n' or whatever */
       +}
       +
       +String *
       +collecttoken(char *end)
       +{
       +        String *s = newstring();
       +        int c;
       +
       +        while((c=nextc())==' ' || c=='\t')
       +                Straddc(s, getch()); /* blanks significant for getname() */
       +        while((c=getch())>0 && utfrune(end, c)==0)
       +                Straddc(s, c);
       +        Straddc(s, 0);
       +        if(c != '\n')
       +                atnl();
       +        return s;
       +}
       +
       +String *
       +collecttext(void)
       +{
       +        String *s = newstring();
       +        int begline, i, c, delim;
       +
       +        if(skipbl()=='\n'){
       +                getch();
       +                i = 0;
       +                do{
       +                        begline = i;
       +                        while((c = getch())>0 && c!='\n')
       +                                i++, Straddc(s, c);
       +                        i++, Straddc(s, '\n');
       +                        if(c < 0)
       +                                goto Return;
       +                }while(s->s[begline]!='.' || s->s[begline+1]!='\n');
       +                Strdelete(s, s->n-2, s->n);
       +        }else{
       +                okdelim(delim = getch());
       +                getrhs(s, delim, 'a');
       +                if(nextc()==delim)
       +                        getch();
       +                atnl();
       +        }
       +    Return:
       +        Straddc(s, 0);                /* JUST FOR CMDPRINT() */
       +        return s;
       +}
       +
       +Cmd *
       +parsecmd(int nest)
       +{
       +        int i, c;
       +        struct cmdtab *ct;
       +        Cmd *cp, *ncp;
       +        Cmd cmd;
       +
       +        cmd.next = cmd.ccmd = 0;
       +        cmd.re = 0;
       +        cmd.flag = cmd.num = 0;
       +        cmd.addr = compoundaddr();
       +        if(skipbl() == -1)
       +                return 0;
       +        if((c=getch())==-1)
       +                return 0;
       +        cmd.cmdc = c;
       +        if(cmd.cmdc=='c' && nextc()=='d'){        /* sleazy two-character case */
       +                getch();                /* the 'd' */
       +                cmd.cmdc='c'|0x100;
       +        }
       +        i = lookup(cmd.cmdc);
       +        if(i >= 0){
       +                if(cmd.cmdc == '\n')
       +                        goto Return;        /* let nl_cmd work it all out */
       +                ct = &cmdtab[i];
       +                if(ct->defaddr==aNo && cmd.addr)
       +                        error(Enoaddr);
       +                if(ct->count)
       +                        cmd.num = getnum();
       +                if(ct->regexp){
       +                        /* x without pattern -> .*\n, indicated by cmd.re==0 */
       +                        /* X without pattern is all files */
       +                        if((ct->cmdc!='x' && ct->cmdc!='X') ||
       +                           ((c = nextc())!=' ' && c!='\t' && c!='\n')){
       +                                skipbl();
       +                                if((c = getch())=='\n' || c<0)
       +                                        error(Enopattern);
       +                                okdelim(c);
       +                                cmd.re = getregexp(c);
       +                                if(ct->cmdc == 's'){
       +                                        cmd.ctext = newstring();
       +                                        getrhs(cmd.ctext, c, 's');
       +                                        if(nextc() == c){
       +                                                getch();
       +                                                if(nextc() == 'g')
       +                                                        cmd.flag = getch();
       +                                        }
       +                        
       +                                }
       +                        }
       +                }
       +                if(ct->addr && (cmd.caddr=simpleaddr())==0)
       +                        error(Eaddress);
       +                if(ct->defcmd){
       +                        if(skipbl() == '\n'){
       +                                getch();
       +                                cmd.ccmd = newcmd();
       +                                cmd.ccmd->cmdc = ct->defcmd;
       +                        }else if((cmd.ccmd = parsecmd(nest))==0)
       +                                panic("defcmd");
       +                }else if(ct->text)
       +                        cmd.ctext = collecttext();
       +                else if(ct->token)
       +                        cmd.ctext = collecttoken(ct->token);
       +                else
       +                        atnl();
       +        }else
       +                switch(cmd.cmdc){
       +                case '{':
       +                        cp = 0;
       +                        do{
       +                                if(skipbl()=='\n')
       +                                        getch();
       +                                ncp = parsecmd(nest+1);
       +                                if(cp)
       +                                        cp->next = ncp;
       +                                else
       +                                        cmd.ccmd = ncp;
       +                        }while(cp = ncp);
       +                        break;
       +                case '}':
       +                        atnl();
       +                        if(nest==0)
       +                                error(Enolbrace);
       +                        return 0;
       +                default:
       +                        error_c(Eunk, cmd.cmdc);
       +                }
       +    Return:
       +        cp = newcmd();
       +        *cp = cmd;
       +        return cp;
       +}
       +
       +String*                                /* BUGGERED */
       +getregexp(int delim)
       +{
       +        String *r = newre();
       +        int c;
       +
       +        for(Strzero(&genstr); ; Straddc(&genstr, c))
       +                if((c = getch())=='\\'){
       +                        if(nextc()==delim)
       +                                c = getch();
       +                        else if(nextc()=='\\'){
       +                                Straddc(&genstr, c);
       +                                c = getch();
       +                        }
       +                }else if(c==delim || c=='\n')
       +                        break;
       +        if(c!=delim && c)
       +                ungetch();
       +        if(genstr.n > 0){
       +                patset = TRUE;
       +                Strduplstr(&lastpat, &genstr);
       +                Straddc(&lastpat, '\0');
       +        }
       +        if(lastpat.n <= 1)
       +                error(Epattern);
       +        Strduplstr(r, &lastpat);
       +        return r;
       +}
       +
       +Addr *
       +simpleaddr(void)
       +{
       +        Addr addr;
       +        Addr *ap, *nap;
       +
       +        addr.next = 0;
       +        addr.left = 0;
       +        switch(skipbl()){
       +        case '#':
       +                addr.type = getch();
       +                addr.num = getnum();
       +                break;
       +        case '0': case '1': case '2': case '3': case '4':
       +        case '5': case '6': case '7': case '8': case '9': 
       +                addr.num = getnum();
       +                addr.type='l';
       +                break;
       +        case '/': case '?': case '"':
       +                addr.are = getregexp(addr.type = getch());
       +                break;
       +        case '.':
       +        case '$':
       +        case '+':
       +        case '-':
       +        case '\'':
       +                addr.type = getch();
       +                break;
       +        default:
       +                return 0;
       +        }
       +        if(addr.next = simpleaddr())
       +                switch(addr.next->type){
       +                case '.':
       +                case '$':
       +                case '\'':
       +                        if(addr.type!='"')
       +                case '"':
       +                                error(Eaddress);
       +                        break;
       +                case 'l':
       +                case '#':
       +                        if(addr.type=='"')
       +                                break;
       +                        /* fall through */
       +                case '/':
       +                case '?':
       +                        if(addr.type!='+' && addr.type!='-'){
       +                                /* insert the missing '+' */
       +                                nap = newaddr();
       +                                nap->type='+';
       +                                nap->next = addr.next;
       +                                addr.next = nap;
       +                        }
       +                        break;
       +                case '+':
       +                case '-':
       +                        break;
       +                default:
       +                        panic("simpleaddr");
       +                }
       +        ap = newaddr();
       +        *ap = addr;
       +        return ap;
       +}
       +
       +Addr *
       +compoundaddr(void)
       +{
       +        Addr addr;
       +        Addr *ap, *next;
       +
       +        addr.left = simpleaddr();
       +        if((addr.type = skipbl())!=',' && addr.type!=';')
       +                return addr.left;
       +        getch();
       +        next = addr.next = compoundaddr();
       +        if(next && (next->type==',' || next->type==';') && next->left==0)
       +                error(Eaddress);
       +        ap = newaddr();
       +        *ap = addr;
       +        return ap;
       +}
 (DIR) diff --git a/sam/disc.c b/sam/disc.c
       @@ -0,0 +1,335 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include "sam.h"
       +
       +#define        BLOCKFILL        (BLOCKSIZE/2)
       +
       +static Discdesc        desc[NBUFFILES];
       +
       +void                bkalloc(Disc*, int);
       +void                bkfree(Disc*, int);
       +void                bkwrite(Disc*, Rune*, int, int, int);
       +void                bkread(Disc*, Rune*, int, int, int);
       +
       +
       +Discdesc *
       +Dstart(void)
       +{
       +        int i, fd;
       +        Discdesc *dd;
       +
       +        for(i=0, dd=desc; dd->fd; i++, dd++)
       +                if(i == NBUFFILES-1)
       +                        panic("too many buffer files");
       +        fd = newtmp(i);
       +        if(fd < 0)
       +                panic("can't create buffer file");
       +        dd->fd = fd;
       +        return dd;
       +}
       +
       +Disc *
       +Dopen(Discdesc *dd)
       +{
       +        Disc *d;
       +
       +        d = emalloc(sizeof(Disc));
       +        d->desc = dd;
       +        return d;
       +}
       +
       +void
       +Dclose(Disc *d)
       +{
       +        int i;
       +
       +        for(i=d->block.nused; --i>=0; )        /* backwards because bkfree() stacks */
       +                bkfree(d, i);
       +        free(d->block.listptr);
       +        free(d);
       +}
       +
       +int
       +Dread(Disc *d, Rune *addr, int n, Posn p1)
       +{
       +        int i, nb, nr;
       +        Posn p = 0, p2 = p1+n;
       +
       +        for(i=0; i<d->block.nused; i++){
       +                if((p+=d->block.blkptr[i].nrunes) > p1){
       +                        p -= d->block.blkptr[i].nrunes;
       +                        goto out;
       +                }
       +        }
       +        if(p == p1)
       +                return 0;        /* eof */
       +        return -1;                /* past eof */
       +
       +    out:
       +        n = 0;
       +        if(p != p1){        /* trailing partial block */
       +                nb = d->block.blkptr[i].nrunes;
       +                if(p2 > p+nb)
       +                        nr = nb-(p1-p);
       +                else
       +                        nr = p2-p1;
       +                bkread(d, addr, nr, i, p1-p);
       +                /* advance to next block */
       +                p += nb;
       +                addr += nr;
       +                n += nr;
       +                i++;
       +        }
       +        /* whole blocks */
       +        while(p<p2 && (nb = d->block.blkptr[i].nrunes)<=p2-p){
       +                if(i >= d->block.nused)
       +                        return n;        /* eof */
       +                bkread(d, addr, nb, i, 0);
       +                p += nb;
       +                addr += nb;
       +                n += nb;
       +                i++;
       +        }
       +        if(p < p2){        /* any initial partial block left? */
       +                nr = p2-p;
       +                nb = d->block.blkptr[i].nrunes;
       +                if(nr>nb)
       +                        nr = nb;                /* eof */
       +                /* just read in the part that survives */
       +                bkread(d, addr, nr, i, 0);
       +                n += nr;
       +        }
       +        return n;
       +}
       +
       +void
       +Dinsert(Disc *d, Rune *addr, int n, Posn p0) /* if addr null, just make space */
       +{
       +        int i, nb, ni;
       +        Posn p = 0;
       +        Rune hold[BLOCKSIZE];
       +        int nhold;
       +
       +        for(i=0; i<d->block.nused; i++){
       +                if((p+=d->block.blkptr[i].nrunes) >= p0){
       +                        p -= d->block.blkptr[i].nrunes;
       +                        goto out;
       +                }
       +        }
       +        if(p != p0)
       +                panic("Dinsert");        /* beyond eof */
       +
       +    out:
       +        d->nrunes += n;
       +        nhold = 0;
       +        if(i<d->block.nused && (nb=d->block.blkptr[i].nrunes)>p0-p){
       +                nhold = nb-(p0-p);
       +                bkread(d, hold, nhold, i, p0-p);
       +                d->block.blkptr[i].nrunes -= nhold;        /* no write necessary */
       +        }
       +        /* insertion point is now at end of block i (which may not exist) */
       +        while(n > 0){
       +                if(i < d->block.nused
       +                && (nb=d->block.blkptr[i].nrunes) < BLOCKFILL){
       +                        /* fill this block */
       +                        if(nb+n > BLOCKSIZE)
       +                                ni = BLOCKFILL-nb;
       +                        else
       +                                ni = n;
       +                        if(addr)
       +                                bkwrite(d, addr, ni, i, nb);
       +                        nb += ni;
       +                }else{        /* make new block */
       +                        if(i < d->block.nused)
       +                                i++;        /* put after this block, if it exists */
       +                        bkalloc(d, i);
       +                        if(n > BLOCKSIZE)
       +                                ni = BLOCKFILL;
       +                        else
       +                                ni = n;
       +                        if(addr)
       +                                bkwrite(d, addr, ni, i, 0);
       +                        nb = ni;
       +                }
       +                d->block.blkptr[i].nrunes = nb;
       +                if(addr)
       +                        addr += ni;
       +                n -= ni;
       +        }
       +        if(nhold){
       +                if(i < d->block.nused
       +                && (nb=d->block.blkptr[i].nrunes)+nhold < BLOCKSIZE){
       +                        /* fill this block */
       +                        bkwrite(d, hold, nhold, i, nb);
       +                        nb += nhold;
       +                }else{        /* make new block */
       +                        if(i < d->block.nused)
       +                                i++;        /* put after this block, if it exists */
       +                        bkalloc(d, i);
       +                        bkwrite(d, hold, nhold, i, 0);
       +                        nb = nhold;
       +                }
       +                d->block.blkptr[i].nrunes = nb;
       +        }
       +}
       +
       +void
       +Ddelete(Disc *d, Posn p1, Posn p2)
       +{
       +        int i, nb, nd;
       +        Posn p = 0;
       +        Rune buf[BLOCKSIZE];
       +
       +        for(i = 0; i<d->block.nused; i++){
       +                if((p+=d->block.blkptr[i].nrunes) > p1){
       +                        p -= d->block.blkptr[i].nrunes;
       +                        goto out;
       +                }
       +        }
       +        if(p1!=d->nrunes || p2!=p1)
       +                panic("Ddelete");
       +        return;        /* beyond eof */
       +
       +    out:
       +        d->nrunes -= p2-p1;
       +        if(p != p1){        /* throw away partial block */
       +                nb = d->block.blkptr[i].nrunes;
       +                bkread(d, buf, nb, i, 0);
       +                if(p2 >= p+nb)
       +                        nd = nb-(p1-p);
       +                else{
       +                        nd = p2-p1;
       +                        memmove(buf+(p1-p), buf+(p1-p)+nd, RUNESIZE*(nb-((p1-p)+nd)));
       +                }
       +                nb -= nd;
       +                bkwrite(d, buf, nb, i, 0);
       +                d->block.blkptr[i].nrunes = nb;
       +                p2 -= nd;
       +                /* advance to next block */
       +                p += nb;
       +                i++;
       +        }
       +        /* throw away whole blocks */
       +        while(p<p2 && (nb = d->block.blkptr[i].nrunes)<=p2-p){
       +                if(i >= d->block.nused)
       +                        panic("Ddelete 2");
       +                bkfree(d, i);
       +                p2 -= nb;
       +        }
       +        if(p >= p2)        /* any initial partial block left to delete? */
       +                return;        /* no */
       +        nd = p2-p;
       +        nb = d->block.blkptr[i].nrunes;
       +        /* just read in the part that survives */
       +        bkread(d, buf, nb-=nd, i, nd);
       +        /* a little block merging */
       +        if(nb<BLOCKSIZE/2 && i>0 && (nd = d->block.blkptr[i-1].nrunes)<BLOCKSIZE/2){
       +                memmove(buf+nd, buf, RUNESIZE*nb);
       +                bkread(d, buf, nd, --i, 0);
       +                bkfree(d, i);
       +                nb += nd;
       +        }
       +        bkwrite(d, buf, nb, i, 0);
       +        d->block.blkptr[i].nrunes = nb;
       +}
       +
       +void
       +Dreplace(Disc *d, Posn p1, Posn p2, Rune *addr, int n)
       +{
       +        int i, nb, nr;
       +        Posn p = 0;
       +        Rune buf[BLOCKSIZE];
       +
       +        if(p2-p1 > n)
       +                Ddelete(d, p1+n, p2);
       +        else if(p2-p1 < n)
       +                Dinsert(d, 0, n-(p2-p1), p2);
       +        if(n == 0)
       +                return;
       +        p2 = p1+n;
       +        /* they're now conformal; replace in place */
       +        for(i=0; i<d->block.nused; i++){
       +                if((p+=d->block.blkptr[i].nrunes) > p1){
       +                        p -= d->block.blkptr[i].nrunes;
       +                        goto out;
       +                }
       +        }
       +        panic("Dreplace");
       +
       +    out:
       +        if(p != p1){        /* trailing partial block */
       +                nb = d->block.blkptr[i].nrunes;
       +                bkread(d, buf, nb, i, 0);
       +                if(p2 > p+nb)
       +                        nr = nb-(p1-p);
       +                else
       +                        nr = p2-p1;
       +                memmove(buf+p1-p, addr, RUNESIZE*nr);
       +                bkwrite(d, buf, nb, i, 0);
       +                /* advance to next block */
       +                p += nb;
       +                addr += nr;
       +                i++;
       +        }
       +        /* whole blocks */
       +        while(p<p2 && (nb = d->block.blkptr[i].nrunes)<=p2-p){
       +                if(i >= d->block.nused)
       +                        panic("Dreplace 2");
       +                bkwrite(d, addr, nb, i, 0);
       +                p += nb;
       +                addr += nb;
       +                i++;
       +        }
       +        if(p < p2){        /* any initial partial block left? */
       +                nr = p2-p;
       +                nb = d->block.blkptr[i].nrunes;
       +                /* just read in the part that survives */
       +                bkread(d, buf+nr, nb-nr, i, nr);
       +                memmove(buf, addr, RUNESIZE*nr);
       +                bkwrite(d, buf, nb, i, 0);
       +        }
       +}
       +
       +void
       +bkread(Disc *d, Rune *loc, int n, int bk, int off)
       +{
       +        Seek(d->desc->fd, RUNESIZE*(BLOCKSIZE*d->block.blkptr[bk].bnum+off), 0);
       +        Read(d->desc->fd, loc, n*RUNESIZE);
       +}
       +
       +void
       +bkwrite(Disc *d, Rune *loc, int n, int bk, int off)
       +{
       +        Seek(d->desc->fd, RUNESIZE*(BLOCKSIZE*d->block.blkptr[bk].bnum+off), 0);
       +        Write(d->desc->fd, loc, n*RUNESIZE);
       +                /*
       +                 * sleazy hack to avoid silly SGI kernel bug
       +                 *        fill partial block with garbage
       +                 */
       +        if (off+n >= d->block.blkptr[bk].nrunes && off+n < BLOCKSIZE)
       +                Write(d->desc->fd, genbuf, (BLOCKSIZE-(off+n))*RUNESIZE);
       +}
       +
       +void
       +bkalloc(Disc *d, int n)
       +{
       +        Discdesc *dd = d->desc;
       +        ulong bnum;
       +
       +        if(dd->free.nused)
       +                bnum = dd->free.longptr[--dd->free.nused];
       +        else
       +                bnum = dd->nbk++;
       +        if(bnum >= 1<<(8*(sizeof(((Block*)0)->bnum))))
       +                error(Etmpovfl);
       +        inslist(&d->block, n, 0L);
       +        d->block.blkptr[n].bnum = bnum;
       +}
       +
       +void
       +bkfree(Disc *d, int n)
       +{
       +        Discdesc *dd = d->desc;
       +
       +        inslist(&dd->free, dd->free.nused, d->block.blkptr[n].bnum);
       +        dellist(&d->block, n);
       +}
 (DIR) diff --git a/sam/error.c b/sam/error.c
       @@ -0,0 +1,132 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include "sam.h"
       +
       +static char *emsg[]={
       +        /* error_s */
       +        "can't open",
       +        "can't create",
       +        "not in menu:",
       +        "changes to",
       +        "I/O error:",
       +        /* error_c */
       +        "unknown command",
       +        "no operand for",
       +        "bad delimiter",
       +        /* error */
       +        "can't fork",
       +        "interrupt",
       +        "address",
       +        "search",
       +        "pattern",
       +        "newline expected",
       +        "blank expected",
       +        "pattern expected",
       +        "can't nest X or Y",
       +        "unmatched `}'",
       +        "command takes no address",
       +        "addresses overlap",
       +        "substitution",
       +        "& match too long",
       +        "bad \\ in rhs",
       +        "address range",
       +        "changes not in sequence",
       +        "addresses out of order",
       +        "no file name",
       +        "unmatched `('",
       +        "unmatched `)'",
       +        "malformed `[]'",
       +        "malformed regexp",
       +        "reg. exp. list overflow",
       +        "plan 9 command",
       +        "can't pipe",
       +        "no current file",
       +        "string too long",
       +        "changed files",
       +        "empty string",
       +        "file search",
       +        "non-unique match for \"\"",
       +        "tag match too long",
       +        "too many subexpressions",
       +        "temporary file too large",
       +        "file is append-only",
       +};
       +static char *wmsg[]={
       +        /* warn_s */
       +        "duplicate file name",
       +        "no such file",
       +        "write might change good version of",
       +        /* warn_S */
       +        "files might be aliased",
       +        /* warn */
       +        "null characters elided",
       +        "can't run pwd",
       +        "last char not newline",
       +        "exit status not 0",
       +};
       +
       +void
       +error(Err s)
       +{
       +        char buf[512];
       +
       +        sprint(buf, "?%s", emsg[s]);
       +        hiccough(buf);
       +}
       +
       +void
       +error_s(Err s, char *a)
       +{
       +        char buf[512];
       +
       +        sprint(buf, "?%s \"%s\"", emsg[s], a);
       +        hiccough(buf);
       +}
       +
       +void
       +error_c(Err s, int c)
       +{
       +        char buf[512];
       +
       +        sprint(buf, "?%s `%c'", emsg[s], c);
       +        hiccough(buf);
       +}
       +
       +void
       +warn(Warn s)
       +{
       +        dprint("?warning: %s\n", wmsg[s]);
       +}
       +
       +void
       +warn_S(Warn s, String *a)
       +{
       +        print_s(wmsg[s], a);
       +}
       +
       +void
       +warn_SS(Warn s, String *a, String *b)
       +{
       +        print_ss(wmsg[s], a, b);
       +}
       +
       +void
       +warn_s(Warn s, char *a)
       +{
       +        dprint("?warning: %s `%s'\n", wmsg[s], a);
       +}
       +
       +void
       +termwrite(char *s)
       +{
       +        String *p;
       +
       +        if(downloaded){
       +                p = tmpcstr(s);
       +                if(cmd)
       +                        Finsert(cmd, p, cmdpt);
       +                else
       +                        Strinsert(&cmdstr, p, cmdstr.n);
       +                cmdptadv += p->n;
       +        }else
       +                Write(2, s, strlen(s));
       +}
 (DIR) diff --git a/sam/errors.h b/sam/errors.h
       @@ -0,0 +1,63 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +typedef enum Err{
       +        /* error_s */
       +        Eopen,
       +        Ecreate,
       +        Emenu,
       +        Emodified,
       +        Eio,
       +        /* error_c */
       +        Eunk,
       +        Emissop,
       +        Edelim,
       +        /* error */
       +        Efork,
       +        Eintr,
       +        Eaddress,
       +        Esearch,
       +        Epattern,
       +        Enewline,
       +        Eblank,
       +        Enopattern,
       +        EnestXY,
       +        Enolbrace,
       +        Enoaddr,
       +        Eoverlap,
       +        Enosub,
       +        Elongrhs,
       +        Ebadrhs,
       +        Erange,
       +        Esequence,
       +        Eorder,
       +        Enoname,
       +        Eleftpar,
       +        Erightpar,
       +        Ebadclass,
       +        Ebadregexp,
       +        Eoverflow,
       +        Enocmd,
       +        Epipe,
       +        Enofile,
       +        Etoolong,
       +        Echanges,
       +        Eempty,
       +        Efsearch,
       +        Emanyfiles,
       +        Elongtag,
       +        Esubexp,
       +        Etmpovfl,
       +        Eappend
       +}Err;
       +typedef enum Warn{
       +        /* warn_s */
       +        Wdupname,
       +        Wfile,
       +        Wdate,
       +        /* warn_ss */
       +        Wdupfile,
       +        /* warn */
       +        Wnulls,
       +        Wpwd,
       +        Wnotnewline,
       +        Wbadstatus
       +}Warn;
 (DIR) diff --git a/sam/file.c b/sam/file.c
       @@ -0,0 +1,465 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include "sam.h"
       +/*
       + * Files are splayed out a factor of NDISC to reduce indirect block access
       + */
       +Discdesc        *files[NDISC];
       +Discdesc        *transcripts[NDISC];
       +Buffer                *undobuf;
       +static String        *ftempstr(Rune*, int);
       +int                fcount;
       +File                *lastfile;
       +
       +void        puthdr_csl(Buffer*, char, short, Posn);
       +void        puthdr_cs(Buffer*, char, short);
       +void        puthdr_M(Buffer*, Posn, Range, Range, Mod, short);
       +void        puthdr_cll(Buffer*, char, Posn, Posn);
       +void        Fflush(File*);
       +
       +enum{
       +        SKIP=50,                /* max dist between file changes folded together */
       +        MAXCACHE=STRSIZE        /* max length of cache. must be < 32K-BLOCKSIZE */
       +};
       +
       +void
       +Fstart(void)
       +{
       +        undobuf = Bopen(Dstart());
       +        snarfbuf = Bopen(Dstart());
       +        plan9buf = Bopen(Dstart());
       +}
       +
       +void
       +Fmark(File *f, Mod m)
       +{
       +        Buffer *t = f->transcript;
       +        Posn p;
       +
       +        if(f->state == Readerr)
       +                return;
       +        if(f->state == Unread)        /* this is implicit 'e' of a file */
       +                return;
       +        p = m==0? -1 : f->markp;
       +        f->markp = t->nrunes;
       +        puthdr_M(t, p, f->dot.r, f->mark, f->mod, f->state);
       +        f->ndot = f->dot;
       +        f->marked = TRUE;
       +        f->mod = m;
       +        f->hiposn = -1;
       +        /* Safety first */
       +        f->cp1 = f->cp2 = 0;
       +}
       +
       +File *
       +Fopen(void)
       +{
       +        File *f;
       +
       +        f = emalloc(sizeof(File));
       +        if(files[fcount] == 0){
       +                files[fcount] = Dstart();
       +                transcripts[fcount] = Dstart();
       +        }
       +        f->buf = Bopen(files[fcount]);
       +        f->transcript = Bopen(transcripts[fcount]);
       +        if(++fcount == NDISC)
       +                fcount = 0;
       +        f->nrunes = 0;
       +        f->markp = 0;
       +        f->mod = 0;
       +        f->dot.f = f;
       +        f->ndot.f = f;
       +        f->dev = ~0;
       +        f->qid = ~0;
       +        Strinit0(&f->name);
       +        Strinit(&f->cache);
       +        f->state = Unread;
       +        Fmark(f, (Mod)0);
       +        return f;
       +}
       +
       +void
       +Fclose(File *f)
       +{
       +        if(f == lastfile)
       +                lastfile = 0;
       +        Bterm(f->buf);
       +        Bterm(f->transcript);
       +        Strclose(&f->name);
       +        Strclose(&f->cache);
       +        if(f->rasp)
       +                listfree(f->rasp);
       +        free(f);
       +}
       +
       +void
       +Finsert(File *f, String *str, Posn p1)
       +{
       +        Buffer *t = f->transcript;
       +
       +        if(f->state == Readerr)
       +                return;
       +        if(str->n == 0)
       +                return;
       +        if(str->n<0 || str->n>STRSIZE)
       +                panic("Finsert");
       +        if(f->mod < modnum)
       +                Fmark(f, modnum);
       +        if(p1 < f->hiposn)
       +                error(Esequence);
       +        if(str->n >= BLOCKSIZE){        /* don't bother with the cache */
       +                Fflush(f);
       +                puthdr_csl(t, 'i', str->n, p1);
       +                Binsert(t, str, t->nrunes);
       +        }else{        /* insert into the cache instead of the transcript */
       +                if(f->cp2==0 && f->cp1==0 && f->cache.n==0)        /* empty cache */
       +                        f->cp1 = f->cp2 = p1;
       +                if(p1-f->cp2>SKIP || f->cache.n+str->n>MAXCACHE-SKIP){
       +                        Fflush(f);
       +                        f->cp1 = f->cp2 = p1;
       +                }
       +                if(f->cp2 != p1){        /* grab the piece in between */
       +                        Rune buf[SKIP];
       +                        String s;
       +                        Fchars(f, buf, f->cp2, p1);
       +                        s.s = buf;
       +                        s.n = p1-f->cp2;
       +                        Strinsert(&f->cache, &s, f->cache.n);
       +                        f->cp2 = p1;
       +                }
       +                Strinsert(&f->cache, str, f->cache.n);
       +        }
       +        if(f != cmd)
       +                quitok = FALSE;
       +        f->closeok = FALSE;
       +        if(f->state == Clean)
       +                state(f, Dirty);
       +        f->hiposn = p1;
       +}
       +
       +void
       +Fdelete(File *f, Posn p1, Posn p2)
       +{
       +        if(f->state == Readerr)
       +                return;
       +        if(p1==p2)
       +                return;
       +        if(f->mod<modnum)
       +                Fmark(f, modnum);
       +        if(p1<f->hiposn)
       +                error(Esequence);
       +        if(p1-f->cp2>SKIP)
       +                Fflush(f);
       +        if(f->cp2==0 && f->cp1==0 && f->cache.n==0)        /* empty cache */
       +                f->cp1 = f->cp2 = p1;
       +        if(f->cp2 != p1){        /* grab the piece in between */
       +                if(f->cache.n+(p1-f->cp2)>MAXCACHE){
       +                        Fflush(f);
       +                        f->cp1 = f->cp2 = p1;
       +                }else{
       +                        Rune buf[SKIP];
       +                        String s;
       +                        Fchars(f, buf, f->cp2, p1);
       +                        s.s = buf;
       +                        s.n = p1-f->cp2;
       +                        Strinsert(&f->cache, &s, f->cache.n);
       +                }
       +        }
       +        f->cp2 = p2;
       +        if(f!=cmd)
       +                quitok = FALSE;
       +        f->closeok = FALSE;
       +        if(f->state==Clean)
       +                state(f, Dirty);
       +        f->hiposn = p2;
       +}
       +
       +void
       +Fflush(File *f)
       +{
       +        Buffer *t = f->transcript;
       +        Posn p1 = f->cp1, p2 = f->cp2;
       +
       +        if(f->state == Readerr)
       +                return;
       +        if(p1 != p2)
       +                puthdr_cll(t, 'd', p1, p2);
       +        if(f->cache.n){
       +                puthdr_csl(t, 'i', f->cache.n, p2);
       +                Binsert(t, &f->cache, t->nrunes);
       +                Strzero(&f->cache);
       +        }
       +        f->cp1 = f->cp2 = 0;
       +}
       +
       +void
       +Fsetname(File *f, String *s)
       +{
       +        Buffer *t = f->transcript;
       +
       +        if(f->state == Readerr)
       +                return;
       +        if(f->state == Unread){        /* This is setting initial file name */
       +                Strduplstr(&f->name, s);
       +                sortname(f);
       +        }else{
       +                if(f->mod < modnum)
       +                        Fmark(f, modnum);
       +                puthdr_cs(t, 'f', s->n);
       +                Binsert(t, s, t->nrunes);
       +        }
       +}
       +
       +/*
       + * The heart of it all. Fupdate will run along the transcript list, executing
       + * the commands and converting them into their inverses for a later undo pass.
       + * The pass runs top to bottom, so addresses in the transcript are tracked
       + * (by the var. delta) so they stay valid during the operation.  This causes
       + * all operations to appear to happen simultaneously, which is why the addresses
       + * passed to Fdelete and Finsert never take into account other changes occurring
       + * in this command (and is why things are done this way).
       + */
       +int
       +Fupdate(File *f, int mktrans, int toterm)
       +{
       +        Buffer *t = f->transcript;
       +        Buffer *u = undobuf;
       +        int n, ni;
       +        Posn p0, p1, p2, p, deltadot = 0, deltamark = 0, delta = 0;
       +        int changes = FALSE;
       +        union Hdr buf;
       +        Rune tmp[BLOCKSIZE+1];        /* +1 for NUL in 'f' case */
       +
       +        if(f->state == Readerr)
       +                return FALSE;
       +        if(lastfile && f!=lastfile)
       +                Bclean(lastfile->transcript);        /* save memory when multifile */
       +        lastfile = f;
       +        Fflush(f);
       +        if(f->marked)
       +                p0 = f->markp+sizeof(Mark)/RUNESIZE;
       +        else
       +                p0 = 0;
       +        f->dot = f->ndot;
       +        while((n=Bread(t, (Rune*)&buf, sizeof buf/RUNESIZE, p0)) > 0){
       +                switch(buf.cs.c){
       +                default:
       +                        panic("unknown in Fupdate");
       +                case 'd':
       +                        p1 = buf.cll.l;
       +                        p2 = buf.cll.l1;
       +                        p0 += sizeof(struct _cll)/RUNESIZE;
       +                        if(p2 <= f->dot.r.p1)
       +                                deltadot -= p2-p1;
       +                        if(p2 <= f->mark.p1)
       +                                deltamark -= p2-p1;
       +                        p1 += delta, p2+=delta;
       +                        delta -= p2-p1;
       +                        if(!mktrans)
       +                                for(p = p1; p<p2; p+=ni){
       +                                        if(p2-p>BLOCKSIZE)
       +                                                ni = BLOCKSIZE;
       +                                        else
       +                                                ni = p2-p;
       +                                        puthdr_csl(u, 'i', ni, p1);
       +                                        Bread(f->buf, tmp, ni, p);
       +                                        Binsert(u, ftempstr(tmp, ni), u->nrunes);
       +                                }
       +                        f->nrunes -= p2-p1;
       +                        Bdelete(f->buf, p1, p2);
       +                        changes = TRUE;
       +                        break;
       +
       +                case 'f':
       +                        n = buf.cs.s;
       +                        p0 += sizeof(struct _cs)/RUNESIZE;
       +                        Strinsure(&genstr, n+1);
       +                        Bread(t, tmp, n, p0);
       +                        tmp[n] = 0;
       +                        p0 += n;
       +                        Strdupl(&genstr, tmp);
       +                        if(!mktrans){
       +                                puthdr_cs(u, 'f', f->name.n);
       +                                Binsert(u, &f->name, u->nrunes);
       +                        }
       +                        Strduplstr(&f->name, &genstr);
       +                        sortname(f);
       +                        changes = TRUE;
       +                        break;
       +
       +                case 'i':
       +                        n = buf.csl.s;
       +                        p1 = buf.csl.l;
       +                        p0 += sizeof(struct _csl)/RUNESIZE;
       +                        if(p1 < f->dot.r.p1)
       +                                deltadot += n;
       +                        if(p1 < f->mark.p1)
       +                                deltamark += n;
       +                        p1 += delta;
       +                        delta += n;
       +                        if(!mktrans)
       +                                puthdr_cll(u, 'd', p1, p1+n);
       +                        changes = TRUE;
       +                        f->nrunes += n;
       +                        while(n > 0){
       +                                if(n > BLOCKSIZE)
       +                                        ni = BLOCKSIZE;
       +                                else
       +                                        ni = n;
       +                                Bread(t, tmp, ni, p0);
       +                                Binsert(f->buf, ftempstr(tmp, ni), p1);
       +                                n -= ni;
       +                                p1 += ni;
       +                                p0 += ni;
       +                        }
       +                        break;
       +                }
       +        }
       +        toterminal(f, toterm);
       +        f->dot.r.p1 += deltadot;
       +        f->dot.r.p2 += deltadot;
       +        if(f->dot.r.p1 > f->nrunes)
       +                f->dot.r.p1 = f->nrunes;
       +        if(f->dot.r.p2 > f->nrunes)
       +                f->dot.r.p2 = f->nrunes;
       +        f->mark.p1 += deltamark;
       +        f->mark.p2 += deltamark;
       +        if(f->mark.p1 > f->nrunes)
       +                f->mark.p1 = f->nrunes;
       +        if(f->mark.p2 > f->nrunes)
       +                f->mark.p2 = f->nrunes;
       +        if(n < 0)
       +                panic("Fupdate read");
       +        if(f == cmd)
       +                f->mod = 0;        /* can't undo command file */
       +        if(p0 > f->markp+sizeof(Posn)/RUNESIZE){        /* for undo, this throws away the undo transcript */
       +                if(f->mod > 0){        /* can't undo the dawn of time */
       +                        Bdelete(t, f->markp+sizeof(Mark)/RUNESIZE, t->nrunes);
       +                        /* copy the undo list back into the transcript */
       +                        for(p = 0; p<u->nrunes; p+=ni){
       +                                if(u->nrunes-p>BLOCKSIZE)
       +                                        ni = BLOCKSIZE;
       +                                else
       +                                        ni = u->nrunes-p;
       +                                Bread(u, tmp, ni, p);
       +                                Binsert(t, ftempstr(tmp, ni), t->nrunes);
       +                        }
       +                }
       +                Bdelete(u, (Posn)0, u->nrunes);
       +        }
       +        return f==cmd? FALSE : changes;
       +}
       +
       +void
       +puthdr_csl(Buffer *b, char c, short s, Posn p)
       +{
       +        struct _csl buf;
       +
       +        if(p < 0)
       +                panic("puthdr_csP");
       +        buf.c = c;
       +        buf.s = s;
       +        buf.l = p;
       +        Binsert(b, ftempstr((Rune*)&buf, sizeof buf/RUNESIZE), b->nrunes);
       +}
       +
       +void
       +puthdr_cs(Buffer *b, char c, short s)
       +{
       +        struct _cs buf;
       +
       +        buf.c = c;
       +        buf.s = s;
       +        Binsert(b, ftempstr((Rune*)&buf, sizeof buf/RUNESIZE), b->nrunes);
       +}
       +
       +void
       +puthdr_M(Buffer *b, Posn p, Range dot, Range mk, Mod m, short s1)
       +{
       +        Mark mark;
       +        static first = 1;
       +
       +        if(!first && p<0)
       +                panic("puthdr_M");
       +        mark.p = p;
       +        mark.dot = dot;
       +        mark.mark = mk;
       +        mark.m = m;
       +        mark.s1 = s1;
       +        Binsert(b, ftempstr((Rune *)&mark, sizeof mark/RUNESIZE), b->nrunes);
       +}
       +
       +void
       +puthdr_cll(Buffer *b, char c, Posn p1, Posn p2)
       +{
       +        struct _cll buf;
       +
       +        if(p1<0 || p2<0)
       +                panic("puthdr_cll");
       +        buf.c = c;
       +        buf.l = p1;
       +        buf.l1 = p2;
       +        Binsert(b, ftempstr((Rune*)&buf, sizeof buf/RUNESIZE), b->nrunes);
       +}
       +
       +long
       +Fchars(File *f, Rune *addr, Posn p1, Posn p2)
       +{
       +        return Bread(f->buf, addr, p2-p1, p1);
       +}
       +
       +int
       +Fgetcset(File *f, Posn p)
       +{
       +        if(p<0 || p>f->nrunes)
       +                panic("Fgetcset out of range");
       +        if((f->ngetc = Fchars(f, f->getcbuf, p, p+NGETC))<0)
       +                panic("Fgetcset Bread fail");
       +        f->getcp = p;
       +        f->getci = 0;
       +        return f->ngetc;
       +}
       +
       +int
       +Fbgetcset(File *f, Posn p)
       +{
       +        if(p<0 || p>f->nrunes)
       +                panic("Fbgetcset out of range");
       +        if((f->ngetc = Fchars(f, f->getcbuf, p<NGETC? (Posn)0 : p-NGETC, p))<0)
       +                panic("Fbgetcset Bread fail");
       +        f->getcp = p;
       +        f->getci = f->ngetc;
       +        return f->ngetc;
       +}
       +
       +int
       +Fgetcload(File *f, Posn p)
       +{
       +        if(Fgetcset(f, p)){
       +                --f->ngetc;
       +                f->getcp++;
       +                return f->getcbuf[f->getci++];
       +        }
       +        return -1;
       +}
       +
       +int
       +Fbgetcload(File *f, Posn p)
       +{
       +        if(Fbgetcset(f, p)){
       +                --f->getcp;
       +                return f->getcbuf[--f->getci];
       +        }
       +        return -1;
       +}
       +
       +static String*
       +ftempstr(Rune *s, int n)
       +{
       +        static String p;
       +
       +        p.s = s;
       +        p.n = n;
       +        p.size = n;
       +        return &p;
       +}
 (DIR) diff --git a/sam/io.c b/sam/io.c
       @@ -0,0 +1,257 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include "sam.h"
       +
       +#define        NSYSFILE        3
       +#define        NOFILE                128
       +
       +void
       +checkqid(File *f)
       +{
       +        int i, w;
       +        File *g;
       +
       +        w = whichmenu(f);
       +        for(i=1; i<file.nused; i++){
       +                g = file.filepptr[i];
       +                if(w == i)
       +                        continue;
       +                if(f->dev==g->dev && f->qid==g->qid)
       +                        warn_SS(Wdupfile, &f->name, &g->name);
       +        }
       +}
       +
       +void
       +writef(File *f)
       +{
       +        Rune c;
       +        Posn n;
       +        char *name;
       +        int i, samename, newfile;
       +        ulong dev, qid;
       +        long mtime, appendonly, length;
       +
       +        newfile = 0;
       +        samename = Strcmp(&genstr, &f->name) == 0;
       +        name = Strtoc(&f->name);
       +        i = statfile(name, &dev, &qid, &mtime, 0, 0);
       +        if(i == -1)
       +                newfile++;
       +        else if(samename &&
       +                (f->dev!=dev || f->qid!=qid || f->date<mtime)){
       +                f->dev = dev;
       +                f->qid = qid;
       +                f->date = mtime;
       +                warn_S(Wdate, &genstr);
       +                return;
       +        }
       +        if(genc)
       +                free(genc);
       +        genc = Strtoc(&genstr);
       +        if((io=create(genc, 1, 0666L)) < 0)
       +                error_s(Ecreate, genc);
       +        dprint("%s: ", genc);
       +        if(statfd(io, 0, 0, 0, &length, &appendonly) > 0 && appendonly && length>0)
       +                error(Eappend);
       +        n = writeio(f);
       +        if(f->name.s[0]==0 || samename)
       +                state(f, addr.r.p1==0 && addr.r.p2==f->nrunes? Clean : Dirty);
       +        if(newfile)
       +                dprint("(new file) ");
       +        if(addr.r.p2>0 && Fchars(f, &c, addr.r.p2-1, addr.r.p2) && c!='\n')
       +                warn(Wnotnewline);
       +        closeio(n);
       +        if(f->name.s[0]==0 || samename){
       +                if(statfile(name, &dev, &qid, &mtime, 0, 0) > 0){
       +                        f->dev = dev;
       +                        f->qid = qid;
       +                        f->date = mtime;
       +                        checkqid(f);
       +                }
       +        }
       +}
       +
       +Posn
       +readio(File *f, int *nulls, int setdate)
       +{
       +        int n, b, w;
       +        Rune *r;
       +        Posn nt;
       +        Posn p = addr.r.p2;
       +        ulong dev, qid;
       +        long mtime;
       +        char buf[BLOCKSIZE+1], *s;
       +
       +        *nulls = FALSE;
       +        b = 0;
       +        for(nt = 0; (n = read(io, buf+b, BLOCKSIZE-b))>0; nt+=(r-genbuf)){
       +                n += b;
       +                b = 0;
       +                r = genbuf;
       +                s = buf;
       +                while(n > 0){
       +                        if((*r = *(uchar*)s) < Runeself){
       +                                if(*r)
       +                                        r++;
       +                                else
       +                                        *nulls = TRUE;
       +                                --n;
       +                                s++;
       +                                continue;
       +                        }
       +                        if(fullrune(s, n)){
       +                                w = chartorune(r, s);
       +                                if(*r)
       +                                        r++;
       +                                else
       +                                        *nulls = TRUE;
       +                                n -= w;
       +                                s += w;
       +                                continue;
       +                        }
       +                        b = n;
       +                        memmove(buf, s, b);
       +                        break;
       +                }
       +                Finsert(f, tmprstr(genbuf, r-genbuf), p);
       +        }
       +        if(b)
       +                *nulls = TRUE;
       +        if(*nulls)
       +                warn(Wnulls);
       +        if(setdate){
       +                if(statfd(io, &dev, &qid, &mtime, 0, 0) > 0){
       +                        f->dev = dev;
       +                        f->qid = qid;
       +                        f->date = mtime;
       +                        checkqid(f);
       +                }
       +        }
       +        return nt;
       +}
       +
       +Posn
       +writeio(File *f)
       +{
       +        int m, n;
       +        Posn p = addr.r.p1;
       +        char *c;
       +
       +        while(p < addr.r.p2){
       +                if(addr.r.p2-p>BLOCKSIZE)
       +                        n = BLOCKSIZE;
       +                else
       +                        n = addr.r.p2-p;
       +                if(Fchars(f, genbuf, p, p+n)!=n)
       +                        panic("writef read");
       +                c = Strtoc(tmprstr(genbuf, n));
       +                m = strlen(c);
       +                if (m < n)
       +                        panic("corrupted file");
       +                if(Write(io, c, m) != m){
       +                        free(c);
       +                        if(p > 0)
       +                                p += n;
       +                        break;
       +                }
       +                free(c);
       +                p += n;
       +        }
       +        return p-addr.r.p1;
       +}
       +void
       +closeio(Posn p)
       +{
       +        close(io);
       +        io = 0;
       +        if(p >= 0)
       +                dprint("#%lu\n", p);
       +}
       +
       +int        remotefd0 = 0;
       +int        remotefd1 = 1;
       +
       +void
       +bootterm(char *machine, char **argv, char **end)
       +{
       +        int ph2t[2], pt2h[2];
       +
       +        if(machine){
       +                dup(remotefd0, 0);
       +                dup(remotefd1, 1);
       +                close(remotefd0);
       +                close(remotefd1);
       +                argv[0] = "samterm";
       +                *end = 0;
       +                exec(samterm, argv);
       +                fprint(2, "can't exec: ");
       +                perror(samterm);
       +                _exits("damn");
       +        }
       +        if(pipe(ph2t)==-1 || pipe(pt2h)==-1)
       +                panic("pipe");
       +        switch(fork()){
       +        case 0:
       +                dup(ph2t[0], 0);
       +                dup(pt2h[1], 1);
       +                close(ph2t[0]);
       +                close(ph2t[1]);
       +                close(pt2h[0]);
       +                close(pt2h[1]);
       +                argv[0] = "samterm";
       +                *end = 0;
       +                exec(samterm, argv);
       +                fprint(2, "can't exec: ");
       +                perror(samterm);
       +                _exits("damn");
       +        case -1:
       +                panic("can't fork samterm");
       +        }
       +        dup(pt2h[0], 0);
       +        dup(ph2t[1], 1);
       +        close(ph2t[0]);
       +        close(ph2t[1]);
       +        close(pt2h[0]);
       +        close(pt2h[1]);
       +}
       +
       +void
       +connectto(char *machine)
       +{
       +        int p1[2], p2[2];
       +
       +        if(pipe(p1)<0 || pipe(p2)<0){
       +                dprint("can't pipe\n");
       +                exits("pipe");
       +        }
       +        remotefd0 = p1[0];
       +        remotefd1 = p2[1];
       +        switch(fork()){
       +        case 0:
       +                dup(p2[0], 0);
       +                dup(p1[1], 1);
       +                close(p1[0]);
       +                close(p1[1]);
       +                close(p2[0]);
       +                close(p2[1]);
       +                execl(getenv("RSH") ? getenv("RSH") : RXPATH, getenv("RSH") ? getenv("RSH") : RX, machine, rsamname, "-R", (char*)0);
       +                dprint("can't exec %s\n", RXPATH);
       +                exits("exec");
       +
       +        case -1:
       +                dprint("can't fork\n");
       +                exits("fork");
       +        }
       +        close(p1[1]);
       +        close(p2[0]);
       +}
       +
       +void
       +startup(char *machine, int Rflag, char **argv, char **end)
       +{
       +        if(machine)
       +                connectto(machine);
       +        if(!Rflag)
       +                bootterm(machine, argv, end);
       +        downloaded = 1;
       +        outTs(Hversion, VERSION);
       +}
 (DIR) diff --git a/sam/list.c b/sam/list.c
       @@ -0,0 +1,48 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include "sam.h"
       +
       +/*
       + * Check that list has room for one more element.
       + */
       +void
       +growlist(List *l)
       +{
       +        if(l->listptr==0 || l->nalloc==0){
       +                l->nalloc = INCR;
       +                l->listptr = emalloc(INCR*sizeof(long));
       +                l->nused = 0;
       +        }else if(l->nused == l->nalloc){
       +                l->listptr = erealloc(l->listptr, (l->nalloc+INCR)*sizeof(long));
       +                memset((void*)(l->longptr+l->nalloc), 0, INCR*sizeof(long));
       +                l->nalloc += INCR;
       +        }
       +}
       +
       +/*
       + * Remove the ith element from the list
       + */
       +void
       +dellist(List *l, int i)
       +{
       +        memmove(&l->longptr[i], &l->longptr[i+1], (l->nused-(i+1))*sizeof(long));
       +        l->nused--;
       +}
       +
       +/*
       + * Add a new element, whose position is i, to the list
       + */
       +void
       +inslist(List *l, int i, long val)
       +{
       +        growlist(l);
       +        memmove(&l->longptr[i+1], &l->longptr[i], (l->nused-i)*sizeof(long));
       +        l->longptr[i] = val;
       +        l->nused++;
       +}
       +
       +void
       +listfree(List *l)
       +{
       +        free(l->listptr);
       +        free(l);
       +}
 (DIR) diff --git a/sam/mesg.c b/sam/mesg.c
       @@ -0,0 +1,762 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include "sam.h"
       +
       +Header        h;
       +uchar        indata[DATASIZE];
       +uchar        outdata[2*DATASIZE+3];        /* room for overflow message */
       +uchar        *inp;
       +uchar        *outp;
       +uchar        *outmsg = outdata;
       +Posn        cmdpt;
       +Posn        cmdptadv;
       +Buffer        *snarfbuf;
       +int        waitack;
       +int        noflush;
       +int        tversion;
       +
       +long        inlong(void);
       +long        invlong(void);
       +int        inshort(void);
       +int        inmesg(Tmesg);
       +void        setgenstr(File*, Posn, Posn);
       +#ifdef DEBUG
       +char *hname[] = {
       +        [Hversion]        "Hversion",
       +        [Hbindname]        "Hbindname",
       +        [Hcurrent]        "Hcurrent",
       +        [Hnewname]        "Hnewname",
       +        [Hmovname]        "Hmovname",
       +        [Hgrow]                "Hgrow",
       +        [Hcheck0]        "Hcheck0",
       +        [Hcheck]        "Hcheck",
       +        [Hunlock]        "Hunlock",
       +        [Hdata]                "Hdata",
       +        [Horigin]        "Horigin",
       +        [Hunlockfile]        "Hunlockfile",
       +        [Hsetdot]        "Hsetdot",
       +        [Hgrowdata]        "Hgrowdata",
       +        [Hmoveto]        "Hmoveto",
       +        [Hclean]        "Hclean",
       +        [Hdirty]        "Hdirty",
       +        [Hcut]                "Hcut",
       +        [Hsetpat]        "Hsetpat",
       +        [Hdelname]        "Hdelname",
       +        [Hclose]        "Hclose",
       +        [Hsetsnarf]        "Hsetsnarf",
       +        [Hsnarflen]        "Hsnarflen",
       +        [Hack]                "Hack",
       +        [Hextcmd]   "Hextcmd",
       +        [Hexit]                "Hexit",
       +};
       +
       +char *tname[] = {
       +        [Tversion]        "Tversion",
       +        [Tstartcmdfile]        "Tstartcmdfile",
       +        [Tcheck]        "Tcheck",
       +        [Trequest]        "Trequest",
       +        [Torigin]        "Torigin",
       +        [Tstartfile]        "Tstartfile",
       +        [Tworkfile]        "Tworkfile",
       +        [Ttype]                "Ttype",
       +        [Tcut]                "Tcut",
       +        [Tpaste]        "Tpaste",
       +        [Tsnarf]        "Tsnarf",
       +        [Tstartnewfile]        "Tstartnewfile",
       +        [Twrite]        "Twrite",
       +        [Tclose]        "Tclose",
       +        [Tlook]                "Tlook",
       +        [Tsearch]        "Tsearch",
       +        [Tsend]                "Tsend",
       +        [Tdclick]        "Tdclick",
       +        [Tstartsnarf]        "Tstartsnarf",
       +        [Tsetsnarf]        "Tsetsnarf",
       +        [Tack]                "Tack",
       +        [Texit]                "Texit",
       +};
       +
       +void
       +journal(int out, char *s)
       +{
       +        static int fd = 0;
       +
       +        if(fd <= 0)
       +                fd = create("/tmp/sam.out", 1, 0666L);
       +        fprint(fd, "%s%s\n", out? "out: " : "in:  ", s);
       +}
       +
       +void
       +journaln(int out, long n)
       +{
       +        char buf[32];
       +        sprint(buf, sizeof (long) > 4 ? "%ld" : "%d", n);
       +        journal(out, buf);
       +}
       +#else
       +#define        journal(a, b)
       +#define journaln(a, b)
       +#endif
       +
       +int
       +rcvchar(void){
       +        static uchar buf[64];
       +        static i, nleft = 0;
       +
       +        if(nleft <= 0){
       +                nleft = read(0, (char *)buf, sizeof buf);
       +                if(nleft <= 0)
       +                        return -1;
       +                i = 0;
       +        }
       +        --nleft;
       +        return buf[i++];
       +}
       +
       +int
       +rcv(void){
       +        int c;
       +        static state = 0;
       +        static count = 0;
       +        static i = 0;
       +
       +        while((c=rcvchar()) != -1)
       +                switch(state){
       +                case 0:
       +                        h.type = c;
       +                        state++;
       +                        break;
       +
       +                case 1:
       +                        h.count0 = c;
       +                        state++;
       +                        break;
       +
       +                case 2:
       +                        h.count1 = c;
       +                        count = h.count0|(h.count1<<8);
       +                        i = 0;
       +                        if(count > DATASIZE)
       +                                panic("count>DATASIZE");
       +                        if(count == 0)
       +                                goto zerocount;
       +                        state++;
       +                        break;
       +
       +                case 3:
       +                        indata[i++] = c;
       +                        if(i == count){
       +                zerocount:
       +                                indata[i] = 0;
       +                                state = count = 0;
       +                                return inmesg(h.type);
       +                        }
       +                        break;
       +                }
       +        return 0;
       +}
       +
       +File *
       +whichfile(int tag)
       +{
       +        int i;
       +
       +        for(i = 0; i<file.nused; i++)
       +                if(file.filepptr[i]->tag==tag)
       +                        return file.filepptr[i];
       +        hiccough((char *)0);
       +        return 0;
       +}
       +
       +int
       +inmesg(Tmesg type)
       +{
       +        Rune buf[1025];
       +        int i, m;
       +        short s;
       +        long l, l1;
       +        File *f;
       +        Posn p0, p1;
       +        Range r;
       +        String *str;
       +        char *c;
       +        Rune *rp;
       +
       +        if(type > TMAX)
       +                panic("inmesg");
       +
       +        journal(0, tname[type]);
       +
       +        inp = indata;
       +        switch(type){
       +        case -1:
       +                panic("rcv error");
       +
       +        default:
       +                fprint(2, "unknown type %d\n", type);
       +                panic("rcv unknown");
       +
       +        case Tversion:
       +                tversion = inshort();
       +                journaln(0, tversion);
       +                break;
       +
       +        case Tstartcmdfile:
       +                l = invlong();                /* for 64-bit pointers */
       +                journaln(0, l);
       +                Strdupl(&genstr, samname);
       +                cmd = newfile();
       +                outTsv(Hbindname, cmd->tag, l);
       +                outTs(Hcurrent, cmd->tag);
       +                Fsetname(cmd, &genstr);
       +                cmd->rasp = emalloc(sizeof(List));
       +                cmd->state = Clean;
       +                if(cmdstr.n){
       +                        Finsert(cmd, &cmdstr, 0L);
       +                        Strdelete(&cmdstr, 0L, (Posn)cmdstr.n);
       +                }
       +                Fupdate(cmd, FALSE, TRUE);
       +                outT0(Hunlock);
       +                break;
       +
       +        case Tcheck:
       +                /* go through whichfile to check the tag */
       +                outTs(Hcheck, whichfile(inshort())->tag);
       +                break;
       +
       +        case Trequest:
       +                f = whichfile(inshort());
       +                p0 = inlong();
       +                p1 = p0+inshort();
       +                journaln(0, p0);
       +                journaln(0, p1-p0);
       +                if(f->state == Unread)
       +                        panic("Trequest: unread");
       +                if(p1>f->nrunes)
       +                        p1 = f->nrunes;
       +                if(p0>f->nrunes) /* can happen e.g. scrolling during command */
       +                        p0 = f->nrunes;
       +                if(p0 == p1){
       +                        i = 0;
       +                        r.p1 = r.p2 = p0;
       +                }else{
       +                        r = rdata(f->rasp, p0, p1-p0);
       +                        i = r.p2-r.p1;
       +                        if(Fchars(f, buf, r.p1, r.p2)!=i)
       +                                panic("Trequest 2");
       +                }
       +                buf[i]=0;
       +                outTslS(Hdata, f->tag, r.p1, tmprstr(buf, i+1));
       +                break;
       +
       +        case Torigin:
       +                s = inshort();
       +                l = inlong();
       +                l1 = inlong();
       +                journaln(0, l1);
       +                lookorigin(whichfile(s), l, l1);
       +                break;
       +
       +        case Tstartfile:
       +                termlocked++;
       +                f = whichfile(inshort());
       +                if(!f->rasp)        /* this might be a duplicate message */
       +                        f->rasp = emalloc(sizeof(List));
       +                current(f);
       +                outTsv(Hbindname, f->tag, invlong());        /* for 64-bit pointers */
       +                outTs(Hcurrent, f->tag);
       +                journaln(0, f->tag);
       +                if(f->state == Unread)
       +                        load(f);
       +                else{
       +                        if(f->nrunes>0){
       +                                rgrow(f->rasp, 0L, f->nrunes);
       +                                outTsll(Hgrow, f->tag, 0L, f->nrunes);
       +                        }
       +                        outTs(Hcheck0, f->tag);
       +                        moveto(f, f->dot.r);
       +                }
       +                break;
       +
       +        case Tworkfile:
       +                i = inshort();
       +                f = whichfile(i);
       +                current(f);
       +                f->dot.r.p1 = inlong();
       +                f->dot.r.p2 = inlong();
       +                f->tdot = f->dot.r;
       +                journaln(0, i);
       +                journaln(0, f->dot.r.p1);
       +                journaln(0, f->dot.r.p2);
       +                break;
       +
       +        case Ttype:
       +                f = whichfile(inshort());
       +                p0 = inlong();
       +                journaln(0, p0);
       +                journal(0, (char*)inp);
       +                str = tmpcstr((char*)inp);
       +                i = str->n;
       +                Finsert(f, str, p0);
       +                if(Fupdate(f, FALSE, FALSE))
       +                        modnum++;
       +                if(f==cmd && p0==f->nrunes-i && i>0 && str->s[i-1]=='\n'){
       +                        freetmpstr(str);
       +                        termlocked++;
       +                        termcommand();
       +                }else
       +                        freetmpstr(str);
       +                f->dot.r.p1 = f->dot.r.p2 = p0+i; /* terminal knows this already */
       +                f->tdot = f->dot.r;
       +                break;
       +
       +        case Tcut:
       +                f = whichfile(inshort());
       +                p0 = inlong();
       +                p1 = inlong();
       +                journaln(0, p0);
       +                journaln(0, p1);
       +                Fdelete(f, p0, p1);
       +                if(Fupdate(f, FALSE, FALSE))
       +                        modnum++;
       +                f->dot.r.p1 = f->dot.r.p2 = p0;
       +                f->tdot = f->dot.r;   /* terminal knows the value of dot already */
       +                break;
       +
       +        case Tpaste:
       +                f = whichfile(inshort());
       +                p0 = inlong();
       +                journaln(0, p0);
       +                for(l=0; l<snarfbuf->nrunes; l+=m){
       +                        m = snarfbuf->nrunes-l;
       +                        if(m>BLOCKSIZE)
       +                                m = BLOCKSIZE;
       +                        Bread(snarfbuf, genbuf, m, l);
       +                        Finsert(f, tmprstr(genbuf, m), p0);
       +                }
       +                if(Fupdate(f, FALSE, TRUE))
       +                        modnum++;
       +                f->dot.r.p1 = p0;
       +                f->dot.r.p2 = p0+snarfbuf->nrunes;
       +                f->tdot.p1 = -1; /* force telldot to tell (arguably a BUG) */
       +                telldot(f);
       +                outTs(Hunlockfile, f->tag);
       +                break;
       +
       +        case Tsnarf:
       +                i = inshort();
       +                p0 = inlong();
       +                p1 = inlong();
       +                snarf(whichfile(i), p0, p1, snarfbuf, 0);
       +                break;
       +
       +        case Tstartnewfile:
       +                l = invlong();
       +                Strdupl(&genstr, empty);
       +                f = newfile();
       +                f->rasp = emalloc(sizeof(List));
       +                outTsv(Hbindname, f->tag, l);
       +                Fsetname(f, &genstr);
       +                outTs(Hcurrent, f->tag);
       +                current(f);
       +                load(f);
       +                break;
       +
       +        case Twrite:
       +                termlocked++;
       +                i = inshort();
       +                journaln(0, i);
       +                f = whichfile(i);
       +                addr.r.p1 = 0;
       +                addr.r.p2 = f->nrunes;
       +                if(f->name.s[0] == 0)
       +                        error(Enoname);
       +                Strduplstr(&genstr, &f->name);
       +                writef(f);
       +                break;
       +
       +        case Tclose:
       +                termlocked++;
       +                i = inshort();
       +                journaln(0, i);
       +                f = whichfile(i);
       +                current(f);
       +                trytoclose(f);
       +                /* if trytoclose fails, will error out */
       +                delete(f);
       +                break;
       +
       +        case Tlook:
       +                f = whichfile(inshort());
       +                termlocked++;
       +                p0 = inlong();
       +                p1 = inlong();
       +                journaln(0, p0);
       +                journaln(0, p1);
       +                setgenstr(f, p0, p1);
       +                for(l = 0; l<genstr.n; l++){
       +                        i = genstr.s[l];
       +                        if(utfrune(".*+?(|)\\[]^$", i))
       +                                Strinsert(&genstr, tmpcstr("\\"), l++);
       +                }
       +                Straddc(&genstr, '\0');
       +                nextmatch(f, &genstr, p1, 1);
       +                moveto(f, sel.p[0]);
       +                break;
       +
       +        case Tsearch:
       +                termlocked++;
       +                if(curfile == 0)
       +                        error(Enofile);
       +                if(lastpat.s[0] == 0)
       +                        panic("Tsearch");
       +                nextmatch(curfile, &lastpat, curfile->dot.r.p2, 1);
       +                moveto(curfile, sel.p[0]);
       +                break;
       +
       +        case Tsend:
       +                termlocked++;
       +                inshort();        /* ignored */
       +                p0 = inlong();
       +                p1 = inlong();
       +                setgenstr(cmd, p0, p1);
       +                Bdelete(snarfbuf, (Posn)0, snarfbuf->nrunes);
       +                Binsert(snarfbuf, &genstr, (Posn)0);
       +                outTl(Hsnarflen, genstr.n);
       +                if(genstr.s[genstr.n-1] != '\n')
       +                        Straddc(&genstr, '\n');
       +                Finsert(cmd, &genstr, cmd->nrunes);
       +                Fupdate(cmd, FALSE, TRUE);
       +                cmd->dot.r.p1 = cmd->dot.r.p2 = cmd->nrunes;
       +                telldot(cmd);
       +                termcommand();
       +                break;
       +
       +        case Tdclick:
       +                f = whichfile(inshort());
       +                p1 = inlong();
       +                doubleclick(f, p1);
       +                f->tdot.p1 = f->tdot.p2 = p1;
       +                telldot(f);
       +                outTs(Hunlockfile, f->tag);
       +                break;
       +
       +        case Tstartsnarf:
       +                if (snarfbuf->nrunes <= 0) {        /* nothing to export */
       +                        outTs(Hsetsnarf, 0);
       +                        break;
       +                }
       +                c = 0;
       +                i = 0;
       +                m = snarfbuf->nrunes;
       +                if(m > 32000) {                /* tmprstr stores len in a short */
       +                        m = 32000;
       +                        dprint("?warning: snarf buffer truncated\n");
       +                }
       +                rp = malloc(m*sizeof(Rune));
       +                if(rp){
       +                        Bread(snarfbuf, rp, m, 0);
       +                        c = Strtoc(tmprstr(rp, m));
       +                        free(rp);
       +                        i = strlen(c);
       +                }
       +                outTs(Hsetsnarf, i);
       +                if(c){
       +                        Write(1, c, i);
       +                        free(c);
       +                } else
       +                        dprint("snarf buffer too long\n");
       +                break;
       +
       +        case Tsetsnarf:
       +                m = inshort();
       +                if(m > SNARFSIZE)
       +                        error(Etoolong);
       +                c = malloc(m+1);
       +                if(c){
       +                        for(i=0; i<m; i++)
       +                                c[i] = rcvchar();
       +                        c[m] = 0;
       +                        str = tmpcstr(c);
       +                        free(c);
       +                        Bdelete(snarfbuf, (Posn)0, snarfbuf->nrunes);
       +                        Binsert(snarfbuf, str, (Posn)0);
       +                        freetmpstr(str);
       +                        outT0(Hunlock);
       +                }
       +                break;
       +
       +        case Tack:
       +                waitack = 0;
       +                break;
       +
       +        case Texit:
       +                exits(0);
       +        }
       +        return TRUE;
       +}
       +
       +void
       +snarf(File *f, Posn p1, Posn p2, Buffer *buf, int emptyok)
       +{
       +        Posn l;
       +        int i;
       +
       +        if(!emptyok && p1==p2)
       +                return;
       +        Bdelete(buf, (Posn)0, buf->nrunes);
       +        /* Stage through genbuf to avoid compaction problems (vestigial) */
       +        for(l=p1; l<p2; l+=i){
       +                i = p2-l>BLOCKSIZE? BLOCKSIZE : p2-l;
       +                Fchars(f, genbuf, l, l+i);
       +                Binsert(buf, tmprstr(genbuf, i), buf->nrunes);
       +        }
       +}
       +
       +int
       +inshort(void)
       +{
       +        ushort n;
       +
       +        n = inp[0] | (inp[1]<<8);
       +        inp += 2;
       +        return n;
       +}
       +
       +long
       +inlong(void)
       +{
       +        ulong n;
       +
       +        n = inp[0] | (inp[1]<<8) | (inp[2]<<16) | (inp[3]<<24);
       +        inp += 4;
       +        return n;
       +}
       +
       +long
       +invlong(void)
       +{
       +        ulong n;
       +        
       +        n = (inp[7]<<24) | (inp[6]<<16) | (inp[5]<<8) | inp[4];
       +        n = (n<<16) | (inp[3]<<8) | inp[2];
       +        n = (n<<16) | (inp[1]<<8) | inp[0];
       +        inp += 8;
       +        return n;
       +}
       +
       +void
       +setgenstr(File *f, Posn p0, Posn p1)
       +{
       +        if(p0 != p1){
       +                if(p1-p0 >= TBLOCKSIZE)
       +                        error(Etoolong);
       +                Strinsure(&genstr, p1-p0);
       +                Fchars(f, genbuf, p0, p1);
       +                memmove(genstr.s, genbuf, RUNESIZE*(p1-p0));
       +                genstr.n = p1-p0;
       +        }else{
       +                if(snarfbuf->nrunes == 0)
       +                        error(Eempty);
       +                if(snarfbuf->nrunes > TBLOCKSIZE)
       +                        error(Etoolong);
       +                Bread(snarfbuf, genbuf, snarfbuf->nrunes, (Posn)0);
       +                Strinsure(&genstr, snarfbuf->nrunes);
       +                memmove(genstr.s, genbuf, RUNESIZE*snarfbuf->nrunes);
       +                genstr.n = snarfbuf->nrunes;
       +        }
       +}
       +
       +void
       +outT0(Hmesg type)
       +{
       +        outstart(type);
       +        outsend();
       +}
       +
       +void
       +outTl(Hmesg type, long l)
       +{
       +        outstart(type);
       +        outlong(l);
       +        outsend();
       +}
       +
       +void
       +outTs(Hmesg type, int s)
       +{
       +        outstart(type);
       +        journaln(1, s);
       +        outshort(s);
       +        outsend();
       +}
       +
       +void
       +outS(String *s)
       +{
       +        char *c;
       +        int i;
       +
       +        c = Strtoc(s);
       +        i = strlen(c);
       +        outcopy(i, c);
       +        if(i > 99)
       +                c[99] = 0;
       +        journaln(1, i);
       +        journal(1, c);
       +        free(c);
       +}
       +
       +void
       +outTsS(Hmesg type, int s1, String *s)
       +{
       +        outstart(type);
       +        outshort(s1);
       +        outS(s);
       +        outsend();
       +}
       +
       +void
       +outTslS(Hmesg type, int s1, Posn l1, String *s)
       +{
       +        outstart(type);
       +        outshort(s1);
       +        journaln(1, s1);
       +        outlong(l1);
       +        journaln(1, l1);
       +        outS(s);
       +        outsend();
       +}
       +
       +void
       +outTS(Hmesg type, String *s)
       +{
       +        outstart(type);
       +        outS(s);
       +        outsend();
       +}
       +
       +void
       +outTsllS(Hmesg type, int s1, Posn l1, Posn l2, String *s)
       +{
       +        outstart(type);
       +        outshort(s1);
       +        outlong(l1);
       +        outlong(l2);
       +        journaln(1, l1);
       +        journaln(1, l2);
       +        outS(s);
       +        outsend();
       +}
       +
       +void
       +outTsll(Hmesg type, int s, Posn l1, Posn l2)
       +{
       +        outstart(type);
       +        outshort(s);
       +        outlong(l1);
       +        outlong(l2);
       +        journaln(1, l1);
       +        journaln(1, l2);
       +        outsend();
       +}
       +
       +void
       +outTsl(Hmesg type, int s, Posn l)
       +{
       +        outstart(type);
       +        outshort(s);
       +        outlong(l);
       +        journaln(1, l);
       +        outsend();
       +}
       +
       +void
       +outTsv(Hmesg type, int s, Posn l)
       +{
       +        outstart(type);
       +        outshort(s);
       +        outvlong((void*)l);
       +        journaln(1, l);
       +        outsend();
       +}
       +
       +void
       +outstart(Hmesg type)
       +{
       +        journal(1, hname[type]);
       +        outmsg[0] = type;
       +        outp = outmsg+3;
       +}
       +
       +void
       +outcopy(int count, void *data)
       +{
       +        memmove(outp, data, count);
       +        outp += count;
       +}
       +
       +void
       +outshort(int s)
       +{
       +        *outp++ = s;
       +        *outp++ = s>>8; 
       +}
       +
       +void
       +outlong(long l)
       +{
       +        *outp++ = l;
       +        *outp++ = l>>8;
       +        *outp++ = l>>16;
       +        *outp++ = l>>24;
       +}
       +
       +void
       +outvlong(void *v)
       +{
       +        int i;
       +        ulong l;
       +
       +        l = (ulong) v;
       +        for(i = 0; i < 8; i++, l >>= 8)
       +                *outp++ = l;
       +}
       +
       +void
       +outsend(void)
       +{
       +        int outcount;
       +
       +        outcount = outp-outmsg;
       +        outcount -= 3;
       +        outmsg[1] = outcount;
       +        outmsg[2] = outcount>>8;
       +        outmsg = outp;
       +        if(!noflush){
       +                outcount = outmsg-outdata;
       +                if (write(1, (char*) outdata, outcount) != outcount)
       +                        rescue();
       +                outmsg = outdata;
       +                return;
       +        }
       +        if(outmsg < outdata+DATASIZE)
       +                return;
       +        outflush();
       +}
       +
       +void
       +outflush(void)
       +{
       +        if(outmsg == outdata)
       +                return;
       +        noflush = 0;
       +        outT0(Hack);
       +        waitack = 1;
       +        do
       +                if(rcv() == 0){
       +                        rescue();
       +                        exits("eof");
       +                }
       +        while(waitack);
       +        outmsg = outdata;
       +        noflush = 1;
       +}
 (DIR) diff --git a/sam/mesg.h b/sam/mesg.h
       @@ -0,0 +1,102 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#define        VERSION        0
       +
       +#define        TBLOCKSIZE 512                  /* largest piece of text sent to terminal */
       +#define        DATASIZE  (UTFmax*TBLOCKSIZE+30) /* ... including protocol header stuff */
       +#define        SNARFSIZE 16384                /* maximum length of exchanged snarf buffer */
       +/*
       + * Messages originating at the terminal
       + */
       +typedef enum Tmesg
       +{
       +        Tversion,        /* version */
       +        Tstartcmdfile,        /* terminal just opened command frame */
       +        Tcheck,                /* ask host to poke with Hcheck */
       +        Trequest,        /* request data to fill a hole */
       +        Torigin,        /* gimme an Horigin near here */
       +        Tstartfile,        /* terminal just opened a file's frame */
       +        Tworkfile,        /* set file to which commands apply */
       +        Ttype,                /* add some characters, but terminal already knows */
       +        Tcut,
       +        Tpaste,
       +        Tsnarf,
       +        Tstartnewfile,        /* terminal just opened a new frame */
       +        Twrite,                /* write file */
       +        Tclose,                /* terminal requests file close; check mod. status */
       +        Tlook,                /* search for literal current text */
       +        Tsearch,        /* search for last regular expression */
       +        Tsend,                /* pretend he typed stuff */
       +        Tdclick,        /* double click */
       +        Tstartsnarf,        /* initiate snarf buffer exchange */
       +        Tsetsnarf,        /* remember string in snarf buffer */
       +        Tack,                /* acknowledge Hack */
       +        Texit,                /* exit */
       +        TMAX
       +}Tmesg;
       +/*
       + * Messages originating at the host
       + */
       +typedef enum Hmesg
       +{
       +        Hversion,        /* version */
       +        Hbindname,        /* attach name[0] to text in terminal */
       +        Hcurrent,        /* make named file the typing file */
       +        Hnewname,        /* create "" name in menu */
       +        Hmovname,        /* move file name in menu */
       +        Hgrow,                /* insert space in rasp */
       +        Hcheck0,        /* see below */
       +        Hcheck,                /* ask terminal to check whether it needs more data */
       +        Hunlock,        /* command is finished; user can do things */
       +        Hdata,                /* store this data in previously allocated space */
       +        Horigin,        /* set origin of file/frame in terminal */
       +        Hunlockfile,        /* unlock file in terminal */
       +        Hsetdot,        /* set dot in terminal */
       +        Hgrowdata,        /* Hgrow + Hdata folded together */
       +        Hmoveto,        /* scrolling, context search, etc. */
       +        Hclean,                /* named file is now 'clean' */
       +        Hdirty,                /* named file is now 'dirty' */
       +        Hcut,                /* remove space from rasp */
       +        Hsetpat,        /* set remembered regular expression */
       +        Hdelname,        /* delete file name from menu */
       +        Hclose,                /* close file and remove from menu */
       +        Hsetsnarf,        /* remember string in snarf buffer */
       +        Hsnarflen,        /* report length of implicit snarf */
       +        Hack,                /* request acknowledgement */
       +        Hexit,
       +    Hextcmd,   /* execute a external command */
       +        HMAX
       +}Hmesg;
       +typedef struct Header{
       +        uchar        type;                /* one of the above */
       +        uchar        count0;                /* low bits of data size */
       +        uchar        count1;                /* high bits of data size */
       +        uchar        data[1];        /* variable size */
       +}Header;
       +/*
       + * File transfer protocol schematic, a la Holzmann
       + *        
       + *        proc h
       + *        {        pvar n = 0;
       + *                queue h[4];
       + *        
       + *                do
       + *                :: (n <  N)  -> n++; t!Hgrow
       + *                :: (n == N)  -> n++; t!Hcheck0
       + *                :: h?Trequest -> t!Hdata
       + *                :: h?Tcheck  -> t!Hcheck
       + *                od
       + *        }
       + *        proc t
       + *        {        queue t[4];
       + *                do
       + *                :: t?Hgrow -> h!Trequest
       + *                :: t?Hdata -> skip
       + *                :: t?Hcheck0 -> h!Tcheck
       + *                :: t?Hcheck ->
       + *                        if
       + *                        :: break
       + *                        :: h!Trequest; h!Tcheck
       + *                        fi
       + *                od
       + *        }
       + */
 (DIR) diff --git a/sam/moveto.c b/sam/moveto.c
       @@ -0,0 +1,168 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include "sam.h"
       +
       +void
       +moveto(File *f, Range r)
       +{
       +        Posn p1 = r.p1, p2 = r.p2;
       +
       +        f->dot.r.p1 = p1;
       +        f->dot.r.p2 = p2;
       +        if(f->rasp){
       +                telldot(f);
       +                outTsl(Hmoveto, f->tag, f->dot.r.p1);
       +        }
       +}
       +
       +void
       +telldot(File *f)
       +{
       +        if(f->rasp == 0)
       +                panic("telldot");
       +        if(f->dot.r.p1==f->tdot.p1 && f->dot.r.p2==f->tdot.p2)
       +                return;
       +        outTsll(Hsetdot, f->tag, f->dot.r.p1, f->dot.r.p2);
       +        f->tdot = f->dot.r;
       +}
       +
       +void
       +tellpat(void)
       +{
       +        outTS(Hsetpat, &lastpat);
       +        patset = FALSE;
       +}
       +
       +#define        CHARSHIFT        128
       +
       +void
       +lookorigin(File *f, Posn p0, Posn ls)
       +{
       +        int nl, nc, c;
       +        Posn oldp0;
       +
       +        if(p0 > f->nrunes)
       +                p0 = f->nrunes;
       +        oldp0 = p0;
       +        Fgetcset(f, p0);
       +        for(nl=nc=c=0; c!=-1 && nl<ls && nc<ls*CHARSHIFT; nc++)
       +                if((c=Fbgetc(f)) == '\n'){
       +                        nl++;
       +                        oldp0 = p0-nc;
       +                }
       +        if(c == -1)
       +                p0 = 0;
       +        else if(nl==0){
       +                if(p0>=CHARSHIFT/2)
       +                        p0-=CHARSHIFT/2;
       +                else
       +                        p0 = 0;
       +        }else
       +                p0 = oldp0;
       +        outTsl(Horigin, f->tag, p0);
       +}
       +
       +int
       +alnum(int c)
       +{
       +        /*
       +         * Hard to get absolutely right.  Use what we know about ASCII
       +         * and assume anything above the Latin control characters is
       +         * potentially an alphanumeric.
       +         */
       +        if(c<=' ')
       +                return 0;
       +        if(0x7F<=c && c<=0xA0)
       +                return 0;
       +        if(utfrune("!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~", c))
       +                return 0;
       +        return 1;
       +}
       +
       +int
       +clickmatch(File *f, int cl, int cr, int dir)
       +{
       +        int c;
       +        int nest = 1;
       +
       +        while((c=(dir>0? Fgetc(f) : Fbgetc(f))) > 0)
       +                if(c == cr){
       +                        if(--nest==0)
       +                                return 1;
       +                }else if(c == cl)
       +                        nest++;
       +        return cl=='\n' && nest==1;
       +}
       +
       +Rune*
       +strrune(Rune *s, Rune c)
       +{
       +        Rune c1;
       +
       +        if(c == 0) {
       +                while(*s++)
       +                        ;
       +                return s-1;
       +        }
       +
       +        while(c1 = *s++)
       +                if(c1 == c)
       +                        return s-1;
       +        return 0;
       +}
       +
       +void
       +doubleclick(File *f, Posn p1)
       +{
       +        int c, i;
       +        Rune *r, *l;
       +
       +        if(p1 > f->nrunes)
       +                return;
       +        f->dot.r.p1 = f->dot.r.p2 = p1;
       +        for(i=0; left[i]; i++){
       +                l = left[i];
       +                r = right[i];
       +                /* try left match */
       +                if(p1 == 0){
       +                        Fgetcset(f, p1);
       +                        c = '\n';
       +                }else{
       +                        Fgetcset(f, p1-1);
       +                        c = Fgetc(f);
       +                }
       +                if(c!=-1 && strrune(l, c)){
       +                        if(clickmatch(f, c, r[strrune(l, c)-l], 1)){
       +                                f->dot.r.p1 = p1;
       +                                f->dot.r.p2 = f->getcp-(c!='\n');
       +                        }
       +                        return;
       +                }
       +                /* try right match */
       +                if(p1 == f->nrunes){
       +                        Fbgetcset(f, p1);
       +                        c = '\n';
       +                }else{
       +                        Fbgetcset(f, p1+1);
       +                        c = Fbgetc(f);
       +                }
       +                if(c!=-1 && strrune(r, c)){
       +                        if(clickmatch(f, c, l[strrune(r, c)-r], -1)){
       +                                f->dot.r.p1 = f->getcp;
       +                                if(c!='\n' || f->getcp!=0 ||
       +                                   (Fgetcset(f, (Posn)0),Fgetc(f))=='\n')
       +                                        f->dot.r.p1++;
       +                                f->dot.r.p2 = p1+(p1<f->nrunes && c=='\n');
       +                        }
       +                        return;
       +                }
       +        }
       +        /* try filling out word to right */
       +        Fgetcset(f, p1);
       +        while((c=Fgetc(f))!=-1 && alnum(c))
       +                f->dot.r.p2++;
       +        /* try filling out word to left */
       +        Fbgetcset(f, p1);
       +        while((c=Fbgetc(f))!=-1 && alnum(c))
       +                f->dot.r.p1--;
       +}
       +
 (DIR) diff --git a/sam/multi.c b/sam/multi.c
       @@ -0,0 +1,91 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include "sam.h"
       +
       +List        file;
       +ushort        tag;
       +
       +File *
       +newfile(void)
       +{
       +        File *f;
       +
       +        inslist(&file, 0, (long)(f = Fopen()));
       +        f->tag = tag++;
       +        if(downloaded)
       +                outTs(Hnewname, f->tag);
       +        /* already sorted; file name is "" */
       +        return f;
       +}
       +
       +int
       +whichmenu(File *f)
       +{
       +        int i;
       +
       +        for(i=0; i<file.nused; i++)
       +                if(file.filepptr[i]==f)
       +                        return i;
       +        return -1;
       +}
       +
       +void
       +delfile(File *f)
       +{
       +        int w = whichmenu(f);
       +
       +        if(w < 0)        /* e.g. x/./D */
       +                return;
       +        if(downloaded)
       +                outTs(Hdelname, f->tag);
       +        dellist(&file, w);
       +        Fclose(f);
       +}
       +
       +void
       +sortname(File *f)
       +{
       +        int i, cmp, w;
       +        int dupwarned;
       +
       +        w = whichmenu(f);
       +        dupwarned = FALSE;
       +        dellist(&file, w);
       +        if(f == cmd)
       +                i = 0;
       +        else for(i=0; i<file.nused; i++){
       +                cmp = Strcmp(&f->name, &file.filepptr[i]->name);
       +                if(cmp==0 && !dupwarned){
       +                        dupwarned = TRUE;
       +                        warn_S(Wdupname, &f->name);
       +                }else if(cmp<0 && (i>0 || cmd==0))
       +                        break;
       +        }
       +        inslist(&file, i, (long)f);
       +        if(downloaded)
       +                outTsS(Hmovname, f->tag, &f->name);
       +}
       +
       +void
       +state(File *f, int cleandirty)
       +{
       +        if(f == cmd)
       +                return;
       +        if(downloaded && whichmenu(f)>=0){        /* else flist or menu */
       +                if(f->state==Dirty && cleandirty!=Dirty)
       +                        outTs(Hclean, f->tag);
       +                else if(f->state!=Dirty && cleandirty==Dirty)
       +                        outTs(Hdirty, f->tag);
       +        }
       +        f->state = cleandirty;
       +}
       +
       +File *
       +lookfile(String *s)
       +{
       +        int i;
       +
       +        for(i=0; i<file.nused; i++)
       +                if(Strcmp(&file.filepptr[i]->name, s) == 0)
       +                        return file.filepptr[i];
       +        return 0;
       +}
 (DIR) diff --git a/sam/parse.h b/sam/parse.h
       @@ -0,0 +1,69 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +typedef struct Addr Addr;
       +typedef struct Cmd Cmd;
       +struct Addr
       +{
       +        char        type;        /* # (char addr), l (line addr), / ? . $ + - , ; */
       +        union{
       +                String        *re;
       +                Addr        *aleft;                /* left side of , and ; */
       +        } g;
       +        Posn        num;
       +        Addr        *next;                        /* or right side of , and ; */
       +};
       +
       +#define        are        g.re
       +#define        left        g.aleft
       +
       +struct Cmd
       +{
       +        Addr        *addr;                        /* address (range of text) */
       +        String        *re;                        /* regular expression for e.g. 'x' */
       +        union{
       +                Cmd        *cmd;                /* target of x, g, {, etc. */
       +                String        *text;                /* text of a, c, i; rhs of s */
       +                Addr        *addr;                /* address for m, t */
       +        } g;
       +        Cmd        *next;                        /* pointer to next element in {} */
       +        short        num;
       +        ushort        flag;                        /* whatever */
       +        ushort        cmdc;                        /* command character; 'x' etc. */
       +};
       +
       +#define        ccmd        g.cmd
       +#define        ctext        g.text
       +#define        caddr        g.addr
       +
       +extern struct cmdtab{
       +        ushort        cmdc;                /* command character */
       +        uchar        text;                /* takes a textual argument? */
       +        uchar        regexp;                /* takes a regular expression? */
       +        uchar        addr;                /* takes an address (m or t)? */
       +        uchar        defcmd;                /* default command; 0==>none */
       +        uchar        defaddr;        /* default address */
       +        uchar        count;                /* takes a count e.g. s2/// */
       +        char        *token;                /* takes text terminated by one of these */
       +        int        (*fn)(File*, Cmd*);        /* function to call with parse tree */
       +}cmdtab[];
       +
       +enum Defaddr{        /* default addresses */
       +        aNo,
       +        aDot,
       +        aAll
       +};
       +
       +int        nl_cmd(File*, Cmd*), a_cmd(File*, Cmd*), b_cmd(File*, Cmd*);
       +int        c_cmd(File*, Cmd*), cd_cmd(File*, Cmd*), d_cmd(File*, Cmd*);
       +int        D_cmd(File*, Cmd*), e_cmd(File*, Cmd*);
       +int        f_cmd(File*, Cmd*), g_cmd(File*, Cmd*), i_cmd(File*, Cmd*);
       +int        k_cmd(File*, Cmd*), m_cmd(File*, Cmd*), n_cmd(File*, Cmd*);
       +int        p_cmd(File*, Cmd*), q_cmd(File*, Cmd*);
       +int        s_cmd(File*, Cmd*), u_cmd(File*, Cmd*), w_cmd(File*, Cmd*);
       +int        x_cmd(File*, Cmd*), X_cmd(File*, Cmd*), plan9_cmd(File*, Cmd*);
       +int        eq_cmd(File*, Cmd*);
       +
       +
       +String        *getregexp(int);
       +Addr        *newaddr(void);
       +Address        address(Addr*, Address, int);
       +int        cmdexec(File*, Cmd*);
 (DIR) diff --git a/sam/plan9.c b/sam/plan9.c
       @@ -0,0 +1,174 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include "sam.h"
       +
       +Rune        samname[] = L"~~sam~~";
       +
       +Rune *left[]= {
       +        L"{[(<«",
       +        L"\n",
       +        L"'\"`",
       +        0
       +};
       +Rune *right[]= {
       +        L"}])>»",
       +        L"\n",
       +        L"'\"`",
       +        0
       +};
       +
       +char        RSAM[] = "sam";
       +char        SAMTERM[] = "/bin/aux/samterm";
       +char        HOME[] = "home";
       +char        TMPDIR[] = "/tmp";
       +char        SH[] = "rc";
       +char        SHPATH[] = "/bin/rc";
       +char        RX[] = "rx";
       +char        RXPATH[] = "/bin/rx";
       +char        SAMSAVECMD[] = "/bin/rc\n/sys/lib/samsave";
       +
       +void
       +dprint(char *z, ...)
       +{
       +        char buf[BLOCKSIZE];
       +        va_list arg;
       +
       +        va_start(arg, z);
       +        doprint(buf, &buf[BLOCKSIZE], z, arg);
       +        va_end(arg);
       +        termwrite(buf);
       +}
       +
       +void
       +print_ss(char *s, String *a, String *b)
       +{
       +        dprint("?warning: %s: `%.*S' and `%.*S'\n", s, a->n, a->s, b->n, b->s);
       +}
       +
       +void
       +print_s(char *s, String *a)
       +{
       +        dprint("?warning: %s `%.*S'\n", s, a->n, a->s);
       +}
       +
       +char*
       +getuser(void)
       +{
       +        static char user[NAMELEN];
       +        int fd;
       +
       +        if(user[0] == 0){
       +                fd = open("/dev/user", 0);
       +                if(fd<0 || read(fd, user, sizeof user)<=0)
       +                        strcpy(user, "none");
       +                close(fd);
       +        }
       +        return user;
       +}
       +
       +int
       +statfile(char *name, ulong *dev, ulong *id, long *time, long *length, long *appendonly)
       +{
       +        Dir dirb;
       +
       +        if(dirstat(name, &dirb) == -1)
       +                return -1;
       +        if(dev)
       +                *dev = dirb.type|(dirb.dev<<16);
       +        if(id)
       +                *id = dirb.qid.path;
       +        if(time)
       +                *time = dirb.mtime;
       +        if(length)
       +                *length = dirb.length;
       +        if(appendonly)
       +                *appendonly = dirb.mode & CHAPPEND;
       +        return 1;
       +}
       +
       +int
       +statfd(int fd, ulong *dev, ulong *id, long *time, long *length, long *appendonly)
       +{
       +        Dir dirb;
       +
       +        if(dirfstat(fd, &dirb) == -1)
       +                return -1;
       +        if(dev)
       +                *dev = dirb.type|(dirb.dev<<16);
       +        if(id)
       +                *id = dirb.qid.path;
       +        if(time)
       +                *time = dirb.mtime;
       +        if(length)
       +                *length = dirb.length;
       +        if(appendonly)
       +                *appendonly = dirb.mode & CHAPPEND;
       +        return 1;
       +}
       +
       +void
       +notifyf(void *a, char *s)
       +{
       +        USED(a);
       +        if(bpipeok && strcmp(s, "sys: write on closed pipe") == 0)
       +                noted(NCONT);
       +        if(strcmp(s, "interrupt") == 0)
       +                noted(NCONT);
       +        panicking = 1;
       +        rescue();
       +        noted(NDFLT);
       +}
       +
       +int
       +newtmp(int num)
       +{
       +        int i, fd;
       +        static char        tempnam[30];
       +
       +        i = getpid();
       +        do
       +                sprint(tempnam, "%s/%d%.4s%dsam", TMPDIR, num, getuser(), i++);
       +        while(access(tempnam, 0) == 0);
       +        fd = create(tempnam, ORDWR|OCEXEC|ORCLOSE, 0000);
       +        if(fd < 0){
       +                remove(tempnam);
       +                fd = create(tempnam, ORDWR|OCEXEC|ORCLOSE, 0000);
       +        }
       +        return fd;
       +}
       +
       +int
       +waitfor(int pid)
       +{
       +        int rpid;
       +        Waitmsg wm;
       +
       +        do; while((rpid = wait(&wm)) != pid && rpid != -1);
       +        return wm.msg[0];
       +}
       +
       +void
       +samerr(char *buf)
       +{
       +        sprint(buf, "%s/sam.err", TMPDIR);
       +}
       +
       +void*
       +emalloc(ulong n)
       +{
       +        void *p;
       +
       +        p = malloc(n);
       +        if(p == 0)
       +                panic("malloc fails");
       +        memset(p, 0, n);
       +        return p;
       +}
       +
       +void*
       +erealloc(void *p, ulong n)
       +{
       +        p = realloc(p, n);
       +        if(p == 0)
       +                panic("realloc fails");
       +        return p;
       +}
 (DIR) diff --git a/sam/rasp.c b/sam/rasp.c
       @@ -0,0 +1,276 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include "sam.h"
       +/*
       + * GROWDATASIZE must be big enough that all errors go out as Hgrowdata's,
       + * so they will be scrolled into visibility in the ~~sam~~ window (yuck!).
       + */
       +#define        GROWDATASIZE        50        /* if size is > this, send data with grow */
       +
       +void        rcut(List*, Posn, Posn);
       +int        rterm(List*, Posn);
       +void        rgrow(List*, Posn, Posn);
       +
       +void
       +toterminal(File *f, int toterm)
       +{
       +        Buffer *t = f->transcript;
       +        Posn n, p0, p1, p2, delta = 0, deltacmd = 0;
       +        Range r;
       +        union{
       +                union        Hdr g;
       +                Rune        buf[8+GROWDATASIZE];
       +        }hdr;
       +        Posn growpos, grown;
       +
       +        if(f->rasp == 0)
       +                return;
       +        if(f->marked)
       +                p0 = f->markp+sizeof(Mark)/RUNESIZE;
       +        else
       +                p0 = 0;
       +        grown = 0;
       +        noflush = 1;
       +        SET(growpos);
       +        while(Bread(t, (Rune*)&hdr, sizeof(hdr)/RUNESIZE, p0) > 0){
       +                switch(hdr.g.cs.c){
       +                default:
       +                        fprint(2, "char %c %.2x\n", hdr.g.cs.c, hdr.g.cs.c);
       +                        panic("unknown in toterminal");
       +
       +                case 'd':
       +                        if(grown){
       +                                outTsll(Hgrow, f->tag, growpos, grown);
       +                                grown = 0;
       +                        }
       +                        p1 = hdr.g.cll.l;
       +                        p2 = hdr.g.cll.l1;
       +                        if(p2 <= p1)
       +                                panic("toterminal delete 0");
       +                        if(f==cmd && p1<cmdpt){
       +                                if(p2 <= cmdpt)
       +                                        deltacmd -= (p2-p1);
       +                                else
       +                                        deltacmd -= cmdpt-p1;
       +                        }
       +                        p1 += delta;
       +                        p2 += delta;
       +                        p0 += sizeof(struct _cll)/RUNESIZE;
       +                        if(toterm)
       +                                outTsll(Hcut, f->tag, p1, p2-p1);
       +                        rcut(f->rasp, p1, p2);
       +                        delta -= p2-p1;
       +                        break;
       +
       +                case 'f':
       +                        if(grown){
       +                                outTsll(Hgrow, f->tag, growpos, grown);
       +                                grown = 0;
       +                        }
       +                        n = hdr.g.cs.s;
       +                        p0 += sizeof(struct _cs)/RUNESIZE + n;
       +                        break;
       +
       +                case 'i':
       +                        n = hdr.g.csl.s;
       +                        p1 = hdr.g.csl.l;
       +                        p0 += sizeof(struct _csl)/RUNESIZE + n;
       +                        if(n <= 0)
       +                                panic("toterminal insert 0");
       +                        if(f==cmd && p1<cmdpt)
       +                                deltacmd += n;
       +                        p1 += delta;
       +                        if(toterm){
       +                                if(n>GROWDATASIZE || !rterm(f->rasp, p1)){
       +                                        rgrow(f->rasp, p1, n);
       +                                        if(grown && growpos+grown!=p1){
       +                                                outTsll(Hgrow, f->tag, growpos, grown);
       +                                                grown = 0;
       +                                        }
       +                                        if(grown)
       +                                                grown += n;
       +                                        else{
       +                                                growpos = p1;
       +                                                grown = n;
       +                                        }
       +                                }else{
       +                                        Rune *rp;
       +                                        if(grown){
       +                                                outTsll(Hgrow, f->tag, growpos, grown);
       +                                                grown = 0;
       +                                        }
       +                                        rp = hdr.buf+sizeof(hdr.g.csl)/RUNESIZE;
       +                                        rgrow(f->rasp, p1, n);
       +                                        r = rdata(f->rasp, p1, n);
       +                                        if(r.p1!=p1 || r.p2!=p1+n)
       +                                                panic("rdata in toterminal");
       +                                        outTsllS(Hgrowdata, f->tag, p1, n, tmprstr(rp, n));
       +                                }
       +                        }else{
       +                                rgrow(f->rasp, p1, n);
       +                                r = rdata(f->rasp, p1, n);
       +                                if(r.p1!=p1 || r.p2!=p1+n)
       +                                        panic("rdata in toterminal");
       +                        }
       +                        delta += n;
       +                        break;
       +                }
       +        }
       +        if(grown)
       +                outTsll(Hgrow, f->tag, growpos, grown);
       +        if(toterm)
       +                outTs(Hcheck0, f->tag);
       +        outflush();
       +        noflush = 0;
       +        if(f == cmd){
       +                cmdpt += deltacmd+cmdptadv;
       +                cmdptadv = 0;
       +        }
       +}
       +
       +#define        M        0x80000000L
       +#define        P(i)        r->longptr[i]
       +#define        T(i)        (P(i)&M)        /* in terminal */
       +#define        L(i)        (P(i)&~M)        /* length of this piece */
       +
       +void
       +rcut(List *r, Posn p1, Posn p2)
       +{
       +        Posn p, x;
       +        int i;
       +
       +        if(p1 == p2)
       +                panic("rcut 0");
       +        for(p=0,i=0; i<r->nused && p+L(i)<=p1; p+=L(i++))
       +                ;
       +        if(i == r->nused)
       +                panic("rcut 1");
       +        if(p<p1){        /* chop this piece */
       +                if(p+L(i) < p2){
       +                        x = p1-p;
       +                        p += L(i);
       +                }else{
       +                        x = L(i)-(p2-p1);
       +                        p = p2;
       +                }
       +                if(T(i))
       +                        P(i) = x|M;
       +                else
       +                        P(i) = x;
       +                i++;
       +        }
       +        while(i<r->nused && p+L(i)<=p2){
       +                p += L(i);
       +                dellist(r, i);
       +        }
       +        if(p<p2){
       +                if(i == r->nused)
       +                        panic("rcut 2");
       +                x = L(i)-(p2-p);
       +                if(T(i))
       +                        P(i) = x|M;
       +                else
       +                        P(i) = x;
       +        }
       +        /* can we merge i and i-1 ? */
       +        if(i>0 && i<r->nused && T(i-1)==T(i)){
       +                x = L(i-1)+L(i);
       +                dellist(r, i--);
       +                if(T(i))
       +                        P(i)=x|M;
       +                else
       +                        P(i)=x;
       +        }
       +}
       +
       +void
       +rgrow(List *r, Posn p1, Posn n)
       +{
       +        Posn p;
       +        int i;
       +
       +        if(n == 0)
       +                panic("rgrow 0");
       +        for(p=0,i=0; i<r->nused && p+L(i)<=p1; p+=L(i++))
       +                ;
       +        if(i == r->nused){        /* stick on end of file */
       +                if(p!=p1)
       +                        panic("rgrow 1");
       +                if(i>0 && !T(i-1))
       +                        P(i-1)+=n;
       +                else
       +                        inslist(r, i, n);
       +        }else if(!T(i))                /* goes in this empty piece */
       +                P(i)+=n;
       +        else if(p==p1 && i>0 && !T(i-1))        /* special case; simplifies life */
       +                P(i-1)+=n;
       +        else if(p==p1)
       +                inslist(r, i, n);
       +        else{                        /* must break piece in terminal */
       +                inslist(r, i+1, (L(i)-(p1-p))|M);
       +                inslist(r, i+1, n);
       +                P(i) = (p1-p)|M;
       +        }
       +}
       +
       +int
       +rterm(List *r, Posn p1)
       +{
       +        Posn p;
       +        int i;
       +
       +        for(p = 0,i = 0; i<r->nused && p+L(i)<=p1; p+=L(i++))
       +                ;
       +        if(i==r->nused && (i==0 || !T(i-1)))
       +                return 0;
       +        return T(i);
       +}
       +
       +Range
       +rdata(List *r, Posn p1, Posn n)
       +{
       +        Posn p;
       +        int i;
       +        Range rg;
       +
       +        if(n==0)
       +                panic("rdata 0");
       +        for(p = 0,i = 0; i<r->nused && p+L(i)<=p1; p+=L(i++))
       +                ;
       +        if(i==r->nused)
       +                panic("rdata 1");
       +        if(T(i)){
       +                n-=L(i)-(p1-p);
       +                if(n<=0){
       +                        rg.p1 = rg.p2 = p1;
       +                        return rg;
       +                }
       +                p+=L(i++);
       +                p1 = p;
       +        }
       +        if(T(i) || i==r->nused)
       +                panic("rdata 2");
       +        if(p+L(i)<p1+n)
       +                n = L(i)-(p1-p);
       +        rg.p1 = p1;
       +        rg.p2 = p1+n;
       +        if(p!=p1){
       +                inslist(r, i+1, L(i)-(p1-p));
       +                P(i)=p1-p;
       +                i++;
       +        }
       +        if(L(i)!=n){
       +                inslist(r, i+1, L(i)-n);
       +                P(i)=n;
       +        }
       +        P(i)|=M;
       +        /* now i is set; can we merge? */
       +        if(i<r->nused-1 && T(i+1)){
       +                P(i)=(n+=L(i+1))|M;
       +                dellist(r, i+1);
       +        }
       +        if(i>0 && T(i-1)){
       +                P(i)=(n+L(i-1))|M;
       +                dellist(r, i-1);
       +        }
       +        return rg;
       +}
 (DIR) diff --git a/sam/regexp.c b/sam/regexp.c
       @@ -0,0 +1,820 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include "sam.h"
       +
       +Rangeset        sel;
       +String                lastregexp;
       +/*
       + * Machine Information
       + */
       +typedef struct Inst Inst;
       +
       +struct Inst
       +{
       +        long        type;        /* < 0x10000 ==> literal, otherwise action */
       +        union {
       +                int rsid;
       +                int rsubid;
       +                int class;
       +                struct Inst *rother;
       +                struct Inst *rright;
       +        } r;
       +        union{
       +                struct Inst *lleft;
       +                struct Inst *lnext;
       +        } l;
       +};
       +#define        sid        r.rsid
       +#define        subid        r.rsubid
       +#define        rclass        r.class
       +#define        other        r.rother
       +#define        right        r.rright
       +#define        left        l.lleft
       +#define        next        l.lnext
       +
       +#define        NPROG        1024
       +Inst        program[NPROG];
       +Inst        *progp;
       +Inst        *startinst;        /* First inst. of program; might not be program[0] */
       +Inst        *bstartinst;        /* same for backwards machine */
       +
       +typedef struct Ilist Ilist;
       +struct Ilist
       +{
       +        Inst        *inst;                /* Instruction of the thread */
       +        Rangeset se;
       +        Posn        startp;                /* first char of match */
       +};
       +
       +#define        NLIST        128
       +
       +Ilist        *tl, *nl;        /* This list, next list */
       +Ilist        list[2][NLIST];
       +static        Rangeset sempty;
       +
       +/*
       + * Actions and Tokens
       + *
       + *        0x100xx are operators, value == precedence
       + *        0x200xx are tokens, i.e. operands for operators
       + */
       +#define        OPERATOR        0x10000        /* Bitmask of all operators */
       +#define        START                0x10000        /* Start, used for marker on stack */
       +#define        RBRA                0x10001        /* Right bracket, ) */
       +#define        LBRA                0x10002        /* Left bracket, ( */
       +#define        OR                0x10003        /* Alternation, | */
       +#define        CAT                0x10004        /* Concatentation, implicit operator */
       +#define        STAR                0x10005        /* Closure, * */
       +#define        PLUS                0x10006        /* a+ == aa* */
       +#define        QUEST                0x10007        /* a? == a|nothing, i.e. 0 or 1 a's */
       +#define        ANY                0x20000        /* Any character but newline, . */
       +#define        NOP                0x20001        /* No operation, internal use only */
       +#define        BOL                0x20002        /* Beginning of line, ^ */
       +#define        EOL                0x20003        /* End of line, $ */
       +#define        CCLASS                0x20004        /* Character class, [] */
       +#define        NCCLASS                0x20005        /* Negated character class, [^] */
       +#define        END                0x20077        /* Terminate: match found */
       +
       +#define        ISATOR                0x10000
       +#define        ISAND                0x20000
       +
       +/*
       + * Parser Information
       + */
       +typedef struct Node Node;
       +struct Node
       +{
       +        Inst        *first;
       +        Inst        *last;
       +};
       +
       +#define        NSTACK        20
       +Node        andstack[NSTACK];
       +Node        *andp;
       +int        atorstack[NSTACK];
       +int        *atorp;
       +int        lastwasand;        /* Last token was operand */
       +int        cursubid;
       +int        subidstack[NSTACK];
       +int        *subidp;
       +int        backwards;
       +int        nbra;
       +Rune        *exprp;                /* pointer to next character in source expression */
       +#define        DCLASS        10        /* allocation increment */
       +int        nclass;                /* number active */
       +int        Nclass;                /* high water mark */
       +Rune        **class;
       +int        negateclass;
       +
       +void        addinst(Ilist *l, Inst *inst, Rangeset *sep);
       +void        newmatch(Rangeset*);
       +void        bnewmatch(Rangeset*);
       +void        pushand(Inst*, Inst*);
       +void        pushator(int);
       +Node        *popand(int);
       +int        popator(void);
       +void        startlex(Rune*);
       +int        lex(void);
       +void        operator(int);
       +void        operand(int);
       +void        evaluntil(int);
       +void        optimize(Inst*);
       +void        bldcclass(void);
       +
       +void
       +regerror(Err e)
       +{
       +        Strzero(&lastregexp);
       +        error(e);
       +}
       +
       +void
       +regerror_c(Err e, int c)
       +{
       +        Strzero(&lastregexp);
       +        error_c(e, c);
       +}
       +
       +Inst *
       +newinst(int t)
       +{
       +        if(progp >= &program[NPROG])
       +                regerror(Etoolong);
       +        progp->type = t;
       +        progp->left = 0;
       +        progp->right = 0;
       +        return progp++;
       +}
       +
       +Inst *
       +realcompile(Rune *s)
       +{
       +        int token;
       +
       +        startlex(s);
       +        atorp = atorstack;
       +        andp = andstack;
       +        subidp = subidstack;
       +        cursubid = 0;
       +        lastwasand = FALSE;
       +        /* Start with a low priority operator to prime parser */
       +        pushator(START-1);
       +        while((token=lex()) != END){
       +                if((token&ISATOR) == OPERATOR)
       +                        operator(token);
       +                else
       +                        operand(token);
       +        }
       +        /* Close with a low priority operator */
       +        evaluntil(START);
       +        /* Force END */
       +        operand(END);
       +        evaluntil(START);
       +        if(nbra)
       +                regerror(Eleftpar);
       +        --andp;        /* points to first and only operand */
       +        return andp->first;
       +}
       +
       +void
       +compile(String *s)
       +{
       +        int i;
       +        Inst *oprogp;
       +
       +        if(Strcmp(s, &lastregexp)==0)
       +                return;
       +        for(i=0; i<nclass; i++)
       +                free(class[i]);
       +        nclass = 0;
       +        progp = program;
       +        backwards = FALSE;
       +        startinst = realcompile(s->s);
       +        optimize(program);
       +        oprogp = progp;
       +        backwards = TRUE;
       +        bstartinst = realcompile(s->s);
       +        optimize(oprogp);
       +        Strduplstr(&lastregexp, s);
       +}
       +
       +void
       +operand(int t)
       +{
       +        Inst *i;
       +        if(lastwasand)
       +                operator(CAT);        /* catenate is implicit */
       +        i = newinst(t);
       +        if(t == CCLASS){
       +                if(negateclass)
       +                        i->type = NCCLASS;        /* UGH */
       +                i->rclass = nclass-1;                /* UGH */
       +        }
       +        pushand(i, i);
       +        lastwasand = TRUE;
       +}
       +
       +void
       +operator(int t)
       +{
       +        if(t==RBRA && --nbra<0)
       +                regerror(Erightpar);
       +        if(t==LBRA){
       +/*
       + *                if(++cursubid >= NSUBEXP)
       + *                        regerror(Esubexp);
       + */
       +                cursubid++;        /* silently ignored */
       +                nbra++;
       +                if(lastwasand)
       +                        operator(CAT);
       +        }else
       +                evaluntil(t);
       +        if(t!=RBRA)
       +                pushator(t);
       +        lastwasand = FALSE;
       +        if(t==STAR || t==QUEST || t==PLUS || t==RBRA)
       +                lastwasand = TRUE;        /* these look like operands */
       +}
       +
       +void
       +cant(char *s)
       +{
       +        char buf[100];
       +
       +        sprint(buf, "regexp: can't happen: %s", s);
       +        panic(buf);
       +}
       +
       +void
       +pushand(Inst *f, Inst *l)
       +{
       +        if(andp >= &andstack[NSTACK])
       +                cant("operand stack overflow");
       +        andp->first = f;
       +        andp->last = l;
       +        andp++;
       +}
       +
       +void
       +pushator(int t)
       +{
       +        if(atorp >= &atorstack[NSTACK])
       +                cant("operator stack overflow");
       +        *atorp++=t;
       +        if(cursubid >= NSUBEXP)
       +                *subidp++= -1;
       +        else
       +                *subidp++=cursubid;
       +}
       +
       +Node *
       +popand(int op)
       +{
       +        if(andp <= &andstack[0])
       +                if(op)
       +                        regerror_c(Emissop, op);
       +                else
       +                        regerror(Ebadregexp);
       +        return --andp;
       +}
       +
       +int
       +popator(void)
       +{
       +        if(atorp <= &atorstack[0])
       +                cant("operator stack underflow");
       +        --subidp;
       +        return *--atorp;
       +}
       +
       +void
       +evaluntil(int pri)
       +{
       +        Node *op1, *op2, *t;
       +        Inst *inst1, *inst2;
       +
       +        while(pri==RBRA || atorp[-1]>=pri){
       +                switch(popator()){
       +                case LBRA:
       +                        op1 = popand('(');
       +                        inst2 = newinst(RBRA);
       +                        inst2->subid = *subidp;
       +                        op1->last->next = inst2;
       +                        inst1 = newinst(LBRA);
       +                        inst1->subid = *subidp;
       +                        inst1->next = op1->first;
       +                        pushand(inst1, inst2);
       +                        return;                /* must have been RBRA */
       +                default:
       +                        panic("unknown regexp operator");
       +                        break;
       +                case OR:
       +                        op2 = popand('|');
       +                        op1 = popand('|');
       +                        inst2 = newinst(NOP);
       +                        op2->last->next = inst2;
       +                        op1->last->next = inst2;
       +                        inst1 = newinst(OR);
       +                        inst1->right = op1->first;
       +                        inst1->left = op2->first;
       +                        pushand(inst1, inst2);
       +                        break;
       +                case CAT:
       +                        op2 = popand(0);
       +                        op1 = popand(0);
       +                        if(backwards && op2->first->type!=END)
       +                                t = op1, op1 = op2, op2 = t;
       +                        op1->last->next = op2->first;
       +                        pushand(op1->first, op2->last);
       +                        break;
       +                case STAR:
       +                        op2 = popand('*');
       +                        inst1 = newinst(OR);
       +                        op2->last->next = inst1;
       +                        inst1->right = op2->first;
       +                        pushand(inst1, inst1);
       +                        break;
       +                case PLUS:
       +                        op2 = popand('+');
       +                        inst1 = newinst(OR);
       +                        op2->last->next = inst1;
       +                        inst1->right = op2->first;
       +                        pushand(op2->first, inst1);
       +                        break;
       +                case QUEST:
       +                        op2 = popand('?');
       +                        inst1 = newinst(OR);
       +                        inst2 = newinst(NOP);
       +                        inst1->left = inst2;
       +                        inst1->right = op2->first;
       +                        op2->last->next = inst2;
       +                        pushand(inst1, inst2);
       +                        break;
       +                }
       +        }
       +}
       +
       +
       +void
       +optimize(Inst *start)
       +{
       +        Inst *inst, *target;
       +
       +        for(inst=start; inst->type!=END; inst++){
       +                target = inst->next;
       +                while(target->type == NOP)
       +                        target = target->next;
       +                inst->next = target;
       +        }
       +}
       +
       +#ifdef        DEBUG
       +void
       +dumpstack(void){
       +        Node *stk;
       +        int *ip;
       +
       +        dprint("operators\n");
       +        for(ip = atorstack; ip<atorp; ip++)
       +                dprint("0%o\n", *ip);
       +        dprint("operands\n");
       +        for(stk = andstack; stk<andp; stk++)
       +                dprint("0%o\t0%o\n", stk->first->type, stk->last->type);
       +}
       +void
       +dump(void){
       +        Inst *l;
       +
       +        l = program;
       +        do{
       +                dprint("%d:\t0%o\t%d\t%d\n", l-program, l->type,
       +                        l->left-program, l->right-program);
       +        }while(l++->type);
       +}
       +#endif
       +
       +void
       +startlex(Rune *s)
       +{
       +        exprp = s;
       +        nbra = 0;
       +}
       +
       +
       +int
       +lex(void){
       +        int c= *exprp++;
       +
       +        switch(c){
       +        case '\\':
       +                if(*exprp)
       +                        if((c= *exprp++)=='n')
       +                                c='\n';
       +                break;
       +        case 0:
       +                c = END;
       +                --exprp;        /* In case we come here again */
       +                break;
       +        case '*':
       +                c = STAR;
       +                break;
       +        case '?':
       +                c = QUEST;
       +                break;
       +        case '+':
       +                c = PLUS;
       +                break;
       +        case '|':
       +                c = OR;
       +                break;
       +        case '.':
       +                c = ANY;
       +                break;
       +        case '(':
       +                c = LBRA;
       +                break;
       +        case ')':
       +                c = RBRA;
       +                break;
       +        case '^':
       +                c = BOL;
       +                break;
       +        case '$':
       +                c = EOL;
       +                break;
       +        case '[':
       +                c = CCLASS;
       +                bldcclass();
       +                break;
       +        }
       +        return c;
       +}
       +
       +long
       +nextrec(void){
       +        if(exprp[0]==0 || (exprp[0]=='\\' && exprp[1]==0))
       +                regerror(Ebadclass);
       +        if(exprp[0] == '\\'){
       +                exprp++;
       +                if(*exprp=='n'){
       +                        exprp++;
       +                        return '\n';
       +                }
       +                return *exprp++|0x10000;
       +        }
       +        return *exprp++;
       +}
       +
       +void
       +bldcclass(void)
       +{
       +        long c1, c2, n, na;
       +        Rune *classp;
       +
       +        classp = emalloc(DCLASS*RUNESIZE);
       +        n = 0;
       +        na = DCLASS;
       +        /* we have already seen the '[' */
       +        if(*exprp == '^'){
       +                classp[n++] = '\n';        /* don't match newline in negate case */
       +                negateclass = TRUE;
       +                exprp++;
       +        }else
       +                negateclass = FALSE;
       +        while((c1 = nextrec()) != ']'){
       +                if(c1 == '-'){
       +    Error:
       +                        free(classp);
       +                        regerror(Ebadclass);
       +                }
       +                if(n+4 >= na){                /* 3 runes plus NUL */
       +                        na += DCLASS;
       +                        classp = erealloc(classp, na*RUNESIZE);
       +                }
       +                if(*exprp == '-'){
       +                        exprp++;        /* eat '-' */
       +                        if((c2 = nextrec()) == ']')
       +                                goto Error;
       +                        classp[n+0] = 0xFFFF;
       +                        classp[n+1] = c1;
       +                        classp[n+2] = c2;
       +                        n += 3;
       +                }else
       +                        classp[n++] = c1;
       +        }
       +        classp[n] = 0;
       +        if(nclass == Nclass){
       +                Nclass += DCLASS;
       +                class = erealloc(class, Nclass*sizeof(Rune*));
       +        }
       +        class[nclass++] = classp;
       +}
       +
       +int
       +classmatch(int classno, int c, int negate)
       +{
       +        Rune *p;
       +
       +        p = class[classno];
       +        while(*p){
       +                if(*p == 0xFFFF){
       +                        if(p[1]<=c && c<=p[2])
       +                                return !negate;
       +                        p += 3;
       +                }else if(*p++ == c)
       +                        return !negate;
       +        }
       +        return negate;
       +}
       +
       +/*
       + * Note optimization in addinst:
       + *         *l must be pending when addinst called; if *l has been looked
       + *                at already, the optimization is a bug.
       + */
       +void
       +addinst(Ilist *l, Inst *inst, Rangeset *sep)
       +{
       +        Ilist *p;
       +
       +        for(p = l; p->inst; p++){
       +                if(p->inst==inst){
       +                        if((sep)->p[0].p1 < p->se.p[0].p1)
       +                                p->se= *sep;        /* this would be bug */
       +                        return;        /* It's already there */
       +                }
       +        }
       +        p->inst = inst;
       +        p->se= *sep;
       +        (p+1)->inst = 0;
       +}
       +
       +int
       +execute(File *f, Posn startp, Posn eof)
       +{
       +        int flag = 0;
       +        Inst *inst;
       +        Ilist *tlp;
       +        Posn p = startp;
       +        int nnl = 0, ntl;
       +        int c;
       +        int wrapped = 0;
       +        int startchar = startinst->type<OPERATOR? startinst->type : 0;
       +
       +        list[0][0].inst = list[1][0].inst = 0;
       +        sel.p[0].p1 = -1;
       +        Fgetcset(f, startp);
       +        /* Execute machine once for each character */
       +        for(;;p++){
       +        doloop:
       +                c = Fgetc(f);
       +                if(p>=eof || c<0){
       +                        switch(wrapped++){
       +                        case 0:                /* let loop run one more click */
       +                        case 2:
       +                                break;
       +                        case 1:                /* expired; wrap to beginning */
       +                                if(sel.p[0].p1>=0 || eof!=INFINITY)
       +                                        goto Return;
       +                                list[0][0].inst = list[1][0].inst = 0;
       +                                Fgetcset(f, (Posn)0);
       +                                p = 0;
       +                                goto doloop;
       +                        default:
       +                                goto Return;
       +                        }
       +                }else if(((wrapped && p>=startp) || sel.p[0].p1>0) && nnl==0)
       +                        break;
       +                /* fast check for first char */
       +                if(startchar && nnl==0 && c!=startchar)
       +                        continue;
       +                tl = list[flag];
       +                nl = list[flag^=1];
       +                nl->inst = 0;
       +                ntl = nnl;
       +                nnl = 0;
       +                if(sel.p[0].p1<0 && (!wrapped || p<startp || startp==eof)){
       +                        /* Add first instruction to this list */
       +                        if(++ntl >= NLIST)
       +        Overflow:
       +                                error(Eoverflow);
       +                        sempty.p[0].p1 = p;
       +                        addinst(tl, startinst, &sempty);
       +                }
       +                /* Execute machine until this list is empty */
       +                for(tlp = tl; inst = tlp->inst; tlp++){        /* assignment = */
       +        Switchstmt:
       +                        switch(inst->type){
       +                        default:        /* regular character */
       +                                if(inst->type==c){
       +        Addinst:
       +                                        if(++nnl >= NLIST)
       +                                                goto Overflow;
       +                                        addinst(nl, inst->next, &tlp->se);
       +                                }
       +                                break;
       +                        case LBRA:
       +                                if(inst->subid>=0)
       +                                        tlp->se.p[inst->subid].p1 = p;
       +                                inst = inst->next;
       +                                goto Switchstmt;
       +                        case RBRA:
       +                                if(inst->subid>=0)
       +                                        tlp->se.p[inst->subid].p2 = p;
       +                                inst = inst->next;
       +                                goto Switchstmt;
       +                        case ANY:
       +                                if(c!='\n')
       +                                        goto Addinst;
       +                                break;
       +                        case BOL:
       +                                if(p == 0){
       +        Step:
       +                                        inst = inst->next;
       +                                        goto Switchstmt;
       +                                }
       +                                if(f->getci > 1){
       +                                        if(f->getcbuf[f->getci-2]=='\n')
       +                                                goto Step;
       +                                }else{
       +                                        Rune c;
       +                                        if(Fchars(f, &c, p-1, p)==1 && c=='\n')
       +                                                goto Step;
       +                                }
       +                                break;
       +                        case EOL:
       +                                if(c == '\n')
       +                                        goto Step;
       +                                break;
       +                        case CCLASS:
       +                                if(c>=0 && classmatch(inst->rclass, c, 0))
       +                                        goto Addinst;
       +                                break;
       +                        case NCCLASS:
       +                                if(c>=0 && classmatch(inst->rclass, c, 1))
       +                                        goto Addinst;
       +                                break;
       +                        case OR:
       +                                /* evaluate right choice later */
       +                                if(++ntl >= NLIST)
       +                                        goto Overflow;
       +                                addinst(tlp, inst->right, &tlp->se);
       +                                /* efficiency: advance and re-evaluate */
       +                                inst = inst->left;
       +                                goto Switchstmt;
       +                        case END:        /* Match! */
       +                                tlp->se.p[0].p2 = p;
       +                                newmatch(&tlp->se);
       +                                break;
       +                        }
       +                }
       +        }
       +    Return:
       +        return sel.p[0].p1>=0;
       +}
       +
       +void
       +newmatch(Rangeset *sp)
       +{
       +        int i;
       +
       +        if(sel.p[0].p1<0 || sp->p[0].p1<sel.p[0].p1 ||
       +           (sp->p[0].p1==sel.p[0].p1 && sp->p[0].p2>sel.p[0].p2))
       +                for(i = 0; i<NSUBEXP; i++)
       +                        sel.p[i] = sp->p[i];
       +}
       +
       +int
       +bexecute(File *f, Posn startp)
       +{
       +        int flag = 0;
       +        Inst *inst;
       +        Ilist *tlp;
       +        Posn p = startp;
       +        int nnl = 0, ntl;
       +        int c;
       +        int wrapped = 0;
       +        int startchar = bstartinst->type<OPERATOR? bstartinst->type : 0;
       +
       +        list[0][0].inst = list[1][0].inst = 0;
       +        sel.p[0].p1= -1;
       +        Fgetcset(f, startp);
       +        /* Execute machine once for each character, including terminal NUL */
       +        for(;;--p){
       +        doloop:
       +                if((c = Fbgetc(f))==-1){
       +                        switch(wrapped++){
       +                        case 0:                /* let loop run one more click */
       +                        case 2:
       +                                break;
       +                        case 1:                /* expired; wrap to end */
       +                                if(sel.p[0].p1>=0)
       +                        case 3:
       +                                        goto Return;
       +                                list[0][0].inst = list[1][0].inst = 0;
       +                                Fgetcset(f, f->nrunes);
       +                                p = f->nrunes;
       +                                goto doloop;
       +                        default:
       +                                goto Return;
       +                        }
       +                }else if(((wrapped && p<=startp) || sel.p[0].p1>0) && nnl==0)
       +                        break;
       +                /* fast check for first char */
       +                if(startchar && nnl==0 && c!=startchar)
       +                        continue;
       +                tl = list[flag];
       +                nl = list[flag^=1];
       +                nl->inst = 0;
       +                ntl = nnl;
       +                nnl = 0;
       +                if(sel.p[0].p1<0 && (!wrapped || p>startp)){
       +                        /* Add first instruction to this list */
       +                        if(++ntl >= NLIST)
       +        Overflow:
       +                                error(Eoverflow);
       +                        /* the minus is so the optimizations in addinst work */
       +                        sempty.p[0].p1 = -p;
       +                        addinst(tl, bstartinst, &sempty);
       +                }
       +                /* Execute machine until this list is empty */
       +                for(tlp = tl; inst = tlp->inst; tlp++){        /* assignment = */
       +        Switchstmt:
       +                        switch(inst->type){
       +                        default:        /* regular character */
       +                                if(inst->type == c){
       +        Addinst:
       +                                        if(++nnl >= NLIST)
       +                                                goto Overflow;
       +                                        addinst(nl, inst->next, &tlp->se);
       +                                }
       +                                break;
       +                        case LBRA:
       +                                if(inst->subid>=0)
       +                                        tlp->se.p[inst->subid].p1 = p;
       +                                inst = inst->next;
       +                                goto Switchstmt;
       +                        case RBRA:
       +                                if(inst->subid >= 0)
       +                                        tlp->se.p[inst->subid].p2 = p;
       +                                inst = inst->next;
       +                                goto Switchstmt;
       +                        case ANY:
       +                                if(c != '\n')
       +                                        goto Addinst;
       +                                break;
       +                        case BOL:
       +                                if(c=='\n' || p==0){
       +        Step:
       +                                        inst = inst->next;
       +                                        goto Switchstmt;
       +                                }
       +                                break;
       +                        case EOL:
       +                                if(f->getci<f->ngetc-1){
       +                                        if(f->getcbuf[f->getci+1]=='\n')
       +                                                goto Step;
       +                                }else if(p<f->nrunes-1){
       +                                        Rune c;
       +                                        if(Fchars(f, &c, p, p+1)==1 && c=='\n')
       +                                                goto Step;
       +                                }
       +                                break;
       +                        case CCLASS:
       +                                if(c>=0 && classmatch(inst->rclass, c, 0))
       +                                        goto Addinst;
       +                                break;
       +                        case NCCLASS:
       +                                if(c>=0 && classmatch(inst->rclass, c, 1))
       +                                        goto Addinst;
       +                                break;
       +                        case OR:
       +                                /* evaluate right choice later */
       +                                if(++ntl >= NLIST)
       +                                        goto Overflow;
       +                                addinst(tlp, inst->right, &tlp->se);
       +                                /* efficiency: advance and re-evaluate */
       +                                inst = inst->left;
       +                                goto Switchstmt;
       +                        case END:        /* Match! */
       +                                tlp->se.p[0].p1 = -tlp->se.p[0].p1; /* minus sign */
       +                                tlp->se.p[0].p2 = p;
       +                                bnewmatch(&tlp->se);
       +                                break;
       +                        }
       +                }
       +        }
       +    Return:
       +        return sel.p[0].p1>=0;
       +}
       +
       +void
       +bnewmatch(Rangeset *sp)
       +{
       +        int  i;
       +        if(sel.p[0].p1<0 || sp->p[0].p1>sel.p[0].p2 || (sp->p[0].p1==sel.p[0].p2 && sp->p[0].p2<sel.p[0].p1))
       +                for(i = 0; i<NSUBEXP; i++){       /* note the reversal; p1<=p2 */
       +                        sel.p[i].p1 = sp->p[i].p2;
       +                        sel.p[i].p2 = sp->p[i].p1;
       +                }
       +}
 (DIR) diff --git a/sam/sam.c b/sam/sam.c
       @@ -0,0 +1,682 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include "sam.h"
       +
       +Rune        genbuf[BLOCKSIZE];
       +int        io;
       +int        panicking;
       +int        rescuing;
       +Mod        modnum;
       +String        genstr;
       +String        rhs;
       +String        wd;
       +String        cmdstr;
       +Rune        empty[] = { 0 };
       +char        *genc;
       +File        *curfile;
       +File        *flist;
       +File        *cmd;
       +jmp_buf        mainloop;
       +List tempfile;
       +int        quitok = TRUE;
       +int        downloaded;
       +int        dflag;
       +int        Rflag;
       +char        *machine;
       +char        *home;
       +int        bpipeok;
       +int        termlocked;
       +char        *samterm = SAMTERM;
       +char        *rsamname = RSAM;
       +
       +Rune        baddir[] = { '<', 'b', 'a', 'd', 'd', 'i', 'r', '>', '\n'};
       +
       +void        usage(void);
       +
       +int main(int argc, char *argv[])
       +{
       +        int i;
       +        String *t;
       +        char **ap, **arg;
       +
       +        arg = argv++;
       +        ap = argv;
       +        while(argc>1 && argv[0] && argv[0][0]=='-'){
       +                switch(argv[0][1]){
       +                case 'd':
       +                        dflag++;
       +                        break;
       +
       +                case 'r':
       +                        *ap++ = *argv++;
       +                        *ap++ = *argv;
       +                        --argc;
       +                        if(argc == 1)
       +                                usage();
       +                        machine = *argv;
       +
       +                        rsamname = RXSAMNAME;
       +                        break;
       +
       +                case 'R':
       +                        Rflag++;
       +                        break;
       +
       +                case 't':
       +                        --argc, argv++;
       +                        if(argc == 1)
       +                                usage();
       +                        samterm = *argv;
       +                        break;
       +
       +                case 's':
       +                        --argc, argv++;
       +                        if(argc == 1)
       +                                usage();
       +                        rsamname = *argv;
       +                        break;
       +
       +                default:
       +                        dprint("sam: unknown flag %c\n", argv[0][1]);
       +                        exits("usage");
       +                }
       +                --argc, argv++;
       +        }
       +        Strinit(&cmdstr);
       +        Strinit0(&lastpat);
       +        Strinit0(&lastregexp);
       +        Strinit0(&genstr);
       +        Strinit0(&rhs);
       +        Strinit0(&wd);
       +        tempfile.listptr = emalloc(0);
       +        Strinit0(&plan9cmd);
       +        home = getenv(HOME);
       +        if(home == 0)
       +                home = "/";
       +        if(!dflag)
       +                startup(machine, Rflag, arg, ap);
       +        Fstart();
       +        notify(notifyf);
       +        if(argc>1){
       +                for(i=0; i<argc-1; i++)
       +                        if(!setjmp(mainloop)){
       +                                t = tmpcstr(argv[i]);
       +                                Straddc(t, '\0');
       +                                Strduplstr(&genstr, t);
       +                                freetmpstr(t);
       +                                Fsetname(newfile(), &genstr);
       +                        }
       +        }else if(!downloaded)
       +                newfile()->state = Clean;
       +        modnum++;
       +        if(file.nused)
       +                current(file.filepptr[0]);
       +        setjmp(mainloop);
       +        cmdloop();
       +        trytoquit();        /* if we already q'ed, quitok will be TRUE */
       +        exits(0);
       +}
       +
       +void
       +usage(void)
       +{
       +        dprint("usage: sam [-d] [-t samterm] [-s sam name] -r machine\n");
       +        exits("usage");
       +}
       +
       +void
       +rescue(void)
       +{
       +        int i, nblank = 0;
       +        File *f;
       +        char *c;
       +        char buf[256];
       +
       +        if(rescuing++)
       +                return;
       +        io = -1;
       +        for(i=0; i<file.nused; i++){
       +                f = file.filepptr[i];
       +                if(f==cmd || f->nrunes==0 || f->state!=Dirty)
       +                        continue;
       +                if(io == -1){
       +                        sprint(buf, "%s/sam.save", home);
       +                        io = create(buf, 1, 0777);
       +                        if(io<0)
       +                                return;
       +                }
       +                if(f->name.s[0]){
       +                        c = Strtoc(&f->name);
       +                        strncpy(buf, c, sizeof buf-1);
       +                        buf[sizeof buf-1] = 0;
       +                        free(c);
       +                }else
       +                        sprint(buf, "nameless.%d", nblank++);
       +                fprint(io, "#!%s '%s' $* <<'---%s'\n", SAMSAVECMD, buf, buf);
       +                addr.r.p1 = 0, addr.r.p2 = f->nrunes;
       +                writeio(f);
       +                fprint(io, "\n---%s\n", (char *)buf);
       +        }
       +}
       +
       +void
       +panic(char *s)
       +{
       +        int wasd;
       +
       +        if(!panicking++ && !setjmp(mainloop)){
       +                wasd = downloaded;
       +                downloaded = 0;
       +                dprint("sam: panic: %s\n", s);
       +                if(wasd)
       +                        fprint(2, "sam: panic: %s\n", s);
       +                rescue();
       +                abort();
       +        }
       +}
       +
       +void
       +hiccough(char *s)
       +{
       +        if(rescuing)
       +                exits("rescue");
       +        if(s)
       +                dprint("%s\n", s);
       +        resetcmd();
       +        resetxec();
       +        resetsys();
       +        if(io > 0)
       +                close(io);
       +        if(undobuf->nrunes)
       +                Bdelete(undobuf, (Posn)0, undobuf->nrunes);
       +        update();
       +        if (curfile) {
       +                if (curfile->state==Unread)
       +                        curfile->state = Clean;
       +                else if (downloaded)
       +                        outTs(Hcurrent, curfile->tag);
       +        }
       +        longjmp(mainloop, 1);
       +}
       +
       +void
       +intr(void)
       +{
       +        error(Eintr);
       +}
       +
       +void
       +trytoclose(File *f)
       +{
       +        char *t;
       +        char buf[256];
       +
       +        if(f == cmd)        /* possible? */
       +                return;
       +        if(f->deleted)
       +                return;
       +        if(f->state==Dirty && !f->closeok){
       +                f->closeok = TRUE;
       +                if(f->name.s[0]){
       +                        t = Strtoc(&f->name);
       +                        strncpy(buf, t, sizeof buf-1);
       +                        free(t);
       +                }else
       +                        strcpy(buf, "nameless file");
       +                error_s(Emodified, buf);
       +        }
       +        f->deleted = TRUE;
       +}
       +
       +void
       +trytoquit(void)
       +{
       +        int c;
       +        File *f;
       +
       +        if(!quitok)
       +{
       +                for(c = 0; c<file.nused; c++){
       +                        f = file.filepptr[c];
       +                        if(f!=cmd && f->state==Dirty){
       +                                quitok = TRUE;
       +                                eof = FALSE;
       +                                error(Echanges);
       +                        }
       +                }
       +}
       +}
       +
       +void
       +load(File *f)
       +{
       +        Address saveaddr;
       +
       +        Strduplstr(&genstr, &f->name);
       +        filename(f);
       +        if(f->name.s[0]){
       +                saveaddr = addr;
       +                edit(f, 'I');
       +                addr = saveaddr;
       +        }else
       +                f->state = Clean;
       +        Fupdate(f, TRUE, TRUE);
       +}
       +
       +void
       +cmdupdate(void)
       +{
       +        if(cmd && cmd->mod!=0){
       +                Fupdate(cmd, FALSE, downloaded);
       +                cmd->dot.r.p1 = cmd->dot.r.p2 = cmd->nrunes;
       +                telldot(cmd);
       +        }
       +}
       +
       +void
       +delete(File *f)
       +{
       +        if(downloaded && f->rasp)
       +                outTs(Hclose, f->tag);
       +        delfile(f);
       +        if(f == curfile)
       +                current(0);
       +}
       +
       +void
       +update(void)
       +{
       +        int i, anymod;
       +        File *f;
       +
       +        settempfile();
       +        for(anymod = i=0; i<tempfile.nused; i++){
       +                f = tempfile.filepptr[i];
       +                if(f==cmd)        /* cmd gets done in main() */
       +                        continue;
       +                if(f->deleted) {
       +                        delete(f);
       +                        continue;
       +                }
       +                if(f->mod==modnum && Fupdate(f, FALSE, downloaded))
       +                        anymod++;
       +                if(f->rasp)
       +                        telldot(f);
       +        }
       +        if(anymod)
       +                modnum++;
       +}
       +
       +File *
       +current(File *f)
       +{
       +        return curfile = f;
       +}
       +
       +void
       +edit(File *f, int cmd)
       +{
       +        int empty = TRUE;
       +        Posn p;
       +        int nulls;
       +
       +        if(cmd == 'r')
       +                Fdelete(f, addr.r.p1, addr.r.p2);
       +        if(cmd=='e' || cmd=='I'){
       +                Fdelete(f, (Posn)0, f->nrunes);
       +                addr.r.p2 = f->nrunes;
       +        }else if(f->nrunes!=0 || (f->name.s[0] && Strcmp(&genstr, &f->name)!=0))
       +                empty = FALSE;
       +        if((io = open(genc, OREAD))<0) {
       +                if (curfile && curfile->state == Unread)
       +                        curfile->state = Clean;
       +                error_s(Eopen, genc);
       +        }
       +        p = readio(f, &nulls, empty);
       +        closeio((cmd=='e' || cmd=='I')? -1 : p);
       +        if(cmd == 'r')
       +                f->ndot.r.p1 = addr.r.p2, f->ndot.r.p2 = addr.r.p2+p;
       +        else
       +                f->ndot.r.p1 = f->ndot.r.p2 = 0;
       +        f->closeok = empty;
       +        if (quitok)
       +                quitok = empty;
       +        else
       +                quitok = FALSE;
       +        state(f, empty && !nulls? Clean : Dirty);
       +        if(cmd == 'e')
       +                filename(f);
       +}
       +
       +int
       +getname(File *f, String *s, int save)
       +{
       +        int c, i;
       +
       +        Strzero(&genstr);
       +        if(genc){
       +                free(genc);
       +                genc = 0;
       +        }
       +        if(s==0 || (c = s->s[0])==0){                /* no name provided */
       +                if(f)
       +                        Strduplstr(&genstr, &f->name);
       +                else
       +                        Straddc(&genstr, '\0');
       +                goto Return;
       +        }
       +        if(c!=' ' && c!='\t')
       +                error(Eblank);
       +        for(i=0; (c=s->s[i])==' ' || c=='\t'; i++)
       +                ;
       +        while(s->s[i] > ' ')
       +                Straddc(&genstr, s->s[i++]);
       +        if(s->s[i])
       +                error(Enewline);
       +        Straddc(&genstr, '\0');
       +        if(f && (save || f->name.s[0]==0)){
       +                Fsetname(f, &genstr);
       +                if(Strcmp(&f->name, &genstr)){
       +                        quitok = f->closeok = FALSE;
       +                        f->qid = 0;
       +                        f->date = 0;
       +                        state(f, Dirty); /* if it's 'e', fix later */
       +                }
       +        }
       +    Return:
       +        genc = Strtoc(&genstr);
       +        return genstr.n-1;        /* strlen(name) */
       +}
       +
       +void
       +filename(File *f)
       +{
       +        if(genc)
       +                free(genc);
       +        genc = Strtoc(&genstr);
       +        dprint("%c%c%c %s\n", " '"[f->state==Dirty],
       +                "-+"[f->rasp!=0], " ."[f==curfile], genc);
       +}
       +
       +void
       +undostep(File *f)
       +{
       +        Buffer *t;
       +        int changes;
       +        Mark mark;
       +
       +        t = f->transcript;
       +        changes = Fupdate(f, TRUE, TRUE);
       +        Bread(t, (Rune*)&mark, (sizeof mark)/RUNESIZE, f->markp);
       +        Bdelete(t, f->markp, t->nrunes);
       +        f->markp = mark.p;
       +        f->dot.r = mark.dot;
       +        f->ndot.r = mark.dot;
       +        f->mark = mark.mark;
       +        f->mod = mark.m;
       +        f->closeok = mark.s1!=Dirty;
       +        if(mark.s1==Dirty)
       +                quitok = FALSE;
       +        if(f->state==Clean && mark.s1==Clean && changes)
       +                state(f, Dirty);
       +        else
       +                state(f, mark.s1);
       +}
       +
       +int
       +undo(void)
       +{
       +        File *f;
       +        int i;
       +        Mod max;
       +        if((max = curfile->mod)==0)
       +                return 0;
       +        settempfile();
       +        for(i = 0; i<tempfile.nused; i++){
       +                f = tempfile.filepptr[i];
       +                if(f!=cmd && f->mod==max)
       +                        undostep(f);
       +        }
       +        return 1;
       +}
       +
       +int
       +readcmd(String *s)
       +{
       +        int retcode;
       +
       +        if(flist == 0)
       +                (flist = Fopen())->state = Clean;
       +        addr.r.p1 = 0, addr.r.p2 = flist->nrunes;
       +        retcode = plan9(flist, '<', s, FALSE);
       +        Fupdate(flist, FALSE, FALSE);
       +        flist->mod = 0;
       +        if (flist->nrunes > BLOCKSIZE)
       +                error(Etoolong);
       +        Strzero(&genstr);
       +        Strinsure(&genstr, flist->nrunes);
       +        Fchars(flist, genbuf, (Posn)0, flist->nrunes);
       +        memmove(genstr.s, genbuf, flist->nrunes*RUNESIZE);
       +        genstr.n = flist->nrunes;
       +        Straddc(&genstr, '\0');
       +        return retcode;
       +}
       +
       +void
       +cd(String *str)
       +{
       +        int i;
       +        File *f;
       +        String *t;
       +
       +        t = tmpcstr("/bin/pwd");
       +        Straddc(t, '\0');
       +        if (flist) {
       +                Fclose(flist);
       +                flist = 0;
       +        }
       +        if (readcmd(t) != 0) {
       +                Strduplstr(&genstr, tmprstr(baddir, sizeof(baddir)/sizeof(Rune)));
       +                Straddc(&genstr, '\0');
       +        }
       +        freetmpstr(t);
       +        Strduplstr(&wd, &genstr);
       +        if(wd.s[0] == 0){
       +                wd.n = 0;
       +                warn(Wpwd);
       +        }else if(wd.s[wd.n-2] == '\n'){
       +                --wd.n;
       +                wd.s[wd.n-1]='/';
       +        }
       +        if(chdir(getname((File *)0, str, FALSE)? genc : home))
       +                syserror("chdir");
       +        settempfile();
       +        for(i=0; i<tempfile.nused; i++){
       +                f = tempfile.filepptr[i];
       +                if(f!=cmd && f->name.s[0]!='/' && f->name.s[0]!=0){
       +                        Strinsert(&f->name, &wd, (Posn)0);
       +                        sortname(f);
       +                }
       +        }
       +}
       +
       +int
       +loadflist(String *s)
       +{
       +        int c, i;
       +
       +        c = s->s[0];
       +        for(i = 0; s->s[i]==' ' || s->s[i]=='\t'; i++)
       +                ;
       +        if((c==' ' || c=='\t') && s->s[i]!='\n'){
       +                if(s->s[i]=='<'){
       +                        Strdelete(s, 0L, (long)i+1);
       +                        readcmd(s);
       +                }else{
       +                        Strzero(&genstr);
       +                        while((c = s->s[i++]) && c!='\n')
       +                                Straddc(&genstr, c);
       +                        Straddc(&genstr, '\0');
       +                }
       +        }else{
       +                if(c != '\n')
       +                        error(Eblank);
       +                Strdupl(&genstr, empty);
       +        }
       +        if(genc)
       +                free(genc);
       +        genc = Strtoc(&genstr);
       +        return genstr.s[0];
       +}
       +
       +File *
       +readflist(int readall, int delete)
       +{
       +        Posn i;
       +        int c;
       +        File *f;
       +        String *t;
       +
       +        for(i=0,f=0; f==0 || readall || delete; i++){        /* ++ skips blank */
       +                Strdelete(&genstr, (Posn)0, i);
       +                for(i=0; (c = genstr.s[i])==' ' || c=='\t' || c=='\n'; i++)
       +                        ;
       +                if(i >= genstr.n)
       +                        break;
       +                Strdelete(&genstr, (Posn)0, i);
       +                for(i=0; (c=genstr.s[i]) && c!=' ' && c!='\t' && c!='\n'; i++)
       +                        ;
       +
       +                if(i == 0)
       +                        break;
       +                genstr.s[i] = 0;
       +                t = tmprstr(genstr.s, i+1);
       +                f = lookfile(t);
       +                if(delete){
       +                        if(f == 0)
       +                                warn_S(Wfile, t);
       +                        else
       +                                trytoclose(f);
       +                }else if(f==0 && readall)
       +                        Fsetname(f = newfile(), t);
       +        }
       +        return f;
       +}
       +
       +File *
       +tofile(String *s)
       +{
       +        File *f;
       +
       +        if(s->s[0] != ' ')
       +                error(Eblank);
       +        if(loadflist(s) == 0){
       +                f = lookfile(&genstr);        /* empty string ==> nameless file */
       +                if(f == 0)
       +                        error_s(Emenu, genc);
       +        }else if((f=readflist(FALSE, FALSE)) == 0)
       +                error_s(Emenu, genc);
       +        return current(f);
       +}
       +
       +File *
       +getfile(String *s)
       +{
       +        File *f;
       +
       +        if(loadflist(s) == 0)
       +                Fsetname(f = newfile(), &genstr);
       +        else if((f=readflist(TRUE, FALSE)) == 0)
       +                error(Eblank);
       +        return current(f);
       +}
       +
       +void
       +closefiles(File *f, String *s)
       +{
       +        if(s->s[0] == 0){
       +                if(f == 0)
       +                        error(Enofile);
       +                trytoclose(f);
       +                return;
       +        }
       +        if(s->s[0] != ' ')
       +                error(Eblank);
       +        if(loadflist(s) == 0)
       +                error(Enewline);
       +        readflist(FALSE, TRUE);
       +}
       +
       +void
       +copy(File *f, Address addr2)
       +{
       +        Posn p;
       +        int ni;
       +        for(p=addr.r.p1; p<addr.r.p2; p+=ni){
       +                ni = addr.r.p2-p;
       +                if(ni > BLOCKSIZE)
       +                        ni = BLOCKSIZE;
       +                Fchars(f, genbuf, p, p+ni);
       +                Finsert(addr2.f, tmprstr(genbuf, ni), addr2.r.p2);
       +        }
       +        addr2.f->ndot.r.p2 = addr2.r.p2+(f->dot.r.p2-f->dot.r.p1);
       +        addr2.f->ndot.r.p1 = addr2.r.p2;
       +}
       +
       +void
       +move(File *f, Address addr2)
       +{
       +        if(addr.r.p2 <= addr2.r.p2){
       +                Fdelete(f, addr.r.p1, addr.r.p2);
       +                copy(f, addr2);
       +        }else if(addr.r.p1 >= addr2.r.p2){
       +                copy(f, addr2);
       +                Fdelete(f, addr.r.p1, addr.r.p2);
       +        }else
       +                error(Eoverlap);
       +}
       +
       +Posn
       +nlcount(File *f, Posn p0, Posn p1)
       +{
       +        Posn nl = 0;
       +
       +        Fgetcset(f, p0);
       +        while(p0++<p1)
       +                if(Fgetc(f)=='\n')
       +                        nl++;
       +        return nl;
       +}
       +
       +void
       +printposn(File *f, int charsonly)
       +{
       +        Posn l1, l2;
       +
       +        if(!charsonly){
       +                l1 = 1+nlcount(f, (Posn)0, addr.r.p1);
       +                l2 = l1+nlcount(f, addr.r.p1, addr.r.p2);
       +                /* check if addr ends with '\n' */
       +                if(addr.r.p2>0 && addr.r.p2>addr.r.p1 && (Fgetcset(f, addr.r.p2-1),Fgetc(f)=='\n'))
       +                        --l2;
       +                dprint("%lu", l1);
       +                if(l2 != l1)
       +                        dprint(",%lu", l2);
       +                dprint("; ");
       +        }
       +        dprint("#%lu", addr.r.p1);
       +        if(addr.r.p2 != addr.r.p1)
       +                dprint(",#%lu", addr.r.p2);
       +        dprint("\n");
       +}
       +
       +void
       +settempfile(void)
       +{
       +        if(tempfile.nalloc < file.nused){
       +                free(tempfile.listptr);
       +                tempfile.listptr = emalloc(sizeof(*tempfile.filepptr)*file.nused);
       +                tempfile.nalloc = file.nused;
       +        }
       +        tempfile.nused = file.nused;
       +        memmove(&tempfile.filepptr[0], &file.filepptr[0], file.nused*sizeof(File*));
       +}
 (DIR) diff --git a/sam/sam.h b/sam/sam.h
       @@ -0,0 +1,408 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include "errors.h"
       +
       +/*
       + * BLOCKSIZE is relatively small to keep memory consumption down.
       + */
       +
       +#define        BLOCKSIZE        2048
       +#define        RUNESIZE        sizeof(Rune)
       +#define        NDISC                5
       +#define        NBUFFILES        3+2*NDISC        /* plan 9+undo+snarf+NDISC*(transcript+buf) */
       +#define NSUBEXP        10
       +
       +#define        TRUE                1
       +#define        FALSE                0
       +
       +#define        INFINITY        0x7FFFFFFFL
       +#define        INCR                25
       +#define        STRSIZE                (2*BLOCKSIZE)
       +
       +typedef long                Posn;                /* file position or address */
       +typedef        ushort                Mod;                /* modification number */
       +
       +typedef struct Address        Address;
       +typedef struct Block        Block;
       +typedef struct Buffer        Buffer;
       +typedef struct Disc        Disc;
       +typedef struct Discdesc        Discdesc;
       +typedef struct File        File;
       +typedef struct List        List;
       +typedef struct Mark        Mark;
       +typedef struct Range        Range;
       +typedef struct Rangeset        Rangeset;
       +typedef struct String        String;
       +
       +enum State
       +{
       +        Clean =                ' ',
       +        Dirty =                '\'',
       +        Unread =        '-',
       +        Readerr =        '~'
       +};
       +
       +struct Range
       +{
       +        Posn        p1, p2;
       +};
       +
       +struct Rangeset
       +{
       +        Range        p[NSUBEXP];
       +};
       +
       +struct Address
       +{
       +        Range        r;
       +        File        *f;
       +};
       +
       +struct List        /* code depends on a long being able to hold a pointer */
       +{
       +        int        nalloc;
       +        int        nused;
       +        union{
       +                void        *listp;
       +                Block        *blkp;
       +                long        *longp;
       +                uchar*        *ucharp;
       +                String*        *stringp;
       +                File*        *filep;
       +                long        listv;
       +        }g;
       +};
       +
       +#define        listptr                g.listp
       +#define        blkptr                g.blkp
       +#define        longptr                g.longp
       +#define        ucharpptr        g.ucharp
       +#define        stringpptr        g.stringp
       +#define        filepptr        g.filep
       +#define        listval                g.listv
       +
       +/*
       + * Block must fit in a long because the list routines manage arrays of
       + * blocks.  Two problems: some machines (e.g. Cray) can't pull this off
       + * -- on them, use bitfields -- and the ushort bnum limits temp file sizes
       + * to about 200 megabytes.  Advantages: small, simple code and small
       + * memory overhead.  If you really want to edit huge files, making BLOCKSIZE
       + * bigger is the easiest way.
       +*
       +* The necessary conditions are even stronger:
       +*           sizeof(struct Block)==sizeof(long)
       +*        && the first 32 bits must hold bnum and nrunes.
       +* When sizeof(ushort)+sizeof(short) < sizeof(long),
       +* add padding at the beginning on a little endian and at
       +* the end on a big endian, as shown below for the DEC Alpha.
       + */
       +struct Block
       +{
       +#if USE64BITS == 1
       +        char        pad[sizeof(long)-sizeof(ushort)-sizeof(short)];
       +#endif
       +        ushort        bnum;                /* absolute number on disk */
       +        short        nrunes;                /* runes stored in this block */
       +#if USE64BITS == 2
       +        char        pad[sizeof(long)-sizeof(ushort)-sizeof(short)];
       +#endif
       +};
       +
       +struct Discdesc
       +{
       +        int        fd;                /* plan 9 file descriptor of temp file */
       +        ulong        nbk;                /* high water mark */
       +        List        free;                /* array of free block indices */
       +};
       +
       +struct Disc
       +{
       +        Discdesc *desc;                /* descriptor of temp file */
       +        Posn        nrunes;                /* runes on disc file */
       +        List        block;                /* list of used block indices */
       +};
       +
       +struct String
       +{
       +        short        n;
       +        short        size;
       +        Rune        *s;
       +};
       +
       +struct Buffer
       +{
       +        Disc        *disc;                /* disc storage */
       +        Posn        nrunes;                /* total length of buffer */
       +        String        cache;                /* in-core storage for efficiency */
       +        Posn        c1, c2;                /* cache start and end positions in disc */
       +                                /* note: if dirty, cache is really c1, c1+cache.n */
       +        int        dirty;                /* cache dirty */
       +};
       +
       +#define        NGETC        128
       +
       +struct File
       +{
       +        Buffer        *buf;                /* cached disc storage */
       +        Buffer        *transcript;        /* what's been done */
       +        Posn        markp;                /* file pointer to start of latest change */
       +        Mod        mod;                /* modification stamp */
       +        Posn        nrunes;                /* total length of file */
       +        Posn        hiposn;                /* highest address touched this Mod */
       +        Address        dot;                /* current position */
       +        Address        ndot;                /* new current position after update */
       +        Range        tdot;                /* what terminal thinks is current range */
       +        Range        mark;                /* tagged spot in text (don't confuse with Mark) */
       +        List        *rasp;                /* map of what terminal's got */
       +        String        name;                /* file name */
       +        short        tag;                /* for communicating with terminal */
       +        char        state;                /* Clean, Dirty, Unread, or Readerr*/
       +        char        closeok;        /* ok to close file? */
       +        char        deleted;        /* delete at completion of command */
       +        char        marked;                /* file has been Fmarked at least once; once
       +                                 * set, this will never go off as undo doesn't
       +                                 * revert to the dawn of time */
       +        long        dev;                /* file system from which it was read */
       +        long        qid;                /* file from which it was read */
       +        long        date;                /* time stamp of plan9 file */
       +        Posn        cp1, cp2;        /* Write-behind cache positions and */
       +        String        cache;                /* string */
       +        Rune        getcbuf[NGETC];
       +        int        ngetc;
       +        int        getci;
       +        Posn        getcp;
       +};
       +
       +struct Mark
       +{
       +        Posn        p;
       +        Range        dot;
       +        Range        mark;
       +        Mod        m;
       +        short        s1;
       +};
       +
       +/*
       + * The precedent to any message in the transcript.
       + * The component structures must be an integral number of Runes long.
       + */
       +union Hdr
       +{
       +        struct _csl
       +        {
       +                short        c;
       +                short        s;
       +                long        l;
       +        }csl;
       +        struct _cs
       +        {
       +                short        c;
       +                short        s;
       +        }cs;
       +        struct _cll
       +        {
       +                short        c;
       +                long        l;
       +                long        l1;
       +        }cll;
       +        Mark        mark;
       +};
       +
       +#define        Fgetc(f)  ((--(f)->ngetc<0)? Fgetcload(f, (f)->getcp) : (f)->getcbuf[(f)->getcp++, (f)->getci++])
       +#define        Fbgetc(f) (((f)->getci<=0)? Fbgetcload(f, (f)->getcp) : (f)->getcbuf[--(f)->getcp, --(f)->getci])
       +
       +int        alnum(int);
       +void        Bclean(Buffer*);
       +void        Bterm(Buffer*);
       +void        Bdelete(Buffer*, Posn, Posn);
       +void        Bflush(Buffer*);
       +void        Binsert(Buffer*, String*, Posn);
       +Buffer        *Bopen(Discdesc*);
       +int        Bread(Buffer*, Rune*, int, Posn);
       +void        Dclose(Disc*);
       +void        Ddelete(Disc*, Posn, Posn);
       +void        Dinsert(Disc*, Rune*, int, Posn);
       +Disc        *Dopen(Discdesc*);
       +int        Dread(Disc*, Rune*, int, Posn);
       +void        Dreplace(Disc*, Posn, Posn, Rune*, int);
       +int        Fbgetcload(File*, Posn);
       +int        Fbgetcset(File*, Posn);
       +long        Fchars(File*, Rune*, Posn, Posn);
       +void        Fclose(File*);
       +void        Fdelete(File*, Posn, Posn);
       +int        Fgetcload(File*, Posn);
       +int        Fgetcset(File*, Posn);
       +void        Finsert(File*, String*, Posn);
       +File        *Fopen(void);
       +void        Fsetname(File*, String*);
       +void        Fstart(void);
       +int        Fupdate(File*, int, int);
       +int        Read(int, void*, int);
       +void        Seek(int, long, int);
       +int        plan9(File*, int, String*, int);
       +int        Write(int, void*, int);
       +int        bexecute(File*, Posn);
       +void        cd(String*);
       +void        closefiles(File*, String*);
       +void        closeio(Posn);
       +void        cmdloop(void);
       +void        cmdupdate(void);
       +void        compile(String*);
       +void        copy(File*, Address);
       +File        *current(File*);
       +void        delete(File*);
       +void        delfile(File*);
       +void        dellist(List*, int);
       +void        doubleclick(File*, Posn);
       +void        dprint(char*, ...);
       +void        edit(File*, int);
       +void        *emalloc(ulong);
       +void        *erealloc(void*, ulong);
       +void        error(Err);
       +void        error_c(Err, int);
       +void        error_s(Err, char*);
       +int        execute(File*, Posn, Posn);
       +int        filematch(File*, String*);
       +void        filename(File*);
       +File        *getfile(String*);
       +int        getname(File*, String*, int);
       +long        getnum(void);
       +void        hiccough(char*);
       +void        inslist(List*, int, long);
       +Address        lineaddr(Posn, Address, int);
       +void        listfree(List*);
       +void        load(File*);
       +File        *lookfile(String*);
       +void        lookorigin(File*, Posn, Posn);
       +int        lookup(int);
       +void        move(File*, Address);
       +void        moveto(File*, Range);
       +File        *newfile(void);
       +void        nextmatch(File*, String*, Posn, int);
       +int        newtmp(int);
       +void        notifyf(void*, char*);
       +void        panic(char*);
       +void        printposn(File*, int);
       +void        print_ss(char*, String*, String*);
       +void        print_s(char*, String*);
       +int        rcv(void);
       +Range        rdata(List*, Posn, Posn);
       +Posn        readio(File*, int*, int);
       +void        rescue(void);
       +void        resetcmd(void);
       +void        resetsys(void);
       +void        resetxec(void);
       +void        rgrow(List*, Posn, Posn);
       +void        samerr(char*);
       +void        settempfile(void);
       +int        skipbl(void);
       +void        snarf(File*, Posn, Posn, Buffer*, int);
       +void        sortname(File*);
       +void        startup(char*, int, char**, char**);
       +void        state(File*, int);
       +int        statfd(int, ulong*, ulong*, long*, long*, long*);
       +int        statfile(char*, ulong*, ulong*, long*, long*, long*);
       +void        Straddc(String*, int);
       +void        Strclose(String*);
       +int        Strcmp(String*, String*);
       +void        Strdelete(String*, Posn, Posn);
       +void        Strdupl(String*, Rune*);
       +void        Strduplstr(String*, String*);
       +void        Strinit(String*);
       +void        Strinit0(String*);
       +void        Strinsert(String*, String*, Posn);
       +void        Strinsure(String*, ulong);
       +void        Strzero(String*);
       +int        Strlen(Rune*);
       +char        *Strtoc(String*);
       +void        syserror(char*);
       +void        telldot(File*);
       +void        tellpat(void);
       +String        *tmpcstr(char*);
       +String        *tmprstr(Rune*, int);
       +void        freetmpstr(String*);
       +void        termcommand(void);
       +void        termwrite(char*);
       +File        *tofile(String*);
       +void        toterminal(File*, int);
       +void        trytoclose(File*);
       +void        trytoquit(void);
       +int        undo(void);
       +void        update(void);
       +int        waitfor(int);
       +void        warn(Warn);
       +void        warn_s(Warn, char*);
       +void        warn_SS(Warn, String*, String*);
       +void        warn_S(Warn, String*);
       +int        whichmenu(File*);
       +void        writef(File*);
       +Posn        writeio(File*);
       +Discdesc *Dstart(void);
       +
       +extern Rune        samname[];        /* compiler dependent */
       +extern Rune        *left[];
       +extern Rune        *right[];
       +
       +extern char        RSAM[];                /* system dependent */
       +extern char        SAMTERM[];
       +extern char        HOME[];
       +extern char        TMPDIR[];
       +extern char        SH[];
       +extern char        SHPATH[];
       +extern char        RX[];
       +extern char        RXPATH[];
       +extern char        SAMSAVECMD[];
       +
       +extern char        *rsamname;        /* globals */
       +extern char        *samterm;
       +extern Rune        genbuf[];
       +extern char        *genc;
       +extern int        io;
       +extern int        patset;
       +extern int        quitok;
       +extern Address        addr;
       +extern Buffer        *undobuf;
       +extern Buffer        *snarfbuf;
       +extern Buffer        *plan9buf;
       +extern List        file;
       +extern List        tempfile;
       +extern File        *cmd;
       +extern File        *curfile;
       +extern File        *lastfile;
       +extern Mod        modnum;
       +extern Posn        cmdpt;
       +extern Posn        cmdptadv;
       +extern Rangeset        sel;
       +extern String        cmdstr;
       +extern String        genstr;
       +extern String        lastpat;
       +extern String        lastregexp;
       +extern String        plan9cmd;
       +extern int        downloaded;
       +extern int        eof;
       +extern int        bpipeok;
       +extern int        panicking;
       +extern Rune        empty[];
       +extern int        termlocked;
       +extern int        noflush;
       +
       +#include "mesg.h"
       +
       +void        outTs(Hmesg, int);
       +void        outT0(Hmesg);
       +void        outTl(Hmesg, long);
       +void        outTslS(Hmesg, int, long, String*);
       +void        outTS(Hmesg, String*);
       +void        outTsS(Hmesg, int, String*);
       +void        outTsllS(Hmesg, int, long, long, String*);
       +void        outTsll(Hmesg, int, long, long);
       +void        outTsl(Hmesg, int, long);
       +void        outTsv(Hmesg, int, long);
       +void        outstart(Hmesg);
       +void        outcopy(int, void*);
       +void        outshort(int);
       +void        outlong(long);
       +void        outvlong(void*);
       +void        outsend(void);
       +void        outflush(void);
 (DIR) diff --git a/sam/samsave b/sam/samsave
       @@ -0,0 +1,14 @@
       +#!/bin/sh
       +#        Copyright (c) 1998 Lucent Technologies - All rights reserved.
       +PATH=/bin:/usr/bin
       +file=$1
       +case "$2" in
       +-f)        echo "$file"
       +        cat > $file
       +        ;;
       +"")        echo "$file?"
       +        read yn < /dev/tty
       +        case "$yn" in
       +        [Yy]*)        cat > $file
       +        esac
       +esac
 (DIR) diff --git a/sam/shell.c b/sam/shell.c
       @@ -0,0 +1,155 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include "sam.h"
       +#include "parse.h"
       +
       +extern        jmp_buf        mainloop;
       +
       +char        errfile[64];
       +String        plan9cmd;        /* null terminated */
       +Buffer        *plan9buf;
       +void        checkerrs(void);
       +
       +int
       +plan9(File *f, int type, String *s, int nest)
       +{
       +        long l;
       +        int m;
       +        int pid, fd;
       +        int retcode;
       +        int pipe1[2], pipe2[2];
       +
       +        if(s->s[0]==0 && plan9cmd.s[0]==0)
       +                error(Enocmd);
       +        else if(s->s[0])
       +                Strduplstr(&plan9cmd, s);
       +        if(downloaded)
       +                samerr(errfile);
       +        else
       +                strcpy(errfile, "/dev/tty");
       +        if(type!='!' && pipe(pipe1)==-1)
       +                error(Epipe);
       +        if(type=='|')
       +                snarf(f, addr.r.p1, addr.r.p2, plan9buf, 1);
       +        if(downloaded)
       +                remove(errfile);
       +        if((pid=fork()) == 0){
       +                if(downloaded){        /* also put nasty fd's into errfile */
       +                        fd = create(errfile, 1, 0666L);
       +                        if(fd < 0)
       +                                fd = create("/dev/null", 1, 0666L);
       +                        dup(fd, 2);
       +                        close(fd);
       +                        /* 2 now points at err file */
       +                        if(type == '>')
       +                                dup(2, 1);
       +                        else if(type=='!'){
       +                                dup(2, 1);
       +                                fd = open("/dev/null", 0);
       +                                dup(fd, 0);
       +                                close(fd);
       +                        }
       +                }
       +                if(type != '!') {
       +                        if(type=='<' || type=='|')
       +                                dup(pipe1[1], 1);
       +                        else if(type == '>')
       +                                dup(pipe1[0], 0);
       +                        close(pipe1[0]);
       +                        close(pipe1[1]);
       +                }
       +                if(type == '|'){
       +                        if(pipe(pipe2) == -1)
       +                                exits("pipe");
       +                        if((pid = fork())==0){
       +                                /*
       +                                 * It's ok if we get SIGPIPE here
       +                                 */
       +                                close(pipe2[0]);
       +                                io = pipe2[1];
       +                                if(retcode=!setjmp(mainloop)){        /* assignment = */
       +                                        char *c;
       +                                        for(l = 0; l<plan9buf->nrunes; l+=m){
       +                                                m = plan9buf->nrunes-l;
       +                                                if(m>BLOCKSIZE-1)
       +                                                        m = BLOCKSIZE-1;
       +                                                Bread(plan9buf, genbuf, m, l);
       +                                                genbuf[m] = 0;
       +                                                c = Strtoc(tmprstr(genbuf, m+1));
       +                                                Write(pipe2[1], c, strlen(c));
       +                                                free(c);
       +                                        }
       +                                }
       +                                exits(retcode? "error" : 0);
       +                        }
       +                        if(pid==-1){
       +                                fprint(2, "Can't fork?!\n");
       +                                exits("fork");
       +                        }
       +                        dup(pipe2[0], 0);
       +                        close(pipe2[0]);
       +                        close(pipe2[1]);
       +                }
       +                if(type=='<'){
       +                        close(0);        /* so it won't read from terminal */
       +                        open("/dev/null", 0);
       +                }
       +                execl(SHPATH, SH, "-c", Strtoc(&plan9cmd), (char *)0);
       +                exits("exec");
       +        }
       +        if(pid == -1)
       +                error(Efork);
       +        if(type=='<' || type=='|'){
       +                int nulls;
       +                if(downloaded && addr.r.p1 != addr.r.p2)
       +                        outTl(Hsnarflen, addr.r.p2-addr.r.p1);
       +                snarf(f, addr.r.p1, addr.r.p2, snarfbuf, 0);
       +                Fdelete(f, addr.r.p1, addr.r.p2);
       +                close(pipe1[1]);
       +                io = pipe1[0];
       +                f->tdot.p1 = -1;
       +                f->ndot.r.p2 = addr.r.p2+readio(f, &nulls, 0);
       +                f->ndot.r.p1 = addr.r.p2;
       +                closeio((Posn)-1);
       +        }else if(type=='>'){
       +                close(pipe1[0]);
       +                io = pipe1[1];
       +                bpipeok = 1;
       +                writeio(f);
       +                bpipeok = 0;
       +                closeio((Posn)-1);
       +        }
       +        retcode = waitfor(pid);
       +        if(type=='|' || type=='<')
       +                if(retcode!=0)
       +                        warn(Wbadstatus);
       +        if(downloaded)
       +                checkerrs();
       +        if(!nest)
       +                dprint("!\n");
       +        return retcode;
       +}
       +
       +void
       +checkerrs(void)
       +{
       +        char buf[256];
       +        int f, n, nl;
       +        char *p;
       +        long l;
       +
       +        if(statfile(errfile, 0, 0, 0, &l, 0) > 0 && l != 0){
       +                if((f=open((char *)errfile, 0)) != -1){
       +                        if((n=read(f, buf, sizeof buf-1)) > 0){
       +                                for(nl=0,p=buf; nl<3 && p<&buf[n]; p++)
       +                                        if(*p=='\n')
       +                                                nl++;
       +                                *p = 0;
       +                                dprint("%s", buf);
       +                                if(p-buf < l-1)
       +                                        dprint("(sam: more in %s)\n", errfile);
       +                        }
       +                        close(f);
       +                }
       +        }else
       +                remove((char *)errfile);
       +}
 (DIR) diff --git a/sam/string.c b/sam/string.c
       @@ -0,0 +1,179 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include "sam.h"
       +
       +#define        MINSIZE        16                /* minimum number of chars allocated */
       +#define        MAXSIZE        256                /* maximum number of chars for an empty string */
       +
       +
       +void
       +Strinit(String *p)
       +{
       +        p->s = emalloc(MINSIZE*RUNESIZE);
       +        p->n = 0;
       +        p->size = MINSIZE;
       +}
       +
       +void
       +Strinit0(String *p)
       +{
       +        p->s = emalloc(MINSIZE*RUNESIZE);
       +        p->s[0] = 0;
       +        p->n = 1;
       +        p->size = MINSIZE;
       +}
       +
       +void
       +Strclose(String *p)
       +{
       +        free(p->s);
       +}
       +
       +void
       +Strzero(String *p)
       +{
       +        if(p->size > MAXSIZE){
       +                p->s = erealloc(p->s, RUNESIZE*MAXSIZE); /* throw away the garbage */
       +                p->size = MAXSIZE;
       +        }
       +        p->n = 0;
       +}
       +
       +int
       +Strlen(Rune *r)
       +{
       +        Rune *s;
       +
       +        for(s=r; *s; s++)
       +                ;
       +        return s-r;
       +}
       +
       +void
       +Strdupl(String *p, Rune *s)        /* copies the null */
       +{
       +        p->n = Strlen(s)+1;
       +        Strinsure(p, p->n);
       +        memmove(p->s, s, p->n*RUNESIZE);
       +}
       +
       +void
       +Strduplstr(String *p, String *q)        /* will copy the null if there's one there */
       +{
       +        Strinsure(p, q->n);
       +        p->n = q->n;
       +        memmove(p->s, q->s, q->n*RUNESIZE);
       +}
       +
       +void
       +Straddc(String *p, int c)
       +{
       +        Strinsure(p, p->n+1);
       +        p->s[p->n++] = c;
       +}
       +
       +void
       +Strinsure(String *p, ulong n)
       +{
       +        if(n > STRSIZE)
       +                error(Etoolong);
       +        if(p->size < n){        /* p needs to grow */
       +                n += 100;
       +                p->s = erealloc(p->s, n*RUNESIZE);
       +                p->size = n;
       +        }
       +}
       +
       +void
       +Strinsert(String *p, String *q, Posn p0)
       +{
       +        Strinsure(p, p->n+q->n);
       +        memmove(p->s+p0+q->n, p->s+p0, (p->n-p0)*RUNESIZE);
       +        memmove(p->s+p0, q->s, q->n*RUNESIZE);
       +        p->n += q->n;
       +}
       +
       +void
       +Strdelete(String *p, Posn p1, Posn p2)
       +{
       +        memmove(p->s+p1, p->s+p2, (p->n-p2)*RUNESIZE);
       +        p->n -= p2-p1;
       +}
       +
       +int
       +Strcmp(String *a, String *b)
       +{
       +        int i, c;
       +
       +        for(i=0; i<a->n && i<b->n; i++)
       +                if(c = (a->s[i] - b->s[i]))        /* assign = */
       +                        return c;
       +        /* damn NULs confuse everything */
       +        i = a->n - b->n;
       +        if(i == 1){
       +                if(a->s[a->n-1] == 0)
       +                        return 0;
       +        }else if(i == -1){
       +                if(b->s[b->n-1] == 0)
       +                        return 0;
       +        }
       +        return i;
       +}
       +
       +char*
       +Strtoc(String *s)
       +{
       +        int i;
       +        char *c, *d;
       +        Rune *r;
       +        c = emalloc(s->n*UTFmax + 1);  /* worst case UTFmax bytes per rune, plus NUL */
       +        d = c;
       +        r = s->s;
       +        for(i=0; i<s->n; i++)
       +                d += runetochar(d, r++);
       +        if(d==c || d[-1]!=0)
       +                *d = 0;
       +        return c;
       +
       +}
       +
       +/*
       + * Build very temporary String from Rune*
       + */
       +String*
       +tmprstr(Rune *r, int n)
       +{
       +        static String p;
       +
       +        p.s = r;
       +        p.n = n;
       +        p.size = n;
       +        return &p;
       +}
       +
       +/*
       + * Convert null-terminated char* into String
       + */
       +String*
       +tmpcstr(char *s)
       +{
       +        String *p;
       +        Rune *r;
       +        int i, n;
       +
       +        n = utflen(s);        /* don't include NUL */
       +        p = emalloc(sizeof(String));
       +        r = emalloc(n*RUNESIZE);
       +        p->s = r;
       +        for(i=0; i<n; i++,r++)
       +                s += chartorune(r, s);
       +        p->n = n;
       +        p->size = n;
       +        return p;
       +}
       +
       +void
       +freetmpstr(String *s)
       +{
       +        free(s->s);
       +        free(s);
       +}
 (DIR) diff --git a/sam/sys.c b/sam/sys.c
       @@ -0,0 +1,61 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include "sam.h"
       +
       +static int inerror=FALSE;
       +
       +/*
       + * A reasonable interface to the system calls
       + */
       +
       +void
       +resetsys(void)
       +{
       +        inerror = FALSE;
       +}
       +
       +void
       +syserror(char *a)
       +{
       +        char buf[ERRLEN];
       +
       +        if(!inerror){
       +                inerror=TRUE;
       +                errstr(buf);
       +                dprint("%s: ", a);
       +                error_s(Eio, buf);
       +        }
       +}
       +
       +int
       +Read(int f, void *a, int n)
       +{
       +        char buf[ERRLEN];
       +
       +        if(read(f, (char *)a, n)!=n) {
       +                if (lastfile)
       +                        lastfile->state = Readerr;
       +                errstr(buf);
       +                if (downloaded)
       +                        fprint(2, "read error: %s\n", buf);
       +                rescue();
       +                exits("read");
       +        }
       +        return n;
       +}
       +
       +int
       +Write(int f, void *a, int n)
       +{
       +        int m;
       +
       +        if((m=write(f, (char *)a, n))!=n)
       +                syserror("write");
       +        return m;
       +}
       +
       +void
       +Seek(int f, long n, int w)
       +{
       +        if(seek(f, n, w)==-1)
       +                syserror("seek");
       +}
 (DIR) diff --git a/sam/unix.c b/sam/unix.c
       @@ -0,0 +1,220 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include        "sam.h"
       +#include        <sys/stat.h>
       +#include        <sys/wait.h>
       +#include        <signal.h>
       +
       +#ifdef        NEEDVARARG
       +#include        <varargs.h>
       +#else
       +#include        <stdarg.h>
       +#endif
       +
       +Rune        samname[] = { '~', '~', 's', 'a', 'm', '~', '~', 0 };
       +
       +static Rune l1[] = { '{', '[', '(', '<', 0253, 0};
       +static Rune l2[] = { '\n', 0};
       +static Rune l3[] = { '\'', '"', '`', 0};
       +Rune *left[]= { l1, l2, l3, 0};
       +
       +static Rune r1[] = {'}', ']', ')', '>', 0273, 0};
       +static Rune r2[] = {'\n', 0};
       +static Rune r3[] = {'\'', '"', '`', 0};
       +Rune *right[]= { r1, r2, r3, 0};
       +
       +char        RSAM[] = RSAMNAME;
       +char        SAMTERM[] = TERMNAME;
       +char        HOME[] = HOMEDIR;
       +char        TMPDIR[] = TMP;
       +char        SH[] = SHELLNAME;
       +char        SHPATH[] = SHELLPATH;
       +char        RX[] = RXNAME;
       +char        RXPATH[] = RXPATHNAME;
       +char        SAMSAVECMD[] = SAMSAVE;
       +
       +void
       +print_ss(char *s, String *a, String *b)
       +{
       +        char *ap, *bp, *cp;
       +        Rune *rp;
       +
       +        ap = emalloc(a->n+1);
       +        for (cp = ap, rp = a->s; *rp; rp++)
       +                cp += runetochar(cp, rp);
       +        *cp = 0;
       +        bp = emalloc(b->n+1);
       +        for (cp = bp, rp = b->s; *rp; rp++)
       +                cp += runetochar(cp, rp);
       +        *cp = 0;
       +        dprint("?warning: %s `%.*s' and `%.*s'\n", s, a->n, ap, b->n, bp);
       +        free(ap);
       +        free(bp);
       +}
       +
       +void
       +print_s(char *s, String *a)
       +{
       +        char *ap, *cp;
       +        Rune *rp;
       +
       +        ap = emalloc(a->n+1);
       +        for (cp = ap, rp = a->s; *rp; rp++)
       +                cp += runetochar(cp, rp);
       +        *cp = 0;
       +        dprint("?warning: %s `%.*s'\n", s, a->n, ap);
       +        free(ap);
       +}
       +
       +int
       +statfile(char *name, ulong *dev, ulong *id, long *time, long *length, long *appendonly)
       +{
       +        struct stat dirb;
       +
       +        if (stat(name, &dirb) == -1)
       +                return -1;
       +        if (dev)
       +                *dev = dirb.st_dev;
       +        if (id)
       +                *id = dirb.st_ino;
       +        if (time)
       +                *time = dirb.st_mtime;
       +        if (length)
       +                *length = dirb.st_size;
       +        if(appendonly)
       +                *appendonly = 0;
       +        return 1;
       +}
       +
       +int
       +statfd(int fd, ulong *dev, ulong *id, long *time, long *length, long *appendonly)
       +{
       +        struct stat dirb;
       +
       +        if (fstat(fd, &dirb) == -1)
       +                return -1;
       +        if (dev)
       +                *dev = dirb.st_dev;
       +        if (id)
       +                *id = dirb.st_ino;
       +        if (time)
       +                *time = dirb.st_mtime;
       +        if (length)
       +                *length = dirb.st_size;
       +        if(appendonly)
       +                *appendonly = 0;
       +        return 1;
       +}
       +
       +void
       +hup(int sig)
       +{
       +        rescue();
       +        exit(1);
       +}
       +
       +int
       +notify (void(*f)(void *, char *))
       +{
       +        signal(SIGINT, SIG_IGN);
       +        signal(SIGHUP, hup);
       +        signal(SIGPIPE, SIG_IGN);
       +#ifdef        v10
       +        close(3);                /* redirect v10 /dev/tty */
       +        open("/dev/null", 2);
       +#endif
       +        return 1;
       +}
       +
       +void
       +notifyf(void *a, char *b)        /* never called */
       +{
       +}
       +
       +/*
       + *        if your system doesn't have tempnam(), substitute the following
       + *        code for this function:
       + *        FILE *f;
       + *        f = tmpfile();
       + *        if (f == 0)
       + *                return -1;
       + *        return fileno(f);
       + *
       + *        we use tempnam to allow temp files to be allocated in the
       + *        most efficient place; nodes with disks may mount /usr/tmp
       + *        remotely, causing excessive network traffic.  place
       + *        the temp files locally, if possible.
       + */
       +int
       +newtmp(int i)
       +{
       +        char s[1024] = {0};
       +        sprint(s, "%s/sam.XXXXXX", TMPDIR);
       +        int fd = mkstemp(s);
       +        if (fd >= 0)
       +        {
       +                unlink(s);
       +        }
       +        return fd;
       +}
       +
       +void
       +samerr(char *buf)
       +{
       +        sprint(buf, "%s/sam.err.%.6s", TMPDIR, getuser());
       +}
       +
       +int
       +waitfor(int pid)
       +{
       +        int wm;
       +        int rpid;
       +
       +        do; while((rpid = wait(&wm)) != pid && rpid != -1);
       +        return (WEXITSTATUS(wm));
       +}
       +
       +void*
       +emalloc(ulong n)
       +{
       +        void *p;
       +
       +        if (n < sizeof(int))
       +                n = sizeof(int);
       +        p = malloc(n);
       +        if(p == 0)
       +                panic("malloc fails");
       +        memset(p, 0, n);
       +        return p;
       +}
       +
       +void*
       +erealloc(void *p, ulong n)
       +{
       +        p = realloc(p, n);
       +        if(p == 0)
       +                panic("realloc fails");
       +        return p;
       +}
       +
       +void
       +exits(char *message)
       +{
       +
       +        if (message == 0)
       +                exit(0);
       +        else
       +                exit(1);
       +}
       +
       +void
       +dprint(char *z, ...)
       +{
       +        va_list args;
       +        char buf[BLOCKSIZE];
       +
       +        va_start(args, z);
       +        vsprintf(buf, z, args);
       +        termwrite(buf);
       +        va_end(args);
       +}
       +
 (DIR) diff --git a/sam/xec.c b/sam/xec.c
       @@ -0,0 +1,492 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include "sam.h"
       +#include "parse.h"
       +
       +int        Glooping;
       +int        nest;
       +
       +int        append(File*, Cmd*, Posn);
       +int        display(File*);
       +void        looper(File*, Cmd*, int);
       +void        filelooper(Cmd*, int);
       +void        linelooper(File*, Cmd*);
       +
       +void
       +resetxec(void)
       +{
       +        Glooping = nest = 0;
       +}
       +
       +int
       +cmdexec(File *f, Cmd *cp)
       +{
       +        int i;
       +        Addr *ap;
       +        Address a;
       +
       +        if(f && f->state==Unread)
       +                load(f);
       +        if(f==0 && (cp->addr==0 || cp->addr->type!='"') &&
       +            !utfrune("bBnqUXY!", cp->cmdc) &&
       +            cp->cmdc!=('c'|0x100) && !(cp->cmdc=='D' && cp->ctext))
       +                error(Enofile);
       +        i = lookup(cp->cmdc);
       +        if(i >= 0 && cmdtab[i].defaddr != aNo){
       +                if((ap=cp->addr)==0 && cp->cmdc!='\n'){
       +                        cp->addr = ap = newaddr();
       +                        ap->type = '.';
       +                        if(cmdtab[i].defaddr == aAll)
       +                                ap->type = '*';
       +                }else if(ap && ap->type=='"' && ap->next==0 && cp->cmdc!='\n'){
       +                        ap->next = newaddr();
       +                        ap->next->type = '.';
       +                        if(cmdtab[i].defaddr == aAll)
       +                                ap->next->type = '*';
       +                }
       +                if(cp->addr){        /* may be false for '\n' (only) */
       +                        static Address none = {0,0,0};
       +                        if(f)
       +                                addr = address(ap, f->dot, 0);
       +                        else        /* a " */
       +                                addr = address(ap, none, 0);
       +                        f = addr.f;
       +                }
       +        }
       +        current(f);
       +        switch(cp->cmdc){
       +        case '{':
       +                a = cp->addr? address(cp->addr, f->dot, 0): f->dot;
       +                for(cp = cp->ccmd; cp; cp = cp->next){
       +                        a.f->dot = a;
       +                        cmdexec(a.f, cp);
       +                }
       +                break;
       +        default:
       +                i=(*cmdtab[i].fn)(f, cp);
       +                return i;
       +        }
       +        return 1;
       +}
       +
       +
       +int
       +a_cmd(File *f, Cmd *cp)
       +{
       +        return append(f, cp, addr.r.p2);
       +}
       +
       +int
       +b_cmd(File *f, Cmd *cp)
       +{
       +        USED(f);
       +        f = cp->cmdc=='b'? tofile(cp->ctext) : getfile(cp->ctext);
       +        if(f->state == Unread)
       +                load(f);
       +        else if(nest == 0)
       +                filename(f);
       +        return TRUE;
       +}
       +
       +int
       +c_cmd(File *f, Cmd *cp)
       +{
       +        Fdelete(f, addr.r.p1, addr.r.p2);
       +        f->ndot.r.p1 = f->ndot.r.p2 = addr.r.p2;
       +        return append(f, cp, addr.r.p2);
       +}
       +
       +int
       +d_cmd(File *f, Cmd *cp)
       +{
       +        USED(cp);
       +        Fdelete(f, addr.r.p1, addr.r.p2);
       +        f->ndot.r.p1 = f->ndot.r.p2 = addr.r.p1;
       +        return TRUE;
       +}
       +
       +int
       +D_cmd(File *f, Cmd *cp)
       +{
       +        closefiles(f, cp->ctext);
       +        return TRUE;
       +}
       +
       +int
       +e_cmd(File *f, Cmd *cp)
       +{
       +        if(getname(f, cp->ctext, cp->cmdc=='e')==0)
       +                error(Enoname);
       +        edit(f, cp->cmdc);
       +        return TRUE;
       +}
       +
       +int
       +f_cmd(File *f, Cmd *cp)
       +{
       +        getname(f, cp->ctext, TRUE);
       +        filename(f);
       +        return TRUE;
       +}
       +
       +int
       +g_cmd(File *f, Cmd *cp)
       +{
       +        if(f!=addr.f)panic("g_cmd f!=addr.f");
       +        compile(cp->re);
       +        if(execute(f, addr.r.p1, addr.r.p2) ^ cp->cmdc=='v'){
       +                f->dot = addr;
       +                return cmdexec(f, cp->ccmd);
       +        }
       +        return TRUE;
       +}
       +
       +int
       +i_cmd(File *f, Cmd *cp)
       +{
       +        return append(f, cp, addr.r.p1);
       +}
       +
       +int
       +k_cmd(File *f, Cmd *cp)
       +{
       +        USED(cp);
       +        f->mark = addr.r;
       +        return TRUE;
       +}
       +
       +int
       +m_cmd(File *f, Cmd *cp)
       +{
       +        Address addr2;
       +
       +        addr2 = address(cp->caddr, f->dot, 0);
       +        if(cp->cmdc=='m')
       +                move(f, addr2);
       +        else
       +                copy(f, addr2);
       +        return TRUE;
       +}
       +
       +int
       +n_cmd(File *f, Cmd *cp)
       +{
       +        int i;
       +        USED(f);
       +        USED(cp);
       +        for(i = 0; i<file.nused; i++){
       +                if(file.filepptr[i] == cmd)
       +                        continue;
       +                f = file.filepptr[i];
       +                Strduplstr(&genstr, &f->name);
       +                filename(f);
       +        }
       +        return TRUE;
       +}
       +
       +int
       +p_cmd(File *f, Cmd *cp)
       +{
       +        USED(cp);
       +        return display(f);
       +}
       +
       +int
       +q_cmd(File *f, Cmd *cp)
       +{
       +        USED(cp);
       +        USED(f);
       +        trytoquit();
       +        if(downloaded){
       +                outT0(Hexit);
       +                return TRUE;
       +        }
       +        return FALSE;
       +}
       +
       +int
       +s_cmd(File *f, Cmd *cp)
       +{
       +        int i, j, c, n;
       +        Posn p1, op, didsub = 0, delta = 0;
       +
       +        n = cp->num;
       +        op= -1;
       +        compile(cp->re);
       +        for(p1 = addr.r.p1; p1<=addr.r.p2 && execute(f, p1, addr.r.p2); ){
       +                if(sel.p[0].p1==sel.p[0].p2){        /* empty match? */
       +                        if(sel.p[0].p1==op){
       +                                p1++;
       +                                continue;
       +                        }
       +                        p1 = sel.p[0].p2+1;
       +                }else
       +                        p1 = sel.p[0].p2;
       +                op = sel.p[0].p2;
       +                if(--n>0)
       +                        continue;
       +                Strzero(&genstr);
       +                for(i = 0; i<cp->ctext->n; i++)
       +                        if((c = cp->ctext->s[i])=='\\' && i<cp->ctext->n-1){
       +                                c = cp->ctext->s[++i];
       +                                if('1'<=c && c<='9') {
       +                                        j = c-'0';
       +                                        if(sel.p[j].p2-sel.p[j].p1>BLOCKSIZE)
       +                                                error(Elongtag);
       +                                        Fchars(f, genbuf, sel.p[j].p1, sel.p[j].p2);
       +                                        Strinsert(&genstr, tmprstr(genbuf, (sel.p[j].p2-sel.p[j].p1)), genstr.n);
       +                                }else
       +                                         Straddc(&genstr, c);
       +                        }else if(c!='&')
       +                                Straddc(&genstr, c);
       +                        else{
       +                                if(sel.p[0].p2-sel.p[0].p1>BLOCKSIZE)
       +                                        error(Elongrhs);
       +                                Fchars(f, genbuf, sel.p[0].p1, sel.p[0].p2);
       +                                Strinsert(&genstr,
       +                                        tmprstr(genbuf, (int)(sel.p[0].p2-sel.p[0].p1)),
       +                                        genstr.n);
       +                        }
       +                if(sel.p[0].p1!=sel.p[0].p2){
       +                        Fdelete(f, sel.p[0].p1, sel.p[0].p2);
       +                        delta-=sel.p[0].p2-sel.p[0].p1;
       +                }
       +                if(genstr.n){
       +                        Finsert(f, &genstr, sel.p[0].p2);
       +                        delta+=genstr.n;
       +                }
       +                didsub = 1;
       +                if(!cp->flag)
       +                        break;
       +        }
       +        if(!didsub && nest==0)
       +                error(Enosub);
       +        f->ndot.r.p1 = addr.r.p1, f->ndot.r.p2 = addr.r.p2+delta;
       +        return TRUE;
       +}
       +
       +int
       +u_cmd(File *f, Cmd *cp)
       +{
       +        int n;
       +        USED(f);
       +        USED(cp);
       +        n = cp->num;
       +        while(n-- && undo())
       +                ;
       +        return TRUE;
       +}
       +
       +int
       +w_cmd(File *f, Cmd *cp)
       +{
       +        if(getname(f, cp->ctext, FALSE)==0)
       +                error(Enoname);
       +        writef(f);
       +        return TRUE;
       +}
       +
       +int
       +x_cmd(File *f, Cmd *cp)
       +{
       +        if(cp->re)
       +                looper(f, cp, cp->cmdc=='x');
       +        else
       +                linelooper(f, cp);
       +        return TRUE;
       +}
       +
       +int
       +X_cmd(File *f, Cmd *cp)
       +{
       +        USED(f);
       +        filelooper(cp, cp->cmdc=='X');
       +        return TRUE;
       +}
       +
       +int
       +plan9_cmd(File *f, Cmd *cp)
       +{
       +        plan9(f, cp->cmdc, cp->ctext, nest);
       +        return TRUE;
       +}
       +
       +int
       +eq_cmd(File *f, Cmd *cp)
       +{
       +        int charsonly;
       +
       +        switch(cp->ctext->n){
       +        case 1:
       +                charsonly = FALSE;
       +                break;
       +        case 2:
       +                if(cp->ctext->s[0]=='#'){
       +                        charsonly = TRUE;
       +                        break;
       +                }
       +        default:
       +                SET(charsonly);
       +                error(Enewline);
       +        }
       +        printposn(f, charsonly);
       +        return TRUE;
       +}
       +
       +int
       +nl_cmd(File *f, Cmd *cp)
       +{
       +        if(cp->addr == 0){
       +                /* First put it on newline boundaries */
       +                addr = lineaddr((Posn)0, f->dot, -1);
       +                addr.r.p2 = lineaddr((Posn)0, f->dot, 1).r.p2;
       +                if(addr.r.p1==f->dot.r.p1 && addr.r.p2==f->dot.r.p2)
       +                        addr = lineaddr((Posn)1, f->dot, 1);
       +                display(f);
       +        }else if(downloaded)
       +                moveto(f, addr.r);
       +        else
       +                display(f);
       +        return TRUE;
       +}
       +
       +int
       +cd_cmd(File *f, Cmd *cp)
       +{
       +        USED(f);
       +        cd(cp->ctext);
       +        return TRUE;
       +}
       +
       +int
       +append(File *f, Cmd *cp, Posn p)
       +{
       +        if(cp->ctext->n>0 && cp->ctext->s[cp->ctext->n-1]==0)
       +                --cp->ctext->n;
       +        if(cp->ctext->n>0)
       +                Finsert(f, cp->ctext, p);
       +        f->ndot.r.p1 = p;
       +        f->ndot.r.p2 = p+cp->ctext->n;
       +        return TRUE;
       +}
       +
       +int
       +display(File *f)
       +{
       +        Posn p1, p2;
       +        int np, n;
       +        char *c;
       +
       +        p1 = addr.r.p1;
       +        p2 = addr.r.p2;
       +        while(p1 < p2){
       +                np = p2-p1;
       +                if(np>BLOCKSIZE-1)
       +                        np = BLOCKSIZE-1;
       +                n = Fchars(f, genbuf, p1, p1+np);
       +                if(n <= 0)
       +                        panic("display");
       +                genbuf[n] = 0;
       +                c = Strtoc(tmprstr(genbuf, n+1));
       +                if(downloaded)
       +                        termwrite(c);
       +                else
       +                        Write(1, c, strlen(c));
       +                free(c);
       +                p1+=n;
       +        }
       +        f->dot = addr;
       +        return TRUE;
       +}
       +
       +void
       +looper(File *f, Cmd *cp, int xy)
       +{
       +        Posn p, op;
       +        Range r;
       +
       +        r = addr.r;
       +        op= xy? -1 : r.p1;
       +        nest++;
       +        compile(cp->re);
       +        for(p = r.p1; p<=r.p2; ){
       +                if(!execute(f, p, r.p2)){ /* no match, but y should still run */
       +                        if(xy || op>r.p2)
       +                                break;
       +                        f->dot.r.p1 = op, f->dot.r.p2 = r.p2;
       +                        p = r.p2+1;        /* exit next loop */
       +                }else{
       +                        if(sel.p[0].p1==sel.p[0].p2){        /* empty match? */
       +                                if(sel.p[0].p1==op){
       +                                        p++;
       +                                        continue;
       +                                }
       +                                p = sel.p[0].p2+1;
       +                        }else
       +                                p = sel.p[0].p2;
       +                        if(xy)
       +                                f->dot.r = sel.p[0];
       +                        else
       +                                f->dot.r.p1 = op, f->dot.r.p2 = sel.p[0].p1;
       +                }
       +                op = sel.p[0].p2;
       +                cmdexec(f, cp->ccmd);
       +                compile(cp->re);
       +        }
       +        --nest;
       +}
       +
       +void
       +linelooper(File *f, Cmd *cp)
       +{
       +        Posn p;
       +        Range r, linesel;
       +        Address a3;
       +
       +        nest++;
       +        r = addr.r;
       +        a3.f = f;
       +        a3.r.p1 = a3.r.p2 = r.p1;
       +        for(p = r.p1; p<r.p2; p = a3.r.p2){
       +                a3.r.p1 = a3.r.p2;
       +/*pjw                if(p!=r.p1 || (linesel = lineaddr((Posn)0, a3, 1)).r.p2==p)*/
       +                if(p!=r.p1 || ((linesel = lineaddr((Posn)0, a3, 1).r), linesel.p2==p))
       +                        linesel = lineaddr((Posn)1, a3, 1).r;
       +                if(linesel.p1 >= r.p2)
       +                        break;
       +                if(linesel.p2 >= r.p2)
       +                        linesel.p2 = r.p2;
       +                if(linesel.p2 > linesel.p1)
       +                        if(linesel.p1>=a3.r.p2 && linesel.p2>a3.r.p2){
       +                                f->dot.r = linesel;
       +                                cmdexec(f, cp->ccmd);
       +                                a3.r = linesel;
       +                                continue;
       +                        }
       +                break;
       +        }
       +        --nest;
       +}
       +
       +void
       +filelooper(Cmd *cp, int XY)
       +{
       +        File *f, *cur;
       +        int i;
       +
       +        if(Glooping++)
       +                error(EnestXY);
       +        nest++;
       +        settempfile();
       +        cur = curfile;
       +        for(i = 0; i<tempfile.nused; i++){
       +                f = tempfile.filepptr[i];
       +                if(f==cmd)
       +                        continue;
       +                if(cp->re==0 || filematch(f, cp->re)==XY)
       +                        cmdexec(f, cp->ccmd);
       +        }
       +        if(cur && whichmenu(cur)>=0)        /* check that cur is still a file */
       +                current(cur);
       +        --Glooping;
       +        --nest;
       +}
 (DIR) diff --git a/samterm/Makefile b/samterm/Makefile
       @@ -0,0 +1,49 @@
       +#        Copyright (c) 1998 Lucent Technologies - All rights reserved.
       +#
       +#        Prototype Makefile for samterm
       +#
       +#        define operating system.  ONE of:
       +#                -DIRIX -DSUNOS -DUMIPS -DSYSVR3 -DAIX -DOSF1
       +#                -DHPUX -DAPOLLO -DCONVEX -DDYNIX
       +#
       +#        -DIRIX is the default and should actually work on any modern system.
       +#        Additionally, -D_POSIX_SOURCE (or its equivalent) may be specified
       +#        if your compiler supports posix-compatible compilation.
       +#
       +
       +include ../config.mk
       +
       +#        If your system has 64-bit addresses, add -DUSE64BITS to $(OS).
       +OS=-DIRIX5  -DUSE64BITS=$(USE64BITS)
       +
       +#        add -Iincludedir for any include directories that need to be searched
       +#        for posix header files (for UMIPS, add -I/usr/include/posix)
       +INCS=-I../include -I$(FREETYPEINC)
       +
       +#        SAMTERM contains the name of the file containing the samterm
       +#        executable.
       +SAMTERM=$(BINDIR)/samterm
       +
       +#        set this if your X libraries are in different locations
       +#        or if you need extra libraries to load with X11 applications
       +XLIBS=-lXt -lX11 -lXft
       +
       +CFLAGS=$(OS) $(INCS) -D_LIBXG_EXTENSION
       +
       +LIBS=../libframe/libframe.a ../libXg/libXg.a
       +CC=cc
       +
       +OBJ=main.o flayer.o icons.o io.o menu.o mesg.o rasp.o scroll.o unix.o
       +
       +all:        samterm
       +
       +samterm:        $(OBJ) $(LIBS)
       +        $(CC)  -o samterm $(OBJ) $(LIBS) $(XLIBS)
       +
       +clean:
       +        rm -f *.o core samterm
       +
       +install:        samterm
       +        cp samterm $(SAMTERM)
       +
       +$(OBJ):        samterm.h flayer.h ../include/frame.h ../include/libg.h ../include/u.h ../include/libc.h ../sam/mesg.h
 (DIR) diff --git a/samterm/flayer.c b/samterm/flayer.c
       @@ -0,0 +1,436 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include <frame.h>
       +#include "flayer.h"
       +#include "samterm.h"
       +
       +#define        DELTA        10
       +
       +static Flayer        **llist;        /* front to back */
       +static int        nllist;
       +static int        nlalloc;
       +static Rectangle lDrect;
       +
       +extern Bitmap        screen;
       +extern Mouse        mouse;
       +
       +Vis                visibility(Flayer *);
       +void                newvisibilities(int);
       +void                llinsert(Flayer*);
       +void                lldelete(Flayer*);
       +
       +void
       +flstart(Rectangle r)
       +{
       +        lDrect = r;
       +}
       +
       +void
       +flnew(Flayer *l, Rune *(*fn)(Flayer*, long, ulong*), int u0, void *u1)
       +{
       +        if(nllist == nlalloc){
       +                nlalloc += DELTA;
       +                llist = realloc(llist, nlalloc*sizeof(Flayer**));
       +                if(llist == 0)
       +                        panic("flnew");
       +        }
       +        l->textfn = fn;
       +        l->user0 = u0;
       +        l->user1 = u1;
       +        llinsert(l);
       +}
       +
       +Rectangle
       +flrect(Flayer *l, Rectangle r)
       +{
       +        rectclip(&r, lDrect);
       +        l->entire = r;
       +        l->scroll = inset(r, FLMARGIN);
       +        r.min.x =
       +         l->scroll.max.x = r.min.x+FLMARGIN+FLSCROLLWID+(FLGAP-FLMARGIN);
       +        return r;
       +}
       +
       +void
       +flinit(Flayer *l, Rectangle r, XftFont *ft)
       +{
       +        lldelete(l);
       +        llinsert(l);
       +        l->visible = All;
       +        l->origin = l->p0 = l->p1 = 0;
       +        frinit(&l->f, inset(flrect(l, r), FLMARGIN), ft, &screen);
       +        newvisibilities(1);
       +        bitblt(&screen, l->entire.min, &screen, l->entire, 0);
       +        scrdraw(l, 0L);
       +        flborder(l, 0);
       +}
       +
       +void
       +flclose(Flayer *l)
       +{
       +        if(l->visible == All)
       +                bitblt(&screen, l->entire.min, &screen, l->entire, 0);
       +        else if(l->visible == Some){
       +                if(l->f.b == 0)
       +                        l->f.b = balloc(l->entire, screen.ldepth);
       +                if(l->f.b){
       +                        bitblt(l->f.b, l->entire.min, l->f.b, l->entire, 0);
       +                        flrefresh(l, l->entire, 0);
       +                }
       +        }
       +        frclear(&l->f);
       +        lldelete(l);
       +        if(l->f.b && l->visible!=All)
       +                bfree(l->f.b);
       +        l->textfn = 0;
       +        newvisibilities(1);
       +}
       +
       +void
       +flborder(Flayer *l, int wide)
       +{
       +        if(flprepare(l)){
       +                border(l->f.b, l->entire, FLMARGIN, 0);
       +                border(l->f.b, l->entire, wide? FLMARGIN : 1, F&~D);
       +                if(l->visible==Some)
       +                        flrefresh(l, l->entire, 0);
       +        }
       +}
       +
       +Flayer *
       +flwhich(Point p)
       +{
       +        int i;
       +
       +        if(p.x==0 && p.y==0)
       +                return nllist? llist[0] : 0;
       +        for(i=0; i<nllist; i++)
       +                if(ptinrect(p, llist[i]->entire))
       +                        return llist[i];
       +        return 0;
       +}
       +
       +void
       +flupfront(Flayer *l)
       +{
       +        int v = l->visible;
       +
       +        lldelete(l);
       +        llinsert(l);
       +        if(v!=All)
       +                newvisibilities(0);
       +}
       +
       +void
       +newvisibilities(int redraw)
       +        /* if redraw false, we know it's a flupfront, and needn't
       +         * redraw anyone becoming partially covered */
       +{
       +        int i;
       +        Vis ov;
       +        Flayer *l;
       +
       +        for(i = 0; i<nllist; i++){
       +                l = llist[i];
       +                ov = l->visible;
       +                l->visible = visibility(l);
       +#define        V(a, b)        (((a)<<2)|((b)))
       +                switch(V(ov, l->visible)){
       +                case V(Some, None):
       +                        if(l->f.b)
       +                                bfree(l->f.b);
       +                case V(All, None):
       +                case V(All, Some):
       +                        l->f.b = 0;
       +                        frclear(&l->f);
       +                        break;
       +
       +                case V(Some, Some):
       +                        if(l->f.b==0 && redraw)
       +                case V(None, Some):
       +                                flprepare(l);
       +                        if(l->f.b && redraw){
       +                                flrefresh(l, l->entire, 0);
       +                                bfree(l->f.b);
       +                                l->f.b = 0;
       +                                frclear(&l->f);
       +                        }
       +                case V(None, None):
       +                case V(All, All):
       +                        break;
       +
       +                case V(Some, All):
       +                        if(l->f.b){
       +                                bitblt(&screen, l->entire.min, l->f.b, l->entire, S);
       +                                bfree(l->f.b);
       +                                l->f.b = &screen;
       +                                break;
       +                        }
       +                case V(None, All):
       +                        flprepare(l);
       +                        break;
       +                }
       +                if(ov==None && l->visible!=None)
       +                        flnewlyvisible(l);
       +        }
       +}
       +
       +void
       +llinsert(Flayer *l)
       +{
       +        int i;
       +        for(i=nllist; i>0; --i)
       +                llist[i]=llist[i-1];
       +        llist[0]=l;
       +        nllist++;
       +}
       +
       +void
       +lldelete(Flayer *l)
       +{
       +        int i;
       +
       +        for(i=0; i<nllist; i++)
       +                if(llist[i]==l){
       +                        --nllist;
       +                        for(; i<nllist; i++)
       +                                llist[i] = llist[i+1];
       +                        return;
       +                }
       +        panic("lldelete");
       +}
       +
       +void
       +flinsert(Flayer *l, Rune *sp, Rune *ep, long p0)
       +{
       +        if(flprepare(l)){
       +                frinsert(&l->f, sp, ep, p0-l->origin);
       +                scrdraw(l, scrtotal(l));
       +                if(l->visible==Some)
       +                        flrefresh(l, l->entire, 0);
       +        }
       +}
       +
       +void
       +fldelete(Flayer *l, long p0, long p1)
       +{
       +        if(flprepare(l)){
       +                p0 -= l->origin;
       +                if(p0 < 0)
       +                        p0 = 0;
       +                p1 -= l->origin;
       +                if(p1<0)
       +                        p1 = 0;
       +                frdelete(&l->f, p0, p1);
       +                scrdraw(l, scrtotal(l));
       +                if(l->visible==Some)
       +                        flrefresh(l, l->entire, 0);
       +        }
       +}
       +
       +int
       +flselect(Flayer *l)
       +{
       +        int ret = 0;
       +        if(l->visible!=All)
       +                flupfront(l);
       +        frselect(&l->f, &mouse);
       +        if(l->f.p0==l->f.p1){
       +                if(mouse.msec-l->click<Clicktime && l->f.p0+l->origin==l->p0){
       +                        ret = 1;
       +                        l->click = 0;
       +                }else
       +                        l->click = mouse.msec;
       +        }else
       +                l->click = 0;
       +        l->p0 = l->f.p0+l->origin, l->p1 = l->f.p1+l->origin;
       +        return ret;
       +}
       +
       +void
       +flsetselect(Flayer *l, long p0, long p1)
       +{
       +        ulong fp0, fp1;
       +
       +        l->click = 0;
       +        if(l->visible==None || !flprepare(l)){
       +                l->p0 = p0, l->p1 = p1;
       +                return;
       +        }
       +        l->p0 = p0, l->p1 = p1;
       +        flfp0p1(l, &fp0, &fp1);
       +        if(fp0==l->f.p0 && fp1==l->f.p1)
       +                return;
       +        frselectp(&l->f, F&~D);
       +        l->f.p0 = fp0, l->f.p1 = fp1;
       +        frselectp(&l->f, F&~D);
       +        if(l->visible==Some)
       +                flrefresh(l, l->entire, 0);
       +}
       +
       +void
       +flfp0p1(Flayer *l, ulong *pp0, ulong *pp1)
       +{
       +        long p0 = l->p0-l->origin, p1 = l->p1-l->origin;
       +
       +        if(p0 < 0)
       +                p0 = 0;
       +        if(p1 < 0)
       +                p1 = 0;
       +        if(p0 > l->f.nchars)
       +                p0 = l->f.nchars;
       +        if(p1 > l->f.nchars)
       +                p1 = l->f.nchars;
       +        *pp0 = p0;
       +        *pp1 = p1;
       +}
       +
       +Rectangle
       +rscale(Rectangle r, Point old, Point new)
       +{
       +        r.min.x = r.min.x*new.x/old.x;
       +        r.min.y = r.min.y*new.y/old.y;
       +        r.max.x = r.max.x*new.x/old.x;
       +        r.max.y = r.max.y*new.y/old.y;
       +        return r;
       +}
       +
       +void
       +flreshape(Rectangle dr)
       +{
       +        int i;
       +        Flayer *l;
       +        Frame *f;
       +        Rectangle r, olDrect;
       +        int move;
       +
       +        olDrect = lDrect;
       +        lDrect = dr;
       +        move = 0;
       +        /* no moving on rio; must repaint */
       +                bitblt(&screen, lDrect.min, &screen, lDrect, 0);
       +        for(i=0; i<nllist; i++){
       +                l = llist[i];
       +                f = &l->f;
       +                if(move)
       +                        r = raddp(rsubp(l->entire, olDrect.min), dr.min);
       +                else{
       +                        r = raddp(rscale(rsubp(l->entire, olDrect.min),
       +                                sub(olDrect.max, olDrect.min),
       +                                sub(dr.max, dr.min)), dr.min);
       +                        if(l->visible==Some && f->b){
       +                                bfree(f->b);
       +                                frclear(f);
       +                        }
       +                        f->b = 0;
       +                        if(l->visible!=None)
       +                                frclear(f);
       +                }
       +                if(!rectclip(&r, dr))
       +                        panic("flreshape");
       +                if(r.max.x-r.min.x<100)
       +                        r.min.x = dr.min.x;
       +                if(r.max.x-r.min.x<100)
       +                        r.max.x = dr.max.x;
       +                if(r.max.y-r.min.y<2*FLMARGIN+f->font->height)
       +                        r.min.y = dr.min.y;
       +                if(r.max.y-r.min.y<2*FLMARGIN+f->font->height)
       +                        r.max.y = dr.max.y;
       +                if(!move)
       +                        l->visible = None;
       +                frsetrects(f, inset(flrect(l, r), FLMARGIN), f->b);
       +                if(!move && f->b)
       +                        scrdraw(l, scrtotal(l));
       +        }
       +        newvisibilities(1);
       +}
       +
       +int
       +flprepare(Flayer *l)
       +{
       +        Frame *f;
       +        ulong n;
       +        Rune *r;
       +
       +        if(l->visible == None)
       +                return 0;
       +        f = &l->f;
       +        if(f->b == 0){
       +                if(l->visible == All)
       +                        f->b = &screen;
       +                else if((f->b = balloc(l->entire, screen.ldepth))==0)
       +                        return 0;
       +                bitblt(f->b, l->entire.min, f->b, l->entire, 0);
       +                border(f->b, l->entire, l==llist[0]? FLMARGIN : 1, F&~D);
       +                n = f->nchars;
       +                frinit(f, f->entire, f->font, f->b);
       +                r = (*l->textfn)(l, n, &n);
       +                frinsert(f, r, r+n, (ulong)0);
       +                frselectp(f, F&~D);
       +                flfp0p1(l, &l->f.p0, &l->f.p1);
       +                frselectp(f, F&~D);
       +                scrdraw(l, scrtotal(l));
       +        }
       +        return 1;
       +}
       +
       +static        int        somevis, someinvis, justvis;
       +
       +Vis
       +visibility(Flayer *l)
       +{
       +        somevis = someinvis = 0;
       +        justvis = 1;
       +        flrefresh(l, l->entire, 0);
       +        justvis = 0;
       +        if(somevis==0)
       +                return None;
       +        if(someinvis==0)
       +                return All;
       +        return Some;
       +}
       +
       +void
       +flrefresh(Flayer *l, Rectangle r, int i)
       +{
       +        Flayer *t;
       +        Rectangle s;
       +
       +    Top:
       +        if((t=llist[i++]) == l){
       +                if(!justvis)
       +                        bitblt(&screen, r.min, l->f.b, r, S);
       +                somevis = 1;
       +        }else{
       +                if(!rectXrect(t->entire, r))
       +                        goto Top;        /* avoid stacking unnecessarily */
       +                if(t->entire.min.x>r.min.x){
       +                        s = r;
       +                        s.max.x = t->entire.min.x;
       +                        flrefresh(l, s, i);
       +                        r.min.x = t->entire.min.x;
       +                }
       +                if(t->entire.min.y>r.min.y){
       +                        s = r;
       +                        s.max.y = t->entire.min.y;
       +                        flrefresh(l, s, i);
       +                        r.min.y = t->entire.min.y;
       +                }
       +                if(t->entire.max.x<r.max.x){
       +                        s = r;
       +                        s.min.x = t->entire.max.x;
       +                        flrefresh(l, s, i);
       +                        r.max.x = t->entire.max.x;
       +                }
       +                if(t->entire.max.y<r.max.y){
       +                        s = r;
       +                        s.min.y = t->entire.max.y;
       +                        flrefresh(l, s, i);
       +                        r.max.y = t->entire.max.y;
       +                }
       +                /* remaining piece of r is blocked by t; forget about it */
       +                someinvis = 1;
       +        }
       +}
 (DIR) diff --git a/samterm/flayer.h b/samterm/flayer.h
       @@ -0,0 +1,48 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#undef None
       +typedef enum Vis{
       +        None=0,
       +        Some,
       +        All
       +}Vis;
       +
       +enum{
       +        Clicktime=1000                /* one second */
       +};
       +
       +typedef struct Flayer Flayer;
       +
       +struct Flayer
       +{
       +        Frame                f;
       +        long                origin;        /* offset of first char in flayer */
       +        long                p0, p1;
       +        long                click;        /* time at which selection click occurred, in HZ */
       +        Rune                *(*textfn)(Flayer*, long, ulong*);
       +        int                user0;
       +        void                *user1;
       +        Rectangle        entire;
       +        Rectangle        scroll;
       +        Vis                visible;
       +};
       +
       +void        flborder(Flayer*, int);
       +void        flclose(Flayer*);
       +void        fldelete(Flayer*, long, long);
       +void        flfp0p1(Flayer*, ulong*, ulong*);
       +void        flinit(Flayer*, Rectangle, XftFont*);
       +void        flinsert(Flayer*, Rune*, Rune*, long);
       +void        flnew(Flayer*, Rune *(*fn)(Flayer*, long, ulong*), int, void*);
       +int        flprepare(Flayer*);
       +Rectangle flrect(Flayer*, Rectangle);
       +void        flrefresh(Flayer*, Rectangle, int);
       +void        flreshape(Rectangle);
       +int        flselect(Flayer*);
       +void        flsetselect(Flayer*, long, long);
       +void        flstart(Rectangle);
       +void        flupfront(Flayer*);
       +Flayer        *flwhich(Point);
       +
       +#define        FLMARGIN        4
       +#define        FLSCROLLWID        12
       +#define        FLGAP                4
 (DIR) diff --git a/samterm/icons.c b/samterm/icons.c
       @@ -0,0 +1,54 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +
       +Cursor bullseye={
       +        {-7, -7},
       +        {0x1F, 0xF8, 0x3F, 0xFC, 0x7F, 0xFE, 0xFB, 0xDF,
       +         0xF3, 0xCF, 0xE3, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF,
       +         0xFF, 0xFF, 0xFF, 0xFF, 0xE3, 0xC7, 0xF3, 0xCF,
       +         0x7B, 0xDF, 0x7F, 0xFE, 0x3F, 0xFC, 0x1F, 0xF8,},
       +        {0x00, 0x00, 0x0F, 0xF0, 0x31, 0x8C, 0x21, 0x84,
       +         0x41, 0x82, 0x41, 0x82, 0x41, 0x82, 0x7F, 0xFE,
       +         0x7F, 0xFE, 0x41, 0x82, 0x41, 0x82, 0x41, 0x82,
       +         0x21, 0x84, 0x31, 0x8C, 0x0F, 0xF0, 0x00, 0x00,}
       +};
       +Cursor deadmouse={
       +        {-7, -7},
       +        {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
       +         0x00, 0x00, 0x00, 0x0C, 0x00, 0x8E, 0x1D, 0xC7,
       +         0xFF, 0xE3, 0xFF, 0xF3, 0xFF, 0xFF, 0x7F, 0xFE, 
       +         0x3F, 0xF8, 0x17, 0xF0, 0x03, 0xE0, 0x00, 0x00,},
       +        {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
       +         0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0x00, 0x82,
       +         0x04, 0x41, 0xFF, 0xE1, 0x5F, 0xF1, 0x3F, 0xFE, 
       +         0x17, 0xF0, 0x03, 0xE0, 0x00, 0x00, 0x00, 0x00,}
       +};
       +Cursor lockarrow={
       +        {-7, -7},
       +        {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
       +         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
       +         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
       +         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,},
       +        {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
       +         0x00, 0x00, 0x00, 0x00, 0x0F, 0xC0, 0x0F, 0xC0,
       +         0x03, 0xC0, 0x07, 0xC0, 0x0E, 0xC0, 0x1C, 0xC0,
       +         0x38, 0x00, 0x70, 0x00, 0xE0, 0xDB, 0xC0, 0xDB,}
       +};
       +
       +uchar darkgreybits[] = {
       +        0xDD, 0xDD, 0x77, 0x77, 0xDD, 0xDD, 0x77, 0x77,
       +        0xDD, 0xDD, 0x77, 0x77, 0xDD, 0xDD, 0x77, 0x77,
       +        0xDD, 0xDD, 0x77, 0x77, 0xDD, 0xDD, 0x77, 0x77,
       +        0xDD, 0xDD, 0x77, 0x77, 0xDD, 0xDD, 0x77, 0x77,
       +};
       +
       +Bitmap        *darkgrey;
       +
       +void
       +iconinit(void)
       +{
       +        darkgrey = balloc(Rect(0, 0, 16, 16), 0);
       +        wrbitmap(darkgrey, 0, 16, darkgreybits);
       +}
 (DIR) diff --git a/samterm/io.c b/samterm/io.c
       @@ -0,0 +1,204 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include <frame.h>
       +#include "flayer.h"
       +#include "samterm.h"
       +
       +int        cursorfd;
       +int        input;
       +int        got;
       +int        block;
       +int        kbdc;
       +int        reshaped;
       +uchar        *hostp;
       +uchar        *hoststop;
       +uchar        *externbase;
       +uchar        *externp;
       +uchar        *externstop;
       +void        panic(char*);
       +
       +void
       +initio(void){
       +        einit(Emouse|Ekeyboard);
       +        estart(Ehost, 0, 0);
       +        extstart();
       +}
       +
       +void
       +frgetmouse(void)
       +{
       +        mouse = emouse();
       +}
       +
       +void
       +mouseunblock(void)
       +{
       +        got &= ~Emouse;
       +}
       +
       +void
       +kbdblock(void)
       +{                /* ca suffit */
       +        block = Ekeyboard|Eextern;
       +}
       +
       +int
       +button(int but)
       +{
       +        frgetmouse();
       +        return mouse.buttons&(1<<(but-1));
       +}
       +
       +void
       +externload(Event *e)
       +{
       +        externbase = malloc(e->n);
       +        if(externbase == 0)
       +                return;
       +        memmove(externbase, e->data, e->n);
       +        externp = externbase;
       +        externstop = externbase + e->n;
       +        got |= Eextern;
       +}
       +
       +int
       +waitforio(void)
       +{
       +        ulong type;
       +        static Event e;
       +
       +        if(got & ~block)
       +                return got & ~block;
       +        type = eread(~(got|block), &e);
       +        switch(type){
       +        case Ehost:
       +                hostp = e.data;
       +                hoststop = hostp + e.n;
       +                block = 0;
       +                break;
       +        case Eextern:
       +                externload(&e);
       +                break;
       +        case Ekeyboard:
       +                kbdc = e.kbdc;
       +                break;
       +        case Emouse:
       +                mouse = e.mouse;
       +                break;
       +        }
       +        got |= type;
       +        return got; 
       +}
       +
       +int
       +rcvchar(void)
       +{
       +        int c;
       +
       +        if(!(got & Ehost))
       +                return -1;
       +        c = *hostp++;
       +        if(hostp == hoststop)
       +                got &= ~Ehost;
       +        return c;
       +}
       +
       +char*
       +rcvstring(void)
       +{
       +        *hoststop = 0;
       +        got &= ~Ehost;
       +        return (char*)hostp;
       +}
       +
       +int
       +getch(void)
       +{
       +        int c;
       +
       +        while((c = rcvchar()) == -1){
       +                block = ~Ehost;
       +                waitforio();
       +                block = 0;
       +        }
       +        return c;
       +}
       +
       +int
       +externchar(void)
       +{
       +        Rune r;
       +
       +    loop:
       +        if(got & (Eextern & ~block)){
       +                externp += chartorune(&r, (char*)externp);
       +                if(externp >= externstop){
       +                        got &= ~Eextern;
       +                        free(externbase);
       +                }
       +                if(r == 0)
       +                        goto loop;
       +                return r;
       +        }
       +        return -1;
       +}
       +
       +int
       +kbdchar(void)
       +{
       +        int c;
       +        static Event e;
       +
       +        c = externchar();
       +        if(c > 0)
       +                return c;
       +        if(got & Ekeyboard){
       +                c = kbdc;
       +                kbdc = -1;
       +                got &= ~Ekeyboard;
       +                return c;
       +        }
       +        while(ecanread(Eextern)){
       +                eread(Eextern, &e);
       +                externload(&e);
       +                c = externchar();
       +                if(c > 0)
       +                        return c;
       +        }
       +        if(!ecankbd())
       +                return -1;
       +        return ekbd();
       +}
       +
       +int
       +qpeekc(void)
       +{
       +        return kbdc;
       +}
       +
       +void
       +ereshaped(Rectangle r)
       +{
       +        USED(r);
       +
       +        reshaped = 1;
       +}
       +
       +int
       +RESHAPED(void)
       +{
       +        if(reshaped){
       +                screen.r = bscreenrect(&screen.clipr);
       +                reshaped = 0;
       +                return 1;
       +        }
       +        return 0;
       +}
       +
       +void
       +mouseexit(void)
       +{
       +        exits(0);
       +}
 (DIR) diff --git a/samterm/main.c b/samterm/main.c
       @@ -0,0 +1,592 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include <frame.h>
       +#include "flayer.h"
       +#include "samterm.h"
       +
       +Text        cmd;
       +Rune        *scratch;
       +long        nscralloc;
       +Cursor        *cursor;
       +extern Bitmap        screen;
       +Mouse        mouse;
       +Flayer        *which = 0;
       +Flayer        *work = 0;
       +long        snarflen;
       +long        typestart = -1;
       +long        typeend = -1;
       +long        typeesc = -1;
       +long        modified = 0;                /* strange lookahead for menus */
       +char        lock = 1;
       +char        hasunlocked = 0;
       +char *machine = "localhost";
       +
       +void
       +main(int argc, char *argv[])
       +{
       +        int i, got, scr;
       +        Text *t;
       +        Rectangle r;
       +        Flayer *nwhich;
       +
       +        int fwdbut;
       +
       +        if (argc >= 3 && strcmp(argv[1], "-r") == 0)
       +        {
       +                machine = argv[2];
       +        }
       +
       +        getscreen(argc, argv);
       +        fwdbut = scrollfwdbut();
       +        iconinit();
       +        initio();
       +        scratch = alloc(100*RUNESIZE);
       +        nscralloc = 100;
       +        r = screen.r;
       +        r.max.y = r.min.y+Dy(r)/5;
       +        flstart(screen.clipr);
       +        rinit(&cmd.rasp);
       +        flnew(&cmd.l[0], stgettext, 1, &cmd);
       +        flinit(&cmd.l[0], r, font);
       +        cmd.nwin = 1;
       +        which = &cmd.l[0];
       +        cmd.tag = Untagged;
       +        outTs(Tversion, VERSION);
       +        startnewfile(Tstartcmdfile, &cmd);
       +
       +        got = 0;
       +        for(;;got = waitforio()){
       +                if(hasunlocked && RESHAPED())
       +                        reshape();
       +                if(got&RHost)
       +                        rcv();
       +                if(got&RExtern){
       +                        for(i=0; cmd.l[i].textfn==0; i++)
       +                                ;
       +                        current(&cmd.l[i]);
       +                        flsetselect(which, cmd.rasp.nrunes, cmd.rasp.nrunes);
       +                        type(which, RExtern);
       +                }
       +                if(got&RKeyboard)
       +                        if(which)
       +                                type(which, RKeyboard);
       +                        else
       +                                kbdblock();
       +                if(got&RMouse){
       +                        if(lock==2 || !ptinrect(mouse.xy, screen.r)){
       +                                mouseunblock();
       +                                continue;
       +                        }
       +                        nwhich = flwhich(mouse.xy);
       +                        scr = which && ptinrect(mouse.xy, which->scroll);
       +                        if(mouse.buttons)
       +                                flushtyping(1);
       +                        if(mouse.buttons&1){
       +                                if(nwhich){
       +                                        if(nwhich!=which)
       +                                                current(nwhich);
       +                                        else if(scr)
       +                                                scroll(which, 1, fwdbut == 3 ? 1 : 3);
       +                                        else{
       +                                                t=(Text *)which->user1;
       +                                                if(flselect(which)){
       +                                                        outTsl(Tdclick, t->tag, which->p0);
       +                                                        t->lock++;
       +                                                }else if(t!=&cmd)
       +                                                        outcmd();
       +                                        }
       +                                }
       +                        }else if((mouse.buttons&2) && which){
       +                                if(scr)
       +                                        scroll(which, 2, 2);
       +                                else
       +                                        menu2hit();
       +                        }else if((mouse.buttons&4)){
       +                                if(scr)
       +                                        scroll(which, 3, fwdbut == 3 ? 3 : 1);
       +                                else
       +                                        menu3hit();
       +                        }
       +                        mouseunblock();
       +                }
       +        }
       +}
       +
       +
       +void
       +reshape(void){
       +        int i;
       +
       +        flreshape(screen.clipr);
       +        for(i = 0; i<nname; i++)
       +                if(text[i])
       +                        hcheck(text[i]->tag);
       +}
       +
       +void
       +current(Flayer *nw)
       +{
       +        Text *t;
       +
       +        if(which)
       +                flborder(which, 0);
       +        if(nw){
       +                flushtyping(1);
       +                flupfront(nw);
       +                flborder(nw, 1);
       +                buttons(Up);
       +                t = (Text *)nw->user1;
       +                t->front = nw-&t->l[0];
       +                if(t != &cmd)
       +                        work = nw;
       +        }
       +        which = nw;
       +}
       +
       +void
       +closeup(Flayer *l)
       +{
       +        Text *t=(Text *)l->user1;
       +        int m;
       +
       +        m = whichmenu(t->tag);
       +        if(m < 0)
       +                return;
       +        flclose(l);
       +        if(l == which){
       +                which = 0;
       +                current(flwhich(Pt(0, 0)));
       +        }
       +        if(l == work)
       +                work = 0;
       +        if(--t->nwin == 0){
       +                rclear(&t->rasp);
       +                free((uchar *)t);
       +                text[m] = 0;
       +        }else if(l == &t->l[t->front]){
       +                for(m=0; m<NL; m++)        /* find one; any one will do */
       +                        if(t->l[m].textfn){
       +                                t->front = m;
       +                                return;
       +                        }
       +                panic("close");
       +        }
       +}
       +
       +Flayer *
       +findl(Text *t)
       +{
       +        int i;
       +        for(i = 0; i<NL; i++)
       +                if(t->l[i].textfn==0)
       +                        return &t->l[i];
       +        return 0;
       +}
       +
       +void
       +duplicate(Flayer *l, Rectangle r, XftFont *f, int close)
       +{
       +        Text *t=(Text *)l->user1;
       +        Flayer *nl = findl(t);
       +        Rune *rp;
       +        ulong n;
       +
       +        if(nl){
       +                flnew(nl, stgettext, l->user0, (char *)t);
       +                flinit(nl, r, f);
       +                nl->origin = l->origin;
       +                rp = (*l->textfn)(l, l->f.nchars, &n);
       +                flinsert(nl, rp, rp+n, l->origin);
       +                flsetselect(nl, l->p0, l->p1);
       +                if(close){
       +                        flclose(l);
       +                        if(l==which)
       +                                which = 0;
       +                }else
       +                        t->nwin++;
       +                current(nl);
       +                hcheck(t->tag);
       +        }
       +        cursorswitch(cursor);
       +}
       +
       +void
       +buttons(int updown)
       +{
       +        while(((mouse.buttons&7)!=0) != updown)
       +                frgetmouse();
       +}
       +
       +int
       +getr(Rectangle *rp)
       +{
       +        Point p;
       +        Rectangle r;
       +
       +        *rp = getrect(3, &mouse);
       +        if(rp->max.x && rp->max.x-rp->min.x<=5 && rp->max.y-rp->min.y<=5){
       +                p = rp->min;
       +                r = cmd.l[cmd.front].entire;
       +                *rp = screen.r;
       +                if(cmd.nwin==1){
       +                        if (p.y <= r.min.y)
       +                                rp->max.y = r.min.y;
       +                        else if (p.y >= r.max.y)
       +                                rp->min.y = r.max.y;
       +                        if (p.x <= r.min.x)
       +                                rp->max.x = r.min.x;
       +                        else if (p.x >= r.max.x)
       +                                rp->min.x = r.max.x;
       +                }
       +        }
       +        return rectclip(rp, screen.r) &&
       +           rp->max.x-rp->min.x>100 && rp->max.y-rp->min.y>40;
       +}
       +
       +void
       +snarf(Text *t, int w)
       +{
       +        Flayer *l = &t->l[w];
       +
       +        if(l->p1>l->p0){
       +                snarflen = l->p1-l->p0;
       +                outTsll(Tsnarf, t->tag, l->p0, l->p1);
       +        }
       +}
       +
       +void
       +cut(Text *t, int w, int save, int check)
       +{
       +        long p0, p1;
       +        Flayer *l;
       +
       +        l = &t->l[w];
       +        p0 = l->p0;
       +        p1 = l->p1;
       +        if(p0 == p1)
       +                return;
       +        if(p0 < 0)
       +                panic("cut");
       +        if(save)
       +                snarf(t, w);
       +        outTsll(Tcut, t->tag, p0, p1);
       +        flsetselect(l, p0, p0);
       +        t->lock++;
       +        hcut(t->tag, p0, p1-p0);
       +        if(check)
       +                hcheck(t->tag);
       +}
       +
       +void
       +paste(Text *t, int w)
       +{
       +        if(snarflen){
       +                cut(t, w, 0, 0);
       +                t->lock++;
       +                outTsl(Tpaste, t->tag, t->l[w].p0);
       +        }
       +}
       +
       +void
       +scrorigin(Flayer *l, int but, long p0)
       +{
       +        Text *t=(Text *)l->user1;
       +
       +        switch(but){
       +        case 1:
       +                outTsll(Torigin, t->tag, l->origin, p0);
       +                break;
       +        case 2:
       +                outTsll(Torigin, t->tag, p0, 1L);
       +                break;
       +        case 3:
       +                horigin(t->tag,p0);
       +        }
       +}
       +
       +int
       +alnum(int c)
       +{
       +        /*
       +         * Hard to get absolutely right.  Use what we know about ASCII
       +         * and assume anything above the Latin control characters is
       +         * potentially an alphanumeric.
       +         */
       +        if(c<=' ')
       +                return 0;
       +        if(0x7F<=c && c<=0xA0)
       +                return 0;
       +        if(utfrune("!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~", c))
       +                return 0;
       +        return 1;
       +}
       +
       +int
       +raspc(Rasp *r, long p)
       +{
       +        ulong n;
       +        rload(r, p, p+1, &n);
       +        if(n)
       +                return scratch[0];
       +        return 0;
       +}
       +
       +long
       +ctlw(Rasp *r, long o, long p)
       +{
       +        int c;
       +
       +        if(--p < o)
       +                return o;
       +        if(raspc(r, p)=='\n')
       +                return p;
       +        for(; p>=o && !alnum(c=raspc(r, p)); --p)
       +                if(c=='\n')
       +                        return p+1;
       +        for(; p>o && alnum(raspc(r, p-1)); --p)
       +                ;
       +        return p>=o? p : o;
       +}
       +
       +long
       +ctlu(Rasp *r, long o, long p)
       +{
       +        for(; p-1>=o && raspc(r, p-1)!='\n'; --p)
       +                ;
       +        return p>=o? p : o;
       +}
       +
       +int
       +center(Flayer *l, long a)
       +{
       +        Text *t;
       +
       +        t = l->user1;
       +        if(!t->lock && (a<l->origin || l->origin+l->f.nchars<a)){
       +                if(a > t->rasp.nrunes)
       +                        a = t->rasp.nrunes;
       +                outTsll(Torigin, t->tag, a, 2L);
       +                return 1;
       +        }
       +        return 0;
       +}
       +
       +int
       +onethird(Flayer *l, long a)
       +{
       +        Text *t;
       +        Rectangle s;
       +        long lines;
       +
       +        t = l->user1;
       +        if(!t->lock && (a<l->origin || l->origin+l->f.nchars<a)){
       +                if(a > t->rasp.nrunes)
       +                        a = t->rasp.nrunes;
       +                s = inset(l->scroll, 1);
       +                lines = ((s.max.y-s.min.y)/l->f.font->height+1)/3;
       +                if (lines < 2)
       +                        lines = 2;
       +                outTsll(Torigin, t->tag, a, lines);
       +                return 1;
       +        }
       +        return 0;
       +}
       +
       +
       +int
       +XDisplay(Display *);
       +
       +extern Display * _dpy;
       +
       +void
       +flushtyping(int clearesc)
       +{
       +        Text *t;
       +        ulong n;
       +
       +        if(clearesc)
       +                typeesc = -1;        
       +        if(typestart == typeend) {
       +                modified = 0;
       +                return;
       +        }
       +        t = which->user1;
       +        if(t != &cmd)
       +                modified = 1;
       +        rload(&t->rasp, typestart, typeend, &n);
       +        scratch[n] = 0;
       +        if(t==&cmd && typeend==t->rasp.nrunes && scratch[typeend-typestart-1]=='\n'){
       +                setlock();
       +                outcmd();
       +        }
       +        outTslS(Ttype, t->tag, typestart, scratch);
       +        typestart = -1;
       +        typeend = -1;
       +        XFlush(_dpy);
       +}
       +
       +#define        SCROLLKEY        0x80
       +#define        UPKEY                0x81
       +#define        ESC                0x1B
       +
       +void
       +type(Flayer *l, int res)        /* what a bloody mess this is */
       +{
       +        Text *t = (Text *)l->user1;
       +        Rune buf[100];
       +        Rune *p = buf;
       +        int c, backspacing;
       +        long a;
       +        int scrollkey, upkey;
       +
       +        scrollkey = 0;
       +        upkey = 0;
       +        if(res == RKeyboard) {
       +                scrollkey = qpeekc()==SCROLLKEY;        /* ICK */
       +                upkey = qpeekc() == UPKEY;
       +        }
       +
       +        if(lock || t->lock){
       +                kbdblock();
       +                return;
       +        }
       +        a = l->p0;
       +        if(a!=l->p1 && !scrollkey && !upkey){
       +                flushtyping(1);
       +                cut(t, t->front, 1, 1);
       +                return;        /* it may now be locked */
       +        }
       +        backspacing = 0;
       +        while((c = kbdchar())>0){
       +                if(res == RKeyboard){
       +                        if(c == UPKEY || c==SCROLLKEY || c==ESC)
       +                                break;
       +                        /* backspace, ctrl-u, ctrl-w, del */
       +                        if(c=='\b' || c==0x15 || c==0x17 || c==0x7F){
       +                                backspacing = 1;
       +                                break;
       +                        }
       +                }
       +                *p++ = c;
       +                if(c == '\n' || p >= buf+sizeof(buf)/sizeof(buf[0]))
       +                        break;
       +        }
       +        if(p > buf){
       +                if(typestart < 0)
       +                        typestart = a;
       +                if(typeesc < 0)
       +                        typeesc = a;
       +                hgrow(t->tag, a, p-buf, 0);
       +                t->lock++;        /* pretend we Trequest'ed for hdatarune*/
       +                hdatarune(t->tag, a, buf, p-buf);
       +                a += p-buf;
       +                l->p0 = a;
       +                l->p1 = a;
       +                typeend = a;
       +                if(c=='\n' || typeend-typestart>100)
       +                        flushtyping(0);
       +                onethird(l, a);
       +        }
       +        if(c == SCROLLKEY){
       +                flushtyping(0);
       +                center(l, l->origin+l->f.nchars+1);
       +        } else if (c == UPKEY) {
       +                flushtyping(0);
       +                outTsll(Torigin, t->tag, l->origin, l->f.maxlines+1);
       +                /* backspacing immediately after outcmd(): sorry */
       +        }else if(backspacing && !lock){
       +                if(l->f.p0>0 && a>0){
       +                        switch(c){
       +                        case '\b':
       +                        case 0x7F:        /* del */
       +                                l->p0 = a-1;
       +                                break;
       +                        case 0x15:        /* ctrl-u */
       +                                l->p0 = ctlu(&t->rasp, l->origin, a);
       +                                break;
       +                        case 0x17:        /* ctrl-w */
       +                                l->p0 = ctlw(&t->rasp, l->origin, a);
       +                                break;
       +                        }
       +                        l->p1 = a;
       +                        if(l->p1 != l->p0){
       +                                /* cut locally if possible */
       +                                if(typestart<=l->p0 && l->p1<=typeend){
       +                                        t->lock++;        /* to call hcut */
       +                                        hcut(t->tag, l->p0, l->p1-l->p0);
       +                                        /* hcheck is local because we know rasp is contiguous */
       +                                        hcheck(t->tag);
       +                                }else{
       +                                        flushtyping(0);
       +                                        cut(t, t->front, 0, 1);
       +                                }
       +                        }
       +                        if(typeesc >= l->p0)
       +                                typeesc = l->p0;
       +                        if(typestart >= 0){
       +                                if(typestart >= l->p0)
       +                                        typestart = l->p0;
       +                                typeend = l->p0;
       +                                if(typestart == typeend){
       +                                        typestart = -1;
       +                                        typeend = -1;
       +                                        modified = 0;
       +                                }
       +                        }
       +                }
       +        }else{
       +                if(c==ESC && typeesc>=0){
       +                        l->p0 = typeesc;
       +                        l->p1 = a;
       +                        flushtyping(1);
       +                }
       +                for(l=t->l; l<&t->l[NL]; l++)
       +                        if(l->textfn)
       +                                flsetselect(l, l->p0, l->p1);
       +        }
       +}
       +
       +
       +void
       +outcmd(void){
       +        if(work)
       +                outTsll(Tworkfile, ((Text *)work->user1)->tag, work->p0, work->p1);
       +}
       +
       +void
       +panic(char *s)
       +{
       +        fprint(2, "samterm:panic: ");
       +        perror(s);
       +        abort();
       +}
       +
       +Rune*
       +stgettext(Flayer *l, long n, ulong *np)
       +{
       +        Text *t;
       +
       +        t = l->user1;
       +        rload(&t->rasp, l->origin, l->origin+n, np);
       +        return scratch;
       +}
       +
       +long
       +scrtotal(Flayer *l)
       +{
       +        return ((Text *)l->user1)->rasp.nrunes;
       +}
       +
       +void*
       +alloc(ulong n)
       +{
       +        void *p;
       +
       +        p = malloc(n);
       +        if(p == 0)
       +                panic("alloc");
       +        memset(p, 0, n);
       +        return p;
       +}
 (DIR) diff --git a/samterm/menu.c b/samterm/menu.c
       @@ -0,0 +1,380 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include <frame.h>
       +#include "flayer.h"
       +#include "samterm.h"
       +
       +uchar        *name[MAXFILES];        /* first byte is ' ' or '\'': modified state */
       +Text        *text[MAXFILES];        /* pointer to Text associated with file */
       +ushort        tag[MAXFILES];                /* text[i].tag, even if text[i] not defined */
       +int        nname;
       +int        mw;
       +
       +char        *genmenu3(int);
       +char        *genmenu2(int);
       +char        *genmenu2c(int);
       +
       +enum Menu2
       +{
       +        Cut,
       +        Paste,
       +        Snarf,
       +        Look,
       +        Exch,
       +        Search,
       +        NMENU2 = Search,
       +        Send = Search,
       +        NMENU2C
       +};
       +
       +enum Menu3
       +{
       +        New,
       +        Zerox,
       +        Reshape,
       +        Close,
       +        Write,
       +        NMENU3
       +};
       +
       +char        *menu2str[] = {
       +        "cut",
       +        "paste",
       +        "snarf",
       +        "look",
       +        "<exch>",
       +        0,                /* storage for last pattern */
       +};
       +
       +char        *menu3str[] = {
       +        "new",
       +        "zerox",
       +        "reshape",
       +        "close",
       +        "write",
       +};
       +
       +Menu        menu2 =        {0, genmenu2};
       +Menu        menu2c ={0, genmenu2c};
       +Menu        menu3 =        {0, genmenu3};
       +
       +void
       +menu2hit(void)
       +{
       +        Text *t=(Text *)which->user1;
       +        int w = which-t->l;
       +        int m;
       +
       +        m = menuhit(2, &mouse, t==&cmd? &menu2c : &menu2);
       +        if(lock || t->lock)
       +                return;
       +
       +        switch(m){
       +        case Cut:
       +                cut(t, w, 1, 1);
       +                break;
       +
       +        case Paste:
       +                paste(t, w);
       +                break;
       +
       +        case Snarf:
       +                snarf(t, w);
       +                break;
       +
       +        case Exch:
       +                snarf(t, w);
       +                outT0(Tstartsnarf);
       +                setlock();
       +                break;
       +
       +        case Look:
       +                outTsll(Tlook, t->tag, which->p0, which->p1);
       +                setlock();
       +                break;
       +
       +        case Search:
       +                outcmd();
       +                if(t==&cmd)
       +                        outTsll(Tsend, 0 /*ignored*/, which->p0, which->p1);
       +                else
       +                        outT0(Tsearch);
       +                setlock();
       +                break;
       +        }
       +}
       +
       +void
       +menu3hit(void)
       +{
       +        Rectangle r;
       +        Flayer *l;
       +        int m, i;
       +        Text *t;
       +
       +        mw = -1;
       +        switch(m = menuhit(3, &mouse, &menu3)){
       +        case -1:
       +                break;
       +
       +        case New:
       +                if(!lock)
       +                        sweeptext(1, 0);
       +                break;
       +
       +        case Zerox:
       +        case Reshape:
       +                if(!lock){
       +                        cursorswitch(&bullseye);
       +                        buttons(Down);
       +                        if((mouse.buttons&4) && (l = flwhich(mouse.xy)) && getr(&r))
       +                                duplicate(l, r, l->f.font, m==Reshape);
       +                        else
       +                                cursorswitch(cursor);
       +                        buttons(Up);
       +                }
       +                break;
       +
       +        case Close:
       +                if(!lock){
       +                        cursorswitch(&bullseye);
       +                        buttons(Down);
       +                        if((mouse.buttons&4) && (l = flwhich(mouse.xy)) && !lock){
       +                                t=(Text *)l->user1;
       +                                if (t->nwin>1)
       +                                        closeup(l);
       +                                else if(t!=&cmd) {
       +                                        outTs(Tclose, t->tag);
       +                                        setlock();
       +                                }
       +                        }
       +                        cursorswitch(cursor);
       +                        buttons(Up);
       +                }
       +                break;
       +
       +        case Write:
       +                if(!lock){
       +                        cursorswitch(&bullseye);
       +                        buttons(Down);
       +                        if((mouse.buttons&4) && (l = flwhich(mouse.xy))){
       +                                outTs(Twrite, ((Text *)l->user1)->tag);
       +                                setlock();
       +                        }else
       +                                cursorswitch(cursor);
       +                        buttons(Up);
       +                }
       +                break;
       +
       +        default:
       +                if(t = text[m-NMENU3]){
       +                        i = t->front;
       +                        if(t->nwin==0 || t->l[i].textfn==0)
       +                                return;        /* not ready yet; try again later */
       +                        if(t->nwin>1 && which==&t->l[i])
       +                                do
       +                                        if(++i==NL)
       +                                                i = 0;
       +                                while(i!=t->front && t->l[i].textfn==0);
       +                        current(&t->l[i]);
       +                }else if(!lock)
       +                        sweeptext(0, tag[m-NMENU3]);
       +                break;
       +        }
       +}
       +
       +
       +Text *
       +sweeptext(int new, int tag)
       +{
       +        Rectangle r;
       +        Text *t;
       +
       +        if(getr(&r) && (t = malloc(sizeof(Text)))){
       +                memset((void*)t, 0, sizeof(Text));
       +                current((Flayer *)0);
       +                flnew(&t->l[0], stgettext, 0, (char *)t);
       +                flinit(&t->l[0], r, font);        /*bnl*/
       +                t->nwin = 1;
       +                rinit(&t->rasp);
       +                if(new)
       +                        startnewfile(Tstartnewfile, t);
       +                else{
       +                        rinit(&t->rasp);
       +                        t->tag = tag;
       +                        startfile(t);
       +                }
       +                return t;
       +        }
       +        return 0;
       +}
       +
       +int
       +whichmenu(int tg)
       +{
       +        int i;
       +
       +        for(i=0; i<nname; i++)
       +                if(tag[i] == tg)
       +                        return i;
       +        return -1;
       +}
       +
       +void
       +menuins(int n, uchar *s, Text *t, int m, int tg)
       +{
       +        int i;
       +
       +        if(nname == MAXFILES)
       +                panic("menuins");
       +        for(i=nname; i>n; --i)
       +                name[i]=name[i-1], text[i]=text[i-1], tag[i]=tag[i-1];
       +        text[n] = t;
       +        tag[n] = tg;
       +        name[n] = alloc(strlen((char*)s)+2);
       +        name[n][0] = m;
       +        strcpy((char*)name[n]+1, (char*)s);
       +        nname++;
       +        menu3.lasthit = n+NMENU3;
       +}
       +
       +void
       +menudel(int n)
       +{
       +        int i;
       +
       +        if(nname==0 || n>=nname || text[n])
       +                panic("menudel");
       +        free(name[n]);
       +        --nname;
       +        for(i = n; i<nname; i++)
       +                name[i]=name[i+1], text[i]=text[i+1], tag[i]=tag[i+1];
       +}
       +
       +void
       +setpat(char *s)
       +{
       +        static char pat[17];
       +
       +        pat[0] = '/';
       +        strncpy(pat+1, s, 15);
       +        menu2str[Search] = pat;
       +}
       +
       +#define        NBUF        64
       +static uchar buf[NBUF*UTFmax]={' ', ' ', ' ', ' '};
       +
       +char *
       +paren(char *s)
       +{
       +        uchar *t = buf;
       +
       +        *t++ = '(';
       +        do; while(*t++ = *s++);
       +        t[-1] = ')';
       +        *t = 0;
       +        return (char *)buf;
       +}
       +char*
       +genmenu2(int n)
       +{
       +        Text *t=(Text *)which->user1;
       +        char *p;
       +        if(n>=NMENU2+(menu2str[Search]!=0))
       +                return 0;
       +        p = menu2str[n];
       +        if(!lock && !t->lock || n==Search || n==Look)
       +                return p;
       +        return paren(p);
       +}
       +char*
       +genmenu2c(int n)
       +{
       +        Text *t=(Text *)which->user1;
       +        char *p;
       +        if(n >= NMENU2C)
       +                return 0;
       +        if(n == Send)
       +                p="send";
       +        else
       +                p = menu2str[n];
       +        if(!lock && !t->lock)
       +                return p;
       +        return paren(p);
       +}
       +char *
       +genmenu3(int n)
       +{
       +        Text *t;
       +        int c, i, k, l, w;
       +        Rune r;
       +        char *p;
       +
       +        if(n >= NMENU3+nname)
       +                return 0;
       +        if(n < NMENU3){
       +                p = menu3str[n];
       +                if(lock)
       +                        p = paren(p);
       +                return p;
       +        }
       +        n -= NMENU3;
       +        if(n == 0)        /* unless we've been fooled, this is cmd */
       +                return (char *)&name[n][1];
       +        if(mw == -1){
       +                mw = 7;        /* strlen("~~sam~~"); */
       +                for(i=1; i<nname; i++){
       +                        w = utflen((char*)name[i]+1)+4;        /* include "'+. " */
       +                        if(w > mw)
       +                                mw = w;
       +                }
       +        }
       +        if(mw > NBUF)
       +                mw = NBUF;
       +        t = text[n];
       +        buf[0] = name[n][0];
       +        buf[1] = '-';
       +        buf[2] = ' ';
       +        buf[3] = ' ';
       +        if(t){
       +                if(t->nwin == 1)
       +                        buf[1] = '+';
       +                else if(t->nwin > 1)
       +                        buf[1] = '*';
       +                if(work && t==(Text *)work->user1) {
       +                        buf[2]= '.';
       +                        if(modified)
       +                                buf[0] = '\'';
       +                }
       +        }
       +        l = utflen((char*)name[n]+1);
       +        if(l > NBUF-4-2){
       +                i = 4;
       +                k = 1;
       +                while(i < NBUF/2){
       +                        k += chartorune(&r, (char*)name[n]+k);
       +                        i++;
       +                }
       +                c = name[n][k];
       +                name[n][k] = 0;
       +                strcpy((char*)buf+4, (char*)name[n]+1);
       +                name[n][k] = c;
       +                strcat((char*)buf, "...");
       +                while((l-i) >= NBUF/2-4){
       +                        k += chartorune(&r, (char*)name[n]+k);
       +                        i++;
       +                }
       +                strcat((char*)buf, (char*)name[n]+k);
       +        }else
       +                strcpy((char*)buf+4, (char*)name[n]+1);
       +        i = utflen((char*)buf);
       +        k = strlen((char*)buf);
       +        while(i<mw && k<sizeof buf-1){
       +                buf[k++] = ' ';
       +                i++;
       +        }
       +        buf[k] = 0;
       +        return (char *)buf;
       +}
 (DIR) diff --git a/samterm/mesg.c b/samterm/mesg.c
       @@ -0,0 +1,794 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <string.h>
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include <frame.h>
       +#include "flayer.h"
       +#include "samterm.h"
       +
       +extern char *exname;
       +
       +#define        HSIZE        3        /* Type + short count */
       +Header        h;
       +uchar        indata[DATASIZE+1];        /* room for NUL */
       +uchar        outdata[DATASIZE];
       +short        outcount;
       +int        hversion;
       +
       +void        inmesg(Hmesg, int);
       +int        inshort(int);
       +long        inlong(int);
       +long        invlong(int);
       +void        hsetdot(int, long, long);
       +void        hmoveto(int, long);
       +void        hsetsnarf(int);
       +void        clrlock(void);
       +int        snarfswap(char*, int, char**);
       +
       +void
       +rcv(void)
       +{
       +        int c;
       +        static state = 0;
       +        static count = 0;
       +        static i = 0;
       +        static int errs = 0;
       +
       +        while((c=rcvchar()) != -1)
       +                switch(state){
       +                case 0:
       +                        h.type = c;
       +                        state++;
       +                        break;
       +
       +                case 1:
       +                        h.count0 = c;
       +                        state++;
       +                        break;
       +
       +                case 2:
       +                        h.count1 = c;
       +                        count = h.count0|(h.count1<<8);
       +                        i = 0;
       +                        if(count > DATASIZE){
       +                                if(++errs < 5){
       +                                        dumperrmsg(count, h.type, h.count0, c);
       +                                        state = 0;
       +                                        continue;
       +                                }
       +                                fprint(2, "type %d count %d\n", h.type, count);
       +                                panic("count>DATASIZE");
       +                        }
       +                        if(count == 0)
       +                                goto zerocount;
       +                        state++;
       +                        break;
       +
       +                case 3:
       +                        indata[i++] = c;
       +                        if(i == count){
       +                zerocount:
       +                                indata[i] = 0;
       +                                inmesg(h.type, count);
       +                                state = count = 0;
       +                                continue;
       +                        }
       +                        break;
       +                }
       +}
       +
       +Text *
       +whichtext(int tg)
       +{
       +        int i;
       +
       +        for(i=0; i<nname; i++)
       +                if(tag[i] == tg)
       +                        return text[i];
       +        panic("whichtext");
       +        return 0;
       +}
       +
       +void
       +inmesg(Hmesg type, int count)
       +{
       +        Text *t;
       +        int i, m;
       +        long l;
       +        Flayer *lp;
       +    char syscmd[512];
       +
       +        m = inshort(0);
       +        l = inlong(2);
       +        switch(type){
       +        case -1:
       +                panic("rcv error");
       +        default:
       +                fprint(2, "type %d\n", type);
       +                panic("rcv unknown");
       +
       +        case Hversion:
       +                hversion = m;
       +                break;
       +
       +        case Hbindname:
       +                l = invlong(2);                /* for 64-bit pointers */
       +                if((i=whichmenu(m)) < 0)
       +                        break;
       +                /* in case of a race, a bindname may already have occurred */
       +                if((t=whichtext(m)) == 0)
       +                        t=(Text *)l;
       +                else        /* let the old one win; clean up the new one */
       +                        while(((Text *)l)->nwin>0)
       +                                closeup(&((Text *)l)->l[((Text *)l)->front]);
       +                text[i] = t;
       +                text[i]->tag = m;
       +                break;
       +
       +        case Hcurrent:
       +                if(whichmenu(m)<0)
       +                        break;
       +                t = whichtext(m);
       +                i = which && ((Text *)which->user1)==&cmd && m!=cmd.tag;
       +                if(t==0 && (t = sweeptext(0, m))==0)
       +                        break;
       +                if(t->l[t->front].textfn==0)
       +                        panic("Hcurrent");
       +                lp = &t->l[t->front];
       +                if(i){
       +                        flupfront(lp);
       +                        flborder(lp, 0);
       +                        work = lp;
       +                }else
       +                        current(lp);
       +                break;
       +
       +        case Hmovname:
       +                if((m=whichmenu(m)) < 0)
       +                        break;
       +                t = text[m];
       +                l = tag[m];
       +                i = name[m][0];
       +                text[m] = 0;        /* suppress panic in menudel */
       +                menudel(m);
       +                if(t == &cmd)
       +                        m = 0;
       +                else{
       +                        if (nname>0 && text[0]==&cmd)
       +                                m = 1;
       +                        else m = 0;
       +                        for(; m<nname; m++)
       +                                if(strcmp((char*)indata+2, (char*)name[m]+1)<0)
       +                                        break;
       +                }
       +                menuins(m, indata+2, t, i, (int)l);
       +                break;
       +
       +        case Hgrow:
       +                if(whichmenu(m) >= 0)
       +                        hgrow(m, l, inlong(6), 1);
       +                break;
       +
       +        case Hnewname:
       +                menuins(0, (uchar *)"", (Text *)0, ' ', m);
       +                break;
       +
       +        case Hcheck0:
       +                i = whichmenu(m);
       +                if(i>=0) {
       +                        t = text[i];
       +                        if (t)
       +                                t->lock++;
       +                        outTs(Tcheck, m);
       +                }
       +                break;
       +
       +        case Hcheck:
       +                i = whichmenu(m);
       +                if(i>=0) {
       +                        t = text[i];
       +                        if (t && t->lock)
       +                                t->lock--;
       +                        hcheck(m);
       +                }
       +                break;
       +
       +        case Hunlock:
       +                clrlock();
       +                break;
       +
       +        case Hdata:
       +                if(whichmenu(m) >= 0)
       +                        l += hdata(m, l, indata+6, count-6);
       +        Checkscroll:
       +                if(m == cmd.tag){
       +                        for(i=0; i<NL; i++){
       +                                lp = &cmd.l[i];
       +                                if(lp->textfn)
       +                                        center(lp, l>=0? l : lp->p1);
       +                        }
       +                }
       +                break;
       +
       +        case Horigin:
       +                if(whichmenu(m) >= 0)
       +                        horigin(m, l);
       +                break;
       +
       +        case Hunlockfile:
       +                if(whichmenu(m)>=0 && (t = whichtext(m))->lock){
       +                        --t->lock;
       +                        l = -1;
       +                        goto Checkscroll;
       +                }
       +                break;
       +
       +        case Hsetdot:
       +                if(whichmenu(m) >= 0)
       +                        hsetdot(m, l, inlong(6));
       +                break;
       +
       +        case Hgrowdata:
       +                if(whichmenu(m)<0)
       +                        break;
       +                hgrow(m, l, inlong(6), 0);
       +                whichtext(m)->lock++;        /* fake the request */
       +                l += hdata(m, l, indata+10, count-10);
       +                goto Checkscroll;
       +
       +        case Hmoveto:
       +                if(whichmenu(m)>=0)
       +                        hmoveto(m, l);
       +                break;
       +
       +        case Hclean:
       +                if((m = whichmenu(m)) >= 0)
       +                        name[m][0] = ' ';
       +                break;
       +
       +        case Hdirty:
       +                if((m = whichmenu(m))>=0)
       +                        name[m][0] = '\'';
       +                break;
       +
       +        case Hdelname:
       +                if((m=whichmenu(m)) >= 0)
       +                        menudel(m);
       +                break;
       +
       +        case Hcut:
       +                if(whichmenu(m) >= 0)
       +                        hcut(m, l, inlong(6));
       +                break;
       +
       +        case Hclose:
       +                if(whichmenu(m)<0 || (t = whichtext(m))==0)
       +                        break;
       +                l = t->nwin;
       +                for(i = 0,lp = t->l; l>0 && i<NL; i++,lp++)
       +                        if(lp->textfn){
       +                                closeup(lp);
       +                                --l;
       +                        }
       +                break;
       +
       +        case Hsetpat:
       +                setpat((char *)indata);
       +                break;
       +
       +        case Hsetsnarf:
       +                hsetsnarf(m);
       +                break;
       +
       +        case Hsnarflen:
       +                snarflen = inlong(0);
       +                break;
       +
       +        case Hack:
       +                outT0(Tack);
       +                break;
       +
       +#ifndef NOFIFO
       +    case Hextcmd:
       +        if (exname != NULL)
       +        {
       +            int fifofd = open(exname, O_WRONLY);
       +            if (fifofd >= 0)
       +            {
       +                memset(syscmd, 0, 512);
       +                snprintf(syscmd, 511, "%s", (char *)indata);
       +                write(fifofd, syscmd, 511);
       +                close(fifofd);
       +            }
       +        }
       +        break;
       +#endif
       +
       +        case Hexit:
       +                outT0(Texit);
       +                mouseexit();
       +                break;
       +        }
       +}
       +
       +void
       +setlock(void)
       +{
       +        lock++;
       +        cursorswitch(cursor = &lockarrow);
       +}
       +
       +void
       +clrlock(void)
       +{
       +        hasunlocked = 1;
       +        if(lock > 0)
       +                lock--;
       +        if(lock == 0)
       +                cursorswitch(cursor=(Cursor *)0);
       +}
       +
       +void
       +startfile(Text *t)
       +{
       +        outTsv(Tstartfile, t->tag, t);                /* for 64-bit pointers */
       +        setlock();
       +}
       +
       +void
       +startnewfile(int type, Text *t)
       +{
       +        t->tag = Untagged;
       +        outTv(type, t);                                /* for 64-bit pointers */
       +}
       +
       +int
       +inshort(int n)
       +{
       +        return indata[n]|(indata[n+1]<<8);
       +}
       +
       +long
       +inlong(int n)
       +{
       +        return indata[n]|(indata[n+1]<<8)|
       +                ((long)indata[n+2]<<16)|((long)indata[n+3]<<24);
       +}
       +
       +long
       +invlong(int n)
       +{
       +        long l;
       +
       +        l = (indata[n+7]<<24) | (indata[n+6]<<16) | (indata[n+5]<<8) | indata[n+4];
       +        l = (l<<16) | (indata[n+3]<<8) | indata[n+2];
       +        l = (l<<16) | (indata[n+1]<<8) | indata[n];
       +        return l;
       +}
       +
       +void
       +outT0(Tmesg type)
       +{
       +        outstart(type);
       +        outsend();
       +}
       +
       +void
       +outTl(Tmesg type, long l)
       +{
       +        outstart(type);
       +        outlong(l);
       +        outsend();
       +}
       +
       +void
       +outTs(Tmesg type, int s)
       +{
       +        outstart(type);
       +        outshort(s);
       +        outsend();
       +}
       +
       +void
       +outTss(Tmesg type, int s1, int s2)
       +{
       +        outstart(type);
       +        outshort(s1);
       +        outshort(s2);
       +        outsend();
       +}
       +
       +void
       +outTsll(Tmesg type, int s1, long l1, long l2)
       +{
       +        outstart(type);
       +        outshort(s1);
       +        outlong(l1);
       +        outlong(l2);
       +        outsend();
       +}
       +
       +void
       +outTsl(Tmesg type, int s1, long l1)
       +{
       +        outstart(type);
       +        outshort(s1);
       +        outlong(l1);
       +        outsend();
       +}
       +
       +void
       +outTsv(Tmesg type, int s1, void *l1)
       +{
       +        outstart(type);
       +        outshort(s1);
       +        outvlong(l1);
       +        outsend();
       +}
       +
       +void
       +outTv(Tmesg type, void *l1)
       +{
       +        outstart(type);
       +        outvlong(l1);
       +        outsend();
       +}
       +
       +void
       +outTslS(Tmesg type, int s1, long l1, Rune *s)
       +{
       +        char buf[DATASIZE*3+1];
       +        char *c;
       +
       +        outstart(type);
       +        outshort(s1);
       +        outlong(l1);
       +        c = buf;
       +        while(*s)
       +                c += runetochar(c, s++);
       +        *c++ = 0;
       +        outcopy(c-buf, (uchar *)buf);
       +        outsend();
       +}
       +
       +void
       +outTsls(Tmesg type, int s1, long l1, int s2)
       +{
       +        outstart(type);
       +        outshort(s1);
       +        outlong(l1);
       +        outshort(s2);
       +        outsend();
       +}
       +
       +void
       +outstart(Tmesg type)
       +{
       +        outdata[0] = type;
       +        outcount = 0;
       +}
       +
       +void
       +outcopy(int count, uchar *data)
       +{
       +        while(count--)
       +                outdata[HSIZE+outcount++] = *data++;        
       +}
       +
       +void
       +outshort(int s)
       +{
       +        uchar buf[2];
       +
       +        buf[0]=s;
       +        buf[1]=s>>8;
       +        outcopy(2, buf);
       +}
       +
       +void
       +outlong(long l)
       +{
       +        uchar buf[4];
       +
       +        buf[0]=l;
       +        buf[1]=l>>8;
       +        buf[2]=l>>16;
       +        buf[3]=l>>24;
       +        outcopy(4, buf);
       +}
       +
       +void
       +outvlong(void *v)
       +{
       +        int i;
       +        ulong l;
       +        uchar buf[8];
       +
       +        l = (ulong) v;
       +        for(i = 0; i < sizeof(buf); i++, l >>= 8)
       +                buf[i] = l;
       +
       +        outcopy(8, buf);
       +}
       +
       +void
       +outsend(void)
       +{
       +        if(outcount>DATASIZE-HSIZE)
       +                panic("outcount>sizeof outdata");
       +        outdata[1]=outcount;
       +        outdata[2]=outcount>>8;
       +        if(write(1, (char *)outdata, outcount+HSIZE)!=outcount+HSIZE)
       +                exits("write error");
       +}
       +
       +
       +void
       +hsetdot(int m, long p0, long p1)
       +{
       +        Text *t = whichtext(m);
       +        Flayer *l = &t->l[t->front];
       +
       +        flushtyping(1);
       +        flsetselect(l, p0, p1);
       +}
       +
       +void
       +horigin(int m, long p0)
       +{
       +        Text *t = whichtext(m);
       +        Flayer *l = &t->l[t->front];
       +        long a;
       +        ulong n;
       +        Rune *r;
       +
       +        if(!flprepare(l)){
       +                l->origin = p0;
       +                return;
       +        }
       +        a = p0-l->origin;
       +        if(a>=0 && a<l->f.nchars)
       +                frdelete(&l->f, 0, a);
       +        else if(a<0 && -a<l->f.nchars){
       +                r = rload(&t->rasp, p0, l->origin, &n);
       +                frinsert(&l->f, r, r+n, 0);
       +        }else
       +                frdelete(&l->f, 0, l->f.nchars);
       +        l->origin = p0;
       +        scrdraw(l, t->rasp.nrunes);
       +        if(l->visible==Some)
       +                flrefresh(l, l->entire, 0);
       +        hcheck(m);
       +}
       +
       +void
       +hmoveto(int m, long p0)
       +{
       +        Text *t = whichtext(m);
       +        Flayer *l = &t->l[t->front];
       +
       +        if(p0<l->origin || p0-l->origin>l->f.nchars*9/10)
       +                outTsll(Torigin, m, p0, 2L);
       +}
       +
       +void
       +hcheck(int m)
       +{
       +        Flayer *l;
       +        Text *t;
       +        int reqd = 0, i;
       +        long n, nl, a;
       +        Rune *r;
       +
       +        if(m == Untagged)
       +                return;
       +        t = whichtext(m);
       +        if(t == 0)                /* possible in a half-built window */
       +                return;
       +        for(l = &t->l[0], i = 0; i<NL; i++, l++){
       +                if(l->textfn==0 || !flprepare(l))        /* BUG: don't
       +                                                           need this if BUG below
       +                                                           is fixed */
       +                        continue;
       +                a = t->l[i].origin;
       +                n = rcontig(&t->rasp, a, a+l->f.nchars, 1);
       +                if(n<l->f.nchars)        /* text missing in middle of screen */
       +                        a+=n;
       +                else{                        /* text missing at end of screen? */
       +        Again:
       +                         if(l->f.lastlinefull)
       +                                goto Checksel;        /* all's well */
       +                        a = t->l[i].origin+l->f.nchars;
       +                        n = t->rasp.nrunes-a;
       +                        if(n==0)
       +                                goto Checksel;
       +                        if(n>TBLOCKSIZE)
       +                                n = TBLOCKSIZE;
       +                        n = rcontig(&t->rasp, a, a+n, 1);
       +                        if(n>0){
       +                                rload(&t->rasp, a, a+n, 0);
       +                                nl = l->f.nchars;
       +                                r = scratch;
       +                                flinsert(l, r, r+n, l->origin+nl);
       +                                if(nl == l->f.nchars)        /* made no progress */
       +                                        goto Checksel;
       +                                goto Again;
       +                        }
       +                }
       +                if(!reqd){
       +                        n = rcontig(&t->rasp, a, a+TBLOCKSIZE, 0);
       +                        if(n <= 0)
       +                                panic("hcheck request==0");
       +                        outTsls(Trequest, m, a, (int)n);
       +                        outTs(Tcheck, m);
       +                        t->lock++;        /* for the Trequest */
       +                        t->lock++;        /* for the Tcheck */
       +                        reqd++;
       +                }
       +            Checksel:
       +                flsetselect(l, l->p0, l->p1);
       +        }
       +}
       +
       +void
       +flnewlyvisible(Flayer *l)
       +{
       +        hcheck(((Text *)l->user1)->tag);
       +}
       +
       +void
       +hsetsnarf(int nc)
       +{
       +        char *s2;
       +        char *s1;
       +        int i;
       +        int n;
       +
       +        cursorswitch(&deadmouse);
       +        s2 = alloc(nc+1);
       +        for(i=0; i<nc; i++)
       +                s2[i] = getch();
       +        s2[nc] = 0;
       +        n = snarfswap(s2, nc, &s1);
       +        if(n >= 0){
       +                if(!s1)
       +                        n = 0;
       +                if(n > SNARFSIZE-1)
       +                        n = SNARFSIZE-1;
       +                s1 = realloc(s1, n+1);
       +                if (!s1)
       +                        exits("malloc");
       +                s1[n] = 0;
       +                snarflen = n;
       +                outTs(Tsetsnarf, n);
       +                if(n>0 && write(1, s1, n)!=n)
       +                        exits("write error");
       +                free(s1);
       +        }else
       +                outTs(Tsetsnarf, 0);
       +        free(s2);
       +        cursorswitch(cursor);
       +}
       +
       +void
       +hgrow(int m, long a, long new, int req)
       +{
       +        int i;
       +        Flayer *l;
       +        Text *t = whichtext(m);
       +        long o, b;
       +
       +        if(new <= 0)
       +                panic("hgrow");
       +        rresize(&t->rasp, a, 0L, new);
       +        for(l = &t->l[0], i = 0; i<NL; i++, l++){
       +                if(l->textfn == 0)
       +                        continue;
       +                o = l->origin;
       +                b = a-o-rmissing(&t->rasp, o, a);
       +                if(a < o)
       +                        l->origin+=new;
       +                if(a < l->p0)
       +                        l->p0+=new;
       +                if(a < l->p1)
       +                        l->p1+=new;
       +                /* must prevent b temporarily becoming unsigned */
       +                if(!req || a<o || (b>0 && b>l->f.nchars) ||
       +                    (l->f.nchars==0 && a-o>0))
       +                        continue;
       +                if(new>TBLOCKSIZE)
       +                        new = TBLOCKSIZE;
       +                outTsls(Trequest, m, a, (int)new);
       +                t->lock++;
       +                req = 0;
       +        }
       +}
       +
       +int
       +hdata1(Text *t, long a, Rune *r, int len)
       +{
       +        int i;
       +        Flayer *l;
       +        long o, b;
       +
       +        for(l = &t->l[0], i=0; i<NL; i++, l++){
       +                if(l->textfn==0)
       +                        continue;
       +                o = l->origin;
       +                b = a-o-rmissing(&t->rasp, o, a);
       +                /* must prevent b temporarily becoming unsigned */
       +                if(a<o || (b>0 && b>l->f.nchars))
       +                        continue;
       +                flinsert(l, r, r+len, o+b);
       +        }
       +        rdata(&t->rasp, a, a+len, r);
       +        rclean(&t->rasp);
       +        return len;
       +}
       +
       +int
       +hdata(int m, long a, uchar *s, int len)
       +{
       +        int i, w;
       +        Text *t = whichtext(m);
       +        Rune buf[DATASIZE], *r;
       +
       +        if(t->lock)
       +                --t->lock;
       +        if(len == 0)
       +                return 0;
       +        r = buf;
       +        for(i=0; i<len; i+=w,s+=w)
       +                w = chartorune(r++, (char*)s);
       +        return hdata1(t, a, buf, r-buf);
       +}
       +
       +int
       +hdatarune(int m, long a, Rune *r, int len)
       +{
       +        Text *t = whichtext(m);
       +
       +        if(t->lock)
       +                --t->lock;
       +        if(len == 0)
       +                return 0;
       +        return hdata1(t, a, r, len);
       +}
       +
       +void
       +hcut(int m, long a, long old)
       +{
       +        Flayer *l;
       +        Text *t = whichtext(m);
       +        int i;
       +        long o, b;
       +
       +        if(t->lock)
       +                --t->lock;
       +        for(l = &t->l[0], i = 0; i<NL; i++, l++){
       +                if(l->textfn == 0)
       +                        continue;
       +                o = l->origin;
       +                b = a-o-rmissing(&t->rasp, o, a);
       +                /* must prevent b temporarily becoming unsigned */
       +                if((b<0 || b<l->f.nchars) && a+old>=o){
       +                        fldelete(l, b<0? o : o+b,
       +                            a+old-rmissing(&t->rasp, o, a+old));
       +                }
       +                if(a+old<o)
       +                        l->origin-=old;
       +                else if(a<=o)
       +                        l->origin = a;
       +                if(a+old<l->p0)
       +                        l->p0-=old;
       +                else if(a<=l->p0)
       +                        l->p0 = a;
       +                if(a+old<l->p1)
       +                        l->p1-=old;
       +                else if(a<=l->p1)
       +                        l->p1 = a;
       +        }
       +        rresize(&t->rasp, a, old, 0L);
       +        rclean(&t->rasp);
       +}
 (DIR) diff --git a/samterm/plan9.c b/samterm/plan9.c
       @@ -0,0 +1,113 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include <frame.h>
       +#include "flayer.h"
       +#include "samterm.h"
       +
       +static char exname[64];
       +
       +void
       +getscreen(int argc, char **argv)
       +{
       +        USED(argc);
       +        USED(argv);
       +        binit1(panic, 0, "sam", 1);
       +        bitblt(&screen, screen.clipr.min, &screen, screen.clipr, 0);
       +}
       +
       +int
       +screensize(int *w, int *h)
       +{
       +        int fd, n;
       +        char buf[5*12+1];
       +
       +        fd = open("/dev/screen", OREAD);
       +        if(fd < 0)
       +                return 0;
       +        n = read(fd, buf, sizeof(buf)-1);
       +        close(fd);
       +        if (n != sizeof(buf)-1)
       +                return 0;
       +        buf[n] = 0;
       +        if (h) {
       +                *h = atoi(buf+4*12)-atoi(buf+2*12);
       +                if (*h < 0)
       +                        return 0;
       +        }
       +        if (w) {
       +                *w = atoi(buf+3*12)-atoi(buf+1*12);
       +                if (*w < 0)
       +                        return 0;
       +        }
       +        return 1;
       +}
       +
       +int
       +snarfswap(char *fromsam, int nc, char **tosam)
       +{
       +        char *s1;
       +        int f, n;
       +
       +        f = open("/dev/snarf", 0);
       +        if(f < 0)
       +                return -1;
       +        *tosam = s1 = alloc(SNARFSIZE);
       +        n = read(f, s1, SNARFSIZE-1);
       +        close(f);
       +        if(n < 0)
       +                n = 0;
       +        if (n == 0) {
       +                *tosam = 0;
       +                free(s1);
       +        } else
       +                s1[n] = 0;
       +        f = create("/dev/snarf", 1, 0666);
       +        if(f >= 0){
       +                write(f, fromsam, nc);
       +                close(f);
       +        }
       +        return n;
       +}
       +
       +void
       +dumperrmsg(int count, int type, int count0, int c)
       +{
       +        fprint(2, "samterm: host mesg: count %d %x %x %x %s...ignored\n",
       +                count, type, count0, c, rcvstring());
       +}
       +
       +void
       +removeextern(void)
       +{
       +        remove(exname);
       +}
       +
       +void
       +extstart(void)
       +{
       +        char buf[32];
       +        int fd, p[2];
       +
       +        if(pipe(p) < 0)
       +                return;
       +        sprint(exname, "/srv/sam.%s", getuser());
       +        fd = create(exname, 1, 0600);
       +        if(fd < 0){        /* assume existing guy is more important */
       +    Err:
       +                close(p[0]);
       +                close(p[1]);
       +                return;
       +        }
       +        sprint(buf, "%d", p[0]);
       +        if(write(fd, buf, strlen(buf)) <= 0)
       +                goto Err;
       +        close(fd);
       +        /*
       +         * leave p[0] open so if the file is removed the event
       +         * library won't get an error
       +         */
       +        estart(Eextern, p[1], 8192);
       +        atexit(removeextern);
       +}
 (DIR) diff --git a/samterm/rasp.c b/samterm/rasp.c
       @@ -0,0 +1,263 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include <frame.h>
       +#include "flayer.h"
       +#include "samterm.h"
       +
       +void
       +rinit(Rasp *r)
       +{
       +        r->nrunes=0;
       +        r->sect=0;
       +}
       +
       +void
       +rclear(Rasp *r)
       +{
       +        Section *s, *ns;
       +
       +        for(s=r->sect; s; s=ns){
       +                ns = s->next;
       +                free(s->text);
       +                free(s);
       +        }
       +        r->sect = 0;
       +}
       +
       +Section*
       +rsinsert(Rasp *r, Section *s)        /* insert before s */
       +{
       +        Section *t;
       +        Section *u;
       +
       +        t = alloc(sizeof(Section));
       +        if(r->sect == s){        /* includes empty list case: r->sect==s==0 */
       +                r->sect = t;
       +                t->next = s;
       +        }else{
       +                u = r->sect;
       +                if(u == 0)
       +                        panic("rsinsert 1");
       +                do{
       +                        if(u->next == s){
       +                                t->next = s;
       +                                u->next = t;
       +                                goto Return;
       +                        }
       +                        u=u->next;
       +                }while(u);
       +                panic("rsinsert 2");
       +        }
       +    Return:
       +        return t;
       +}
       +
       +void
       +rsdelete(Rasp *r, Section *s)
       +{
       +        Section *t;
       +
       +        if(s == 0)
       +                panic("rsdelete");
       +        if(r->sect == s){
       +                r->sect = s->next;
       +                goto Free;
       +        }
       +        for(t=r->sect; t; t=t->next)
       +                if(t->next == s){
       +                        t->next = s->next;
       +        Free:
       +                        if(s->text)
       +                                free(s->text);
       +                        free(s);
       +                        return;
       +                }
       +        panic("rsdelete 2");
       +}
       +
       +void
       +splitsect(Rasp *r, Section *s, long n0)
       +{
       +        if(s == 0)
       +                panic("splitsect");
       +        rsinsert(r, s->next);
       +        if(s->text == 0)
       +                s->next->text = 0;
       +        else{
       +                s->next->text = alloc(RUNESIZE*(TBLOCKSIZE+1));
       +                Strcpy(s->next->text, s->text+n0);
       +                s->text[n0] = 0;
       +        }
       +        s->next->nrunes = s->nrunes-n0;
       +        s->nrunes = n0;
       +}
       +
       +Section *
       +findsect(Rasp *r, Section *s, long p, long q)        /* find sect containing q and put q on a sect boundary */
       +{
       +        if(s==0 && p!=q)
       +                panic("findsect");
       +        for(; s && p+s->nrunes<=q; s=s->next)
       +                p += s->nrunes;
       +        if(p != q){
       +                splitsect(r, s, q-p);
       +                s = s->next;
       +        }
       +        return s;
       +}
       +
       +void
       +rresize(Rasp *r, long a, long old, long new)
       +{
       +        Section *s, *t, *ns;
       +
       +        s = findsect(r, r->sect, 0L, a);
       +        t = findsect(r, s, a, a+old);
       +        for(; s!=t; s=ns){
       +                ns=s->next;
       +                rsdelete(r, s);
       +        }
       +        /* now insert the new piece before t */
       +        if(new > 0){
       +                ns=rsinsert(r, t);
       +                ns->nrunes=new;
       +                ns->text=0;
       +        }
       +        r->nrunes += new-old;
       +}
       +
       +void
       +rdata(Rasp *r, long p0, long p1, Rune *cp)
       +{
       +        Section *s, *t, *ns;
       +
       +        s = findsect(r, r->sect, 0L, p0);
       +        t = findsect(r, s, p0, p1);
       +        for(; s!=t; s=ns){
       +                ns=s->next;
       +                if(s->text)
       +                        panic("rdata");
       +                rsdelete(r, s);
       +        }
       +        p1 -= p0;
       +        s = rsinsert(r, t);
       +        s->text = alloc(RUNESIZE*(TBLOCKSIZE+1));
       +        memmove(s->text, cp, RUNESIZE*p1);
       +        s->text[p1] = 0;
       +        s->nrunes = p1;
       +}
       +
       +void
       +rclean(Rasp *r)
       +{
       +        Section *s;
       +
       +        for(s=r->sect; s; s=s->next)
       +                while(s->next && (s->text!=0)==(s->next->text!=0)){
       +                        if(s->text){
       +                                if(s->nrunes+s->next->nrunes>TBLOCKSIZE)
       +                                        break;
       +                                Strcpy(s->text+s->nrunes, s->next->text);
       +                        }
       +                        s->nrunes += s->next->nrunes;
       +                        rsdelete(r, s->next);
       +                }
       +}
       +
       +void
       +Strcpy(Rune *to, Rune *from)
       +{
       +        do; while(*to++ = *from++);
       +}
       +
       +Rune*
       +rload(Rasp *r, ulong p0, ulong p1, ulong *nrp)
       +{
       +        Section *s;
       +        long p;
       +        int n, nb;
       +
       +        nb = 0;
       +        Strgrow(&scratch, &nscralloc, p1-p0+1);
       +        scratch[0] = 0;
       +        for(p=0,s=r->sect; s && p+s->nrunes<=p0; s=s->next)
       +                p += s->nrunes;
       +        while(p<p1 && s){
       +                /*
       +                 * Subtle and important.  If we are preparing to handle an 'rdata'
       +                 * call, it's because we have an 'rresize' hole here, so the
       +                 * screen doesn't have data for that space anyway (it got cut
       +                 * first).  So pretend it isn't there.
       +                 */
       +                if(s->text){
       +                        n = s->nrunes-(p0-p);
       +                        if(n>p1-p0)        /* all in this section */
       +                                n = p1-p0;
       +                        memmove(scratch+nb, s->text+(p0-p), n*RUNESIZE);
       +                        nb += n;
       +                        scratch[nb] = 0;
       +                }
       +                p += s->nrunes;
       +                p0 = p;
       +                s = s->next;
       +        }
       +        if(nrp)
       +                *nrp = nb;
       +        return scratch;
       +}
       +
       +int
       +rmissing(Rasp *r, ulong p0, ulong p1)
       +{
       +        Section *s;
       +        long p;
       +        int n, nm=0;
       +
       +        for(p=0,s=r->sect; s && p+s->nrunes<=p0; s=s->next)
       +                p += s->nrunes;
       +        while(p<p1 && s){
       +                if(s->text == 0){
       +                        n = s->nrunes-(p0-p);
       +                        if(n > p1-p0)        /* all in this section */
       +                                n = p1-p0;
       +                        nm += n;
       +                }
       +                p += s->nrunes;
       +                p0 = p;
       +                s = s->next;
       +        }
       +        return nm;
       +}
       +
       +int
       +rcontig(Rasp *r, ulong p0, ulong p1, int text)
       +{
       +        Section *s;
       +        long p, n;
       +        int np=0;
       +
       +        for(p=0,s=r->sect; s && p+s->nrunes<=p0; s=s->next)
       +                p += s->nrunes;
       +        while(p<p1 && s && (text? (s->text!=0) : (s->text==0))){
       +                n = s->nrunes-(p0-p);
       +                if(n > p1-p0)        /* all in this section */
       +                        n = p1-p0;
       +                np += n;
       +                p += s->nrunes;
       +                p0 = p;
       +                s = s->next;
       +        }
       +        return np;
       +}
       +
       +void
       +Strgrow(Rune **s, long *n, int want)        /* can always toss the old data when called */
       +{
       +        if(*n >= want)
       +                return;
       +        free(*s);
       +        *s = alloc(RUNESIZE*want);
       +        *n = want;
       +}
 (DIR) diff --git a/samterm/samterm.h b/samterm/samterm.h
       @@ -0,0 +1,158 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#define        SAMTERM
       +
       +#define        RUNESIZE        sizeof(Rune)
       +#define        MAXFILES        256
       +#define        NL        5
       +
       +enum{
       +        Up,
       +        Down
       +};
       +
       +typedef struct Text        Text;
       +typedef struct Section        Section;
       +typedef struct Rasp        Rasp;
       +
       +struct Section
       +{
       +        long        nrunes;
       +        Rune        *text;                /* if null, we haven't got it */
       +        Section        *next;
       +};
       +
       +struct Rasp
       +{
       +        long        nrunes;
       +        Section        *sect;
       +};
       +
       +#define        Untagged        ((ushort)65535)
       +
       +struct Text
       +{
       +        Rasp        rasp;
       +        short        nwin;
       +        short        front;                /* input window */
       +        ushort        tag;
       +        char        lock;
       +        Flayer        l[NL];                /* screen storage */
       +};
       +
       +enum Resource
       +{
       +        Eextern                = 0x08,
       +        Ehost                = 0x04,
       +        RHost                = Ehost,
       +        RExtern                = Eextern,
       +        RKeyboard        = Ekeyboard,
       +        RMouse                = Emouse
       +};
       +
       +extern Text        *text[];
       +extern uchar        *name[];
       +extern ushort        tag[];
       +extern int        nname;
       +extern Cursor        bullseye;
       +extern Cursor        deadmouse;
       +extern Cursor        lockarrow;
       +extern Cursor        *cursor;
       +extern Flayer        *which;
       +extern Flayer        *work;
       +extern Text        cmd;
       +extern Rune        *scratch;
       +extern long        nscralloc;
       +extern char        lock;
       +extern char        hasunlocked;
       +extern long        snarflen;
       +extern Mouse        mouse;
       +extern long        modified;
       +
       +Rune        *stgettext(Flayer*, long, ulong*);
       +void        *alloc(ulong n);
       +
       +void        iconinit(void);
       +void        getscreen(int, char**);
       +void        initio(void);
       +void        setlock(void);
       +void        outcmd(void);
       +void        rinit(Rasp*);
       +void        startnewfile(int, Text*);
       +void        cursorset(Point);
       +void        getmouse(void);
       +void        mouseunblock(void);
       +void        kbdblock(void);
       +void        extstart(void);
       +int        button(int but);
       +int        load(char*, int);
       +int        waitforio(void);
       +int        rcvchar(void);
       +int        getch(void);
       +int        kbdchar(void);
       +int        qpeekc(void);
       +void        mouseexit(void);
       +void        cut(Text*, int, int, int);
       +void        paste(Text*, int);
       +void        snarf(Text*, int);
       +int        center(Flayer*, long);
       +int        xmenuhit(int, Menu*);
       +void        buttons(int);
       +int        getr(Rectangle*);
       +void        current(Flayer*);
       +void        duplicate(Flayer*, Rectangle, XftFont*, int);
       +void        startfile(Text*);
       +void        panic(char*);
       +void        closeup(Flayer*);
       +void        Strgrow(Rune**, long*, int);
       +int        RESHAPED(void);
       +void        reshape(void);
       +void        rcv(void);
       +void        type(Flayer*, int);
       +void        menu2hit(void);
       +void        menu3hit(void);
       +void        scroll(Flayer*, int, int);
       +void        hcheck(int);
       +void        rclear(Rasp*);
       +int        whichmenu(int);
       +void        hcut(int, long, long);
       +void        horigin(int, long);
       +void        hgrow(int, long, long, int);
       +int        hdata(int, long, uchar*, int);
       +int        hdatarune(int, long, Rune*, int);
       +Rune        *rload(Rasp*, ulong, ulong, ulong*);
       +void        menuins(int, uchar*, Text*, int, int);
       +void        menudel(int);
       +Text        *sweeptext(int, int);
       +void        setpat(char*);
       +void        scrdraw(Flayer*, long tot);
       +int        rcontig(Rasp*, ulong, ulong, int);
       +int        rmissing(Rasp*, ulong, ulong);
       +void        rresize(Rasp *, long, long, long);
       +void        rdata(Rasp*, long, long, Rune*);
       +void        rclean(Rasp*);
       +void        scrorigin(Flayer*, int, long);
       +long        scrtotal(Flayer*);
       +void        flnewlyvisible(Flayer*);
       +char        *rcvstring(void);
       +void        Strcpy(Rune*, Rune*);
       +void        Strncpy(Rune*, Rune*, long);
       +void        flushtyping(int);
       +void        dumperrmsg(int, int, int, int);
       +int        screensize(int*,int*);
       +
       +#include "../sam/mesg.h"
       +
       +void        outTs(Tmesg, int);
       +void        outT0(Tmesg);
       +void        outTl(Tmesg, long);
       +void        outTslS(Tmesg, int, long, Rune*);
       +void        outTsll(Tmesg, int, long, long);
       +void        outTsl(Tmesg, int, long);
       +void        outTsv(Tmesg, int, void*);
       +void        outTv(Tmesg, void*);
       +void        outstart(Tmesg);
       +void        outcopy(int, uchar*);
       +void        outshort(int);
       +void        outlong(long);
       +void        outvlong(void*);
       +void        outsend(void);
 (DIR) diff --git a/samterm/scroll.c b/samterm/scroll.c
       @@ -0,0 +1,145 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include <frame.h>
       +#include "flayer.h"
       +#include "samterm.h"
       +
       +extern Bitmap *darkgrey;
       +extern Mouse mouse;
       +
       +Rectangle
       +scrpos(Rectangle r, long p0, long p1, long tot)
       +{
       +        long h;
       +        Rectangle q;
       +
       +        q = inset(r, 1);
       +        h = q.max.y-q.min.y;
       +        if(tot == 0)
       +                return q;
       +        if(tot > 1024L*1024L)
       +                tot>>=10, p0>>=10, p1>>=10;
       +        if(p0 > 0)
       +                q.min.y += h*p0/tot;
       +        if(p1 < tot)
       +                q.max.y -= h*(tot-p1)/tot;
       +        if(q.max.y < q.min.y+2){
       +                if(q.min.y+2 <= r.max.y)
       +                        q.max.y = q.min.y+2;
       +                else
       +                        q.min.y = q.max.y-2;
       +        }
       +        return q;
       +}
       +
       +void
       +scrflip(Flayer *l, Rectangle r)
       +{
       +        if(rectclip(&r, l->scroll))
       +                bitblt(l->f.b, r.min, l->f.b, r, F&~D);
       +}
       +
       +void
       +scrdraw(Flayer *l, long tot)
       +{
       +        Rectangle r, r1, r2;
       +        Bitmap *b;
       +        static Bitmap *x;
       +        int h;
       +
       +        if(l->f.b == 0)
       +                panic("scrdraw");
       +        r = l->scroll;
       +        r.min.x += 1;        /* border between margin and bar */
       +        r1 = r;
       +        if(l->visible == All){
       +                if(x == 0){
       +                        if (screensize(0, &h) == 0)
       +                                h = 2048;
       +                        x = balloc(Rect(0, 0, 32, h), l->f.b->ldepth);
       +                        if(x == 0)
       +                                panic("scrdraw balloc");
       +                }
       +                b = x;
       +                r1.min.x = 0;
       +                r1.max.x = Dx(r);
       +        }else
       +                b = l->f.b;
       +        bitblt(b, r1.min, b, r1, F);
       +        texture(b, inset(r1, 1), darkgrey, S);
       +        r2 = scrpos(r1, l->origin, l->origin+l->f.nchars, tot);
       +        bitblt(b, r2.min, b, r2, 0);
       +        if(b!=l->f.b)
       +                bitblt(l->f.b, r.min, b, r1, S);
       +}
       +
       +void
       +scroll(Flayer *l, int pbut, int but)
       +{
       +        int in = 0, oin;
       +        long tot = scrtotal(l);
       +        Rectangle scr, r, s, rt;
       +        int x, y, my, oy, h;
       +        long p0;
       +
       +        s = inset(l->scroll, 1);
       +        x = s.min.x+FLSCROLLWID/2;
       +        scr = scrpos(l->scroll, l->origin, l->origin+l->f.nchars, tot);
       +        r = scr;
       +        y = scr.min.y;
       +        my = mouse.xy.y;
       +        do{
       +                oin = in;
       +                in = abs(x-mouse.xy.x)<=FLSCROLLWID/2;
       +                if(oin != in)
       +                        scrflip(l, r);
       +                if(in){
       +                        oy = y;
       +                        my = mouse.xy.y;
       +                        if(my < s.min.y)
       +                                my = s.min.y;
       +                        if(my >= s.max.y)
       +                                my = s.max.y;
       +                        if(!eqpt(mouse.xy, Pt(x, my)))
       +                                cursorset(Pt(x, my));
       +                        if(but == 1){
       +                                p0 = l->origin-frcharofpt(&l->f, Pt(s.max.x, my));
       +                                rt = scrpos(l->scroll, p0, p0+l->f.nchars, tot);
       +                                y = rt.min.y;
       +                        }else if(but == 2){
       +                                y = my;
       +                                if(y > s.max.y-2)
       +                                        y = s.max.y-2;
       +                        }else if(but == 3){
       +                                p0 = l->origin+frcharofpt(&l->f, Pt(s.max.x, my));
       +                                rt = scrpos(l->scroll, p0, p0+l->f.nchars, tot);
       +                                y = rt.min.y;
       +                        }
       +                        if(y != oy){
       +                                scrflip(l, r);
       +                                r = raddp(scr, Pt(0, y-scr.min.y));
       +                                scrflip(l, r);
       +                        }
       +                }
       +        }while(button(pbut));
       +        if(in){
       +                h = s.max.y-s.min.y;
       +                scrflip(l, r);
       +                p0 = 0;
       +                if(but == 1)
       +                        p0 = (long)(my-s.min.y)/l->f.font->height+1;
       +                else if(but == 2){
       +                        if(tot > 1024L*1024L)
       +                                p0 = ((tot>>10)*(y-s.min.y)/h)<<10;
       +                        else
       +                                p0 = tot*(y-s.min.y)/h;
       +                }else if(but == 3){
       +                        p0 = l->origin+frcharofpt(&l->f, Pt(s.max.x, my));
       +                        if(p0 > tot)
       +                                p0 = tot;
       +                }
       +                scrorigin(l, but, p0);
       +        }
       +}
 (DIR) diff --git a/samterm/unix.c b/samterm/unix.c
       @@ -0,0 +1,159 @@
       +/* Copyright (c) 1998 Lucent Technologies - All rights reserved. */
       +#include <u.h>
       +#include <libc.h>
       +#include <libg.h>
       +#include <frame.h>
       +#include "flayer.h"
       +#include "samterm.h"
       +
       +#include <sys/stat.h>
       +#include <errno.h>
       +#include <signal.h>
       +
       +#ifdef APOLLO
       +#define        O_NONBLOCK        O_NDELAY
       +#endif
       +
       +#if        defined(UMIPS) || defined(SUNOS)
       +#define        atexit(p)                /* sigh */
       +#endif
       +
       +char *exname = NULL;
       +static char *fallbacks[] = {
       +        "*scrollForwardR: true",
       +        "*geometry: 740x780",
       +        NULL
       +};
       +
       +void
       +getscreen(int argc, char **argv)
       +{
       +        int fd;
       +        Rectangle r;
       +
       +        signal(SIGINT, SIG_IGN);
       +        xtbinit(0, "Sam", &argc, argv, fallbacks);
       +        r = inset(screen.r, 4);
       +        bitblt(&screen, r.min, &screen, r, 0);
       +}
       +
       +int
       +screensize(int *w, int *h)
       +{
       +        return scrpix(w,h);
       +}
       +
       +void
       +dumperrmsg(int count, int type, int count0, int c)
       +{
       +        uchar *cp;
       +        int i;
       +
       +        cp = (uchar *) rcvstring();
       +        fprint(2, "samterm: host mesg: count %d %ux %ux %ux %s...ignored\n",
       +                count, type, count0, c, cp);
       +        i = 0;
       +        while (*cp) {
       +                fprint(2, "%x ", *cp);
       +                if (i++ >= 20) {
       +                        fprint(2, "\n");
       +                        i = 0;
       +                }
       +                cp++;
       +        }
       +}
       +
       +void
       +removeextern(void)
       +{
       +        if (exname) {
       +                (void)unlink(exname);
       +                exname = 0;
       +        }
       +}
       +/*
       + *        some systems do not support non-blocking i/o on named pipes
       + *        or do not provide working POSIX interfaces to the pipes.
       + *        in that case, add the name of the system to the 'ifdef' that
       + *        disables the code at the beginning of the function.
       + *        The external 'B' command will not work.
       + */
       +
       +void
       +extstart(void)
       +{
       +#ifndef        NOFIFO
       +        extern char *machine;
       +        char        *disp;
       +        char        *user;
       +        char        *home;
       +        int        fd;
       +        int        flags;
       +
       +        user = getuser();
       +        disp = getenv("DISPLAY");
       +        home = getenv("HOME");
       +
       +        if (home == NULL)
       +        {
       +                return;
       +        }
       +
       +        exname = (char *)alloc(4 + 6 + strlen(home) + 1 + strlen(user) + 1 + strlen(machine) + 100);
       +        sprint(exname, "%s/.sam.%s", home, machine);
       +
       +        /* Make the named pipe. */
       +        if (mkfifo(exname, 0600) == -1) {
       +                struct stat        statb;
       +                extern int        errno;
       +
       +                if (errno != EEXIST || stat(exname, &statb) == -1)
       +                        return;
       +
       +                if (!S_ISFIFO(statb.st_mode)) {
       +                        removeextern();
       +                        if (mkfifo(exname, 0600) == -1)
       +                                return;
       +                }
       +        }
       +
       +        fd = open(exname, O_RDONLY | O_NONBLOCK);
       +        if (fd == -1) {
       +                removeextern();
       +                return;
       +        }
       +
       +        /*
       +         * Turn off no-delay and provide ourselves as a lingering
       +         * writer so as not to get end of file on read.
       +         */ 
       +        flags = fcntl(fd, F_GETFL, 0);
       +        if (flags == -1 || fcntl(fd, F_SETFL, flags & ~O_NONBLOCK) == -1
       +                        || open(exname, O_WRONLY) == -1) {
       +                (void)close(fd);
       +                removeextern();
       +                return;
       +        }
       +
       +        estart(Eextern, fd, 8192);
       +        atexit(removeextern);
       +#endif
       +}
       +
       +/*
       + *        we have to supply a dummy exit function, because some vendors can't be
       + *        bothered to provide atexit().  we clean up the named pipes on a normal
       + *        exit, but leave them laying around on abnormal exits.
       + */
       +void
       +exits(char *message)
       +{
       +        if (exname) {
       +                unlink(exname);
       +                exname = 0;
       +        }
       +        if (message == 0)
       +                exit (0);
       +        else
       +                exit(1);
       +}