Source file ush.icn
#
# This "Unicon shell" is a large enough subset of a normal UNIX shell to
# execute all the commands used in Unicon builds circa 2008.
#
# It was used for a time when Windows Unicon builds needed it, because
# building Unicon on various new Windows versions died on legacy
# sh.exe executables that were bundled with MingGCC / MSYS.
#

link basename, getpaths

procedure main(av)
#   write("ush: (", *av, ")")
#   every writes(" ", image(!av))
#   write()
   if av[1][-3:0] == ".sh" then {
      runscript(av)
      }
   else {
       if av[1] == "-c" then pop(av)
#      write("directly execute")
      line := ""
      every line ||:= !av || " "
      ush(line[1:-1])
      }
end

procedure runlist(L)
   saveLscript := Lscript
   savei := Lscript_i
   Lscript := L
   Lscript_i := 1
   while Lscript_i <= *Lscript do {
      ush(Lscript[Lscript_i])
      Lscript_i +:= 1
      }
   Lscript := saveLscript
   Lscript_i := savei
end

global Lscript, Lscript_i, variables

procedure runscript(av)
#   write("script")
   if fscript := open(av[1]) then {
      write("opened ", av[1], " script")
      pop(av)
      Lscript := [ ]
      Lscript_i := 1
      tab(many(' \t'))
      while line := read(fscript) do {
         if Lscript[-1][-1] == "\\" then Lscript[-1][-1] := line
         else put(Lscript, line)
         }
      close(fscript)
      fscript := &null
      runlist(Lscript)
      }
   else write("ush: can't open ", av[1])
end

procedure ush(s)
#write("ush: ", image(s))
   av := [ ]
   s ? {
      tab(many(' \t'))
      if ="(" then {  # should do paren balancing; too much work for now
	 subcmd := tab(find(")")) |stop("unclosed parentheses in\n  ",image(s))
	 =")"
#write("trying subcmd ", image(subcmd))
	 i2 := system("sh.exe " || subcmd)
	 tab(many(' \t'))
	 if pos(0) then return
	 if ="&&" then {
#write("trying &&, i2 ", image(i2))
	    if i2 ~=== 0 then exit(i2)
	    return ush(tab(0))
	    }
	 else if ="||" then {
#write("trying ||, i2 ", image(i2))
	    if i2 === 0 then return
	    return ush(tab(0))
	    }
	 else stop("syntax error after parens in\n  ",image(s))
	 }

       # perform variable substitution
       while tab((i := find("$"))+1) do {
	   var := tab(many(&letters++&digits++'_'))
#	   write("var ", var, " replaced as ", variables[var])
	   s := &subject := s[1:i] || (\variables)[var] || s[i+1+*var:0] |
	       stop("can't variable substitute in\n\t", image(s))
       }

       # perform command substitutions
       while tab((i := find("`"))+1) do {
	   cmdsub := tab(find("`"))
#if match("basename",cmdsub) then cmdsub[1:9] := "c:\\bin\\basename.exe"
           if not (fcmd := open(cmdsub, "p")) then
	       stop("popen failed ", image(cmdsub))
	   cmdout := read(fcmd)
	   close(fcmd)
#	   write("cmdsub ", cmdsub, " replaced as ", cmdout)
	   s := &subject := s[1:i] || cmdout || s[i+2+*cmdsub:0] |
	       stop("can't command substitute in\n\t", image(s))
       }

      tab(many(' \t'))
      while cmd := tab(many(&cset -- '= \t')) do {

#write("cmd is ", cmd, " *av = ", *av)
         # handle assignments, but only in initial position
         if ="=" then {
            if *av ~=== 0 then {
		cmd ||:= "="
		cmd ||:= tab(many(&cset -- ' \t'))
#write("cmd revised to ", cmd)
		put(av, cmd)
		tab(many(' \t'))
		next
	    }
	    tab(many(' \t'))
	    if ="\"" then {
		val := tab(find("\""))
		move(1)
		}
	    else val := tab(many(&letters++&digits++'._/'))
	    setenv(cmd, val)
#write("setting ", image(cmd), " -> ", image(val))
            /variables := table()
	    variables[cmd] := val
         tab(many(' \t'))
	    next
	    }
         tab(many(' \t'))
         if cmd == "&&" then {
             if (i := run(av)) ~= 0 then exit(i)
             av := [ ]
             }
         else if cmd == "#" then {
             if (i := run(av)) ~= 0 then exit(i)
             av := [ ]
	     return
	     }
         else if (av[1] ~=== "if") & (cmd[-1] == ";") then {
	     if *cmd>1 then put(av, cmd[1:-1])
             run(av)
             av := [ ]
             }
          else {
	      sizcmd := *av
	      every put(av, glob(cmd))
	      if *av = sizcmd then {
		  write(image(s), ": no match")
		  fail
	      }
	  }
      }
      if (i := run(av)) ~=== 0 then exit(i)
   }
