========== edit program from chapter 6 ========== define(MAXPAT,128) define(andif,if) define(GLOBAL,LETG) define(PRINT,LETP) define(MARKED,LETY) define(NOMARK,LETN) define(FORWARD,0) define(BACKWARD,-1) define(EXCLUDE,LETX) define(APPENDCOM,LETA) define(CHANGE,LETC) define(DELCOM,LETD) define(ENTER,LETE) define(PRINTFIL,LETF) define(READCOM,LETR) define(WRITECOM,LETW) define(INSERT,LETI) define(PRINTCUR,EQUALS) define(MOVECOM,LETM) define(QUIT,LETQ) define(SUBSTITUTE,LETS) define(CURLINE,PERIOD) define(LASTLINE,DOLLAR) define(SCAN,SLASH) define(BACKSCAN,BACKSLASH) define(NOSTATUS,1) define(LINE0,1) define(PREV,0) define(NEXT,1) define(MARK,2) define(TEXT,3) define(MAXBUF,1000) common /cbuf/ buf(MAXBUF), lastbf character buf # buffer for pointers plus text integer lastbf # last element used in buf common /clines/ line1, line2, nlines, curln, lastln integer line1 # first line number integer line2 # second line number integer nlines # number of line numbers specified integer curln # current line: value of dot integer lastln # last line: value of $ common /cpat/ pat(MAXPAT) character pat # pattern common /ctxt/ txt(MAXLINE) character txt # text line for matching and output common /cfile/ savfil(MAXLINE) character savfil # remembered file name # append - append lines after "line" integer function append(line, glob) character lin(MAXLINE) integer getlin, inject integer line, glob include clines if (glob == YES) append = ERR else { curln = line for (append = NOSTATUS; append == NOSTATUS; ) if (getlin(lin, STDIN) == EOF) append = EOF else if (lin(1) == PERIOD & lin(2) == NEWLINE) append = OK else if (inject(lin) == ERR) append = ERR $@$ return end # ckglob - if global prefix, mark lines to be affected integer function ckglob(lin, i, status) character lin(MAXLINE) integer defalt, getind, gettxt, match, nextln, optpat integer gflag, i, k, line, status include cbuf include clines include cpat include ctxt if (lin(i) ~= GLOBAL & lin(i) ~= EXCLUDE) status = EOF else { if (lin(i) == GLOBAL) gflag = YES else gflag = NO i = i + 1 if (optpat(lin, i) == ERR | defalt(1, lastln, status) == ERR) status = ERR else { i = i + 1 for (line = line1; line <= line2; line = line + 1) { k = gettxt(line) if (match(txt, pat) == gflag) buf(k+MARK) = YES else buf(k+MARK) = NO $@$ for (line=nextln(line2); line~=line1; line=nextln(line)) { k = getind(line) buf(k+MARK) = NO $@$ status = OK $@$ $@$ ckglob = status return end # ckp - check for "p" after command integer function ckp(lin, i, pflag, status) character lin(MAXLINE) integer i, j, pflag, status j = i if (lin(j) == PRINT) { j = j + 1 pflag = YES $@$ else pflag = NO if (lin(j) == NEWLINE) status = OK else status = ERR ckp = status return end # clrbuf (in memory) - initialize for new file subroutine clrbuf return # nothing to do end # defalt - set defaulted line numbers integer function defalt(def1, def2, status) integer def1, def2, status include clines if (nlines == 0) { line1 = def1 line2 = def2 $@$ if (line1 > line2 | line1 <= 0) status = ERR else status = OK defalt = status return end # delete - delete lines from through to integer function delete(from, to, status) integer getind, nextln, prevln integer from, k1, k2, status, to include clines if (from <= 0) status = ERR else { k1 = getind(prevln(from)) k2 = getind(nextln(to)) lastln = lastln - (to - from + 1) curln = prevln(from) call relink(k1, k2, k1, k2) status = OK $@$ delete = status return end # docmd - handle all commands except globals integer function docmd(lin, i, glob, status) character file(MAXLINE), lin(MAXLINE), sub(MAXPAT) integer append, delete, doprnt, doread, dowrit, move, subst integer ckp, defalt, getfn, getone, getrhs, nextln, optpat, prevln integer gflag, glob, i, line3, pflag, status include cfile include clines include cpat pflag = NO # may be set by d, m, s status = ERR if (lin(i) == APPENDCOM) { if (lin(i + 1) == NEWLINE) status = append(line2, glob) $@$ else if (lin(i) == CHANGE) { if (lin(i + 1) == NEWLINE) andif (defalt(curln, curln, status) == OK) andif (delete(line1, line2, status) == OK) status = append(prevln(line1), glob) $@$ else if (lin(i) == DELCOM) { if (ckp(lin, i + 1, pflag, status) == OK) andif (defalt(curln, curln, status) == OK) andif (delete(line1, line2, status) == OK) andif (nextln(curln) ~= 0) curln = nextln(curln) $@$ else if (lin(i) == INSERT) { if (lin(i + 1) == NEWLINE) status = append(prevln(line2), glob) $@$ else if (lin(i) == PRINTCUR) { if (ckp(lin, i + 1, pflag, status) == OK) { call putdec(line2, 1) call putc(NEWLINE) $@$ $@$ else if (lin(i) == MOVECOM) { i = i + 1 if (getone(lin, i, line3, status) == EOF) status = ERR if (status == OK) andif (ckp(lin, i, pflag, status) == OK) andif (defalt(curln, curln, status) == OK) status = move(line3) $@$ else if (lin(i) == SUBSTITUTE) { i = i + 1 if (optpat(lin, i) == OK) andif (getrhs(lin, i, sub, gflag) == OK) andif (ckp(lin, i + 1, pflag, status) == OK) andif (defalt(curln, curln, status) == OK) status = subst(sub, gflag) $@$ else if (lin(i) == ENTER) { if (nlines == 0) andif (getfn(lin, i, file) == OK) { call scopy(file, 1, savfil, 1) call clrbuf call setbuf status = doread(0, file) $@$ $@$ else if (lin(i) == PRINTFIL) { if (nlines == 0) andif (getfn(lin, i, file) == OK) { call scopy(file, 1, savfil, 1) call putlin(savfil, STDOUT) call putc(NEWLINE) status = OK $@$ $@$ else if (lin(i) == READCOM) { if (getfn(lin, i, file) == OK) status = doread(line2, file) $@$ else if (lin(i) == WRITECOM) { if (getfn(lin, i, file) == OK) andif (defalt(1, lastln, status) == OK) status = dowrit(line1, line2, file) $@$ else if (lin(i) == PRINT) { if (lin(i + 1) == NEWLINE) andif (defalt(curln, curln, status) == OK) status = doprnt(line1, line2) $@$ else if (lin(i) == NEWLINE) { if (nlines == 0) line2 = nextln(curln) status = doprnt(line2, line2) $@$ else if (lin(i) == QUIT) { if (lin(i + 1) == NEWLINE & nlines == 0 & glob == NO) status = EOF $@$ # else status is ERR if (status == OK & pflag == YES) status = doprnt(curln, curln) docmd = status return end # doglob - do command at lin(i) on all marked lines integer function doglob(lin, i, cursav, status) character lin(MAXLINE) integer docmd, getind, getlst, nextln integer count, cursav, i, istart, k, line, status include cbuf include clines status = OK count = 0 line = line1 istart = i repeat { k = getind(line) if (buf(k+MARK) == YES) { buf(k+MARK) = NO curln = line cursav = curln i = istart if (getlst(lin, i, status) == OK) andif (docmd(lin, i, YES, status) == OK) count = 0 $@$ else { line = nextln(line) count = count + 1 $@$ $@$ until (count > lastln | status ~= OK) doglob = status return end # doprnt - print lines from through to integer function doprnt(from, to) integer gettxt integer from, i, j, to include clines include ctxt if (from <= 0) doprnt = ERR else { for (i = from; i <= to; i = i + 1) { j = gettxt(i) call putlin(txt, STDOUT) $@$ curln = to doprnt = OK $@$ return end # doread - read "file" after "line" integer function doread(line, file) character file(MAXLINE), lin(MAXLINE) integer getlin, inject, open integer count, fd, line include clines fd = open(file, READ) if (fd == ERR) doread = ERR else { curln = line doread = OK for (count = 0; getlin(lin, fd) ~= EOF; count = count + 1) { doread = inject(lin) if (doread == ERR) break $@$ call close(fd) call putdec(count, 1) call putc(NEWLINE) $@$ return end # dowrit - write "from" through "to" into file integer function dowrit(from, to, file) character file(MAXLINE) integer create, gettxt integer fd, from, k, line, to include ctxt fd = create(file, WRITE) if (fd == ERR) dowrit = ERR else { for (line = from; line <= to; line = line + 1) { k = gettxt(line) call putlin(txt, fd) $@$ call close(fd) call putdec(to-from+1, 1) call putc(NEWLINE) dowrit = OK $@$ return end # edit - main routine character lin(MAXLINE) integer ckglob, docmd, doglob, doread, getarg, getlin, getlst integer cursav, i, status include cfile include clines include cpat call setbuf pat(1) = EOS savfil(1) = EOS if (getarg(1, savfil, MAXLINE) ~= EOF) if (doread(0, savfil) == ERR) call remark("?.") while (getlin(lin, STDIN) ~= EOF) { i = 1 cursav = curln if (getlst(lin, i, status) == OK) { if (ckglob(lin, i, status) == OK) status = doglob(lin, i, cursav, status) else if (status ~= ERR) status = docmd(lin, i, NO, status) # else error, do nothing $@$ if (status == ERR) { call remark("?.") curln = cursav $@$ else if (status == EOF) break # else OK, loop $@$ call clrbuf stop end # getfn - get file name from lin(i)... integer function getfn(lin, i, file) character lin(MAXLINE), file(MAXLINE) integer i, j, k include cfile getfn = ERR if (lin(i + 1) == BLANK) { j = i + 2 # get new file name call skipbl(lin, j) for (k = 1; lin(j) ~= NEWLINE; k = k + 1) { file(k) = lin(j) j = j + 1 $@$ file(k) = EOS if (k > 1) getfn = OK $@$ else if (lin(i + 1) == NEWLINE & savfil(1) ~= EOS) { call scopy(savfil, 1, file, 1) # or old name getfn = OK $@$ # else error if (getfn == OK & savfil(1) == EOS) call scopy(file, 1, savfil, 1) # save if no old one return end # getind - locate line index in buffer integer function getind(line) integer j, k, line include cbuf k = LINE0 for (j = 0; j < line; j = j + 1) k = buf(k + NEXT) getind = k return end # getlst - collect line numbers (if any) at lin(i), increment i integer function getlst(lin, i, status) character lin(MAXLINE) integer getone, min integer i, num, status include clines line2 = 0 for (nlines = 0; getone(lin, i, num, status) == OK; ) { line1 = line2 line2 = num nlines = nlines + 1 if (lin(i) ~= COMMA & lin(i) ~= SEMICOL) break if (lin(i) == SEMICOL) curln = num i = i + 1 $@$ nlines = min(nlines, 2) if (nlines == 0) line2 = curln if (nlines <= 1) line1 = line2 if (status ~= ERR) status = OK getlst = status return end # getnum - convert one term to line number integer function getnum(lin, i, pnum, status) character lin(MAXLINE) integer ctoi, index, optpat, ptscan integer i, pnum, status include clines include cpat # string digits "0123456789" integer digits(11) data digits(01)/DIG0/ data digits(02)/DIG1/ data digits(03)/DIG2/ data digits(04)/DIG3/ data digits(05)/DIG4/ data digits(06)/DIG5/ data digits(07)/DIG6/ data digits(08)/DIG7/ data digits(09)/DIG8/ data digits(10)/DIG9/ data digits(11)/EOS/ getnum = OK if (index(digits, lin(i)) > 0) { pnum = ctoi(lin, i) i = i - 1 # move back; to be advanced at the end $@$ else if (lin(i) == CURLINE) pnum = curln else if (lin(i) == LASTLINE) pnum = lastln else if (lin(i) == SCAN | lin(i) == BACKSCAN) { if (optpat(lin, i) == ERR) # build the pattern getnum = ERR else if (lin(i) == SCAN) getnum = ptscan(FORWARD, pnum) else getnum = ptscan(BACKWARD, pnum) $@$ else getnum = EOF if (getnum == OK) i = i + 1 # point at next character to be examined status = getnum return end # getone - evaluate one line number expression integer function getone(lin, i, num, status) character lin(MAXLINE) integer getnum integer i, istart, mul, num, pnum, status include clines istart = i num = 0 call skipbl(lin, i) if (getnum(lin, i, num, status) == OK) # first term repeat { # + or - terms call skipbl(lin, i) if (lin(i) ~= PLUS & lin(i) ~= MINUS) { status = EOF break $@$ if (lin(i) == PLUS) mul = +1 else mul = -1 i = i + 1 call skipbl(lin, i) if (getnum(lin, i, pnum, status) == OK) num = num + mul * pnum if (status == EOF) status = ERR $@$ until (status ~= OK) if (num < 0 | num > lastln) status = ERR if (status == ERR) getone = ERR else if (i <= istart) getone = EOF else getone = OK status = getone return end # getrhs - get substitution string for "s" command integer function getrhs(lin, i, sub, gflag) character lin(MAXLINE), sub(MAXPAT) integer maksub integer gflag, i getrhs = ERR if (lin(i) == EOS) return if (lin(i + 1) == EOS) return i = maksub(lin, i + 1, lin(i), sub) if (i == ERR) return if (lin(i + 1) == GLOBAL) { i = i + 1 gflag = YES $@$ else gflag = NO getrhs = OK return end # gettxt (in memory) - locate text for line and make available integer function gettxt(line) integer getind integer line include cbuf include ctxt gettxt = getind(line) call scopy(buf, gettxt + TEXT, txt, 1) return end # inject (in memory) - put text from lin after curln integer function inject(lin) character lin(MAXLINE) integer addset, getind, nextln integer i, junk, k1, k2, k3 include cbuf include clines for (i = 1; lin(i) ~= EOS; ) { k3 = lastbf lastbf = lastbf + TEXT while (lin(i) ~= EOS) { junk = addset(lin(i), buf, lastbf, MAXBUF) i = i + 1 if (lin(i - 1) == NEWLINE) break $@$ if (addset(EOS, buf, lastbf, MAXBUF) == NO) { inject = ERR break $@$ k1 = getind(curln) k2 = getind(nextln(curln)) call relink(k1, k3, k3, k2) call relink(k3, k2, k1, k3) curln = curln + 1 lastln = lastln + 1 inject = OK $@$ return end # move - move line1 through line2 after line3 integer function move(line3) integer getind, nextln, prevln integer k0, k1, k2, k3, k4, k5, line3 include clines if (line1 <= 0 | (line1 <= line3 & line3 <= line2)) move = ERR else { k0 = getind(prevln(line1)) k3 = getind(nextln(line2)) k1 = getind(line1) k2 = getind(line2) call relink(k0, k3, k0, k3) if (line3 > line1) { curln = line3 line3 = line3 - (line2 - line1 + 1) $@$ else curln = line3 + (line2 - line1 + 1) k4 = getind(line3) k5 = getind(nextln(line3)) call relink(k4, k1, k2, k5) call relink(k2, k5, k4, k1) move = OK $@$ return end # nextln - get line after "line" integer function nextln(line) integer line include clines nextln = line + 1 if (nextln > lastln) nextln = 0 return end # optpat - make pattern if specified at lin(i) integer function optpat(lin, i) character lin(MAXLINE) integer makpat integer i include cpat if (lin(i) == EOS) i = ERR else if (lin(i + 1) == EOS) i = ERR else if (lin(i + 1) == lin(i)) # repeated delimiter i = i + 1 # leave existing pattern alone else i = makpat(lin, i + 1, lin(i), pat) if (pat(1) == EOS) i = ERR if (i == ERR) { pat(1) = EOS optpat = ERR $@$ else optpat = OK return end # prevln - get line before "line" integer function prevln(line) integer line include clines prevln = line - 1 if (prevln < 0) prevln = lastln return end # ptscan - scan for next occurrence of pattern integer function ptscan(way, num) integer gettxt, match, nextln, prevln integer k, num, way include clines include cpat include ctxt num = curln repeat { if (way == FORWARD) num = nextln(num) else num = prevln(num) k = gettxt(num) if (match(txt, pat) == YES) { ptscan = OK return $@$ $@$ until (num == curln) ptscan = ERR return end # relink - rewrite two half links subroutine relink(a, x, y, b) integer a, b, x, y include cbuf buf(x + PREV) = a buf(y + NEXT) = b return end # setbuf (in memory) - initialize line storage buffer subroutine setbuf integer addset integer junk include cbuf include clines call relink(LINE0, LINE0, LINE0, LINE0) lastbf = LINE0 + TEXT junk = addset(EOS, buf, lastbf, MAXBUF) curln = 0 lastln = 0 return end # skipbl - skip blanks and tabs at lin(i)... subroutine skipbl(lin, i) character lin(ARB) integer i while (lin(i) == BLANK | lin(i) == TAB) i = i + 1 return end # subst - substitute "sub" for occurrences of pattern integer function subst(sub, gflag) character new(MAXLINE), sub(MAXPAT) integer addset, amatch, gettxt, inject integer gflag, j, junk, k, lastm, line, m, status, subbed include clines include cpat include ctxt subst = ERR if (line1 <= 0) return for (line = line1; line <= line2; line = line + 1) { j = 1 subbed = NO junk = gettxt(line) lastm = 0 for (k = 1; txt(k) ~= EOS; ) { if (gflag == YES | subbed == NO) m = amatch(txt, k, pat) else m = 0 if (m > 0 & lastm ~= m) { # replace matched text subbed = YES call catsub(txt, k, m, sub, new, j, MAXLINE) lastm = m $@$ if (m == 0 | m == k) { # no match or null match junk = addset(txt(k), new, j, MAXLINE) k = k + 1 $@$ else # skip matched text k = m $@$ if (subbed == YES) { if (addset(EOS, new, j, MAXLINE) == NO) { subst = ERR break $@$ call delete(line, line, status) # remembers dot subst = inject(new) if (subst == ERR) break subst = OK $@$ $@$ return end