include "osfiles.s7i";
include "shell.s7i";
include "getf.s7i";
include "wildcard.s7i";
const set of char: parameter_char is {'!' .. '~'} - {'<', '>', '|', ';', ')'};
const set of char: dos_parameter_char is {'!' .. '~'} - {'&', '<', '>', '|'};
const proc: doRemoveCmd (in array string: fileList,
    in boolean: recursive, in boolean: force) is func
  local
    var string: fileName is "";
  begin
    for fileName range fileList do
      if fileTypeSL(fileName) = FILE_ABSENT then
        if not force then
          writeln("Cannot remove " <& fileName <& " - No such file or directory");
        end if;
      else
        block
          if recursive then
            removeTree(fileName);
          else
            removeFile(fileName);
          end if;
        exception
          catch FILE_ERROR:
            writeln("Cannot remove " <& fileName <& " - Not permitted");
        end block;
      end if;
    end for;
  end func;
const proc: doCopyCmd (in var array string: fileList,
   in boolean: recursive, in boolean: overwriteExisting, in boolean: archive) is func
  local
    var string: fileName is "";
    var string: destination is "";
    var string: destFileName is "";
    var integer: slashPos is 0;
  begin
    if length(fileList) >= 2 then
      destination := fileList[length(fileList)];
      fileList := fileList[.. pred(length(fileList))];
      if fileType(destination) = FILE_DIR then
        for fileName range fileList do
          if fileType(fileName) = FILE_REGULAR or
              (recursive and fileType(fileName) = FILE_DIR) then
            slashPos := rpos(fileName, "/");
            if slashPos = 0 then
              destFileName := destination & "/" & fileName;
            else
              destFileName := destination & "/" & fileName[succ(slashPos) ..];
            end if;
            if fileTypeSL(destFileName) = FILE_REGULAR and overwriteExisting then
              block
                removeFile(destFileName);
              exception
                catch FILE_ERROR:
                  writeln(" *** Cannot remove " <& destFileName);
              end block;
            end if;
            if fileType(destFileName) = FILE_ABSENT then
              
              if archive then
                cloneFile(fileName, destFileName);
              else
                copyFile(fileName, destFileName);
              end if;
            end if;
          elsif fileType(fileName) = FILE_ABSENT then
            writeln("Cannot copy non-existent file " <& fileName);
          else
            writeln("Cannot copy " <& fileName);
          end if;
        end for;
      elsif length(fileList) = 1 then
        fileName := fileList[1];
        if fileType(fileName) = FILE_REGULAR or
            (recursive and fileType(fileName) = FILE_DIR) then
          if fileTypeSL(destination) = FILE_REGULAR and overwriteExisting then
            block
              removeFile(destination);
            exception
              catch FILE_ERROR:
                writeln(" *** Cannot remove " <& destination);
            end block;
          end if;
          if fileType(destination) = FILE_ABSENT then
            
            if archive then
              cloneFile(fileName, destination);
            else
              copyFile(fileName, destination);
            end if;
          end if;
        elsif fileType(fileName) = FILE_ABSENT then
          writeln("Cannot copy non-existent file " <& fileName);
        else
          writeln("Cannot copy " <& fileName);
        end if;
      else
        writeln("Target " <& destination <& " is not a directory");
      end if;
    else
      writeln("Missing destination file");
    end if;
  end func;
