(********************************************************************)
(*                                                                  *)
(*  makedata.s7i  Function and data structure to read makefiles     *)
(*  Copyright (C) 2010 - 2014  Thomas Mertes                        *)
(*                                                                  *)
(*  This file is part of the Seed7 Runtime Library.                 *)
(*                                                                  *)
(*  The Seed7 Runtime Library is free software; you can             *)
(*  redistribute it and/or modify it under the terms of the GNU     *)
(*  Lesser General Public License as published by the Free Software *)
(*  Foundation; either version 2.1 of the License, or (at your      *)
(*  option) any later version.                                      *)
(*                                                                  *)
(*  The Seed7 Runtime Library is distributed in the hope that it    *)
(*  will be useful, but WITHOUT ANY WARRANTY; without even the      *)
(*  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *)
(*  PURPOSE.  See the GNU Lesser General Public License for more    *)
(*  details.                                                        *)
(*                                                                  *)
(*  You should have received a copy of the GNU Lesser General       *)
(*  Public License along with this program; if not, write to the    *)
(*  Free Software Foundation, Inc., 51 Franklin Street,             *)
(*  Fifth Floor, Boston, MA  02110-1301, USA.                       *)
(*                                                                  *)
(********************************************************************)


include "osfiles.s7i";
include "chartype.s7i";
include "scanfile.s7i";
include "scanstri.s7i";
include "cli_cmds.s7i";


const set of char: target_name_char is {'!' .. '~'} - {':', '='};
const set of char: macro_name_char is {'!' .. '~'} - {')', ':', '='};

(**
 *  Describes a make rule with target, dependencies and commands.
 *)
const type: ruleType is new struct
    var string: target is "";
    var array string: dependencies is 0 times "";
    var array string: commands is 0 times "";
    var boolean: ruleProcessed is FALSE;
  end struct;

const type: ruleHash is hash [string] ruleType;
const type: ruleArray is array ruleType;
const type: ruleArrayHash is hash [string] ruleArray;
const type: stringHash is hash [string] string;

(**
 *  Describes rules and macros from a makefile and flags from the command line.
 *)
const type: makeDataType is new struct
    var file: makefile is STD_NULL;
    var ruleHash: rules is ruleHash.value;
    var ruleArrayHash: patternRules is ruleArrayHash.value;
    var stringHash: macros is stringHash.value;
    var string: targetOfFirstRule is "";
    var boolean: executeCommands is TRUE;
    var boolean: inSilentMode is FALSE;
    var boolean: doIgnoreErrors is FALSE;
  end struct;


const func boolean: patternMatches (in string: stri, in string: pattern) is func
  result
    var boolean: doesMatch is FALSE;
  local
    var integer: patternPercentPos is 0;
  begin
    # writeln("patternMatches(" <& stri <& ", " <& pattern <& ")");
    patternPercentPos := pos(pattern, '%');
    if patternPercentPos <> 0 then
      doesMatch := startsWith(stri, pattern[.. pred(patternPercentPos)]) and
                   endsWith(stri, pattern[succ(patternPercentPos) ..]);
    else
      doesMatch := stri = pattern;
    end if;
  end func;


const func boolean: patternMatches (in string: stri, in array string: patternList) is func
  result
    var boolean: doesMatch is FALSE;
  local
    var string: pattern is "";
  begin
    for pattern range patternList until doesMatch do
      doesMatch := patternMatches(stri, pattern);
    end for;
  end func;


const func string: patternSubstitute (in var string: stri, in string: pattern, in string: replacement) is func
  result
    var string: replaced is ""
  local
    var string: symbol is "";
    var integer: patternPercentPos is 0;
    var integer: replacementPercentPos is 0;
  begin
    # writeln("patternSubstitute(" <& stri <& ", " <& pattern <& ", " <& replacement <& ")");
    patternPercentPos := pos(pattern, '%');
    replacementPercentPos := pos(replacement, '%');
    if patternPercentPos <> 0 and replacementPercentPos <> 0 then
      repeat
        replaced &:= getWhiteSpace(stri);
        symbol := getWord(stri);
        if symbol <> "" then
          if startsWith(symbol, pattern[.. pred(patternPercentPos)]) and
              endsWith(symbol, pattern[succ(patternPercentPos) ..]) then
            symbol := replacement[.. pred(replacementPercentPos)] &
                symbol[patternPercentPos .. length(symbol) - length(pattern) + patternPercentPos] &
                replacement[succ(replacementPercentPos) ..];
          end if;
          replaced &:= symbol;
        end if;
      until symbol = "";
    end if;
  end func;


const func string: replaceSuffixes (in var string: stri, in string: suffix, in string: replacement) is func
  result
    var string: replaced is ""
  local
    var string: symbol is "";
  begin
    # writeln("replaceSuffixes(" <& stri <& ", " <& suffix <& ", " <& replacement <& ")");
    if pos(suffix, '%') <> 0 and pos(replacement, '%') <> 0 then
      replaced := patternSubstitute(stri, suffix, replacement);
    else
      repeat
        replaced &:= getWhiteSpace(stri);
        symbol := getWord(stri);
        if symbol <> "" then
          if endsWith(symbol, suffix) then
            symbol := symbol[.. length(symbol) - length(suffix)] & replacement;
          end if;
          replaced &:= symbol;
        end if;
      until symbol = "";
    end if;
  end func;


const func string: getMacroName (inout string: stri) is func
  result
    var string: symbol is "";
  local
    var integer: pos is 1;
  begin
    while pos <= length(stri) and stri[pos] in macro_name_char do
      incr(pos);
    end while;
    symbol := stri[.. pred(pos)];
    stri := stri[pos ..];
  end func;