end

procedure cp(av)
   if *av ~= 3 then stop("ush: cp file1 file2")
   if not (statrec := stat(av[2])) then
      stop("can't copy from nonexistent ", av[2])
   if not (fin := open(av[2],"ru")) then stop("can't open ", av[2], " to copy")
   if stat(av[3]).mode[1] == "d" then {
      av[3] ||:= "\\" || basename(av[2])
      }

   if not (fout := open(av[3],"wu")) then stop("can't copy to ", av[3])
   if *writes(fout, reads(fin, statrec.size)) ~= statrec.size then
       write(&errout, "warning: output size difference in copy of ", av[2])
   close(fout)
   close(fin)
end

procedure catt(av)
   local fout
   fout := &output
   pop(av)              # discard "cat"
   if match(">>", av[-1]) then {
       if not (fout := open(av[-1][3:0],"a")) then
	   stop("can't append to ", av[3])
       pull(av)
   }
   else if match(">", av[-1]) then {
       if not (fout := open(av[-1][3:0],"w")) then
	   stop("can't write to ", av[3])
       pull(av)
   }
   else if av[-2] == ">>" then {
       if not (fout := open(av[-1],"a")) then
	   stop("can't append to ", av[-1])
       pull(av)
       pull(av)
   }
   else if av[-2] == ">" then {
       if not (fout := open(av[-1],"w")) then
	   stop("can't write to ", av[-1])
       pull(av)
       pull(av)
   }

   every fn := !av do {
      if not (fin := open(fn)) then stop("can't read ", av[2])
      while write(fout, read(fin))
      close(fin)
      }
   if fout ~=== &output then
      close(fout)
end

#
procedure mv(av)
   if *av ~= 3 then stop("ush: mv file dest")
   if not (fin := open(av[2])) then stop("can't move/read from ", av[2])
   if (sr := stat(av[3])).mode[1] == "d" then {
      av[3] ||:= "\\" || basename(av[2])
      }
   if not (fout := open(av[3],"w")) then stop("can't move to ", av[3])
   while write(fout, read(fin))
   close(fout)
   close(fin)
   remove(av[2])
end

procedure rm(av)
   if av[-1] == "2>nul" then pull(av)
   else if av[-1] == "nul" & av[-2] == "2>" then { pull(av); pull(av); }
   every remove(!av)
end

procedure for(av)
local varname
   /variables := table()
   pop(av)              # discard "for"
   varname := pop(av)   # fetch variable name, e.g. "i"
   pop(av)              # discard "in"

   if /Lscript then stop("'for' with no apparent script")
   Lscript_i +:= 1
   line := Lscript[Lscript_i]
   if trim(line, ' \t', 0) ~=== "do" then
       stop("'do' expected on line after 'for', got ", image(line))
   Lscript_i +:= 1
   forstart := Lscript_i
   numdo := 1
   until (numdo = 0) | (Lscript_i > *Lscript) do {
      line := Lscript[Lscript_i]
      Lscript_i +:= 1
      line ? {
	  tab(many(' \t'))
	  case tab(0) of {
	      "do": numdo +:= 1
	      "done": numdo -:= 1
	      }
          }
       }
   if numdo > 0 then stop("malformed 'for' block")
   Lscript_i -:= 1 #points at "done", will advance to line after that
   newList := Lscript[forstart : Lscript_i] # slice not including "done"

   
   every variables[varname] := !av do {
     runlist(newList)
   }
end

procedure echo(av)
   pop(av) # drop the word "echo"
   fout := &output
   if av[-2] == ">>" then {
      if not (fout := open(av[-1],"a")) then
	  stop("can't append to ", av[-1])
      pull(av); pull(av)
   }
   else
   if av[-2] == ">" then {
      if not (fout := open(av[-1],"w")) then
	  stop("can't write to ", av[-1])
      pull(av); pull(av)
   } else if av[-1][1:3] == ">>" then {
      if not (fout := open(av[-1][3:0],"a")) then
	  stop("can't append to ", av[-1][3:0])
      pull(av)
   } else if av[-1][1] == ">" then {
      if not (fout := open(av[-1][2:0],"w")) then
	  stop("can't write to ", av[-1][2:0])
      pull(av)
   }

   every writes(fout, !av, " ")
   write(fout)
   if fout ~=== &output then close(fout)
end

procedure md(av)
   pop(av) # discard "mkdir"
   if av[1][1] == "-" then pop(av) # discard options, perhaps should support
   mkdir(av[1])