const proc: doMoveCmd (in var array string: fileList,
    in boolean: overwriteExisting) is func
  local
    var string: fileName is "";
    var string: destination is "";
    var string: destFileName is "";
    var integer: slashPos is 0;
  begin
    if length(fileList) >= 2 then
      destination := fileList[length(fileList)];
      fileList := fileList[.. pred(length(fileList))];
      if fileType(destination) = FILE_DIR then
        for fileName range fileList do
          if fileType(fileName) = FILE_REGULAR or fileType(fileName) = FILE_DIR then
            slashPos := rpos(fileName, "/");
            if slashPos = 0 then
              destFileName := destination & "/" & fileName;
            else
              destFileName := destination & "/" & fileName[succ(slashPos) ..];
            end if;
            if fileTypeSL(destFileName) = FILE_REGULAR and overwriteExisting then
              block
                removeFile(destFileName);
              exception
                catch FILE_ERROR:
                  writeln(" *** Cannot remove " <& destFileName);
              end block;
            end if;
            if fileType(destFileName) = FILE_ABSENT then
              
              moveFile(fileName, destFileName);
            end if;
          elsif fileType(fileName) = FILE_ABSENT then
            writeln("Cannot move non-existent file " <& fileName);
          else
            writeln("Cannot move " <& fileName);
          end if;
        end for;
      elsif length(fileList) = 1 then
        fileName := fileList[1];
        if fileType(fileName) = FILE_REGULAR or fileType(fileName) = FILE_DIR then
          if fileTypeSL(destination) = FILE_REGULAR and overwriteExisting then
            block
              removeFile(destination);
            exception
              catch FILE_ERROR:
                writeln(" *** Cannot remove " <& destination);
            end block;
          end if;
          if fileType(destination) = FILE_ABSENT then
            
            moveFile(fileName, destination);
          end if;
        elsif fileType(fileName) = FILE_ABSENT then
          writeln("Cannot move non-existent file " <& fileName);
        else
          writeln("Cannot move " <& fileName);
        end if;
      else
        writeln("Target " <& destination <& " is not a directory");
      end if;
    else
      writeln("Missing destination file");
    end if;
  end func;
const proc: doMkdirCmd (in array string: fileList,
    in boolean: parentDirs) is func
  local
    var string: fileName is "";
    var boolean: okay is TRUE;
  begin
    for fileName range fileList do
      okay := TRUE;
      if parentDirs then
        block
          makeParentDirs(fileName);
        exception
          catch FILE_ERROR:
            writeln(" *** Cannot make parent directories of " <& fileName <& " - Element is not a directory");
            okay := FALSE;
        end block;
      end if;
      if okay then
        if fileTypeSL(fileName) = FILE_ABSENT then
          block
            makeDir(fileName);
          exception
            catch FILE_ERROR:
              writeln(" *** Cannot make directory " <& fileName);
          end block;
        elsif fileTypeSL(fileName) = FILE_DIR and not parentDirs then
          writeln(" *** Cannot make directory " <& fileName <& " - File exists");
        end if;
      end if;
    end for;
  end func;
const func string: getCommandParameter (inout string: stri) is func
  result
    var string: symbol is "";
  local
    var integer: leng is 0;
    var integer: pos is 1;
    var char: quotation is ' ';
    var integer: quotedPos is 0;
    var string: quotedPart is "";
    var boolean: quoteMissing is FALSE;
  begin
    leng := length(stri);
    repeat
      if stri[pos] = '"' or stri[pos] = ''' then
        quotation := stri[pos];
        quotedPos := succ(pos);
        quotedPart := "";
        while quotedPos <= leng and stri[quotedPos] <> quotation do
          quotedPart &:= stri[quotedPos];
          incr(quotedPos);
        end while;
        if quotedPos <= leng then
          pos := succ(quotedPos);
          symbol &:= quotedPart;
        else
          quoteMissing := TRUE;
        end if;
      else
        repeat
          symbol &:= stri[pos];
          incr(pos);
        until pos > leng or stri[pos] not in parameter_char or
            stri[pos] = '"' or stri[pos] = ''';
      end if;
    until pos > leng or stri[pos] not in parameter_char or quoteMissing;
    stri := stri[pos ..];
  end func;
