summary refs log blame commit diff stats
path: root/nim/nimconf.pas
blob: 1a70abdbef3c897392abeb434455d9d9a6e1fd91 (plain) (tree)


















                                                                           

                                                                        


                                            

                                                          









































































                                                                           
                   



                                            
                                                                     











                                                                                
                                           










                                                 
                                           








































                                                                       
                                             










































































































                                                                           
                                                                              





                                    






                                                 
                    





                                  


                                           




                                                         
                                              

                                                           




                                                                           



















                                                                             








                                               
                                  
                              
                                                                                 






                                              
//
//
//           The Nimrod Compiler
//        (c) Copyright 2008 Andreas Rumpf
//
//    See the file "copying.txt", included in this
//    distribution, for details about the copyright.
//

unit nimconf;

// This module used to handle the reading of the config file. We now just
// read environment variables. This is easier to avoid bootstraping issues.

{$include 'config.inc'}

interface

uses
  nsystem, llstream, nversion, commands, nos, strutils, msgs, platform, 
  condsyms, scanner, options, idents, wordrecg;
  
procedure LoadConfig(const project: string);

procedure LoadSpecialConfig(const configfilename: string);

implementation


// ---------------- configuration file parser -----------------------------
// we use Nimrod's scanner here to safe space and work

procedure ppGetTok(var L: TLexer; tok: PToken);
begin
  // simple filter
  rawGetTok(L, tok^);
  while (tok.tokType = tkInd) or (tok.tokType = tkSad)
      or (tok.tokType = tkDed) or (tok.tokType = tkComment) do
    rawGetTok(L, tok^)
end;

// simple preprocessor:
function parseExpr(var L: TLexer; tok: PToken): bool; forward;

function parseAtom(var L: TLexer; tok: PToken): bool;
begin
  if tok.tokType = tkParLe then begin
    ppGetTok(L, tok);
    result := parseExpr(L, tok);
    if tok.tokType = tkParRi then ppGetTok(L, tok)
    else lexMessage(L, errTokenExpected, ''')''')
  end
  else if tok.ident.id = ord(wNot) then begin
    ppGetTok(L, tok);
    result := not parseAtom(L, tok)
  end
  else begin
    result := isDefined(tok.ident);
    //condsyms.listSymbols();
    //writeln(tok.ident.s + ' has the value: ', result);
    ppGetTok(L, tok)
  end;
end;

function parseAndExpr(var L: TLexer; tok: PToken): bool;
var
  b: bool;
begin
  result := parseAtom(L, tok);
  while tok.ident.id = ord(wAnd) do begin
    ppGetTok(L, tok); // skip "and"
    b := parseAtom(L, tok);
    result := result and b;
  end
end;

function parseExpr(var L: TLexer; tok: PToken): bool;
var
  b: bool;
begin
  result := parseAndExpr(L, tok);
  while tok.ident.id = ord(wOr) do begin
    ppGetTok(L, tok); // skip "or"
    b := parseAndExpr(L, tok);
    result := result or b;
  end
end;

function EvalppIf(var L: TLexer; tok: PToken): bool;
begin
  ppGetTok(L, tok);  // skip 'if' or 'elif'
  result := parseExpr(L, tok);
  if tok.tokType = tkColon then ppGetTok(L, tok)
  else lexMessage(L, errTokenExpected, ''':''')
end;

var
  condStack: array of bool;
  
{@emit
  condStack := @[];
}

procedure doEnd(var L: TLexer; tok: PToken);
begin
  if high(condStack) < 0 then lexMessage(L, errTokenExpected, '@if');
  ppGetTok(L, tok); // skip 'end'
  setLength(condStack, high(condStack))
end;

type
  TJumpDest = (jdEndif, jdElseEndif);

procedure jumpToDirective(var L: TLexer; tok: PToken; dest: TJumpDest); forward;

procedure doElse(var L: TLexer; tok: PToken);
begin
  if high(condStack) < 0 then
    lexMessage(L, errTokenExpected, '@if');
  ppGetTok(L, tok);
  if tok.tokType = tkColon then ppGetTok(L, tok);
  if condStack[high(condStack)] then
    jumpToDirective(L, tok, jdEndif)
end;

procedure doElif(var L: TLexer; tok: PToken);
var
  res: bool;
begin
  if high(condStack) < 0 then
    lexMessage(L, errTokenExpected, '@if');
  res := EvalppIf(L, tok);
  if condStack[high(condStack)] or not res then
    jumpToDirective(L, tok, jdElseEndif)
  else
    condStack[high(condStack)] := true
end;

procedure jumpToDirective(var L: TLexer; tok: PToken; dest: TJumpDest);
var
  nestedIfs: int;
begin
  nestedIfs := 0;
  while True do begin
    if (tok.ident <> nil) and (tok.ident.s = '@'+'') then begin
      ppGetTok(L, tok);
      case whichKeyword(tok.ident) of
        wIf: Inc(nestedIfs);
        wElse: begin
          if (dest = jdElseEndif) and (nestedIfs = 0) then begin
            doElse(L, tok);
            break
          end
        end;
        wElif: begin
          if (dest = jdElseEndif) and (nestedIfs = 0) then begin
            doElif(L, tok);
            break
          end
        end;
        wEnd: begin
          if nestedIfs = 0 then begin
            doEnd(L, tok);
            break
          end;
          if nestedIfs > 0 then Dec(nestedIfs)
        end;
        else begin end;
      end;
      ppGetTok(L, tok)
    end
    else if tok.tokType = tkEof then
      lexMessage(L, errTokenExpected, '@end')
    else
      ppGetTok(L, tok)
  end
end;

procedure parseDirective(var L: TLexer; tok: PToken);
var
  res: bool;
  key: string;