const func string: getMacroParameter (in string: stri, inout integer: pos,
    in char: delimiter) is func
  result
    var string: parameter is "";
  begin
    while pos <= length(stri) and stri[pos] <> delimiter do
      if stri[pos] = '(' then
        incr(pos);
        parameter &:= "(";
        parameter &:= getMacroParameter(stri, pos, ')');
        parameter &:= ")";
      elsif stri[pos] = '{' then
        incr(pos);
        parameter &:= "{";
        parameter &:= getMacroParameter(stri, pos, '}');
        parameter &:= "}";
      else
        parameter &:= stri[pos];
      end if;
      incr(pos);
    end while;
  end func;


const func string: applyMacros (in stringHash: macros, in string: stri,
    in boolean: leaveUndefMacros) is forward;


const func string: replaceSuffixes (in stringHash: macros, in string: stri,
    in string: parameters, in boolean: leaveUndefMacros) is func
  result
    var string: funcResult is "";
  local
    var integer: pos is 1;
    var string: suffix is "";
    var string: replacement is "";
    var string: evaluatedParam is "";
  begin
    # writeln("replaceSuffixes(" <& stri <& ", " <& parameters <& ")");
    suffix := getMacroParameter(parameters, pos, '=');
    if pos <= length(parameters) then
      incr(pos);
      replacement := parameters[pos ..];
      evaluatedParam := applyMacros(macros, stri, leaveUndefMacros);
      funcResult := replaceSuffixes(evaluatedParam, suffix, replacement);
    end if;
  end func;


const func string: strip (in stringHash: macros, in string: parameters,
    in boolean: leaveUndefMacros) is func
  result
    var string: funcResult is "";
  local
    var integer: pos is 1;
    var string: evaluatedParam is "";
  begin
    # writeln("strip: " <& literal(parameters));
    evaluatedParam := applyMacros(macros, parameters, leaveUndefMacros);
    funcResult := replace(evaluatedParam, "\t", " ");
    funcResult := replaceN(funcResult, "  ", " ");
    funcResult := trim(funcResult);
  end func;


const func string: subst (in stringHash: macros, in string: parameters,
    in boolean: leaveUndefMacros) is func
  result
    var string: funcResult is "";
  local
    var integer: pos is 1;
    var string: from is "";
    var string: replacement is "";
    var string: evaluatedParam is "";
  begin
    # writeln("subst: " <& literal(parameters));
    from := getMacroParameter(parameters, pos, ',');
    from := applyMacros(macros, from, leaveUndefMacros);
    if pos <= length(parameters) then
      incr(pos);
      replacement := getMacroParameter(parameters, pos, ',');
      replacement := applyMacros(macros, replacement, leaveUndefMacros);
      if pos <= length(parameters) then
        incr(pos);
        evaluatedParam := applyMacros(macros, parameters[pos ..], leaveUndefMacros);
        funcResult := replace(evaluatedParam, from, replacement);
      end if;
    end if;
  end func;


const func string: patsubst (in stringHash: macros, in string: parameters,
    in boolean: leaveUndefMacros) is func
  result
    var string: funcResult is "";
  local
    var integer: pos is 1;
    var string: pattern is "";
    var string: replacement is "";
    var string: evaluatedParam is "";
  begin
    # writeln("patsubst: " <& literal(parameters));
    pattern := getMacroParameter(parameters, pos, ',');
    pattern := applyMacros(macros, pattern, leaveUndefMacros);
    if pos <= length(parameters) then
      incr(pos);
      replacement := getMacroParameter(parameters, pos, ',');
      replacement := applyMacros(macros, replacement, leaveUndefMacros);
      if pos <= length(parameters) then
        incr(pos);
        evaluatedParam := applyMacros(macros, parameters[pos ..], leaveUndefMacros);
        funcResult := patternSubstitute(evaluatedParam, pattern, replacement);
      end if;
    end if;
  end func;


const func string: dir (in stringHash: macros, in string: parameters,
    in boolean: leaveUndefMacros) is func
  result
    var string: funcResult is "";
  local
    var string: names is "";
    var string: name is "";
    var integer: slashOrbackslashPos is 0;
    var integer: periodPos is 0;
  begin
    # writeln("dir: " <& literal(parameters));
    names := applyMacros(macros, parameters, leaveUndefMacros);
    repeat
      skipWhiteSpace(names);
      name := getWord(names);
      if name <> "" then
        if funcResult <> "" then
          funcResult &:= " ";
        end if;
        slashOrbackslashPos := max(rpos(name, '/'), rpos(name, '\\'));
        if slashOrbackslashPos <> 0 then
          funcResult &:= name[.. slashOrbackslashPos];
        else
          funcResult &:= "./";
        end if;
      end if;
    until name = "";
    # writeln("dir -> " <& funcResult);
  end func;


const func string: notdir (in stringHash: macros, in string: parameters,
    in boolean: leaveUndefMacros) is func
  result
    var string: funcResult is "";
  local
    var string: names is "";
    var string: name is "";
    var integer: slashOrbackslashPos is 0;
    var integer: periodPos is 0;
  begin
    # writeln("notdir: " <& literal(parameters));
    names := applyMacros(macros, parameters, leaveUndefMacros);
    repeat
      skipWhiteSpace(names);
      name := getWord(names);
      if name <> "" then
        if funcResult <> "" then
          funcResult &:= " ";
        end if;
        slashOrbackslashPos := max(rpos(name, '/'), rpos(name, '\\'));
        if slashOrbackslashPos <> 0 then
          funcResult &:= name[succ(slashOrbackslashPos) ..];
        else
          funcResult &:= name;
        end if;
      end if;
    until name = "";
    # writeln("notdir -> " <& funcResult);
  end func;