const func string: getUnixCommandParameter (inout string: parameters) is func
  result
    var string: symbol is "";
  local
    var integer: leng is 0;
    var integer: pos is 1;
    var integer: quotedPos is 0;
    var string: quotedPart is "";
    var boolean: quoteMissing is FALSE;
  begin
    leng := length(parameters);
    while pos <= leng and parameters[pos] in parameter_char and
        not quoteMissing do
      if parameters[pos] = '"' then
        quotedPos := succ(pos);
        quotedPart := "";
        while quotedPos <= leng and parameters[quotedPos] <> '"' do
          if parameters[quotedPos] = '\\' and quotedPos < leng and
              (parameters[succ(quotedPos)] = '"' or
               parameters[succ(quotedPos)] = '\\') then
            incr(quotedPos);
          end if;
          quotedPart &:= parameters[quotedPos];
          incr(quotedPos);
        end while;
        if quotedPos <= leng then
          pos := succ(quotedPos);
          symbol &:= quotedPart;
        else
          quoteMissing := TRUE;
        end if;
      elsif parameters[pos] = ''' then
        quotedPos := succ(pos);
        quotedPart := "";
        while quotedPos <= leng and parameters[quotedPos] <> ''' do
          quotedPart &:= parameters[quotedPos];
          incr(quotedPos);
        end while;
        if quotedPos <= leng then
          pos := succ(quotedPos);
          symbol &:= quotedPart;
        else
          quoteMissing := TRUE;
        end if;
      else
        repeat
          if parameters[pos] = '\\' and pos < leng then
            incr(pos);
          end if;
          symbol &:= parameters[pos];
          incr(pos);
        until pos > leng or parameters[pos] not in parameter_char or
            parameters[pos] = '"' or parameters[pos] = ''';
      end if;
    end while;
    parameters := parameters[pos ..];
  end func;
const func string: getDosCommandParameter (inout string: parameters) is func
  result
    var string: symbol is "";
  local
    var integer: leng is 0;
    var integer: pos is 1;
    var integer: quotedPos is 0;
    var string: quotedPart is "";
    var boolean: quoteMissing is FALSE;
  begin
    leng := length(parameters);
    while pos <= leng and parameters[pos] in dos_parameter_char and
        not quoteMissing do
      if parameters[pos] = '"' then
        quotedPos := succ(pos);
        quotedPart := "";
        while quotedPos <= leng and parameters[quotedPos] <> '"' do
          quotedPart &:= parameters[quotedPos];
          incr(quotedPos);
        end while;
        if quotedPos <= leng then
          pos := succ(quotedPos);
          symbol &:= quotedPart;
        else
          quoteMissing := TRUE;
        end if;
      else
        repeat
          if parameters[pos] = '^' then
            incr(pos);
            if pos <= leng then
              symbol &:= parameters[pos];
            end if;
          else
            symbol &:= parameters[pos];
          end if;
          incr(pos);
        until pos > leng or parameters[pos] not in dos_parameter_char or
            parameters[pos] = '"';
      end if;
    end while;
    parameters := parameters[pos ..];
  end func;
const func string: getDosEchoParameter (inout string: parameters) is func
  result
    var string: symbol is "";
  local
    var integer: leng is 0;
    var integer: pos is 1;
  begin
    leng := length(parameters);
    repeat
      
      if parameters[pos] = '"' then
        
        repeat
          symbol &:= parameters[pos];
          incr(pos);
        until pos > leng or parameters[pos] = '"';
        if pos <= leng then
          
          symbol &:= '"';
          incr(pos);
        end if;
      else
        
        repeat
          if parameters[pos] = '^' then
            incr(pos);
            if pos <= leng then
              symbol &:= parameters[pos];
            end if;
          else
            symbol &:= parameters[pos];
          end if;
          incr(pos);
        until pos > leng or parameters[pos] not in dos_parameter_char or
            parameters[pos] = '"';
      end if;
    until pos > leng or parameters[pos] not in dos_parameter_char;
    parameters := parameters[pos ..];
  end func;
const func boolean: doOneCommand (inout string: command,
    inout string: commandOutput) is forward;
const func string: execCommand (inout string: command) is func
  result
    var string: backtickOutput is "";
  local
    var string: fullCommand is "";
    var file: commandFile is STD_NULL;
  begin
    fullCommand := command;
    if not doOneCommand(command, backtickOutput) then
      
      commandFile := popen(fullCommand, "r");
      if commandFile <> STD_NULL then
        backtickOutput := gets(commandFile, 999999999);
        while endsWith(backtickOutput, "\r\n") do
          backtickOutput := backtickOutput[.. length(backtickOutput) - 2];
        end while;
        while endsWith(backtickOutput, "\n") do
          backtickOutput := backtickOutput[.. pred(length(backtickOutput))];
        end while;
      else
        backtickOutput := "";
      end if;
    end if;
  end func;
const func string: execBacktickCommands (in string: stri) is func
  result
    var string: withBacktickOutput is "";
  local
    var integer: backtickPos is 0;
    var integer: closingBacktickPos is 0;
    var string: command is "";
    var string: backtickOutput is "";
  begin
    withBacktickOutput := stri;
    backtickPos := pos(withBacktickOutput, '`');
    while backtickPos <> 0 do
      closingBacktickPos := pos(withBacktickOutput, '`', succ(backtickPos));
      if closingBacktickPos <> 0 then
        command := withBacktickOutput[succ(backtickPos) .. pred(closingBacktickPos)];
        backtickOutput := execCommand(command);
        withBacktickOutput := withBacktickOutput[.. pred(backtickPos)] & backtickOutput &
                              withBacktickOutput[succ(closingBacktickPos) ..];
      end if;
      backtickPos := pos(withBacktickOutput, '`', succ(backtickPos));
    end while;
  end func;
const proc: addToFileList (inout array string: fileList, in var string: parameter,
    in boolean: caseSensitive) is func
  local
    var string: fileName is "";
  begin
    parameter := convDosPath(parameter);
    if pos(parameter, "*") <> 0 or pos(parameter, "?") <> 0 then
      for fileName range findMatchingFiles(parameter, caseSensitive) do
        fileList &:= fileName;
      end for;
    else
      fileList &:= parameter;
    end if;
  end func;
const proc: doRm (inout string: parameters) is func
  local
    var string: aParam is "";
    var char: option is ' ';
    var boolean: recursive is FALSE;
    var boolean: force is FALSE;
    var boolean: optionMayFollow is TRUE;
    var array string: fileList is 0 times "";
  begin
    
    skipWhiteSpace(parameters);
    aParam := getUnixCommandParameter(parameters);
    while startsWith(aParam, "-") and optionMayFollow do
      aParam := aParam[2 ..];
      for option range aParam do
        case option of
          when {'r', 'R'}: recursive := TRUE;
          when {'f'}:      force := TRUE;
          when {'-'}:      optionMayFollow := FALSE;
        end case;
      end for;
      skipWhiteSpace(parameters);
      aParam := getUnixCommandParameter(parameters);
    end while;
    while aParam <> "" do
      addToFileList(fileList, aParam, TRUE);
      skipWhiteSpace(parameters);
      aParam := getUnixCommandParameter(parameters);
    end while;
    doRemoveCmd(fileList, recursive, force);
  end func;
const proc: doDel (inout string: parameters) is func
  local
    var string: aParam is "";
    var boolean: recursive is FALSE;
    var array string: fileList is 0 times "";
  begin
    
    skipWhiteSpace(parameters);
    aParam := getDosCommandParameter(parameters);
    while aParam <> "" do
      if upper(aParam) = "/S" then
        recursive := TRUE;
      elsif not startsWith(aParam, "/") then
        addToFileList(fileList, aParam, FALSE);
      end if;
      skipWhiteSpace(parameters);
      aParam := getDosCommandParameter(parameters);
    end while;
    doRemoveCmd(fileList, recursive, FALSE);
  end func;
const proc: doCp (inout string: parameters) is func
  local
    var string: aParam is "";
    var char: option is ' ';
    var boolean: recursive is FALSE;
    var boolean: overwriteExisting is TRUE;
    var boolean: archive is FALSE;
    var boolean: optionMayFollow is TRUE;
    var array string: fileList is 0 times "";
  begin
    
    skipWhiteSpace(parameters);
    aParam := getUnixCommandParameter(parameters);
    while startsWith(aParam, "-") and optionMayFollow do
      aParam := aParam[2 ..];
      for option range aParam do
        case option of
          when {'r', 'R'}: recursive := TRUE;
          when {'n'}:      overwriteExisting := FALSE;
          when {'a', 'p'}: recursive := TRUE;
                           archive := TRUE;
          when {'-'}:      optionMayFollow := FALSE;
        end case;
      end for;
      skipWhiteSpace(parameters);
      aParam := getUnixCommandParameter(parameters);
    end while;
    while aParam <> "" do
      addToFileList(fileList, aParam, TRUE);
      skipWhiteSpace(parameters);
      aParam := getUnixCommandParameter(parameters);
    end while;
    doCopyCmd(fileList, recursive, overwriteExisting, archive);
  end func;
const proc: doCopy (inout string: parameters) is func
  local
    var string: aParam is "";
    var boolean: overwriteExisting is FALSE;
    var array string: fileList is 0 times "";
  begin
    
    skipWhiteSpace(parameters);
    aParam := getDosCommandParameter(parameters);
    while aParam <> "" do
      if upper(aParam) = "/Y" then
        overwriteExisting := TRUE;
      elsif not startsWith(aParam, "/") then
        addToFileList(fileList, aParam, FALSE);
      end if;
      skipWhiteSpace(parameters);
      aParam := getDosCommandParameter(parameters);
    end while;
    doCopyCmd(fileList, FALSE, overwriteExisting, FALSE);
  end func;
const proc: doXCopy (inout string: parameters) is func
  local
    var string: aParam is "";
    var boolean: recursive is FALSE;
    var boolean: overwriteExisting is FALSE;
    var boolean: archive is FALSE;
    var array string: fileList is 0 times "";
  begin
    
    skipWhiteSpace(parameters);
    aParam := getDosCommandParameter(parameters);
    while aParam <> "" do
      if upper(aParam) = "/E" then
        recursive := TRUE;
      elsif upper(aParam) = "/O" then
        archive := TRUE;
      elsif upper(aParam) = "/Y" then
        overwriteExisting := TRUE;
      elsif not startsWith(aParam, "/") then
        addToFileList(fileList, aParam, FALSE);
      end if;
      skipWhiteSpace(parameters);
      aParam := getDosCommandParameter(parameters);
    end while;
    doCopyCmd(fileList, recursive, overwriteExisting, archive);
  end func;
const proc: doMv (inout string: parameters) is func
  local
    var string: aParam is "";
    var char: option is ' ';
    var boolean: overwriteExisting is TRUE;
    var boolean: optionMayFollow is TRUE;
    var array string: fileList is 0 times "";
  begin
    
    skipWhiteSpace(parameters);
    aParam := getUnixCommandParameter(parameters);
    while startsWith(aParam, "-") and optionMayFollow do
      aParam := aParam[2 ..];
      for option range aParam do
        case option of
          when {'n'}:      overwriteExisting := FALSE;
          when {'-'}:      optionMayFollow := FALSE;
        end case;
      end for;
      skipWhiteSpace(parameters);
      aParam := getUnixCommandParameter(parameters);
    end while;
    while aParam <> "" do
      addToFileList(fileList, aParam, TRUE);
      skipWhiteSpace(parameters);
      aParam := getUnixCommandParameter(parameters);
    end while;
    doMoveCmd(fileList, overwriteExisting);
  end func;
const proc: doMove (inout string: parameters) is func
  local
    var string: aParam is "";
    var boolean: overwriteExisting is FALSE;
    var array string: fileList is 0 times "";
  begin
    
    skipWhiteSpace(parameters);
    aParam := getDosCommandParameter(parameters);
    while aParam <> "" do
      if upper(aParam) = "/Y" then
        overwriteExisting := TRUE;
      elsif not startsWith(aParam, "/") then
        addToFileList(fileList, aParam, FALSE);
      end if;
      skipWhiteSpace(parameters);
      aParam := getDosCommandParameter(parameters);
    end while;
    doMoveCmd(fileList, overwriteExisting);
  end func;
const proc: doMkdir (inout string: parameters) is func
  local
    var string: aParam is "";
    var char: option is ' ';
    var boolean: parentDirs is FALSE;
    var boolean: optionMayFollow is TRUE;
    var array string: fileList is 0 times "";
  begin
    
    skipWhiteSpace(parameters);
    aParam := getUnixCommandParameter(parameters);
    while startsWith(aParam, "-") and optionMayFollow do
      aParam := aParam[2 ..];
      for option range aParam do
        case option of
          when {'p'}: parentDirs := TRUE;
          when {'-'}: optionMayFollow := FALSE;
        end case;
      end for;
      skipWhiteSpace(parameters);
      aParam := getUnixCommandParameter(parameters);
    end while;
    while aParam <> "" do
      addToFileList(fileList, aParam, TRUE);
      skipWhiteSpace(parameters);
      aParam := getUnixCommandParameter(parameters);
    end while;
    doMkdirCmd(fileList, TRUE);  
  end func;
const proc: doMd (inout string: parameters) is func
  local
    var string: aParam is "";
    var array string: fileList is 0 times "";
  begin
    
    skipWhiteSpace(parameters);
    aParam := getDosCommandParameter(parameters);
    while aParam <> "" do
      if not startsWith(aParam, "/") then
        addToFileList(fileList, aParam, FALSE);
      end if;
      skipWhiteSpace(parameters);
      aParam := getDosCommandParameter(parameters);
    end while;
    doMkdirCmd(fileList, TRUE);
  end func;
const func string: doPwd (inout string: parameters) is func
  result
    var string: commandOutput is "";
  begin
    
    skipWhiteSpace(parameters);
    if startsWith(parameters, "-W") then
      parameters := parameters[3 ..];
      skipWhiteSpace(parameters);
    end if;
    commandOutput := getcwd & "\n";
  end func;
const func string: doEcho (inout string: parameters) is func
  result
    var string: commandOutput is "";
  local
    var string: whiteSpace is "";
    var string: aParam is "";
  begin
    
    whiteSpace := getWhiteSpace(parameters);
    if parameters <> "" and (parameters[1] = '"' or parameters[1] = ''') then
      while parameters <> "" and parameters[1] in parameter_char do
        if commandOutput <> "" then
          commandOutput &:= whiteSpace;
        end if;
        aParam := getUnixCommandParameter(parameters);
        commandOutput &:= execBacktickCommands(aParam);
        whiteSpace := getWhiteSpace(parameters);
      end while;
    else
      while parameters <> "" and parameters[1] in parameter_char do
        aParam := getDosEchoParameter(parameters);
        commandOutput &:= aParam;
        commandOutput &:= getWhiteSpace(parameters);
      end while;
    end if;
    commandOutput &:= "\n";
  end func;