end

procedure iff(av)
local cond

#writes("if called as: (")
#every writes(!av, " ")
#write(")")

   pop(av) # discard "if"
   cond := [ ]
   if av[1] == "!" then { ifnot := 1; pop(av) }
   while av[1] ~=== "then" do put(cond, pop(av))
   if cond[-1] === ";" then pull(cond)

   i := run(cond)

   #
   # time for then-part
   #

   if *av == 1 then {
      #  multi-line if
      Lscript_i +:= 1
      then_start := Lscript_i
      until (trim(Lscript[Lscript_i],,0) == "fi") | (Lscript_i > *Lscript) do {
	 Lscript_i +:= 1
	 }
      if trim(Lscript[Lscript_i],,0) == "fi" then {
	 newList := Lscript[then_start : Lscript_i] # slice not including "fi"
	 }
      else stop("if without fi: ")
      }

   if (i===0 & /ifnot) | (i~===0 & \ifnot) then {
      # test passed, execute then-part
      if *av == 1 then {
	 # executing separate-line then-part
	 runlist(newList)
	 }
      else if av[1] == "then" then {
	 pop(av)
	 thenpart := [ ]
	 while av[1] ~=== "fi" do put(thenpart, pop(av))
	 if thenpart[1] == "{" then pop(thenpart)
	 if thenpart[-1] == "}" then pull(thenpart)
	 ush(stringify(thenpart))
	 }
      else stop("then expected")
      }
   else {
      # skipping over then-part on *av words
      if *av == 1 then {
	 # skip over separate-line then-part
	 }
      else {
	 while av[1] ~=== "fi" do pop(av)
	 }
      }
end

procedure stringify(L)
   rv := ""
   every rv||:= !L || " "
   if *L>0 then rv := rv[1:-1]
   return rv
end

procedure test(av)
   case av[2] of {
       "-f": {
	   if (str := stat(av[3])) then {
#	      write("test ",av[2]," ",av[3]," succeeded")
	      return 0
	   }
	   else {
#	      write("test ",av[2]," ",av[3]," failed")
	      return -1
	   }
       }
       default: {
	   stop("don't know how to test ", image(av[2]))
       }
   }
end

# special shell conditions in [ ]
procedure cond(L)
    if not pop(L) == "[" then stop("conditional trouble")
    if not pull(L) == "]" then stop("conditional trouble, missing ]")
    case L[1] of {
      "-f": { # should be more picky, and check whether L[2] is regular
	  if stat(L[2]) then return 0
          else return -1
      }
     }
end

# run command, return its exit code
procedure run(av)
   case av[1] of {
   ""    : fail
   "cat"  : catt(av)
   "cd"  : {
       chdir(av[2])
   }
   "cp"  : cp(av)
   "for"  : for(av)
   "if"  : iff(av)
   "echo": echo(av)
   "mkdir"  : md(av)
   "rm"  : rm(av)
   "mv"  : mv(av)
   "test": return test(av)
   "[" : return cond(av)
   default: {
      line := av[1]
#  write("first searching for ", line, " in: ", getenv("PATH"))
      every foo := ((".\\" | getpaths()) || line) do {
         if stat(foo) then {
	  line := foo
	  if not (line[-4:0] == (".exe" | ".com" | ".bat")) then {
             av[1] := line
             runscript(av)
             return
	     }
	  break
          }
        }

      line ||:= " "
      every line ||:= av[2 to *av] || " "
      line := line[1:-1]
      if line == "" then fail
      write("ush: system(", line,") from ", chdir())
      i := &null
      i := system(line)
      if i~===0 then
         write(&errout, image(line), " returns ", image(i))
      return i
      }
   }
end

# glob(s) - generate file-globbed matches for s
procedure glob(s)
dir := "."

   if not find("*", s) then return s
   if find("/", s) then {
       every i := find("/",s)
       dir := s[1:i]
       s := s[i+1:0]
   }
   if not s[1 | -1]=="*" then
       stop("only globbing at front or back so far in ", image(s))

   if not (fdir := open(dir)) then {
       stop("can't open ",image(dir)," in ", chdir(),
	    "\n...wanted to glob ", image(s))
   }
#   write("globbing ", s, " in dir ", dir, " (chdir ", chdir(), ")")
   while fnam := read(fdir) do {
      if (s[1]=="*" & fnam[-(*s-1) : 0] == s[2:0]) |
	  (s[-1]=="*" & fnam[1 : (*s)] == s[1:-1]) then {
	      if dir ~== "." then suspend dir ||"\\"|| fnam
	      else
		  suspend fnam
      }
   }
   close(fdir)
end

This page produced by UniDoc on 2021/04/15 @ 23:59:43.