const func string: suffix (in stringHash: macros, in string: parameters,
    in boolean: leaveUndefMacros) is func
  result
    var string: funcResult is "";
  local
    var string: names is "";
    var string: name is "";
    var integer: slashOrbackslashPos is 0;
    var integer: periodPos is 0;
  begin
    # writeln("suffix: " <& literal(parameters));
    names := applyMacros(macros, parameters, leaveUndefMacros);
    repeat
      skipWhiteSpace(names);
      name := getWord(names);
      if name <> "" then
        periodPos := rpos(name, '.');
        if periodPos <> 0 then
          slashOrbackslashPos := max(rpos(name, '/'), rpos(name, '\\'));
          if slashOrbackslashPos < periodPos then
            if funcResult <> "" then
              funcResult &:= " ";
            end if;
            funcResult &:= name[periodPos ..];
          end if;
        end if;
      end if;
    until name = "";
    # writeln("suffix -> " <& funcResult);
  end func;


const func string: basename (in stringHash: macros, in string: parameters,
    in boolean: leaveUndefMacros) is func
  result
    var string: funcResult is "";
  local
    var string: names is "";
    var string: name is "";
    var integer: slashOrbackslashPos is 0;
    var integer: periodPos is 0;
  begin
    # writeln("basename: " <& literal(parameters));
    names := applyMacros(macros, parameters, leaveUndefMacros);
    repeat
      skipWhiteSpace(names);
      name := getWord(names);
      if name <> "" then
        if funcResult <> "" then
          funcResult &:= " ";
        end if;
        periodPos := rpos(name, '.');
        if periodPos = 0 then
          funcResult &:= name;
        else
          slashOrbackslashPos := max(rpos(name, '/'), rpos(name, '\\'));
          if slashOrbackslashPos < periodPos then
            funcResult &:= name[.. pred(periodPos)];
          else
            funcResult &:= name;
          end if;
        end if;
      end if;
    until name = "";
    # writeln("basename -> " <& funcResult);
  end func;


const func string: addprefix (in stringHash: macros, in string: parameters,
    in boolean: leaveUndefMacros) is func
  result
    var string: funcResult is "";
  local
    var integer: pos is 1;
    var string: prefix is "";
    var string: names is "";
    var string: name is "";
  begin
    # writeln("addprefix: " <& literal(parameters));
    prefix := getMacroParameter(parameters, pos, ',');
    prefix := applyMacros(macros, prefix, leaveUndefMacros);
    if pos <= length(parameters) then
      incr(pos);
      names := applyMacros(macros, parameters[pos ..], leaveUndefMacros);
      repeat
        skipWhiteSpace(names);
        name := getWord(names);
        if name <> "" then
          if funcResult <> "" then
            funcResult &:= " ";
          end if;
          funcResult &:= prefix;
          funcResult &:= name;
        end if;
      until name = "";
    end if;
    # writeln("addprefix -> " <& funcResult);
  end func;


const func string: addsuffix (in stringHash: macros, in string: parameters,
    in boolean: leaveUndefMacros) is func
  result
    var string: funcResult is "";
  local
    var integer: pos is 1;
    var string: suffix is "";
    var string: names is "";
    var string: name is "";
  begin
    # writeln("addsuffix: " <& literal(parameters));
    suffix := getMacroParameter(parameters, pos, ',');
    suffix := applyMacros(macros, suffix, leaveUndefMacros);
    if pos <= length(parameters) then
      incr(pos);
      names := applyMacros(macros, parameters[pos ..], leaveUndefMacros);
      repeat
        skipWhiteSpace(names);
        name := getWord(names);
        if name <> "" then
          if funcResult <> "" then
            funcResult &:= " ";
          end if;
          funcResult &:= name;
          funcResult &:= suffix;
        end if;
      until name = "";
    end if;
    # writeln("addsuffix -> " <& funcResult);
  end func;


const func string: filter (in stringHash: macros, in string: parameters,
    in boolean: leaveUndefMacros) is func
  result
    var string: funcResult is "";
  local
    var integer: pos is 1;
    var string: patterns is "";
    var string: pattern is "";
    var string: names is "";
    var string: name is "";
    var array string: patternList is 0 times "";
  begin
    # writeln("filter: " <& literal(parameters));
    patterns := getMacroParameter(parameters, pos, ',');
    patterns := applyMacros(macros, patterns, leaveUndefMacros);
    if pos <= length(parameters) then
      incr(pos);
      names := applyMacros(macros, parameters[pos ..], leaveUndefMacros);
      repeat
        skipWhiteSpace(patterns);
        pattern := getWord(patterns);
        patternList &:= pattern;
      until pattern = "";
      repeat
        skipWhiteSpace(names);
        name := getWord(names);
        if name <> "" and patternMatches(name, patternList) then
          if funcResult <> "" then
            funcResult &:= " ";
          end if;
          funcResult &:= name;
        end if;
      until name = "";
    end if;
    # writeln("filter -> " <& funcResult);
  end func;


const func string: filterOut (in stringHash: macros, in string: parameters,
    in boolean: leaveUndefMacros) is func
  result
    var string: funcResult is "";
  local
    var integer: pos is 1;
    var string: patterns is "";
    var string: pattern is "";
    var string: names is "";
    var string: name is "";
    var array string: patternList is 0 times "";
  begin
    # writeln("filterOut: " <& literal(parameters));
    patterns := getMacroParameter(parameters, pos, ',');
    patterns := applyMacros(macros, patterns, leaveUndefMacros);
    if pos <= length(parameters) then
      incr(pos);
      names := applyMacros(macros, parameters[pos ..], leaveUndefMacros);
      repeat
        skipWhiteSpace(patterns);
        pattern := getWord(patterns);
        patternList &:= pattern;
      until pattern = "";
      repeat
        skipWhiteSpace(names);
        name := getWord(names);
        if name <> "" and not patternMatches(name, patternList) then
          if funcResult <> "" then
            funcResult &:= " ";
          end if;
          funcResult &:= name;
        end if;
      until name = "";
    end if;
    # writeln("filterOut -> " <& funcResult);
  end func;