const proc: doCd (inout string: parameters) is func
  local
    var string: aParam is "";
  begin
    
    skipWhiteSpace(parameters);
    if parameters <> "" then
      aParam := getCommandParameter(parameters);
      aParam := convDosPath(aParam);
      if fileType(aParam) = FILE_DIR then
        chdir(aParam);
      else
        writeln(" *** cd " <& aParam <& " - No such file or directory");
        
        
      end if;
    end if;
  end func;
const proc: doMake (inout string: parameters) is forward;
const func boolean: doOneCommand (inout string: command,
    inout string: commandOutput) is func
  result
    var boolean: done is TRUE;
  local
    var string: commandName is "";
  begin
    if command <> "" and command[1] = '#' then
      command := "";
      commandOutput := "";
    else
      commandName := lower(getWord(command));
      
      if commandName = "rm" then
        doRm(command);
        commandOutput := "";
      elsif commandName = "del" or commandName = "erase" then
        doDel(command);
        commandOutput := "";
      elsif commandName = "cp" then
        doCp(command);
        commandOutput := "";
      elsif commandName = "copy" then
        doCopy(command);
        commandOutput := "";
      elsif commandName = "xcopy" then
        doXCopy(command);
        commandOutput := "";
      elsif commandName = "mv" then
        doMv(command);
        commandOutput := "";
      elsif commandName = "move" then
        doMove(command);
        commandOutput := "";
      elsif commandName = "mkdir" then
        doMkdir(command);
        commandOutput := "";
      elsif commandName = "md" then
        doMd(command);
        commandOutput := "";
      elsif commandName = "pwd" then
        commandOutput := doPwd(command);
      elsif commandName = "echo" or commandName = "echo." then
        commandOutput := doEcho(command);
      elsif commandName = "cd" then
        doCd(command);
        commandOutput := "";
      elsif commandName = "make" or commandName = "make7" then
        doMake(command);
        commandOutput := "";
      elsif commandName = "rem" then
        command := "";
        commandOutput := "";
      elsif commandName = "(" then
        done := doOneCommand(command, commandOutput);
      else
        done := FALSE;
        commandOutput := "";
      end if;
    end if;
  end func;
