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

// Implements the dispatcher for the different parsers.
{$include 'config.inc'}

interface

uses
  nsystem, strutils, llstream, ast, astalgo, idents, scanner, options, msgs, 
  pnimsyn, pbraces, ptmplsyn, filters, rnimsyn;

type
  TFilterKind = (filtNone, filtTemplate, filtReplace, filtStrip);
  TParserKind = (skinStandard, skinBraces, skinEndX);
  
const
  parserNames: array [TParserKind] of string = ('standard', 'braces', 'endx');
  filterNames: array [TFilterKind] of string = ('none', 'stdtmpl', 'replace',
                                                'strip');
  
type
  TParsers = record
    skin: TParserKind;
    parser: TParser;
  end;

{@ignore} 
function ParseFile(const filename: string): PNode;
{@emit
function ParseFile(const filename: string): PNode; procvar;
}

procedure openParsers(var p: TParsers; const filename: string;
                     inputstream: PLLStream);
procedure closeParsers(var p: TParsers);
function parseAll(var p: TParsers): PNode;

function parseTopLevelStmt(var p: TParsers): PNode;
// implements an iterator. Returns the next top-level statement or nil if end
// of stream.


implementation

function ParseFile(const filename: string): PNode;
var
  p: TParsers;
  f: TBinaryFile;
begin
  if not OpenFile(f, filename) then begin
    rawMessage(errCannotOpenFile, filename);
    exit
  end;
  OpenParsers(p, filename, LLStreamOpen(f));
  result := ParseAll(p);
  CloseParsers(p);
end;

function parseAll(var p: TParsers): PNode;
begin
  case p.skin of
    skinStandard: result := pnimsyn.parseAll(p.parser);
    skinBraces: result := pbraces.parseAll(p.parser);
    skinEndX: InternalError('parser to implement');
    // skinEndX: result := pendx.parseAll(p.parser);
  end
end;

function parseTopLevelStmt(var p: TParsers): PNode;
begin
  case p.skin of
    skinStandard: result := pnimsyn.parseTopLevelStmt(p.parser);
    skinBraces: result := pbraces.parseTopLevelStmt(p.parser); 
    skinEndX: InternalError('parser to implement');
    //skinEndX: result := pendx.parseTopLevelStmt(p.parser);
  end
end;

function UTF8_BOM(const s: string): int;
begin
  if (s[strStart] = #239) and (s[strStart+1] = #187) 
  and (s[strStart+2] = #191) then result := 3
  else result := 0
end;

function containsShebang(const s: string; i: int): bool;
var
  j: int;
begin
  result := false;
  if (s[i] = '#') and (s[i+1] = '!') then begin
    j := i+2;
    while s[j] in WhiteSpace do inc(j);
    result := s[j] = '/'
  end
end;

function parsePipe(const filename: string; inputStream: PLLStream): PNode;
var
  line: string;
  s: PLLStream;
  i: int;
  q: TParser;
begin
  result := nil;
  s := LLStreamOpen(filename, fmRead);
  if s <> nil then begin
    line := LLStreamReadLine(s) {@ignore} + #0 {@emit};
    i := UTF8_Bom(line) + strStart;
    if containsShebang(line, i) then begin
      line := LLStreamReadLine(s) {@ignore} + #0 {@emit};
      i := strStart;
    end;
    if (line[i] = '#') and (line[i+1] = '!') then begin
      inc(i, 2);
      while line[i] in WhiteSpace do inc(i);
      OpenParser(q, filename, LLStreamOpen(ncopy(line, i)));
      result := pnimsyn.parseAll(q);
      CloseParser(q);
    end;
    LLStreamClose(s);
  end
end;

function getFilter(ident: PIdent): TFilterKind;
var
  i: TFilterKind;
begin
  for i := low(TFilterKind) to high(TFilterKind) do
    if IdentEq(ident, filterNames[i]) then begin
      result := i; exit
    end;
  result := filtNone
end;

function getParser(ident: PIdent): TParserKind;
var
  i: TParserKind;
begin
  for i := low(TParserKind) to high(TParserKind) do
    if IdentEq(ident, parserNames[i]) then begin
      result := i; exit
    end;
  rawMessage(errInvalidDirectiveX, ident.s);
end;

function getCallee(n: PNode): PIdent;
begin
  if (n.kind = nkCall) and (n.sons[0].kind = nkIdent) then
    result := n.sons[0].ident
  else if n.kind = nkIdent then result := n.ident
  else rawMessage(errXNotAllowedHere, renderTree(n));
end;

function applyFilter(var p: TParsers; n: PNode; const filename: string; 
                     input: PLLStream): PLLStream;
var
  ident: PIdent;
  f: TFilterKind;
begin
  ident := getCallee(n);
  f := getFilter(ident); 
  case f of
    filtNone: begin
      p.skin := getParser(ident);
      result := input
    end;
    filtTemplate: result := filterTmpl(input, filename, n);
    filtStrip: result := filterStrip(input, filename, n);
    filtReplace: result := filterReplace(input, filename, n);
  end;
  if f <> filtNone then begin
    if gVerbosity >= 2 then begin
      rawMessage(hintCodeBegin);
      messageOut(result.s);
      rawMessage(hintCodeEnd);
    end
  end
end;

function evalPipe(var p: TParsers; n: PNode; const filename: string;
                  start: PLLStream): PLLStream;
var 
  i: int;
begin
  result := start;
  if n = nil then exit;
  if (n.kind = nkInfix) and (n.sons[0].kind = nkIdent)
  and IdentEq(n.sons[0].ident, '|'+'') then begin
    for i := 1 to 2 do begin
      if n.sons[i].kind = nkInfix then
        result := evalPipe(p, n.sons[i], filename, result)
      else
        result := applyFilter(p, n.sons[i], filename, result)
    end
  end
  else if n.kind = nkStmtList then 
    result := evalPipe(p, n.sons[0], filename, result)
  else
    result := applyFilter(p, n, filename, result)
end;

procedure openParsers(var p: TParsers; const filename: string;
                     inputstream: PLLStream);
var
  pipe: PNode;
  s: PLLStream;
begin
  p.skin := skinStandard;
  pipe := parsePipe(filename, inputStream);
  if pipe <> nil then 
    s := evalPipe(p, pipe, filename, inputStream)
  else
    s := inputStream;
  case p.skin of
    skinStandard, skinBraces, skinEndX:
      pnimsyn.openParser(p.parser, filename, s);
  end
end;

procedure closeParsers(var p: TParsers);
begin
  pnimsyn.closeParser(p.parser);
end;

end.