const func string: foreach (in var stringHash: macros, in string: parameters,
    in boolean: leaveUndefMacros) is func
  result
    var string: funcResult is "";
  local
    var integer: pos is 1;
    var string: variable is "";
    var string: wordList is "";
    var string: bodyText is "";
    var string: symbol is "";
    var string: evaluatedBody is "";
  begin
    # writeln("foreach: " <& literal(parameters));
    variable := getMacroParameter(parameters, pos, ',');
    variable := applyMacros(macros, variable, leaveUndefMacros);
    if pos <= length(parameters) then
      incr(pos);
      wordList := getMacroParameter(parameters, pos, ',');
      wordList := applyMacros(macros, wordList, leaveUndefMacros);
      if pos <= length(parameters) then
        incr(pos);
        bodyText := parameters[pos ..];
        repeat
          skipWhiteSpace(wordList);
          symbol := getWord(wordList);
          if symbol <> "" then
            if funcResult <> "" then
              funcResult &:= " ";
            end if;
            macros @:= [variable] symbol;
            evaluatedBody := applyMacros(macros, bodyText, leaveUndefMacros);
            funcResult &:= evaluatedBody;
          end if;
        until symbol = "";
      end if;
    end if;
    # writeln("foreach -> " <& funcResult);
  end func;


const func string: call (in var stringHash: macros, in string: parameters,
    in boolean: leaveUndefMacros) is func
  result
    var string: funcResult is "";
  local
    var integer: pos is 1;
    var string: variable is "";
    var string: variableValue is "";
    var integer: paramNumber is 1;
    var string: paramValue is "";
  begin
    # writeln("call: " <& literal(parameters));
    variable := getMacroParameter(parameters, pos, ',');
    if variable in macros then
      variableValue := macros[variable];
      while pos <= length(parameters) do
        incr(pos);
        paramValue := getMacroParameter(parameters, pos, ',');
        # writeln("param " <& paramNumber <& " = " <& paramValue);
        macros @:= [str(paramNumber)] paramValue;
        incr(paramNumber);
      end while;
      funcResult := applyMacros(macros, variableValue, leaveUndefMacros);
    end if;
    # writeln("call -> " <& funcResult);
  end func;


const func string: sort (in stringHash: macros, in string: parameters,
    in boolean: leaveUndefMacros) is func
  result
    var string: funcResult is "";
  local
    var string: wordList is "";
    var string: name is "";
    var string: lastName is "";
    var array string: wordArray is 0 times "";
  begin
    # writeln("sort: " <& literal(parameters));
    wordList := applyMacros(macros, parameters, leaveUndefMacros);
    repeat
      skipWhiteSpace(wordList);
      name := getWord(wordList);
      if name <> "" then
        wordArray &:= name;
      end if;
    until name = "";
    wordArray := sort(wordArray);
    for name range wordArray do
      if name <> lastName then
        if funcResult <> "" then
          funcResult &:= " ";
        end if;
        funcResult &:= name;
      end if;
      lastName := name;
    end for;
    # writeln("sort -> " <& funcResult);
  end func;


const func string: shell (in stringHash: macros, in string: parameters,
    in boolean: leaveUndefMacros) is func
  result
    var string: funcResult is "";
  local
    var string: evaluatedParam is "";
  begin
    # writeln("shell: " <& literal(parameters));
    evaluatedParam := applyMacros(macros, parameters, leaveUndefMacros);
    funcResult := execCommand(evaluatedParam);
    # writeln("shell -> " <& funcResult);
  end func;


const func string: wildcard (in stringHash: macros, in string: parameters,
    in boolean: leaveUndefMacros) is func
  result
    var string: funcResult is "";
  local
    var string: evaluatedParam is "";
    var array string: matches is 0 times "";
  begin
    # writeln("wildcard: " <& literal(parameters));
    evaluatedParam := applyMacros(macros, parameters, leaveUndefMacros);
    matches := findMatchingFiles(evaluatedParam, TRUE);
    if length(matches) <> 0 then
      funcResult := join(matches, ' ');
    end if;
    # writeln("wildcard -> " <& funcResult);
  end func;


(**
 *  Apply macros from the ''macros'' hash table to the given ''stri''.
 *  Macro invocations in ''stri'' can be written as $(CFLAGS) or as ${CFLAGS}.
 *  Besides the macros defined in ''macros'' the functions strip, subst,
 *  patsubst, dir, notdir, suffix, basename, addprefix, addsuffix, filter,
 *  filter-out, foreach, call, sort, shell, wildcard, error and warning
 *  are also processed. Additionally the shorthand replacement function
 *  $(var:pat=to) is processed. Internal macros like $@ $< $? $^ $+ and $$
 *  are always left unchanged.
 *  @param macros A hash map with all defined macros.
 *  @param stri The string where the macros should be applied.
 *  @param leaveUndefMacros TRUE if undefined macros should be left as is,
 *                          or FALSE if undefined macros should be removed.
 *  @return the string ''stri'' after the macros have been applied.
 *)