const proc: appendToFile (in string: file_name, in string: stri) is func
  local
    var file: work_file is STD_NULL;
  begin
    if stri <> "" then
      work_file := open(file_name, "a");
      if work_file <> STD_NULL then
        write(work_file, stri);
        close(work_file);
      end if;
    end if;
  end func;
const func boolean: doCommands (inout string: command) is func
  result
    var boolean: done is TRUE;
  local
    var integer: quotePos is 0;
    var string: commandOutput is "";
    var string: redirect is "";
    var string: fileName is "";
  begin
    if startsWith(command, "\"") then
      quotePos := rpos(command, "\"");
      if quotePos <> 0 and quotePos <> 1 then
        command := command[2 .. pred(quotePos)] & command[succ(quotePos) ..];
      end if;
    end if;
    repeat
      done := doOneCommand(command, commandOutput);
      if done then
        skipWhiteSpace(command);
        redirect := getWord(command);
        if redirect = ">" then
          skipWhiteSpace(command);
          fileName := getCommandParameter(command);
          if fileName <> "/dev/null" and fileName <> "NUL:" and fileName <> "NUL" then
            fileName := convDosPath(fileName);
            putf(fileName, commandOutput);
          end if;
        elsif redirect = ">>" then
          skipWhiteSpace(command);
          fileName := getCommandParameter(command);
          if fileName <> "/dev/null" and fileName <> "NUL:" and fileName <> "NUL" then
            fileName := convDosPath(fileName);
            appendToFile(fileName, commandOutput);
          end if;
        elsif commandOutput <> "" then
          write(commandOutput);
        end if;
        skipWhiteSpace(command);
        if command <> "" and command[1] = ';' then
          command := command[2 ..];
          skipWhiteSpace(command);
        end if;
      end if;
    until command = "" or not done;
  end func;
