-- file Edit.mesa -- last modified by Satterthwaite, 9-Mar-82 14:22:18 DIRECTORY Ascii: TYPE USING [CR, SP, TAB], DCSFileTypes: TYPE USING [tLeaderPage], Directory: TYPE USING [CreateFile, Error, Lookup, UpdateDates, ignore], Exec: TYPE USING [AddCommand, commandLine, w], File: TYPE USING [Capability, Permissions, delete, grow, read, shrink, write], FileStream: TYPE USING [FileByteIndex, Create, EndOf, GetIndex, SetIndex], Stream: TYPE USING [Handle, Delete, GetChar, PutChar], TTY: TYPE USING [Handle, GetLine, LineOverflow, PutChar, Rubout]; Edit: PROGRAM IMPORTS Directory, Exec, FileStream, Stream, TTY = { -- characters and strings MAXCHAR: NAT = LAST[CHARACTER] - 0c; MAXSTR: NAT = 100; character: TYPE = [-1..MAXCHAR]; string: TYPE = PACKED ARRAY [1..MAXSTR] OF character; ENDFILE: character = -1; ENDSTR: character = 0; BACKSPACE: character = 9; TAB: character = 9; NEWLINE: character = 13; BLANK: character = 32; ESCAPE: character = ord['@]; -- file descriptors and io-related stuff MAXOPEN: NAT = 10; filedesc: TYPE = [0..MAXOPEN]; streamindex: TYPE = FileStream.FileByteIndex; IOERROR: filedesc = 0; STDIN: filedesc = 1; STDOUT: filedesc = 2; STDERR: filedesc = 3; IOREAD: INTEGER = 1; IOWRITE: INTEGER = 2; IOBOTH: INTEGER = 3; -- standard (Pascal) procedures ord: PROC [c: CHARACTER] RETURNS [character] = INLINE {RETURN [c - 0c]}; chr: PROC [c: character] RETURNS [CHARACTER] = INLINE {RETURN [c + 0c]}; -- file primitives tty: TTY.Handle; kbdline: STRING ← [MAXSTR]; kbdx: CARDINAL; kbdend: BOOLEAN; readkbd: PROC = { IF (kbdend) THEN kbdline.length ← 0 ELSE { ENABLE { TTY.LineOverflow => { putcf[NEWLINE, STDERR]; message["*** line too long"L]; putcf[NEWLINE, STDERR]; RETRY}; TTY.Rubout => { IF (kbdline.length) > 0 THEN RESUME ELSE { kbdend ← TRUE; CONTINUE}}}; IF (kbdline.length > 0) AND (kbdline[kbdline.length-1] = Ascii.CR) THEN kbdline.length ← kbdline.length - 1; -- for ESC tty.GetLine[kbdline]; IF kbdline.length >= kbdline.maxlength THEN [] ← ERROR TTY.LineOverflow[kbdline]; kbdline[kbdline.length] ← Ascii.CR; kbdline.length ← kbdline.length + 1}; kbdx ← 0}; filerec: TYPE = RECORD [ type: {none, stream, tty} ← none, handle: Stream.Handle ← NIL]; filetab: ARRAY filedesc OF filerec; accessmap: PROC [mode: INTEGER] RETURNS [File.Permissions] = { RETURN [SELECT mode FROM IOREAD => File.read, IOWRITE => File.write + File.grow + File.shrink + File.delete, IOBOTH => File.read + File.write + File.grow + File.shrink + File.delete, ENDCASE => File.read]}; fdalloc: PROC RETURNS [fd: filedesc] = { -- find a free slot in filetab FOR fd IN (STDERR .. MAXOPEN] DO IF (filetab[fd].type = none) THEN EXIT REPEAT FINISHED => fd ← IOERROR; ENDLOOP; RETURN}; fileerror: ERROR = CODE; findfile: PROC [name: STRING, access: File.Permissions] RETURNS [File.Capability] = { cap: File.Capability; old: BOOLEAN ← (access = File.read); IF ~old THEN { cap ← Directory.CreateFile[name, DCSFileTypes.tLeaderPage, 0 ! Directory.Error => { IF type = fileAlreadyExists THEN GOTO fileExists ELSE GO TO fileProblem}]; EXITS fileExists => old ← TRUE}; IF old THEN cap ← Directory.Lookup[fileName: name, permissions: Directory.ignore ! Directory.Error => {GO TO fileProblem}]; RETURN [Directory.UpdateDates[cap, access]] EXITS fileProblem => ERROR fileerror}; makestream: PROC [name: POINTER TO string, mode: INTEGER, fd: filedesc] = { i: INTEGER ← 0; intname: STRING ← [MAXSTR]; WHILE (name[i+1] # ENDSTR) DO intname[i] ← chr[name[i+1]]; i ← i + 1; ENDLOOP; intname.length ← i; filetab[fd] ← [ type: stream, handle: FileStream.Create[findfile[intname, accessmap[mode]]]]}; open: PROC [name: POINTER TO string, mode: INTEGER] RETURNS [fd: filedesc] = { fd ← fdalloc[]; IF fd # IOERROR THEN { makestream[name, mode, fd ! fileerror => {GOTO fail}]; EXITS fail => {filetab[fd] ← [type: none]; fd ← IOERROR}}; RETURN}; create: PROC [name: POINTER TO string, mode: INTEGER] RETURNS [filedesc] = open; -- check positioning of files opened for output close: PROC [fd: filedesc] = { SELECT filetab[fd].type FROM stream => { h: Stream.Handle = filetab[fd].handle; Stream.Delete[h]}; tty => NULL; ENDCASE => NULL; filetab[fd] ← [type: none]}; remove: PROC [s: POINTER TO string] = { -- this version just prints a message message["If we had remove, we would be deleting "L]; putcf[TAB, STDERR]; putstr[s, STDERR]; putcf[NEWLINE, STDERR]}; getindex: PROC [fd: filedesc] RETURNS [streamindex] = { RETURN [SELECT filetab[fd].type FROM stream => FileStream.GetIndex[filetab[fd].handle], ENDCASE => 0]}; setindex: PROC [i: streamindex, fd: filedesc] = { SELECT filetab[fd].type FROM stream => { h: Stream.Handle = filetab[fd].handle; FileStream.SetIndex[h, i]}; ENDCASE => NULL}; getc: PROC [c: POINTER TO character] RETURNS [character] = INLINE { RETURN [getcf[c, STDIN]]}; getcf: PROC [c: POINTER TO character, fd: filedesc] RETURNS [character] = { SELECT filetab[fd].type FROM stream => { h: Stream.Handle = filetab[fd].handle; c↑ ← IF FileStream.EndOf[h] THEN ENDFILE ELSE ord[h.GetChar[]]}; tty => IF (kbdx < kbdline.length) THEN { c↑ ← ord[kbdline[kbdx]]; kbdx ← kbdx + 1} ELSE { readkbd[]; IF (kbdline.length = 0) THEN -- iff kbdend c↑ ← ENDFILE ELSE { c↑ ← ord[kbdline[kbdx]]; kbdx ← kbdx + 1}}; ENDCASE => error["bad file descriptor"L]; RETURN [c↑]}; getline: PROC [s: POINTER TO string, fd: filedesc, maxsize: INTEGER ← MAXSTR] RETURNS [BOOLEAN] = { i: INTEGER ← 1; c: character; DO s[i] ← getcf[@c, fd]; i ← i + 1; IF (c = ENDFILE) OR (c = NEWLINE) OR (i >= maxsize) THEN EXIT ENDLOOP; IF (c = ENDFILE) THEN -- went one too far i ← i - 1; s[i] ← ENDSTR; RETURN [c # ENDFILE]}; putc: PROC [c: character] = INLINE {putcf[c, STDOUT]}; putcf: PROC [c: character, fd: filedesc] = { SELECT filetab[fd].type FROM stream => { h: Stream.Handle = filetab[fd].handle; h.PutChar[chr[c]]}; tty => tty.PutChar[chr[c]]; ENDCASE => error["bad file descriptor"L]}; putstr: PROC [s: POINTER TO string, fd: filedesc] = { i: INTEGER ← 1; WHILE (s[i] # ENDSTR) DO putcf[s[i], fd]; i ← i + 1 ENDLOOP}; -- string utilities setstring: PROC [s: POINTER TO string, text: STRING] = { FOR i: CARDINAL IN [1 .. text.length] DO s[i] ← ord[text[i-1]] ENDLOOP; s[text.length+1] ← ENDSTR}; -- errors and messages message: PROC [msg: STRING] = { FOR i: CARDINAL IN [0 .. msg.length) DO putcf[ord[msg[i]], STDERR] ENDLOOP}; exit: ERROR = CODE; error: PROC [msg: STRING] = { message[msg]; ERROR exit}; -- command arg primitives MAXCMD: NAT = 20; cmdargs: [0 .. MAXCMD]; cmd: ARRAY [1 .. MAXCMD] OF RECORD [idx, len: CARDINAL]; getarg: PROC [n: INTEGER, str: POINTER TO string, maxsize: INTEGER] RETURNS [BOOLEAN] = { b: BOOLEAN ← ((n > 0) AND (n <= cmdargs)); IF (b) THEN commandstr[cmd[n].idx, cmd[n].len, str, 1, maxsize]; RETURN [b]}; nargs: PROC RETURNS [INTEGER] = {RETURN [cmdargs]}; commandstr: PROC [i, n: INTEGER, dest: POINTER TO string, j: INTEGER, maxsize: INTEGER] = { WHILE (n > 0) AND (j < maxsize) DO dest[j] ← ord[Exec.commandLine.s[i]]; i ← i + 1; j ← j + 1; n ← n - 1; ENDLOOP; dest[j] ← ENDSTR}; -- utilities -- addstr: put c in outset[j] if it fits, increment j addstr: PROC [c: character, outset: POINTER TO string, j: POINTER TO INTEGER, maxset: INTEGER] RETURNS [added: BOOLEAN] = { IF (j↑ > maxset) THEN added ← FALSE ELSE { outset[j↑] ← c; j↑ ← j↑ + 1; added ← TRUE}; RETURN}; -- esc: map s[i↑] into escaped character, increment i↑ esc: PROC [s: POINTER TO string, i: POINTER TO INTEGER] RETURNS [c: character] = { IF (s[i↑] # ESCAPE) THEN c ← s[i↑] ELSE IF (s[i↑+1] = ENDSTR) THEN -- @ not special at end c ← ESCAPE ELSE { i↑ ← i↑ + 1; IF (s[i↑] = ord['n]) THEN c ← NEWLINE ELSE IF (s[i↑] = ord['t]) THEN c ← TAB ELSE c ← s[i↑]}; RETURN}; -- isalphanum: true if c is a letter or digit isalphanum: PROC [c: character] RETURNS [BOOLEAN] = { RETURN [ (c IN [ord['a] .. ord['z]]) OR (c IN [ord['A] .. ord['Z]]) OR (c IN [ord['0] .. ord['9]])]}; -- isdigit: true if c is a digit isdigit: PROC [c: character] RETURNS [BOOLEAN] = INLINE { RETURN [c IN [ord['0] .. ord['9]]]}; -- itoc: convert integer n to char string in s[i] ... itoc: PROC [n: INTEGER, s: POINTER TO string, i: INTEGER] RETURNS [next: INTEGER] = { IF (n < 0) THEN { s[i] ← ord['-]; next ← itoc[-n, s, i+1]} ELSE { IF (n >= 10) THEN i ← itoc[n/10, s, i]; s[i] ← n MOD 10 + ord['0]; s[i+1] ← ENDSTR; next ← i+1}; RETURN}; -- mustcreate: create file or die mustcreate: PROC [name: POINTER TO string, mode: INTEGER] RETURNS [fd: filedesc] = { fd ← create[name, mode]; IF (fd = IOERROR) THEN { putstr[name, STDERR]; error[": can't create file"L]}; RETURN}; -- scopy: copy string ar st src[i] to dest[j] scopy: PROC [src: POINTER TO string, i: INTEGER, dest: POINTER TO string, j: INTEGER] = { WHILE (src[i] # ENDSTR) DO dest[j] ← src[i]; i ← i + 1; j ← j + 1; ENDLOOP; dest[j] ← ENDSTR}; -- ctoi: convert string at s[i] to integer, increment i ctoi: PROC [s: POINTER TO string, i: POINTER TO INTEGER] RETURNS [INTEGER] = { n, sign: INTEGER; WHILE (s[i↑] = BLANK) OR (s[i↑] = TAB) DO i↑ ← i↑ + 1 ENDLOOP; sign ← IF s[i↑] = ord['-] THEN -1 ELSE 1; IF (s[i↑] = ord['+]) OR (s[i↑] = ord['-]) THEN i↑ ← i↑ + 1; n ← 0; WHILE (isdigit[s[i↑]]) DO n ← 10*n + (s[i↑] - ord['0]); i↑ ← i↑ + 1; ENDLOOP; RETURN [sign * n]}; -- putdec: put decimal integer n in field width >= w putdec: PROC [n, w: INTEGER] = { s: string; nd: INTEGER = itoc[n, @s, 1]; FOR i: INTEGER IN [nd .. w] DO putc[BLANK] ENDLOOP; FOR i: INTEGER IN [1 .. nd-1] DO putc[s[i]] ENDLOOP}; -- edittype: types for in-memory version of edit stcode: TYPE = {ENDDATA, ERR, OK}; -- status returns -- editcons: const declarations for edit MAXLINES: NAT = 1000; MAXPAT: NAT = MAXSTR; CLOSIZE: NAT = 1; -- size of a closure entry DITTO: character = -1; CLOSURE: character = ord['*]; BOL: character = ord['%]; EOL: character = ord['$]; ANYCHAR: character = ord['?]; CCL: character = ord['[]; CCLEND: character = ord[']]; NEGATE: character = ord['↑]; NCCL: character = ord['!]; -- cannot be the same as NEGATE LITCHAR: character = ord['c]; CURLINE: character = ord['.]; LASTLINE: character = ord['$]; SCAN: character = ord['/]; BACKSCAN: character = ord['\\]; SEMICOL: character = ord[';]; COMMA: character = ord[',]; PERIOD: character = ord['.]; ACMD: character = ord['a]; CCMD: character = ord['c]; DCMD: character = ord['d]; ECMD: character = ord['e]; EQCMD: character = ord['=]; FCMD: character = ord['f]; GCMD: character = ord['g]; ICMD: character = ord['i]; MCMD: character = ord['m]; PCMD: character = ord['p]; QCMD: character = ord['q]; RCMD: character = ord['r]; SCMD: character = ord['s]; WCMD: character = ord['w]; XCMD: character = ord['x]; -- editvar: variables for edit line1: INTEGER; -- first line number line2: INTEGER; -- second line number nlines: INTEGER; -- # of line numbers specified curln: INTEGER; -- current line, value of dot lastln: INTEGER; -- last line, value of $ pat: string; -- pattern lin: string; -- input line savefile: string; -- remembered file name -- editprim: editor buffer primitives buftype: TYPE = RECORD [ -- scratch-file edit buffer entry txt: streamindex, -- file index for text of line mark: BOOLEAN]; -- mark for line buf: ARRAY [0..MAXLINES] OF buftype; scr: filedesc; -- scratch fd recout: streamindex; -- next record to write on scr edittemp: string; -- temp file name "edit.scratch$" setbuf: PROC = { -- setbuf (scratch file): create scratch file, set up line 0 setstring[@edittemp, "edit.scratch$"L]; scr ← mustcreate[@edittemp, IOBOTH]; recout ← 0; curln ← 0; lastln ← 0}; clrbuf: PROC = { -- clrbuf (scratch file): dispose of scratch file close[scr]; remove[@edittemp]}; gettxt: PROC [n: INTEGER, s: POINTER TO string] = { -- gettext (scratch file): get text from line n into s IF (n = 0) THEN s[1] ← ENDSTR ELSE { setindex[buf[n].txt, scr]; [] ← getline[s, scr, MAXSTR]}}; blkmove: PROC [n1, n2, n3: INTEGER] = { -- blkmove: move block of lines n1..n2 to follow n3 IF (n3 < n1-1) THEN { reverse[n3+1, n1-1]; reverse[n1, n2]; reverse[n3+1, n2]} ELSE IF (n3 > n2) THEN { reverse[n1, n2]; reverse[n2+1, n3]; reverse[n1, n3]}}; reverse: PROC [n1, n2: INTEGER] = { -- reverse: reverse buf[n1]...buf[n2] WHILE (n1 < n2) DO temp: buftype = buf[n1]; buf[n1] ← buf[n2]; buf[n2] ← temp; n1 ← n1 + 1; n2 ← n2 - 1 ENDLOOP}; puttxt: PROC [lin: POINTER TO string] RETURNS [stat: stcode] = { -- puttxt (scratch file): put text from lin after curln stat ← ERR; IF (lastln < MAXLINES) THEN { lastln ← lastln + 1; setindex[recout, scr]; putstr[lin, scr]; putmark[lastln, FALSE]; buf[lastln].txt ← recout; recout ← getindex[scr]; blkmove[lastln, lastln, curln]; curln ← curln + 1; stat ← OK}; RETURN}; getmark: PROC [n: INTEGER] RETURNS [BOOLEAN] = { -- getmark: get mark from nth line RETURN [buf[n].mark]}; putmark: PROC [n: INTEGER, m: BOOLEAN] = { -- putmark: put mark m on nth line buf[n].mark ← m}; -- editproc: procedures for edit amatch: PROC [lin: POINTER TO string, offset: INTEGER, pat: POINTER TO string, j: INTEGER] RETURNS [INTEGER] = { -- amatch: look for match of pat[j] ... at lin[offset] ... omatch: PROC [lin: POINTER TO string, i: POINTER TO INTEGER, pat: POINTER TO string, j: INTEGER] RETURNS [matched: BOOLEAN] = { -- omatch: match one patern element at pat[j] locate: PROC [c: character, pat: POINTER TO string, offset: INTEGER] RETURNS [found: BOOLEAN ← FALSE] = { -- locate: look for c in character class at pat[offset] -- size of class is at pat[offset], characters follow i: INTEGER ← offset + pat[offset]; -- last position WHILE (i > offset) DO IF (c = pat[i]) THEN { found ← TRUE; i ← offset} -- force loop termination ELSE i ← i - 1 ENDLOOP; RETURN}; advance: [-1..1] ← -1; IF (lin[i↑] # ENDSTR) THEN SELECT pat[j] FROM LITCHAR => IF (lin[i↑] = pat[j+1]) THEN advance ← 1; BOL => IF (i↑ = 1) THEN advance ← 0; ANYCHAR => IF (lin[i↑] # NEWLINE) THEN advance ← 1; EOL => IF (lin[i↑] = NEWLINE) THEN advance ← 0; CCL => IF (locate[lin[i↑], pat, j+1]) THEN advance ← 1; NCCL => IF (lin[i↑] # NEWLINE) AND (~locate[lin[i↑], pat, j+1]) THEN advance ← 1; ENDCASE => error["in omatch: can't happen"L]; IF (advance >= 0) THEN { i↑ ← i↑ + advance; matched ← TRUE} ELSE matched ← FALSE; RETURN}; patsize: PROC [pat: POINTER TO string, n: INTEGER] RETURNS [size: INTEGER] = { -- patsize: returns size of pattern entry at pat[n] SELECT pat[n] FROM LITCHAR => size ← 2; BOL, EOL, ANYCHAR => size ← 1; CCL, NCCL => size ← pat[n+1] + 2; CLOSURE => size ← CLOSIZE; ENDCASE => error["in patsize: can't happen"L]; RETURN [size]}; i, k: INTEGER; done: BOOLEAN ← FALSE; WHILE (~done) AND (pat[j] # ENDSTR) DO IF (pat[j] = CLOSURE) THEN { j ← j + patsize[pat, j]; -- step over CLOSURE i ← offset; -- match as many as possible WHILE (~done) AND (lin[i] # ENDSTR) DO IF (~omatch[lin, @i, pat, j]) THEN done ← TRUE ENDLOOP; -- i points to input char that made us fail -- match rest of pattern against rest of input -- shrink closure by 1 after each failure done ← FALSE; WHILE (~done) AND (i >= offset) DO k ← amatch[lin, i, pat, j+patsize[pat, j]]; IF (k > 0) THEN -- matched rest of pattern done ← TRUE ELSE i ← i - 1 ENDLOOP; offset ← k; -- if k = 0 THEN failure else success done ← TRUE} ELSE IF (~omatch[lin, @offset, pat, j]) THEN { offset ← 0; -- non-closure done ← TRUE} ELSE -- omatch succeeded on this pattern element j ← j + patsize[pat, j] ENDLOOP; RETURN [offset]}; match: PROC [lin, pat: POINTER TO string] RETURNS [BOOLEAN] = { -- match: find match anywhere on line i: INTEGER ← 1; pos: INTEGER ← 0; WHILE (lin[i] # ENDSTR) AND (pos = 0) DO pos ← amatch[lin, i, pat, 1]; i ← i + 1 ENDLOOP; RETURN [pos > 0]}; skipbl: PROC [s: POINTER TO string, i: POINTER TO INTEGER] = { -- skipbl: skip blanks and tabs at s[i] ... WHILE (s[i↑] = BLANK) OR (s[i↑] = TAB) DO i↑ ← i↑ + 1 ENDLOOP}; optpat: PROC [lin: POINTER TO string, i: POINTER TO INTEGER] RETURNS [stat: stcode] = { -- optpat: get optional pattern from lin[i], increment i makepat: PROC [arg: POINTER TO string, start: INTEGER, delim: character, pat: POINTER TO string] RETURNS [INTEGER] = { -- makepat: make pattern from arg[i], terminate at delim getccl: PROC [arg: POINTER TO string, i: POINTER TO INTEGER, pat: POINTER TO string, j: POINTER TO INTEGER] RETURNS [BOOLEAN] = { -- getccl: expand char class at arg[i] into pat[j] jstart: INTEGER; dodash: PROC [delim: character, src: POINTER TO string, i: POINTER TO INTEGER, dest: POINTER TO string, j: POINTER TO INTEGER, maxset: INTEGER] = { -- dodash: expand set at src[i↑] into dest[j↑], stop at delim DASH: character = ord['-]; WHILE (src[i↑] # delim) AND (src[i↑] # ENDSTR) DO IF (src[i↑] = ESCAPE) THEN [] ← addstr[esc[src, i], dest, j, maxset] ELSE IF (src[i↑] # DASH) THEN [] ← addstr[src[i↑], dest, j, maxset] ELSE IF (j↑ <= 1) OR (src[i↑+1] = ENDSTR) THEN [] ← addstr[DASH, dest, j, maxset] -- literal - ELSE IF (isalphanum[src[i↑-1]]) AND (isalphanum[src[i↑+1]]) AND (src[i↑-1] <= src[i↑+1]) THEN { FOR k: character IN (src[i↑-1] .. src[i↑+1]] DO [] ← addstr[k, dest, j, maxset] ENDLOOP; i↑ ← i↑ + 1} ELSE [] ← addstr[DASH, dest, j, maxset]; i↑ ← i↑ + 1 ENDLOOP}; i↑ ← i↑ + 1; -- skip over '[ IF (arg[i↑] = NEGATE) THEN { [] ← addstr[NCCL, pat, j, MAXPAT]; i↑ ← i↑ + 1} ELSE [] ← addstr[CCL, pat, j, MAXPAT]; jstart ← j↑; [] ← addstr[0, pat, j, MAXPAT]; -- room for count dodash[CCLEND, arg, i, pat, j, MAXPAT]; pat[jstart] ← j↑ - jstart - 1; RETURN [arg[i↑] = CCLEND]}; stclose: PROC [pat: POINTER TO string, j: POINTER TO INTEGER, lastj: INTEGER] = { -- stclose: insert closure entry at pat[j] FOR jp: INTEGER DECREASING IN [lastj .. j↑) DO jt: INTEGER ← jp + CLOSIZE; [] ← addstr[pat[jp], pat, @jt, MAXPAT] ENDLOOP; j↑ ← j↑ + CLOSIZE; pat[lastj] ← CLOSURE}; -- where original pattern began i: INTEGER ← start; -- arg index j: INTEGER ← 1; -- pat index lastj: INTEGER ← 1; done: BOOLEAN ← FALSE; WHILE (~done) AND (arg[i] # delim) AND (arg[i] # ENDSTR) DO lj: INTEGER ← j; IF arg[i] = ANYCHAR THEN [] ← addstr[ANYCHAR, pat, @j, MAXPAT] ELSE IF (arg[i] = BOL) AND (i = start) THEN [] ← addstr[BOL, pat, @j, MAXPAT] ELSE IF (arg[i] = EOL) AND (arg[i+1] = delim) THEN [] ← addstr[EOL, pat, @j, MAXPAT] ELSE IF (arg[i] = CCL) THEN done ← (~getccl[arg, @i, pat, @j]) ELSE IF (arg[i] = CLOSURE) AND (i > start) THEN { lj ← lastj; IF (pat[lj] = BOL) OR (pat[lj] = EOL) OR (pat[lj] = CLOSURE) THEN done ← TRUE -- force loop termination ELSE stclose[pat, @j, lastj]} ELSE { [] ← addstr[LITCHAR, pat, @j, MAXPAT]; [] ← addstr[esc[arg, @i], pat, @j, MAXPAT]}; lastj ← lj; IF (~done) THEN i ← i + 1 ENDLOOP; RETURN [ IF (done) OR (arg[i] # delim) THEN 0 -- finished early ELSE IF (~addstr[ENDSTR, pat, @j, MAXPAT]) THEN 0 -- no room ELSE i]}; -- all is well; IF (lin[i↑] = ENDSTR) THEN i↑ ← 0 ELSE IF (lin[i↑+1] = ENDSTR) THEN i↑ ← 0 ELSE IF (lin[i↑+1] = lin[i↑]) THEN -- repeated delimiter i↑ ← i↑ + 1 -- leave existing pattern alone ELSE i↑ ← makepat[lin, i↑+1, lin[i↑], @pat]; IF (pat[1] = ENDSTR) THEN i↑ ← 0; IF (i↑ = 0) THEN { pat[1] ← ENDSTR; stat ← ERR} ELSE stat ← OK; RETURN}; nextln: PROC [n: INTEGER] RETURNS [INTEGER] = { -- nextln: get line after n RETURN [IF (n >= lastln) THEN 0 ELSE n + 1]}; prevln: PROC [n: INTEGER] RETURNS [INTEGER] = { -- prevln: get line before n RETURN [IF (n <= 0) THEN lastln ELSE n - 1]}; patscan: PROC [way: character, n: POINTER TO INTEGER] RETURNS [stat: stcode] = { -- patscan: find next occurrence of pattern after line n done: BOOLEAN ← FALSE; line: string; n↑ ← curln; stat ← ERR; DO n↑ ← IF (way = SCAN) THEN nextln[n↑] ELSE prevln[n↑]; gettxt[n↑, @line]; IF (match[@line, @pat]) THEN { stat ← OK; done ← TRUE}; IF (n↑ = curln) OR (done) THEN EXIT ENDLOOP; RETURN}; getnum: PROC [lin: POINTER TO string, i, num: POINTER TO INTEGER, status: POINTER TO stcode] RETURNS [stcode] = { -- getnum: get single line number component status↑ ← OK; skipbl[lin, i]; IF (isdigit[lin[i↑]]) THEN { num↑ ← ctoi[lin, i]; i↑ ← i↑ - 1} -- move back, to be advanced at end ELSE IF (lin[i↑] = CURLINE) THEN num↑ ← curln ELSE IF (lin[i↑] = LASTLINE) THEN num↑ ← lastln ELSE IF (lin[i↑] = SCAN) OR (lin[i↑] = BACKSCAN) THEN { IF (optpat[lin, i] = ERR) THEN -- build pattern status↑ ← ERR ELSE status↑ ← patscan[lin[i↑], num]} ELSE status↑ ← ENDDATA; IF (status↑ = OK) THEN i↑ ← i↑ + 1; -- next character to be examined RETURN [status↑]}; getone: PROC [lin: POINTER TO string, i, num: POINTER TO INTEGER, status: POINTER TO stcode] RETURNS [stcode] = { -- getone: get one line number expression istart: INTEGER = i↑; num↑ ← 0; IF (getnum[lin, i, num, status] = OK) THEN -- first term DO -- + or - terms skipbl[lin, i]; IF (lin[i↑] # ord['+]) AND (lin[i↑] # ord['-]) THEN status↑ ← ENDDATA ELSE { pnum: INTEGER; mul: INTEGER = IF (lin[i↑] = ord['+]) THEN 1 ELSE -1; i↑ ← i↑ + 1; IF (getnum[lin, i, @pnum, status] = OK) THEN num↑ ← num↑ + mul*pnum; IF (status↑ = ENDDATA) THEN status↑ ← ERR}; IF (status↑ # OK) THEN EXIT ENDLOOP; IF (num↑ < 0) OR (num↑ > lastln) THEN status↑ ← ERR; IF (status↑ # ERR) THEN status↑ ← IF (i↑ <= istart) THEN ENDDATA ELSE OK; RETURN [status↑]}; getlist: PROC [lin: POINTER TO string, i: POINTER TO INTEGER, status: POINTER TO stcode] RETURNS [stcode] = { -- getlist: get list of line nums at lin[i], increment i num: INTEGER; done: BOOLEAN; line2 ← 0; nlines ← 0; done ← (getone[lin, i, @num, status] # OK); WHILE (~done) DO line1 ← line2; line2 ← num; nlines ← nlines + 1; IF (lin[i↑] = SEMICOL) THEN curln ← num; IF (lin[i↑] = COMMA) OR (lin[i↑] = SEMICOL) THEN { i↑ ← i↑ + 1; done ← (getone[lin, i, @num, status] # OK)} ELSE done ← TRUE ENDLOOP; nlines ← MIN[nlines, 2]; IF (nlines = 0) THEN line2 ← curln; IF (nlines <= 1) THEN line1 ← line2; IF (status↑ # ERR) THEN status↑ ← OK; RETURN [status↑]}; append: PROC [line: INTEGER, glob: BOOLEAN] RETURNS [stat: stcode] = { -- append: append lines after "line" IF (glob) THEN stat ← ERR ELSE { inline: string; done: BOOLEAN ← FALSE; curln ← line; stat ← OK; WHILE (~done) AND (stat = OK) DO IF (~getline[@inline, STDIN, MAXSTR]) THEN stat ← ENDDATA ELSE IF (inline[1] = PERIOD) AND (inline[2] = NEWLINE) THEN done ← TRUE ELSE IF (puttxt[@inline] = ERR) THEN stat ← ERR ENDLOOP}; RETURN}; lndelete: PROC [n1, n2: INTEGER, status: POINTER TO stcode] RETURNS [stcode] = { -- lndelete: delete lines n1 through n2 IF (n1 <= 0) THEN status↑ ← ERR ELSE { blkmove[n1, n2, lastln]; lastln ← lastln - (n1 - n1 + 1); curln ← prevln[n1]; status↑ ← OK}; RETURN [status↑]}; doprint: PROC [n1, n2: INTEGER] RETURNS [stat: stcode] = { -- doprint: print lines n1 through n2 line: string; IF (n1 <= 0) THEN stat ← ERR ELSE { FOR i: INTEGER IN [n1 .. n2] DO gettxt[i, @line]; putstr[@line, STDOUT] ENDLOOP; curln ← n2; stat ← OK}; RETURN}; doread: PROC [n: INTEGER, fil: POINTER TO string] RETURNS [stat: stcode] = { -- doread: read "fil" after line n fd: filedesc = open[fil, IOREAD]; IF (fd = IOERROR) THEN stat ← ERR ELSE { inline: string; count: INTEGER ← 0; curln ← n; stat ← OK; DO t: BOOLEAN = getline[@inline, fd, MAXSTR]; IF (t) THEN { stat ← puttxt[@inline]; IF (stat # ERR) THEN count ← count + 1}; IF (stat # OK) OR (~t) THEN EXIT ENDLOOP; close[fd]; putdec[count, 1]; putc[NEWLINE]}; RETURN}; dowrite: PROC [n1, n2: INTEGER, fil: POINTER TO string] RETURNS [stat: stcode] = { -- dowrite: write lines n1..n2 into file fd: filedesc = create[fil, IOWRITE]; IF (fd = IOERROR) THEN stat ← ERR ELSE { line: string; FOR i: INTEGER IN [n1 .. n2] DO gettxt[i, @line]; putstr[@line, fd] ENDLOOP; close[fd]; putdec[n2-n1+1, 1]; putc[NEWLINE]; stat ← OK}; RETURN}; move: PROC [line3: INTEGER] RETURNS [stat: stcode] = { -- move: move line1 through line2 after line3 IF (line1 <= 0) OR ((line3 >= line1) AND (line3 < line2)) THEN stat ← ERR ELSE { blkmove[line1, line2, line3]; curln ← IF (line3 > line1) THEN line3 ELSE line3 + (line2 - line1 + 1); stat ← OK}; RETURN}; makesub: PROC [arg: POINTER TO string, from: INTEGER, delim: character, sub: POINTER TO string] RETURNS [INTEGER] = { -- makesub: make substitution string from arg in sub i: INTEGER ← from; j: INTEGER ← 1; WHILE (arg[i] # delim) AND (arg[i] # ENDSTR) DO IF (arg[i] = ord['&]) THEN [] ← addstr[DITTO, sub, @j, MAXPAT] ELSE [] ← addstr[esc[arg, @i], sub, @j, MAXPAT]; i ← i + 1 ENDLOOP; RETURN [ IF (arg[i] # delim) THEN 0 -- missing delimiter ELSE IF (~addstr[ENDSTR, sub, @j, MAXPAT]) THEN 0 ELSE i]}; getrhs: PROC [lin: POINTER TO string, i: POINTER TO INTEGER, sub: POINTER TO string, gflag: POINTER TO BOOLEAN] RETURNS [stat: stcode] = { -- getrhs: get right hand side of "s" command stat ← OK; IF (lin[i↑] = ENDSTR) THEN stat ← ERR ELSE IF (lin[i↑+1] = ENDSTR) THEN stat ← ERR ELSE { i↑ ← makesub[lin, i↑+1, lin[i↑], sub]; IF (i↑ = 0) THEN stat ← ERR ELSE IF (lin[i↑+1] = ord['g]) THEN { i↑ ← i↑ + 1; gflag↑ ← TRUE} ELSE gflag↑ ← FALSE}; RETURN}; catsub: PROC [lin: POINTER TO string, s1, s2: INTEGER, sub, new: POINTER TO string, k: POINTER TO INTEGER, maxnew: INTEGER] = { -- catsub: add replacement text to end of new i: INTEGER ← 1; WHILE (sub[i] # ENDSTR) DO IF (sub[i] = DITTO) THEN FOR j: INTEGER IN [s1 .. s2) DO [] ← addstr[lin[j], new, k, maxnew] ENDLOOP ELSE [] ← addstr[sub[i], new, k, maxnew]; i ← i + 1 ENDLOOP}; subst: PROC [sub: POINTER TO string, gflag, glob: BOOLEAN] RETURNS [stcode] = { -- subst: substitute "sub" for occurrences of pattern stat: stcode ← IF (glob) THEN OK ELSE ERR; done: BOOLEAN ← (line1 <= 0); line: INTEGER ← line1; new, old: string; WHILE (~done) AND (line <= line2) DO j, k: INTEGER ← 1; subbed: BOOLEAN ← FALSE; lastm: INTEGER ← 0; gettxt[line, @old]; WHILE (old[k] # ENDSTR) DO m: INTEGER = IF (gflag) OR (~subbed) THEN amatch[@old, k, @pat, 1] ELSE 0; IF (m > 0) AND (lastm # m) THEN { -- replace matched text subbed ← TRUE; catsub[@old, k, m, sub, @new, @j, MAXSTR]; lastm ← m}; IF (m = 0) OR (m = k) THEN { -- no match or null match [] ← addstr[old[k], @new, @j, MAXSTR]; k ← k + 1} ELSE -- skip matched text k ← m ENDLOOP; IF (subbed) THEN { IF (~addstr[ENDSTR, @new, @j, MAXSTR]) THEN { stat ← ERR; done ← TRUE} ELSE { stat ← lndelete[line, line, @stat]; stat ← puttxt[@new]; line2 ← line2 + curln - line; line ← curln; IF (stat = ERR) THEN done ← TRUE ELSE stat ← OK}}; line ← line + 1 ENDLOOP; RETURN [stat]}; ckp: PROC [lin: POINTER TO string, i: INTEGER, pflag: POINTER TO BOOLEAN, status: POINTER TO stcode] RETURNS [stcode] = { -- ckp: check for "p" after command skipbl[lin, @i]; IF (lin[i] = PCMD) THEN { i ← i + 1; pflag↑ ← TRUE} ELSE pflag↑ ← FALSE; status↑ ← IF (lin[i] = NEWLINE) THEN OK ELSE ERR; RETURN [status↑]}; default: PROC [def1, def2: INTEGER, status: POINTER TO stcode] RETURNS [stcode] = { -- default: set defaulted line numbers IF (nlines = 0) THEN { line1 ← def1; line2 ← def2}; status↑ ← IF (line1 > line2) OR (line1 <= 0) THEN ERR ELSE OK; RETURN [status↑]}; getfn: PROC [lin: POINTER TO string, i: POINTER TO INTEGER, fil: POINTER TO string] RETURNS [stat: stcode] = { -- getfn: get file name from lin[i] ... getword: PROC [s: POINTER TO string, i: INTEGER, out: POINTER TO string] RETURNS [INTEGER] = { j: INTEGER ← 1; WHILE (s[i] = BLANK) OR (s[i] = TAB) OR (s[i] = NEWLINE) DO i ← i + 1 ENDLOOP; WHILE (~((s[i] = ENDSTR) OR (s[i] = BLANK) OR (s[i] = TAB) OR (s[i] = NEWLINE))) DO out[j] ← s[i]; i ← i + 1; j ← j + 1 ENDLOOP; out[j] ← ENDSTR; RETURN [IF (s[i] = ENDSTR) THEN 0 ELSE i]}; stat ← ERR; IF (lin[i↑+1] = BLANK) THEN { k: INTEGER = getword[lin, i↑+2, fil]; -- get new filename IF (k > 0) THEN IF (lin[k] = NEWLINE) THEN stat ← OK} ELSE IF (lin[i↑+1] = NEWLINE) AND (savefile[1] # ENDSTR) THEN { scopy[@savefile, 1, fil, 1]; stat ← OK}; IF (stat = OK) AND (savefile[1] = ENDSTR) THEN scopy[fil, 1, @savefile, 1]; -- save if no old one RETURN}; docmd: PROC [lin: POINTER TO string, i: POINTER TO INTEGER, glob: BOOLEAN, status: POINTER TO stcode] RETURNS [stcode] = { -- docmd: handle all commands except globals pflag: BOOLEAN ← FALSE; -- may be set by d, m, s status↑ ← ERR; IF (lin[i↑] = PCMD) THEN { IF (lin[i↑+1] = NEWLINE) THEN IF (default[curln, curln, status] = OK) THEN status↑ ← doprint[line1, line2]} ELSE IF (lin[i↑] = NEWLINE) THEN { IF (nlines = 0) THEN line2 ← nextln[curln]; status↑ ← doprint[line2, line2]} ELSE IF (lin[i↑] = QCMD) THEN { IF (lin[i↑+1]=NEWLINE) AND (nlines=0) AND (~glob) THEN status↑ ← ENDDATA} ELSE IF (lin[i↑] = ACMD) THEN { IF (lin[i↑+1] = NEWLINE) THEN status↑ ← append[line2, glob]} ELSE IF (lin[i↑] = CCMD) THEN { IF (lin[i↑+1] = NEWLINE) THEN IF (default[curln, curln, status] = OK) THEN IF (lndelete[line1, line2, status] = OK) THEN status↑ ← append[prevln[line1], glob]} ELSE IF (lin[i↑] = DCMD) THEN { IF (ckp[lin, i↑+1, @pflag, status] = OK) THEN IF (default[curln, curln, status] = OK) THEN IF (lndelete[line1, line2, status] = OK) THEN IF (nextln[curln] # 0) THEN curln ← nextln[curln]} ELSE IF (lin[i↑] = ICMD) THEN { IF (lin[i↑+1] = NEWLINE) THEN IF (line2 = 0) THEN status↑ ← append[0, glob] ELSE status↑ ← append[prevln[line2], glob]} ELSE IF (lin[i↑] = EQCMD) THEN { IF (ckp[lin, i↑+1, @pflag, status] = OK) THEN { putdec[line2, 1]; putc[NEWLINE]}} ELSE IF (lin[i↑] = MCMD) THEN { line3: INTEGER; i↑ ← i↑ + 1; IF (getone[lin, i, @line3, status] = ENDDATA) THEN status↑ ← ERR; IF (status↑ = OK) THEN IF (ckp[lin, i↑, @pflag, status] = OK) THEN IF (default[curln, curln, status] = OK) THEN status↑ ← move[line3]} ELSE IF (lin[i↑] = SCMD) THEN { sub: string; gflag: BOOLEAN; i↑ ← i↑ + 1; IF (optpat[lin, i] = OK) THEN IF (getrhs[lin, i, @sub, @gflag] = OK) THEN IF (ckp[lin, i↑+1, @pflag, status] = OK) THEN IF (default[curln, curln, status] = OK) THEN status↑ ← subst[@sub, gflag, glob]} ELSE IF (lin[i↑] = ECMD) THEN { fil: string; IF (nlines = 0) THEN IF (getfn[lin, i, @fil] = OK) THEN { scopy[@fil, 1, @savefile, 1]; clrbuf[]; setbuf[]; status↑ ← doread[0, @fil]}} ELSE IF (lin[i↑] = FCMD) THEN { fil: string; IF (nlines = 0) THEN IF (getfn[lin, i, @fil] = OK) THEN { scopy[@fil, 1, @savefile, 1]; putstr[@savefile, STDOUT]; putc[NEWLINE]; status↑ ← OK}} ELSE IF (lin[i↑] = RCMD) THEN { fil: string; IF (getfn[lin, i, @fil] = OK) THEN status↑ ← doread[line2, @fil]} ELSE IF (lin[i↑] = WCMD) THEN { fil: string; IF (getfn[lin, i, @fil] = OK) THEN IF (default[1, lastln, status] = OK) THEN status↑ ← dowrite[line1, line2, @fil]} -- else status is ERR --; IF (status↑ = OK) AND (pflag) THEN status↑ ← doprint[curln, curln]; RETURN [status↑]}; ckglob: PROC [lin: POINTER TO string, i: POINTER TO INTEGER, status: POINTER TO stcode] RETURNS [stcode] = { -- ckglob: if global prefix, mark lines to be affected IF (lin[i↑] # GCMD) AND (lin[i↑] # XCMD) THEN status↑ ← ENDDATA ELSE { gflag: BOOLEAN = (lin[i↑] = GCMD); i↑ ← i↑ + 1; IF (optpat[lin, i] = ERR) THEN status↑ ← ERR ELSE IF (default[1, lastln, status] # ERR) THEN { temp: string; i↑ ← i↑ + 1; -- mark affected lines FOR n: INTEGER IN [line1 .. line2] DO gettxt[n, @temp]; putmark[n, (match[@temp, @pat] = gflag)] ENDLOOP; FOR n: INTEGER IN [1 .. line1) DO -- erase other marks putmark[n, FALSE] ENDLOOP; FOR n: INTEGER IN (line2 .. lastln] DO putmark[n, FALSE] ENDLOOP; status↑ ← OK}}; RETURN [status↑]}; doglob: PROC [lin: POINTER TO string, i, cursave: POINTER TO INTEGER, status: POINTER TO stcode] RETURNS [stcode] = { -- doglob: do command at lin[i] on all marked lines count: INTEGER ← 0; istart: INTEGER = i↑; n: INTEGER ← line1; status↑ ← OK; DO IF (getmark[n]) THEN { putmark[n, FALSE]; curln ← n; cursave↑ ← curln; i↑ ← istart; IF (getlist[lin, i, status] = OK) THEN IF (docmd[lin, i, TRUE, status] = OK) THEN count ← 0} ELSE { n ← nextln[n]; count ← count + 1}; IF (count > lastln) OR (status↑ # OK) THEN EXIT ENDLOOP; RETURN [status↑]}; -- edit: main routine for text editor edit: PROC = { more: BOOLEAN; setbuf[]; pat[1] ← ENDSTR; savefile[1] ← ENDSTR; IF (getarg[1, @savefile, MAXSTR]) THEN IF (doread[0, @savefile] = ERR) THEN message["?"L]; more ← getline[@lin, STDIN, MAXSTR]; WHILE (more) DO cursave: INTEGER ← curln; i: INTEGER ← 1; status: stcode; IF (getlist[@lin, @i, @status] = OK) THEN { IF (ckglob[@lin, @i, @status] = OK) THEN status ← doglob[@lin, @i, @cursave, @status] ELSE IF (status # ERR) THEN status ← docmd[@lin, @i, FALSE, @status] -- ELSE ERR, do nothing--}; IF (status = ERR) THEN { message["?"L]; curln ← MIN[cursave, lastln]} ELSE IF (status = ENDDATA) THEN more ← FALSE; -- ELSE OK IF (more) THEN more ← getline[@lin, STDIN, MAXSTR] ENDLOOP; clrbuf[]}; -- here begins the shell initcmd: PROC = { idx: CARDINAL ← Exec.commandLine.i; limx: CARDINAL = IF Exec.commandLine.s = NIL THEN 0 ELSE Exec.commandLine.s.length-1; tty ← Exec.w; kbdx ← kbdline.length ← 0; kbdend ← FALSE; filetab[STDIN].type ← filetab[STDOUT].type ← filetab[STDERR].type ← tty; cmdargs ← 0; WHILE (idx < limx) DO WHILE (Exec.commandLine.s[idx] = Ascii.SP) AND (idx < limx) DO idx ← idx + 1 ENDLOOP; IF (idx < limx) THEN { startidx: CARDINAL = idx; WHILE (idx < limx) AND (Exec.commandLine.s[idx] # Ascii.SP) AND (Exec.commandLine.s[idx] # Ascii.TAB) DO idx ← idx + 1 ENDLOOP; SELECT Exec.commandLine.s[startidx] FROM '< => { name: string; commandstr[startidx+1, idx - (startidx+1), @name, 1, MAXSTR]; close[STDIN]; makestream[@name, IOREAD, STDIN]}; '> => { name: string; commandstr[startidx+1, idx - (startidx+1), @name, 1, MAXSTR]; close[STDOUT]; makestream[@name, IOWRITE, STDOUT]}; ENDCASE => { cmdargs ← cmdargs + 1; IF cmdargs > MAXCMD THEN { putcf[NEWLINE, STDERR]; message["*** too many arguments"L]; putcf[NEWLINE, STDERR]} ELSE cmd[cmdargs] ← [idx: startidx, len: idx - startidx]}}; ENDLOOP; putcf[NEWLINE, STDERR]}; endcmd: PROC = { FOR fd: filedesc IN (IOERROR .. MAXOPEN] DO close[fd] ENDLOOP; IF Exec.commandLine.s # NIL THEN Exec.commandLine.i ← Exec.commandLine.s.length}; main: PROC = { initcmd[]; edit[ ! exit => {CONTINUE}]; endcmd[]}; Exec.AddCommand["Edit.~"L, main]; }.