const func string: applyMacros (in stringHash: macros, in string: stri,
    in boolean: leaveUndefMacros) is func
  result
    var string: applied is "";
  local
    var integer: dollarPos is 0;
    var integer: closeParenPos is 0;
    var string: parameter is "";
    var string: name is "";
    var string: macroValue is "";
    var boolean: done is FALSE;
  begin
    # writeln("applyMacros(macros, " <& literal(stri) <& ", " <& leaveUndefMacros <& ")");
    applied := stri;
    dollarPos := pos(applied, '$');
    while dollarPos <> 0 do
      if applied[succ(dollarPos)] in {'(', '{'} then
        closeParenPos := dollarPos + 2;
        if applied[succ(dollarPos)] = '(' then
          parameter := getMacroParameter(applied, closeParenPos, ')');
        else
          parameter := getMacroParameter(applied, closeParenPos, '}');
        end if;
        if closeParenPos <= length(applied) then
          name := getMacroName(parameter);
          done := FALSE;
          if parameter = "" then
            if name in macros then
              macroValue := applyMacros(macros, macros[name], leaveUndefMacros);
              done := TRUE;
            else
              macroValue := getenv(name);
              done := macroValue <> "";
            end if;
          elsif parameter[1] = ':' then
            if name in macros then
              macroValue := replaceSuffixes(macros, macros[name],
                                            parameter[2 ..], leaveUndefMacros);
              done := TRUE;
            end if;
          elsif parameter[1] = ' ' then
            skipSpaceOrTab(parameter);
            if name = "strip" then
              macroValue := strip(macros, parameter, leaveUndefMacros);
              done := TRUE;
            elsif name = "subst" then
              macroValue := subst(macros, parameter, leaveUndefMacros);
              done := TRUE;
            elsif name = "patsubst" then
              macroValue := patsubst(macros, parameter, leaveUndefMacros);
              done := TRUE;
            elsif name = "dir" then
              macroValue := dir(macros, parameter, leaveUndefMacros);
              done := TRUE;
            elsif name = "notdir" then
              macroValue := notdir(macros, parameter, leaveUndefMacros);
              done := TRUE;
            elsif name = "suffix" then
              macroValue := suffix(macros, parameter, leaveUndefMacros);
              done := TRUE;
            elsif name = "basename" then
              macroValue := basename(macros, parameter, leaveUndefMacros);
              done := TRUE;
            elsif name = "addprefix" then
              macroValue := addprefix(macros, parameter, leaveUndefMacros);
              done := TRUE;
            elsif name = "addsuffix" then
              macroValue := addsuffix(macros, parameter, leaveUndefMacros);
              done := TRUE;
            elsif name = "filter" then
              macroValue := filter(macros, parameter, leaveUndefMacros);
              done := TRUE;
            elsif name = "filter-out" then
              macroValue := filterOut(macros, parameter, leaveUndefMacros);
              done := TRUE;
            elsif name = "foreach" then
              macroValue := foreach(macros, parameter, leaveUndefMacros);
              done := TRUE;
            elsif name = "call" then
              macroValue := call(macros, parameter, leaveUndefMacros);
              done := TRUE;
            elsif name = "sort" then
              macroValue := sort(macros, parameter, leaveUndefMacros);
              done := TRUE;
            elsif name = "shell" then
              macroValue := shell(macros, parameter, leaveUndefMacros);
              done := TRUE;
            elsif name = "wildcard" then
              macroValue := wildcard(macros, parameter, leaveUndefMacros);
              done := TRUE;
            elsif name = "error" then
              writeln(" *** Error: " <& applyMacros(macros, parameter, FALSE));
              raise FILE_ERROR;
            elsif name = "warning" then
              writeln(applyMacros(macros, parameter, FALSE));
              macroValue := "";
              done := TRUE;
            else
              writeln(" *** Unknown function " <& literal(name) <& ", ignored.");
            end if;
          end if;
          if done then
            applied := applied[.. pred(dollarPos)] &
                       macroValue &
                       applied[succ(closeParenPos) ..];
          elsif leaveUndefMacros then
            incr(dollarPos);
          else
            applied := applied[.. pred(dollarPos)] &
                       applied[succ(closeParenPos) ..];
          end if;
        else
          incr(dollarPos);
        end if;
      elsif applied[succ(dollarPos)] = '$' then
        dollarPos +:= 2;
      else
        incr(dollarPos);
      end if;
      dollarPos := pos(applied, '$', dollarPos);
    end while;
  end func;


const func string: getMacroParameter (inout file: makefile, in char: delimiter) is func
  result
    var string: parameter is "";
  begin
    while makefile.bufferChar <> delimiter and makefile.bufferChar <> EOF do
      if makefile.bufferChar = '(' then
        parameter &:= "(";
        makefile.bufferChar := getc(makefile);
        parameter &:= getMacroParameter(makefile, ')');
        parameter &:= ")";
      elsif makefile.bufferChar = '{' then
        parameter &:= "{";
        makefile.bufferChar := getc(makefile);
        parameter &:= getMacroParameter(makefile, '}');
        parameter &:= "}";
      else
        parameter &:= makefile.bufferChar;
      end if;
      makefile.bufferChar := getc(makefile);
    end while;
  end func;


(**
 *  Get a line from the ''makefile''.
 *  Data is read until a line ending is not preceeded by a backslash (\).
 *  Line endings can be "\n" or "\r\n" (the "\r" is silently removed).
 *  A backslash at the end of a line is removed together with the line ending.
 *  This is done such that at least one space replaces the line ending.
 *  When the function is called the first char to be read must be in
 *  makefile.bufferChar. When the function is left makefile.bufferChar
 *  contains either '\n' or EOF.
 *  @param makefile Makefile from which the line is read.
 *  @return the line read from the ''makefile''.
 *)
const func string: getMakeLine (inout file: makefile) is func
  result
    var string: data is "";
  begin
    data := "";
    while makefile.bufferChar <> '\n' and makefile.bufferChar <> EOF do
      if makefile.bufferChar = '\r' then
        makefile.bufferChar := getc(makefile);
        if makefile.bufferChar <> '\n' then
          data &:= '\r';
        end if;
      elsif makefile.bufferChar = '\\' then
        makefile.bufferChar := getc(makefile);
        if makefile.bufferChar = '\r' then
          makefile.bufferChar := getc(makefile);
          if makefile.bufferChar = '\n' then
            makefile.bufferChar := getc(makefile);
            skipSpaceOrTab(makefile);
            if data <> "" and data[length(data)] not in space_or_tab then
              data &:= ' ';
            end if;
          else
            data &:= "\\\r";
          end if;
        elsif makefile.bufferChar = '\n' then
          makefile.bufferChar := getc(makefile);
          skipSpaceOrTab(makefile);
          if data <> "" and data[length(data)] not in space_or_tab then
            data &:= ' ';
          end if;
        else
          data &:= '\\';
        end if;
      else
        data &:= makefile.bufferChar;
        makefile.bufferChar := getc(makefile);
      end if;
    end while;
  end func;