const func integer: processCommand (in var string: command) is func
  result
    var integer: commandStatus is 0;
  local
    var string: fullCommand is "";
    var integer: gtPos is 0;
    var boolean: doRedirect is FALSE;
    var boolean: doAppend is FALSE;
    var string: rawFileName is "";
    var string: fileName is "";
    var file: aFile is STD_NULL;
    var string: backtickCommand is "";
    var string: commandOutput is "";
  begin
    
    fullCommand := command;
    if not doCommands(command) then
      gtPos := rpos(fullCommand, ">");
      if gtPos >= 2 then
        rawFileName := fullCommand[succ(gtPos) ..];
        skipWhiteSpace(rawFileName);
        fileName := getCommandParameter(rawFileName);
        skipWhiteSpace(rawFileName);
        if rawFileName = "" then
          if fullCommand[pred(gtPos)] = '>' and gtPos >= 3 and
              fullCommand[gtPos - 2] not in {'\\', '^'} then
            doAppend := TRUE;
            fullCommand := fullCommand[.. gtPos - 2];
          elsif fullCommand[pred(gtPos)] not in {'\\', '^', '2'} then
            doRedirect := TRUE;
            fullCommand := fullCommand[.. pred(gtPos)];
          end if;
        end if;
      end if;
      if doRedirect or doAppend then
        if startsWith(fullCommand, "\"") then
          command := getQuotedText(fullCommand);
        elsif startsWith(fullCommand, "`") then
          backtickCommand := getQuotedText(fullCommand);
          command := execCommand(backtickCommand);
        else
          command := getWord(fullCommand);
        end if;
        
        aFile := popen(convDosPath(command), fullCommand, "r");
        if aFile <> STD_NULL then
          commandOutput := gets(aFile, 999999999);
          close(aFile);
          if fileName <> "/dev/null" and fileName <> "NUL:" and fileName <> "NUL" then
            fileName := convDosPath(fileName);
            if doAppend then
              appendToFile(fileName, commandOutput);
            else
              putf(fileName, commandOutput);
            end if;
          end if;
        end if;
      else
        if startsWith(fullCommand, "\"") then
          command := getQuotedText(fullCommand);
        elsif startsWith(fullCommand, "`") then
          backtickCommand := getQuotedText(fullCommand);
          command := execCommand(backtickCommand);
        else
          command := getWord(fullCommand);
        end if;
        
        commandStatus := shell(convDosPath(command), fullCommand);
      end if;
    end if;
  end func;