(* Compiler Generator Coco/R, for Delphi
 * Copyright (c) 2020 Tomsk, Marat Shaimardanov
 *
 * This file is part of Compiler Generator Coco/R, for Delphi
 * is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This file 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this file. If not, see <https://www.gnu.org/licenses/>.
 *
 * If not otherwise stated, any source code generated by Coco/R (other than
 * Coco/R itself) does not fall under the GNU General Public License.
*)
unit Oz.Cocor.Options;

interface

uses
  System.Classes, System.SysUtils, System.Character, System.IOUtils,
  Oz.Cocor.Utils;

type

{$Region 'TOptions: Compilation settings'}

  TOptions = class
  type
    TSwitches = array [0..9] of Boolean;
  const
    Version = '1.0 (for Delphi)';
    ReleaseDate = '17 May 2020';
  var
    // name of the atg file (including path)
    srcName: string;
    // directory path of the atg file
    srcDir: string;
    // directory containing the frame files
    frameDir: string;
    // directory for generated files
    outDir: string;
    // namespaces for generated files
    namespace: string;
    // name of the trace file
    traceFileName: string;
    // emit #line pragmas for semantic actions in the generated parser
    emitLines: Boolean;
    // should coco generate a check for EOF at the end of Parser.Parse():
    checkEOF: Boolean;
    // debug and test switches
    ddt: TSwitches;
  private
    // trace options
    ddtString: string;
  public
    constructor Create;
    function GetVersion: string;
    procedure Help;
    // Get options from the command line
    procedure ParseCommandLine;
    procedure SetOption(const s: string);
    procedure SetDDT(const s: string);
  end;

{$EndRegion}

// Return current settings (sigleton)
function GetOptions: TOptions;

implementation

var FOptions: TOptions = nil;

function GetOptions: TOptions;
begin
  if FOptions = nil then
    FOptions := TOptions.Create;
  Result := FOptions;
end;

procedure FreeOptions;
begin
  FreeAndNil(FOptions);
end;

{$Region 'TOptions'}

constructor TOptions.Create;
begin
  emitLines := False;
end;

function TOptions.GetVersion: string;
begin
  Result := Format(
    'Coco/R - Compiler Compiler / Recursive Descent, V%s'#13#10 +
    'Delphi version by Marat Shaimardanov %s'#13#10,
    [Version, ReleaseDate]);
end;

procedure TOptions.Help;
begin
  WriteLn('Usage: Coco Grammar.ATG {Option}');
  WriteLn('Options:');
  WriteLn('  -frames    <frameFilesDirectory>');
  WriteLn('  -trace     <traceString>');
  WriteLn('  -o         <outputDirectory>');
  WriteLn('  -namespace <namespaceName>');
  WriteLn('  -lines');
  WriteLn('  -checkEOF');
  WriteLn('Valid characters in the trace string:');
  WriteLn('  A  trace automaton');
  WriteLn('  F  list first/follow sets');
  WriteLn('  G  print syntax graph');
  WriteLn('  I  trace computation of first sets');
  WriteLn('  J  list ANY and SYNC sets');
  WriteLn('  P  print statistics');
  WriteLn('  S  list symbol table');
  WriteLn('  X  list cross reference table');
  WriteLn('Scanner.frame and Parser.frame files needed in ATG directory');
  WriteLn('or in a directory specified in the -frames option.');
end;

procedure TOptions.ParseCommandLine;
var
  i: Integer;
  p: string;

  function GetParam: Boolean;
  begin
    Result := i < ParamCount;
    if Result then
    begin
      Inc(i);
      p := ParamStr(i).Trim;
    end;
  end;

begin
  i := 0;
  while GetParam do
  begin
    if (p = '-frames') and GetParam then
      frameDir := p
    else if (p = '-trace') and GetParam then
      ddtString := p
    else if (p = '-namespace') and GetParam then
      namespace := p
    else if (p = '-o') and GetParam then
      outDir := p
    else if (p = '-lines') then
      emitLines := True
    else if (p = '-checkEOF') then
      checkEOF := True
    else
      srcName := p;
  end;
  if outDir = '' then
    outDir := srcDir;
  if ddtString <> '' then
    SetDDT(ddtString);
end;

procedure TOptions.SetDDT(const s: string);
var
  su: string;
  ch: char;
begin
  su := s.ToUpper;
  for ch in su do
  begin
    if Between(ch, '0', '9') then
      ddt[Ord(ch) - Ord('0')] := true
    else
      case ch of
        'A' : ddt[0] := true;  // trace automaton
        'F' : ddt[1] := true;  // list first/follow sets
        'G' : ddt[2] := true;  // print syntax graph
        'I' : ddt[3] := true;  // trace computation of first sets
        'J' : ddt[4] := true;  // print ANY and SYNC sets
        'P' : ddt[8] := true;  // print statistics
        'S' : ddt[6] := true;  // list symbol table
        'X' : ddt[7] := true;  // list cross reference table
      end;
  end;
end;

procedure TOptions.SetOption(const s: string);
var
  name, value: string;
  option: TArray<string>;
begin
  option := s.Split(['=', ' '], 2);
  name := option[0];
  value := option[1];
  if '$checkEOF'.Equals(name) then
    checkEOF := 'true'.Equals(value);
end;

{$EndRegion}

initialization

finalization
  FreeOptions;

end.