(**
 *  Get a target name from the ''makefile''.
 *  This function is also used to read macro names. When the function is called
 *  the first character of the target name must be in makefile.bufferChar.
 *  When the function is left the first character after the target name is in
 *  makefile.bufferChar.
 *  @param makefile Makefile from which the target name is read.
 *  @return the target name read from the ''makefile''.
 *)
const func string: getMakeTarget (inout file: makefile) is func
  result
    var string: symbol is "";
  begin
    symbol := str(makefile.bufferChar);
    makefile.bufferChar := getc(makefile);
    while makefile.bufferChar in target_name_char do
      symbol &:= makefile.bufferChar;
      makefile.bufferChar := getc(makefile);
    end while;
  end func;


const proc: find_endif_directive (inout file: makefile) is func
  local
    var string: command is "";
  begin
    repeat
      if makefile.bufferChar = '!' then
        makefile.bufferChar := getc(makefile);
        command := lower(getWord(makefile));
        skipLine(makefile);
        makefile.bufferChar := getc(makefile);
        if command = "if" then
          find_endif_directive(makefile);
        end if;
      else
        skipLine(makefile);
        makefile.bufferChar := getc(makefile);
      end if;
    until command = "endif";
  end func;


const proc: find_endif_or_else_directive (inout file: makefile) is func
  local
    var string: command is "";
  begin
    repeat
      if makefile.bufferChar = '!' then
        makefile.bufferChar := getc(makefile);
        command := lower(getWord(makefile));
        skipLine(makefile);
        makefile.bufferChar := getc(makefile);
        if command = "if" then
          find_endif_directive(makefile);
        end if;
      else
        skipLine(makefile);
        makefile.bufferChar := getc(makefile);
      end if;
    until command = "endif" or command = "else";
  end func;


const proc: execIf (inout file: makefile, in stringHash: macros) is func
  local
    var string: functionName is "";
    var string: fileName is "";
    var string: value2 is "";
    var boolean: cond is FALSE;
    var string: command is "";
  begin
    # writeln("if");
    skipSpaceOrTab(makefile);
    functionName := getName(makefile);
    if functionName = "exist" and makefile.bufferChar = '(' then
      makefile.bufferChar := getc(makefile);
      fileName := getMacroParameter(makefile, ')');
      skipLine(makefile);
      makefile.bufferChar := getc(makefile);
      if fileType(fileName) = FILE_ABSENT then
        # writeln("skip");
        find_endif_or_else_directive(makefile);
      end if;
    end if;
  end func;


const proc: find_endif (inout file: makefile) is func
  local
    var string: command is "";
  begin
    repeat
      command := getMakeTarget(makefile);
      skipLine(makefile);
      makefile.bufferChar := getc(makefile);
      if command = "ifeq" or command = "ifneq" then
        find_endif(makefile);
      end if;
    until command = "endif";
  end func;


const proc: find_endif_or_else (inout file: makefile) is func
  local
    var string: command is "";
  begin
    repeat
      command := getMakeTarget(makefile);
      skipLine(makefile);
      makefile.bufferChar := getc(makefile);
      if command = "ifeq" or command = "ifneq" then
        find_endif(makefile);
      end if;
    until command = "endif" or command = "else";
  end func;


const proc: execIfeq (inout file: makefile, in stringHash: macros, in boolean: equal) is func
  local
    var string: parameter is "";
    var string: value1 is "";
    var string: value2 is "";
  begin
    # writeln("ifeq");
    if makefile.bufferChar = '(' then
      makefile.bufferChar := getc(makefile);
      parameter := getMacroParameter(makefile, ',');
      value1 := applyMacros(macros, parameter, TRUE);
      # writeln("value1=" <& literal(value1));
      skipSpaceOrTab(makefile);
      if makefile.bufferChar = ',' then
        makefile.bufferChar := getc(makefile);
        parameter := getMacroParameter(makefile, ')');
        value2 := applyMacros(macros, parameter, TRUE);
        # writeln("value2=" <& literal(value2));
        if makefile.bufferChar = ')' then
          skipLine(makefile);
          makefile.bufferChar := getc(makefile);
          if (value1 = value2) <> equal then
            # writeln("skip");
            find_endif_or_else(makefile);
          end if;
        end if;
      end if;
    end if;
  end func;


const proc: execIfdef (inout file: makefile, in stringHash: macros, in boolean: equal) is func
  local
    var string: name is "";
  begin
    # writeln("ifdef");
    name := getMakeTarget(makefile);
    skipLine(makefile);
    makefile.bufferChar := getc(makefile);
    if (name in macros or getenv(name) <> "") <> equal then
      # writeln("skip");
      find_endif_or_else(makefile);
    end if;
  end func;


(**
 *  Get the first dependency from the ''dependencies'' line.
 *  The function removes the dependency from the ''dependencies'' line.
 *  This way consecutive calls deliver the dependencies one by one.
 *  @param dependencies Line of dependencies from the makefile.
 *  @return the first dependency from the ''dependencies'' line.
 *)
const func string: getMakeDependency (inout string: dependencies) is func
  result
    var string: symbol is "";
  local
    var integer: leng is 0;
    var integer: start is 1;
    var integer: pos is 1;
  begin
    leng := length(dependencies);
    while start <= leng and dependencies[start] in white_space_char do
      incr(start);
    end while;
    if start <= leng then
      pos := start;
      if dependencies[pos] = '"' then
        repeat
          incr(pos);
          while pos <= leng and dependencies[pos] <> '"' do
            symbol &:= dependencies[pos];
            incr(pos);
          end while;
          if pos <= leng then
            incr(pos);
            if pos <= leng and dependencies[pos] = '"' then
              symbol &:= '"';
            end if;
          else
            writeln(" *** Quoted dependency " <& literal(symbol) <&
                    " does not end with \".");
          end if;
        until pos > leng or dependencies[pos] <> '"';
        if pos <= leng and dependencies[pos] not in white_space_char then
          writeln(" *** White space expected after quoted dependency " <&
                  literal(symbol) <& " found " <& literal(dependencies[pos]) <& ".");
        end if;
      else
        repeat
          if dependencies[pos] = '\\' and pos < leng and dependencies[succ(pos)] = ' ' then
            incr(pos);
          end if;
          symbol &:= dependencies[pos];
          incr(pos);
        until pos > leng or dependencies[pos] in white_space_char;
      end if;
      dependencies := dependencies[pos ..];
    else
      dependencies := "";
    end if;
  end func;


