############################################################################
#
#	File:     patchu.icn
#
#	Subject:  Program to implement UNIX-like patch
#
#	Author:   Rich Morin
#
#	Date:     June 18, 1990
#
############################################################################
#
#   This file is in the public domain.
#
############################################################################
#
#  This program reads a source file and a diff file, producing an
#  updated file.  The diff file may be generated by the UNIX diff(1)
#  utility, or by diffu.icn, which uses dif.icn for the hard work.
#
#  The original patch(1) utility, written by Larry Wall, is widely
#  used in the UNIX community.
#
#  The diff file contains edit lines, separators, and text lines.
#  Edit lines may take the forms:
#
#	#a#[,#]			<- add    lines
#	#[,#]c#[,#]		<- change lines
#	#[,#]d#			<- delete lines
#
#  Change lines contain only the string "---".  All other lines are
#  text lines.  See diff(1) in any UNIX manual for more details.
#
############################################################################
#
#  Requires:  co-expressions
#
############################################################################
#
#  Links:  options, patch
#
############################################################################

link options, patch

record diff_rec(pos, diffs)

global n1, n2, n3, n4

procedure main(arg)
   local t, rev, source, dfile, diffs

  t := options(arg, "r")
  rev := t["r"]

  if *arg ~= 2 then
    zot("usage: patchu source diffs")

  source := open(arg[1]) | zot("cannot open " || arg[1])
  dfile  := open(arg[2]) | zot("cannot open " || arg[2])

# every write(patch(source, get_diff(dfile))) # ? shouldn't need diffs ?

  diffs := []
  every put(diffs, get_diff(dfile))
  every write(patch(source, diffs, rev))

end


procedure get_diff(dfile)		# get diff record
  local ef, i1, i2, l1, l2, i, line

  repeat {
    if ef := get_edit(dfile) then {
#     write(">>> ",n1,", ",n2,", ",ef,", ",n3,", ",n4)
      if ef == "a" then i1 := n1+1 else i1 := n1
      if ef == "d" then i2 := n3+1 else i2 := n3
      l1 := []
      l2 := []
      if ef == !"cd" then {
        every i := n1 to n2 do {
          line := !dfile | zot("unexpected end of edit data(1)")
          if line[1:3] ~== "< " then
            zot("bad edit data(1): " || line)
          put(l1, line[3:0])
        }
      }

      if ef == "c" then {
        line := !dfile | zot("unexpected end of edit data(2)")
        if line ~== "---" then
          zot("bad edit data(2): " || line)
      }

      if ef == !"ac" then {
        every i := n3 to n4 do {
          line := !dfile | zot("unexpected end of edit data(3)")
          if line[1:3] ~== "> " then
            zot("bad edit data(3): " || line)
          put(l2, line[3:0])
        }
      }
      suspend [diff_rec(i1,l1), diff_rec(i2,l2)]
    }
    else
      fail
  }

end


procedure get_edit(dfile)		# get edit parameters
  local edit, i1, i2, ef, i3, i4

  edit := !dfile | fail
  i1 := i2 := many(&digits, edit) | zot("bad edit spec(1): " || edit)
  n1 := n2 := edit[1:i1]
  if edit[i1] == "," then {
    i2 := many(&digits, edit, i1+1) | zot("bad edit spec(2): " || edit)
    n2 := edit[i1+1:i2]
  }

  if edit[i2] == !"acd" then {
    ef := edit[i2]
    i3 := i4 := many(&digits, edit, i2+1) | zot("bad edit spec(3): " || edit)
    n3 := n4 := edit[i2+1:i3]
    if edit[i3] == "," then {
      i4 := many(&digits, edit, i3+1) | zot("bad edit spec(4): " || edit)
      n4 := edit[i3+1:i4]
    }
  }
  else
    zot("bad edit spec(5): " || edit)

  if i4 ~= *edit+1 then 
    zot("bad edit spec(6): " || edit)

  if not 0 <= n3 <= n4 then
    zot("bad edit spec(7): " || edit)

  if not 0 <= n1 <= n2 then
    zot("bad edit spec(8): " || edit)

  return ef

end    


procedure zot(msg)				# exit w/message
  write(&errout, "patchu: " || msg)
  exit(1)
end