begin
  ppGetTok(L, tok); // skip @
  case whichKeyword(tok.ident) of
    wIf: begin
      setLength(condStack, length(condStack)+1);
      res := EvalppIf(L, tok);
      condStack[high(condStack)] := res;
      if not res then // jump to "else", "elif" or "endif"
        jumpToDirective(L, tok, jdElseEndif)
    end;
    wElif: doElif(L, tok);
    wElse: doElse(L, tok);
    wEnd:  doEnd(L, tok);
    wWrite: begin
      ppGetTok(L, tok);
      msgs.MessageOut(tokToStr(tok));
      ppGetTok(L, tok)
    end;
    wPutEnv: begin
      ppGetTok(L, tok);
      key := tokToStr(tok);
      ppGetTok(L, tok);
      nos.putEnv(key, tokToStr(tok));
      ppGetTok(L, tok)
    end;
    wPrependEnv: begin
      ppGetTok(L, tok);
      key := tokToStr(tok);
      ppGetTok(L, tok);
      nos.putEnv(key, tokToStr(tok) +{&} nos.getenv(key));
      ppGetTok(L, tok)
    end;
    wAppendenv: begin
      ppGetTok(L, tok);
      key := tokToStr(tok);
      ppGetTok(L, tok);
      nos.putEnv(key, nos.getenv(key) +{&} tokToStr(tok));
      ppGetTok(L, tok)
    end
    else
      lexMessage(L, errUnknownDirective, tokToStr(tok))
  end
end;

procedure confTok(var L: TLexer; tok: PToken);
begin
  ppGetTok(L, tok);
  while (tok.ident <> nil) and (tok.ident.s = '@'+'') do
    parseDirective(L, tok)
  // else: give the token to the parser
end;

// ----------- end of preprocessor ----------------------------------------

procedure checkSymbol(const L: TLexer; tok: PToken);
begin
  if not (tok.tokType in [tkSymbol..pred(tkIntLit),
                          tkStrLit..tkTripleStrLit]) then
    lexMessage(L, errIdentifierExpected, tokToStr(tok))
end;

procedure parseAssignment(var L: TLexer; tok: PToken);
var
  s, val: string;
  info: TLineInfo;
begin
  if (tok.ident.id = getIdent('-'+'').id)
  or (tok.ident.id = getIdent('--').id) then
    confTok(L, tok); // skip unnecessary prefix
  info := getLineInfo(L); // safe for later in case of an error
  checkSymbol(L, tok);
  s := tokToStr(tok);
  confTok(L, tok); // skip symbol
  val := '';
  while tok.tokType = tkDot do begin
    addChar(s, '.');
    confTok(L, tok);
    checkSymbol(L, tok);
    s := s +{&} tokToStr(tok);
    confTok(L, tok)
  end;
  if tok.tokType = tkBracketLe then begin
    // BUGFIX: val, not s!
    // BUGFIX: do not copy '['!
    confTok(L, tok);
    checkSymbol(L, tok);
    val := val +{&} tokToStr(tok);
    confTok(L, tok);
    if tok.tokType = tkBracketRi then confTok(L, tok)
    else lexMessage(L, errTokenExpected, ''']''');
    addChar(val, ']');
  end;
  if (tok.tokType = tkColon) or (tok.tokType = tkEquals) then begin
    if length(val) > 0 then addChar(val, ':'); // BUGFIX
    confTok(L, tok); // skip ':' or '='
    checkSymbol(L, tok);
    val := val +{&} tokToStr(tok);
    confTok(L, tok); // skip symbol
    while (tok.ident <> nil) and (tok.ident.id = getIdent('&'+'').id) do begin
      confTok(L, tok);
      checkSymbol(L, tok);
      val := val +{&} tokToStr(tok);
      confTok(L, tok)
    end
  end;
  processSwitch(s, val, passPP, info)
end;

procedure readConfigFile(const filename: string);
var
  L: TLexer;
  tok: PToken;
  stream: PLLStream;
begin
  new(tok);
{@ignore}
  fillChar(tok^, sizeof(tok^), 0);
  fillChar(L, sizeof(L), 0);
{@emit}
  stream := LLStreamOpen(filename, fmRead);
  if stream <> nil then begin
    openLexer(L, filename, stream);
    tok.tokType := tkEof; // to avoid a pointless warning
    confTok(L, tok); // read in the first token
    while tok.tokType <> tkEof do
      parseAssignment(L, tok);
    if length(condStack) > 0 then
      lexMessage(L, errTokenExpected, '@end');
    closeLexer(L);
    if gVerbosity >= 1 then rawMessage(hintConf, filename);
  end
end;

// ------------------------------------------------------------------------

function getConfigPath(const filename: string): string;
begin
  // try local configuration file:
  result := joinPath(getConfigDir(), filename);
  if not ExistsFile(result) then begin
    // try standard configuration file (installation did not distribute files
    // the UNIX way)
    result := joinPath([getPrefixDir(), 'config', filename]);
    if not ExistsFile(result) then begin
      result := '/etc/' +{&} filename    
    end
  end
end;

procedure LoadSpecialConfig(const configfilename: string);
begin
  if not (optSkipConfigFile in gGlobalOptions) then 
    readConfigFile(getConfigPath(configfilename));
end;

procedure LoadConfig(const project: string);
var
  conffile: string;
begin
  // set default value (can be overwritten):
  if libpath = '' then
    // choose default libpath:
    libpath := joinPath(getPrefixDir(), 'lib');
  // read default config file:
  LoadSpecialConfig('nimrod.cfg');
  // read project config file:
  if not (optSkipProjConfigFile in gGlobalOptions) and (project <> '') then begin
    conffile := changeFileExt(project, 'cfg');
    if existsFile(conffile) then
      readConfigFile(conffile)
  end
end;

end.