(**
 *  Read a rule with the given ''target'' from the makefile specified by ''makeData''.
 *  The function assumes that the colon (:) after the target has been read.
 *  The function reads the dependencies and commands of the rule.
 *  @param makeData Make data containing the file ''makefile'' and the defined macros.
 *  @param target The target of the rule to be read.
 *  @return the makefile rule read.
 *)
const func ruleType: readRule (inout makeDataType: makeData, in string: target) is func
  result
    var ruleType: rule is ruleType.value;
  local
    var file: makefile is STD_NULL;
    var string: dependencies is "";
    var string: dependency is "";
    var string: command is "";
  begin
    makefile := makeData.makefile;
    rule.target := target;
    skipSpaceOrTab(makefile);
    dependencies := applyMacros(makeData.macros, getMakeLine(makefile), TRUE);
    # write(target <& ":");
    while dependencies <> "" do
      dependency := getMakeDependency(dependencies);
      rule.dependencies &:= dependency;
      skipWhiteSpace(dependencies);
      # write(" " <& dependency);
    end while;
    # writeln;
    makefile.bufferChar := getc(makefile);
    while makefile.bufferChar = '\t' or makefile.bufferChar = ' ' or
        makefile.bufferChar = '\r' or makefile.bufferChar = '\n' or
        makefile.bufferChar = '#' do
      skipSpaceOrTab(makefile);
      if makefile.bufferChar = '\r' then
        makefile.bufferChar := getc(makefile);
      end if;
      if makefile.bufferChar = '#' then
        skipLineComment(makefile);
      elsif makefile.bufferChar <> '\n' then
        command := getMakeLine(makefile);
        rule.commands &:= command;
        # writeln("\t" <& command);
      end if;
      makefile.bufferChar := getc(makefile);
    end while;
  end func;


(**
 *  Determine if the pattern rule ''target'' + ''dependency'' is present in ''makeData''.
 *   patternRulePresent(makeData, "%.o", "%.c")
 *   patternRulePresent(makeData, "%.o", "%.cpp")
 *  @param makeData Rules and macros from the makefile and flags from the command line.
 *  @param target Target of the searched pattern rule.
 *  @param dependency Dependency of the searchec pattern rule.
 *  @return TRUE, if the pattern rule ''target'' with the given ''dependency'' is present,
 *          FALSE otherwise.
 *)
const func boolean: patternRulePresent (in makeDataType: makeData, in string: target,
    in string: dependency) is func
  result
    var boolean: patternRulePresent is FALSE;
  local
    var integer: ruleArrayIndex is 0;
    var string: ruleDependency is "";
  begin
    if target in makeData.patternRules then
      for key ruleArrayIndex range makeData.patternRules[target]
          until patternRulePresent do
        for ruleDependency range makeData.patternRules[target][ruleArrayIndex].dependencies
            until patternRulePresent do
          patternRulePresent := ruleDependency = dependency;
        end for;
      end for;
    end if;
  end func;


(**
 *  Add the given ''rule'' as pattern rule to ''makeData''.
 *  @param makeData Destination where the ''rule'' is stored.
 *  @param rule Makefile pattern rule to be added to ''makeData''.
 *)
const proc: addPatternRule (inout makeDataType: makeData, in ruleType: rule) is func
  begin
    if rule.target in makeData.patternRules then
      makeData.patternRules[rule.target] &:= rule;
    else
      makeData.patternRules @:= [rule.target] [] (rule);
    end if;
  end func;


(**
 *  Add the given ''rule'' either as normal rule or pattern rule to ''makeData''.
 *  @param makeData Destination where the ''rule'' is stored.
 *  @param rule Makefile rule to be added to ''makeData''.
 *)
const proc: addRule (inout makeDataType: makeData, in ruleType: rule) is func
  begin
    if length(makeData.rules) = 0 then
      makeData.targetOfFirstRule := rule.target;
    end if;
    if pos(rule.target, '%') <> 0 then
      addPatternRule(makeData, rule);
    elsif rule.target in makeData.rules then
      if length(rule.commands) = 0 then
        makeData.rules[rule.target].dependencies &:= rule.dependencies;
      elsif length(makeData.rules[rule.target].commands) = 0 then
        makeData.rules[rule.target].dependencies &:= rule.dependencies;
        makeData.rules[rule.target].commands := rule.commands;
      else
        writeln(" *** Rule " <& rule.target <& " redefined.");
      end if;
    else
      makeData.rules @:= [rule.target] rule;
    end if;
  end func;


const proc: includeMakefile (inout makeDataType: makeData, in boolean: ignoreError) is forward;


const proc: readMakefile (inout makeDataType: makeData) is func
  local
    var file: makefile is STD_NULL;
    var string: name is "";
    var array string: alternateTargets is 0 times "";
    var string: data is "";
  begin
    makefile := makeData.makefile;
    makefile.bufferChar := getc(makefile);
    while makefile.bufferChar <> EOF do
      skipWhiteSpace(makefile);
      # writeln(literal(makefile.bufferChar));
      if makefile.bufferChar = '#' then
        skipLineComment(makefile);
        makefile.bufferChar := getc(makefile);
      elsif makefile.bufferChar = '!' then
        # nmake commands like if, include, endif, ...
        # currently ignored
        makefile.bufferChar := getc(makefile);
        name := lower(getWord(makefile));
        if name = "if" then
          execIf(makefile, makeData.macros);
        elsif name = "include" then
          includeMakefile(makeData, name = "-include");
          makefile := makeData.makefile;
        elsif name = "endif" then
          skipLineComment(makefile);
          makefile.bufferChar := getc(makefile);
        else
          writeln(" *** Unknown directive !" <& name);
          skipLineComment(makefile);
          makefile.bufferChar := getc(makefile);
        end if;
      elsif makefile.bufferChar in target_name_char then
        name := getMakeTarget(makefile);
        # writeln(name);
        skipSpaceOrTab(makefile);
        if name = "override" and makefile.bufferChar in target_name_char then
          name := getMakeTarget(makefile);
          # writeln("override " <& name);
          skipSpaceOrTab(makefile);
        end if;
        if makefile.bufferChar = '=' then
          makefile.bufferChar := getc(makefile);
          skipSpace(makefile);
          makeData.macros @:= [name] getMakeLine(makefile);
          # writeln(name <& " = " <& data);
          makefile.bufferChar := getc(makefile);
        elsif makefile.bufferChar = ':' then
          makefile.bufferChar := getc(makefile);
          if makefile.bufferChar = '=' then
            makefile.bufferChar := getc(makefile);
            skipSpace(makefile);
            data := applyMacros(makeData.macros, getMakeLine(makefile), FALSE);
            makeData.macros @:= [name] data;
            # writeln(name <& " := " <& data);
            makefile.bufferChar := getc(makefile);
          else
            name := applyMacros(makeData.macros, name, TRUE);
            addRule(makeData, readRule(makeData, name));
          end if;
        elsif makefile.bufferChar = '?' then
          makefile.bufferChar := getc(makefile);
          if makefile.bufferChar = '=' then
            makefile.bufferChar := getc(makefile);
            skipSpace(makefile);
            data := getMakeLine(makefile);
            if name not in makeData.macros and getenv(name) = "" then
              makeData.macros @:= [name] data;
            end if;
            # writeln(name <& " ?= " <& data);
            makefile.bufferChar := getc(makefile);
          end if;
        elsif makefile.bufferChar = '+' then
          makefile.bufferChar := getc(makefile);
          if makefile.bufferChar = '=' then
            makefile.bufferChar := getc(makefile);
            skipSpace(makefile);
            data := applyMacros(makeData.macros, getMakeLine(makefile), TRUE);
            if name in makeData.macros and makeData.macros[name] <> "" then
              makeData.macros @:= [name] makeData.macros[name] & " " & data;
            else
              makeData.macros @:= [name] data;
            end if;
            # writeln(name <& " += " <& data);
            makefile.bufferChar := getc(makefile);
          end if;
        elsif name = "ifeq" then
          execIfeq(makefile, makeData.macros, TRUE);
        elsif name = "ifneq" then
          execIfeq(makefile, makeData.macros, FALSE);
        elsif name = "ifdef" then
          execIfdef(makefile, makeData.macros, TRUE);
        elsif name = "ifndef" then
          execIfdef(makefile, makeData.macros, FALSE);
        elsif name = "else" then
          skipLine(makefile);
          makefile.bufferChar := getc(makefile);
          find_endif(makefile);
        elsif name = "endif" then
          skipLine(makefile);
          makefile.bufferChar := getc(makefile);
        elsif name = "include" or name = "-include" then
          includeMakefile(makeData, name = "-include");
          makefile := makeData.makefile;
        elsif name = ".SILENT" then
          makeData.inSilentMode := TRUE;
          skipLine(makefile);
          makefile.bufferChar := getc(makefile);
        else
          name := applyMacros(makeData.macros, name, TRUE);
          alternateTargets := 0 times "";
          while makefile.bufferChar in target_name_char do
            alternateTargets &:= getMakeTarget(makefile);
            skipSpace(makefile);
          end while;
          if makefile.bufferChar = ':' then
            makefile.bufferChar := getc(makefile);
            addRule(makeData, readRule(makeData, name));
          else
            writeln(" *** Illegal character " <& literal(makefile.bufferChar) <&
                    " after target or variable named " <& literal(name) <& ", ignored.");
            makefile.bufferChar := getc(makefile);
          end if;
        end if;
      elsif makefile.bufferChar <> EOF then
        writeln(" *** Illegal character " <& literal(makefile.bufferChar) <& ", ignored.");
        makefile.bufferChar := getc(makefile);
      end if;
    end while;
  end func;


const proc: includeMakefile (inout makeDataType: makeData, in boolean: ignoreError) is func
  local
    var string: includeFiles is "";
    var string: fileName is "";
    var file: makefile is STD_NULL;
    var file: surroundingFile is STD_NULL;
  begin
    skipSpace(makeData.makefile);
    includeFiles := applyMacros(makeData.macros, getMakeLine(makeData.makefile), TRUE);
    makeData.makefile.bufferChar := getc(makeData.makefile);
    while includeFiles <> "" do
      fileName := getWord(includeFiles);
      makefile := open(fileName, "r");
      if makefile = STD_NULL then
        if not ignoreError then
          writeln(" *** include file " <& fileName <& " not found.");
        end if;
      else
        # writeln("begin include " <& fileName);
        surroundingFile := makeData.makefile;
        makeData.makefile := makefile;
        readMakefile(makeData);
        # writeln("end include " <& fileName);
        close(makefile);
        makeData.makefile := surroundingFile;
      end if;
      skipWhiteSpace(includeFiles);
    end while;
  end func;


const proc: readMakefile (inout makeDataType: makeData, in string: fileName) is func
  local
    var file: makefile is STD_NULL;
    var file: surroundingFile is STD_NULL;
  begin
    makefile := open(fileName, "r");
    if makefile = STD_NULL then
      writeln(" *** Makefile " <& fileName <& " not found.");
    else
      # writeln("begin " <& fileName);
      surroundingFile := makeData.makefile;
      makeData.makefile := makefile;
      readMakefile(makeData);
      # writeln("end " <& fileName);
      close(makefile);
      makeData.makefile := surroundingFile;
    end if;
  end func;