{

    Automatic XML-RPC wrapper generator
    Copyright (c) 2003 by
      Areca Systems GmbH / Sebastian Guenther, sg@freepascal.org

    See the file COPYING.FPC, included in this distribution,
    for details about the copyright.

    This program 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.
}


program MkXMLRPC;
uses SysUtils, Classes, PParser, PasTree, PasWrite;

resourcestring
  SCmdLineInvalidOption = 'Ignoring unknown option "%s"';
  SNoServerClassNameProvided =
    'No server class name provided (use --serverclass=<name>)';
  SNoUnitNameProvided =
    'No name for generated unit provided (use --unitname=<name>)';

type
  TParserEngine = class(TPasTreeContainer)
  protected
    Modules, UsedModules: TList;
    CurModule: TPasModule;
  public
    constructor Create;
    destructor Destroy; override;
    function CreateElement(AClass: TPTreeElement; const AName: String;
      AParent: TPasElement; AVisibility: TPasMemberVisibility;
      const ASourceFilename: String; ASourceLinenumber: Integer): TPasElement;
      override;
    function FindElement(const AName: String): TPasElement; override;
    function FindModule(const AName: String): TPasModule; override;
  end;

  TServerClass = class
    Element: TPasClassType;
    ImplName: String;
  end;

  TRPCList = class
    constructor Create;
    destructor Destroy; override;
    procedure AddServerClass(const AClassName: String);
    ServerClasses: TList;
    UsedModules: TStringList;
  end;

var
  Engine: TParserEngine;


constructor TParserEngine.Create;
begin
  inherited Create;
  Modules := TList.Create;
  UsedModules := TList.Create;
end;

destructor TParserEngine.Destroy;
begin
  UsedModules.Free;
  Modules.Free;
  inherited Destroy;
end;

function TParserEngine.CreateElement(AClass: TPTreeElement; const AName: String;
  AParent: TPasElement; AVisibility: TPasMemberVisibility;
  const ASourceFilename: String; ASourceLinenumber: Integer): TPasElement;
begin
  Result := AClass.Create(AName, AParent);
  Result.Visibility := AVisibility;
  if AClass.InheritsFrom(TPasModule) then
  begin
    Modules.Add(Result);
    CurModule := TPasModule(Result);
  end;
end;

function TParserEngine.FindElement(const AName: String): TPasElement;

  function FindInModule(AModule: TPasModule; const LocalName: String): TPasElement;
  var
    l: TList;
    i, j: Integer;
    Found: Boolean;
  begin
    l := AModule.InterfaceSection.Declarations;
    for i := 0 to l.Count - 1 do
    begin
      Result := TPasElement(l[i]);
      if CompareText(Result.Name, LocalName) = 0 then
      begin
        Found := False;
        for j := 0 to UsedModules.Count - 1 do
          if CompareText(TPasModule(UsedModules[j]).Name, AModule.Name) = 0 then
          begin
            Found := True;
            break;
          end;
        if not Found then
          UsedModules.Add(AModule);
        exit;
      end;
    end;
    Result := nil;
 end;

var
  i: Integer;
  //ModuleName, LocalName: String;
  Module: TPasElement;
begin
{!!!: Don't know if we ever will have to use the following:
  i := Pos('.', AName);
  if i <> 0 then
  begin
    WriteLn('Dot found in name: ', AName);
    Result := nil;
  end else
  begin}
    Result := FindInModule(CurModule, AName);
    if not Assigned(Result) then
      for i := CurModule.InterfaceSection.UsesList.Count - 1 downto 0 do
      begin
        Module := TPasElement(CurModule.InterfaceSection.UsesList[i]);
        if Module.ClassType = TPasModule then
        begin
          Result := FindInModule(TPasModule(Module), AName);
          if Assigned(Result) then
            exit;
        end;
      end;
  {end;}
end;

function TParserEngine.FindModule(const AName: String): TPasModule;
var
  i: Integer;
begin
  for i := Modules.Count - 1 downto 0 do
  begin
    Result := TPasModule(Modules[i]);
    if CompareText(Result.Name, AName) = 0 then
      exit;
  end;
  Result := nil;
end;


constructor TRPCList.Create;
begin
  ServerClasses := TList.Create;
  UsedModules := TStringList.Create;
end;

destructor TRPCList.Destroy;
var
  i: Integer;
begin
  UsedModules.Free;
  for i := 0 to ServerClasses.Count - 1 do
    TServerClass(ServerClasses[i]).Free;
  ServerClasses.Free;
end;

procedure TRPCList.AddServerClass(const AClassName: String);
var
  Element: TPasClassType;
  ServerClass: TServerClass;
begin
  Element := TPasClassType(Engine.FindElement(AClassName));
  if not Assigned(Element) then
  begin
    WriteLn(StdErr, 'Server class "', AClassName, '" not found!');
    Halt(3);
  end;
  if (not Element.InheritsFrom(TPasClassType)) or
    (Element.ObjKind <> okClass) then
  begin
    WriteLn('"', AClassName, '" is not a class!');
    Halt(4);
  end;
  ServerClass := TServerClass.Create;
  ServerClasses.Add(ServerClass);
  ServerClass.Element := Element;
  ServerClass.ImplName := Copy(Element.Name, 2, Length(Element.Name));
  UsedModules.Add(Element.GetModule.Name);
end;


var
  OutputFilename, UnitName: String;
  RPCList: TRPCList;

procedure WriteClassServerSource(ServerClass: TPasClassType;
  ImplementationSection: TPasSection; Method, ProcImpl: TPasProcedureImpl;
  const MethodPrefix: String; NestingLevel: Integer);

{ Method: Main server method
  ProcImpl: Current procedure (may be identical with Method) }

type
  TConversionInfo = record
    ConverterName, TypecastFunction: String;
    ArgIsParent: Boolean;
  end;

  function MakeStructConverter(AClass: TPasClassType;
    Referrer: TPasProcedureImpl): TPasProcedureImpl; forward;

  function MakeArrayConverter(Member, ArraySizeProp: TPasProperty;
    ProcessProc, Referrer: TPasProcedureImpl): TPasProcedureImpl; forward;

  function FindArraySizeProperty(AArrayProp: TPasProperty): TPasProperty;
  var
    i: Integer;
    Name: String;
  begin
    Name := Copy(AArrayProp.Name, 1, Length(AArrayProp.Name) - 1) + 'Count';
    for i := 0 to TPasClassType(AArrayProp.Parent).Members.Count - 1 do
    begin
      Result := TPasProperty(TPasClassType(AArrayProp.Parent).Members[i]);
      if (Result.ClassType = TPasProperty) and (Result.Visibility = visPublic)
        and (CompareStr(Result.Name, Name) = 0) then
        exit;
    end;

    Name := AArrayProp.Name + 'Count';
    for i := 0 to TPasClassType(AArrayProp.Parent).Members.Count - 1 do
    begin
      Result := TPasProperty(TPasClassType(AArrayProp.Parent).Members[i]);
      if (Result.ClassType = TPasProperty) and (Result.Visibility = visPublic)
        and (CompareStr(Result.Name, Name) = 0) then
        exit;
    end;
    Result := nil;
  end;

  function GetConversionInfo(Element: TPasElement;
    Referrer: TPasProcedureImpl): TConversionInfo;
  var
    s: String;
    ArraySizeProp: TPasProperty;
  begin
    FillChar(Result, SizeOf(Result), 0);
    Result.ArgIsParent := False;

    if Element.ClassType = TPasProperty then
    begin
      ArraySizeProp := FindArraySizeProperty(TPasProperty(Element));
      if Assigned(ArraySizeProp) then
      begin
        Result.ConverterName := MakeArrayConverter(TPasProperty(Element),
          ArraySizeProp, ProcImpl, Referrer).Name;
        Result.ArgIsParent := True;
        exit;
      end else
        Element := TPasProperty(Element).VarType;
    end;

    if Element.ClassType = TPasUnresolvedTypeRef then
    begin
      s := UpperCase(Element.Name);
      if (s = 'BYTE') or (s = 'SHORTINT') or (S = 'SMALLINT') or
        (s = 'INTEGER') or (s = 'LONGINT') or (s = 'CARDINAL') or
        (s = 'INT64') or (s = 'QUADWORD') then
        Result.ConverterName := 'AWriter.CreateIntValue'
      else if (s = 'BOOLEAN') or (s = 'WORDBOOL') or (s = 'LONGBOOL') then
        Result.ConverterName := 'AWriter.CreateBooleanValue'
      else if s = 'STRING' then
        Result.ConverterName := 'AWriter.CreateStringValue'
      else if (s = 'FLOAT') or (s = 'SINGLE') or (s = 'DOUBLE') or
        (s = 'EXTENDED') then
        Result.ConverterName := 'AWriter.CreateDoubleValue'
      else if s = 'TDATETIME' then
        Result.ConverterName := 'AWriter.CreateDateTimeValue';
    end else if Element.ClassType = TPasClassType then
      Result.ConverterName := MakeStructConverter(TPasClassType(Element), Referrer).Name
    else if Element.ClassType = TPasEnumType then
    begin
      Result.ConverterName := 'AWriter.CreateIntValue';
      Result.TypecastFunction := 'Ord';
    end;

    if Length(Result.ConverterName) = 0 then
      raise Exception.Create('Result type not supported: ' + Element.ClassName +
        ' ' + Element.Name);
  end;

  function MakeAccessor(ConversionInfo: TConversionInfo;
    const DataSource, ArrayIndex: String): String;
  begin
    Result := ConversionInfo.ConverterName + '(';
    if ConversionInfo.TypecastFunction <> '' then
      Result := Result + ConversionInfo.TypecastFunction + '(';
    Result := Result + DataSource;
    if ConversionInfo.TypecastFunction <> '' then
      Result := Result + ')';
    if ArrayIndex <> '' then
      Result := Result + '[' + ArrayIndex + ']';
    Result := Result + ')';
  end;

  function GetParseValueFnName(PasType: TPasType): String;
  var
    s: String;
  begin
    SetLength(Result, 0);
    if PasType.ClassType = TPasArgument then
    begin
      if TPasArgument(PasType).Access = argVar then
        raise Exception.Create('"var" arguments are not allowed');
      PasType := TPasArgument(PasType).ArgType;
    end;

    if PasType.ClassType = TPasUnresolvedTypeRef then
    begin
      s := UpperCase(PasType.Name);
      if (s = 'BYTE') or (s = 'SHORTINT') or (S = 'SMALLINT') or
        (s = 'INTEGER') or (s = 'LONGINT') or (s = 'CARDINAL') or
        (s = 'INT64') or (s = 'QUADWORD') then
        Result := 'Int'
      else if (s = 'BOOLEAN') or (s = 'WORDBOOL') or (s = 'LONGBOOL') then
        Result := 'Boolean'
      else if s = 'STRING' then
        Result := 'String'
      else if (s = 'FLOAT') or (s = 'SINGLE') or (s = 'DOUBLE') or
        (s = 'EXTENDED') then
        Result := 'Double'
      else if s = 'TDATETIME' then
        Result := 'DateTime';
    end;
    if Length(Result) = 0 then
      raise Exception.Create('Argument type not supported: ' +
        PasType.ClassName + ' ' + PasType.Name);
  end;

  function NeedLocalProc(const ProcName: String;
    Referrer: TPasProcedureImpl): TPasProcedureImpl;
  var
    i, j: Integer;
  begin
    for i := 0 to Method.Locals.Count - 1 do
    begin
      Result := TPasProcedureImpl(Method.Locals[i]);
      if Result.Name = ProcName then
      begin
        j := Method.Locals.IndexOf(Referrer);
        if (j >= 0) and (i >= j) then
        begin
          // Move existing converter to the top and exit
          Method.Locals.Delete(i);
          j := Method.Locals.IndexOf(ProcImpl);
          if j < 0 then
            j := 0;
          Method.Locals.Insert(j, Result);
        end;
        exit;
      end;
    end;
    Result := nil;
  end;

  function MakeStructConverter(AClass: TPasClassType;
    Referrer: TPasProcedureImpl): TPasProcedureImpl;
  var
    ConverterName, s: String;
    Commands: TPasImplCommands;
    i: Integer;
    LocalMember: TPasElement;
    ConversionInfo: TConversionInfo;
  begin
    ConverterName := 'Convert' + AClass.Name;
    Result := NeedLocalProc(ConverterName, Referrer);
    if Assigned(Result) then
      exit;

    Result := TPasProcedureImpl.Create(ConverterName, Method);
    i := Method.Locals.IndexOf(Referrer);
    if i < 0 then
      i := 0;
    Method.Locals.Insert(i, Result);
    Result.ProcType := TPasFunctionType.Create('', Result);
    Result.ProcType.CreateArgument('Inst', AClass.Name);
    TPasFunctionType(Result.ProcType).ResultEl :=
      TPasResultElement.Create('', Result);
    TPasFunctionType(Result.ProcType).ResultEl.ResultType :=
      TPasUnresolvedTypeRef.Create('TXMLRPCStruct', Result);

    Result.Body := TPasImplBlock.Create('', Result);
    Commands := Result.Body.AddCommands;
    Commands.Commands.Add('Result := AWriter.CreateStruct');
    for i := 0 to AClass.Members.Count - 1 do
    begin
      LocalMember := TPasElement(AClass.Members[i]);
      if LocalMember.ClassType = TPasProperty then
      begin
        ConversionInfo := GetConversionInfo(LocalMember, Result);
        if ConversionInfo.ArgIsParent then
          s := 'Inst'
        else
          s := 'Inst.' + LocalMember.Name;
        s := 'AWriter.AddStructMember(Result, ''' + LocalMember.Name + ''', ' +
          MakeAccessor(ConversionInfo, s, '') + ')';
        Commands.Commands.Add(s);
      end;
    end;
  end;

  function MakeArrayConverter(Member, ArraySizeProp: TPasProperty;
    ProcessProc, Referrer: TPasProcedureImpl): TPasProcedureImpl;
  var
    i: Integer;
    ConverterName, s: String;
    Commands: TPasImplCommands;
    VarMember: TPasVariable;
    ForLoop: TPasImplForLoop;
    ConversionInfo: TConversionInfo;
  begin
    ConverterName := 'Convert' + Member.Parent.Name + '_' + Member.Name;
    Result := NeedLocalProc(ConverterName, Referrer);
    if Assigned(Result) then
      exit;

    Result := TPasProcedureImpl.Create(ConverterName, Method);
    i := Method.Locals.IndexOf(Referrer);
    if i < 0 then
      i := 0;
    Method.Locals.Insert(i, Result);
    Result.ProcType := TPasFunctionType.Create('', Result);
    Result.ProcType.CreateArgument('Inst', Member.Parent.Name);
    TPasFunctionType(Result.ProcType).ResultEl :=
      TPasResultElement.Create('', Result);
    TPasFunctionType(Result.ProcType).ResultEl.ResultType :=
      TPasUnresolvedTypeRef.Create('TXMLRPCArray', Result);

    Result.Body := TPasImplBlock.Create('', Result);
    Commands := Result.Body.AddCommands;
    Commands.Commands.Add('Result := AWriter.CreateArray');

    VarMember := TPasVariable.Create('i', Result);
    Result.Locals.Add(VarMember);
    VarMember.VarType := TPasUnresolvedTypeRef.Create('Integer', VarMember);

    ForLoop := Result.Body.AddForLoop(TPasVariable.Create('i', Result),
      '0', MethodPrefix + ArraySizeProp.Name + ' - 1');
    ForLoop.Body := TPasImplCommand.Create('', ForLoop);
    ConversionInfo := GetConversionInfo(Member.VarType, Result);
    if ConversionInfo.ArgIsParent then
      s := 'Inst'
    else
      s := 'Inst.' + Member.Name + '[i]';
    s := 'AWriter.AddArrayElement(Result, ' +
      MakeAccessor(ConversionInfo, s, '') + ')';
    TPasImplCommand(ForLoop.Body).Command := s;
  end;

  function CreateDispatcher(VarType: TPasClassType;
    Referrer: TPasProcedureImpl): TPasProcedureImpl;
  var
    DispatcherName: String;
  begin
    DispatcherName := 'Dispatch' + VarType.Name;
    Result := NeedLocalProc(DispatcherName, Referrer);
    if Assigned(Result) then
      exit;

    // Create new dispatcher method
    Result := TPasProcedureImpl.Create(DispatcherName, Method);
    if ProcImpl = Method then
      Method.Locals.Insert(0, Result)
    else
      Method.Locals.Insert(Method.Locals.IndexOf(Referrer), Result);
    Result.ProcType := TPasProcedureType.Create('', Result);
    Result.ProcType.CreateArgument('Inst', VarType.Name);
    Result.ProcType.CreateArgument('Level', 'Integer');
    WriteClassServerSource(VarType,
      ImplementationSection, Method, Result, 'Inst.', NestingLevel + 1);
  end;


var
  IfElse, ParentIfElse: TPasImplIfElse;

  procedure CreateBranch(const MethodName: String);
  begin
    if Assigned(ParentIfElse) then
    begin
      IfElse := TPasImplIfElse.Create('', ParentIfElse);
      ParentIfElse.ElseBranch := IfElse;
    end else
    begin
      IfElse := TPasImplIfElse.Create('', ProcImpl.Body);
      ProcImpl.Body.Elements.Add(IfElse);
    end;
    ParentIfElse := IfElse;
    IfElse.Condition := 's = ''' + UpperCase(MethodName) + '''';
  end;

  procedure ProcessMethodCall(Member: TPasProcedure);

    function MakeProcArgs(Args: TList): String;
    var
      i: Integer;
    begin
      if (not Assigned(Args)) or (Args.Count = 0) then
        Result := ''
      else
      begin
        Result := '(';
        for i := 0 to Args.Count - 1 do
        begin
          if i > 0 then
            Result := Result + ', ';
          Result := Result + 'AParser.GetPrev' + GetParseValueFnName(TPasType(Args[i]));
        end;
        Result := Result + ')';
      end;
    end;

  var
    Commands: TPasImplCommands;
    s: String;
  begin
    CreateBranch(Member.Name);
    Commands := TPasImplCommands.Create('', IfElse);
    IfElse.IfBranch := Commands;

    if TPasProcedure(Member).ProcType.Args.Count > 0 then
      Commands.Commands.Add('AParser.ResetValueCursor');
    if Member.ClassType = TPasProcedure then
    begin
      Commands.Commands.Add(MethodPrefix + Member.Name +
        MakeProcArgs(TPasProcedure(Member).ProcType.Args));
      Commands.Commands.Add('AWriter.WriteResponse(nil)');
    end else
    begin
      // function
      s := MethodPrefix + Member.Name +
        MakeProcArgs(TPasProcedure(Member).ProcType.Args);
      Commands.Commands.Add('AWriter.WriteResponse(' +
        MakeAccessor(GetConversionInfo(TPasFunctionType(TPasFunction(Member).
          ProcType).ResultEl.ResultType, ProcImpl), s, '') + ')');
    end;
  end;

  procedure ProcessProperty(Member: TPasProperty);
  var
    LocalIfElse: TPasImplIfElse;
    IsArray, IsStruct: Boolean;
    s, s2: String;
    Commands: TPasImplCommands;
    Command: TPasImplCommand;
    ConversionInfo: TConversionInfo;
  begin
    if Member.ReadAccessorName <> '' then
    begin
      CreateBranch('Get' + Member.Name);

      IsArray := (Member.Args.Count = 1) and
        Assigned(FindArraySizeProperty(Member));
      IsStruct := Member.VarType.ClassType = TPasClassType;

      if IsStruct then
        s := CreateDispatcher(TPasClassType(Member.VarType), ProcImpl).Name +
          '(' + MethodPrefix + Member.Name;

      if NestingLevel = 0 then
        s2 := '1'
      else
        s2 := 'Level + 1';

      if IsArray or (IsStruct and (Member.Args.Count = 0)) then
      begin
        LocalIfElse := TPasImplIfElse.Create('', IfElse);
        IfElse.IfBranch := LocalIfElse;
        LocalIfElse.Condition := 'APath.Count <= ' + s2;
      end;

      if IsStruct then
        if IsArray then
        begin
          LocalIfElse.IfBranch := TPasImplCommand.Create('', LocalIfElse);
          TPasImplCommand(LocalIfElse.IfBranch).Command :=
            'AWriter.WriteResponse(' +
            MakeAccessor(GetConversionInfo(Member, ProcImpl),
              Copy(MethodPrefix, 1, Length(MethodPrefix) - 1), '') + ')';

          LocalIfElse.ElseBranch := TPasImplCommand.Create('', LocalIfElse);
          TPasImplCommand(LocalIfElse.ElseBranch).Command :=
            s + '[AParser.GetNext' +
            GetParseValueFnName(TPasArgument(Member.Args[0]).ArgType) + '], ' +
            s2 + ')';
        end else
        begin
          if Member.Args.Count = 0 then
          begin
            LocalIfElse.IfBranch := TPasImplCommand.Create('', LocalIfElse);
            TPasImplCommand(LocalIfElse.IfBranch).Command :=
               'AWriter.WriteResponse(' +
               MakeAccessor(GetConversionInfo(Member, ProcImpl),
                 MethodPrefix + Member.Name, '') + ')';
            LocalIfElse.ElseBranch := TPasImplCommand.Create('', LocalIfElse);
            TPasImplCommand(LocalIfElse.ElseBranch).Command := s + ', ' + s2 + ')';
          end else
          begin
            IfElse.IfBranch := TPasImplCommand.Create('', IfElse);
            TPasImplCommand(IfElse.IfBranch).Command := s + '[AParser.GetNext' +
            GetParseValueFnName(TPasArgument(Member.Args[0]).ArgType) + '], ' +
            s2 + ')';
          end;
        end
      else if IsArray then
      begin
        LocalIfElse.IfBranch := TPasImplCommand.Create('', LocalIfElse);
        TPasImplCommand(LocalIfElse.IfBranch).Command :=
           'AWriter.WriteResponse(' +
           MakeAccessor(GetConversionInfo(Member, ProcImpl),
             Copy(MethodPrefix, 1, Length(MethodPrefix) - 1), '') + ')';

        LocalIfElse.ElseBranch := TPasImplCommand.Create('', LocalIfElse);
        TPasImplCommand(LocalIfElse.ElseBranch).Command :=
          'AWriter.WriteResponse(' +
          MakeAccessor(GetConversionInfo(Member.VarType, ProcImpl),
            MethodPrefix + Member.Name, 'AParser.GetNext' +
            GetParseValueFnName(TPasArgument(Member.Args[0]).ArgType)) + ')';
      end else
      begin
        IfElse.IfBranch := TPasImplCommand.Create('', IfElse);
        TPasImplCommand(IfElse.IfBranch).Command := 'AWriter.WriteResponse(' +
          MakeAccessor(GetConversionInfo(Member.VarType, ProcImpl),
            MethodPrefix + Member.Name, '') + ')';
      end;
    end;

    if Member.WriteAccessorName <> '' then
    begin
      CreateBranch('Set' + Member.Name);
      Commands := TPasImplCommands.Create('', IfElse);
      IfElse.IfBranch := Commands;
      Commands.Commands.Add('// Not supported by mkxmlrpc yet');
    end;
  end;

var
  VarMember: TPasVariable;
  i: Integer;
  Command: TPasImplCommand;
  Member: TPasElement;
begin
  VarMember := TPasVariable.Create('s', ProcImpl);
  ProcImpl.Locals.Add(VarMember);
  VarMember.VarType := TPasUnresolvedTypeRef.Create('String', VarMember);
  ProcImpl.Body := TPasImplBlock.Create('', ProcImpl);
  if NestingLevel = 0 then
    ProcImpl.Body.AddCommand('s := APath[' + IntToStr(NestingLevel) + ']')
  else
    ProcImpl.Body.AddCommand('s := APath[Level]');
  ParentIfElse := nil;
  for i := 0 to ServerClass.Members.Count - 1 do
  begin
    Member := TPasElement(ServerClass.Members[i]);
    if Member.Visibility <> visPublic then
      continue;

    if (Member.ClassType = TPasProcedure) or (Member.ClassType = TPasFunction)
    then
      ProcessMethodCall(TPasProcedure(Member))
    else if Member.ClassType = TPasProperty then
      ProcessProperty(TPasProperty(Member))
    else if (Member.ClassType <> TPasConstructor) and
      (Member.ClassType <> TPasDestructor) then
      WriteLn('Warning: Unsupportet member type: ', Member.ElementTypeName);
  end;

  if Assigned(ParentIfElse) then
  begin
    Command := TPasImplCommand.Create('', ParentIfElse);
    ParentIfElse.ElseBranch := Command;
  end else
  begin
    Command := TPasImplCommand.Create('', ProcImpl.Body);
    ProcImpl.Body.Elements.Add(Command);
  end;
  Command.Command := 'AWriter.WriteFaultResponse(2, ''Invalid method name'')';
end;

procedure WriteFPCServerSource;
var
  i, j: Integer;
  Module: TPasModule;
  InterfaceSection, ImplementationSection: TPasSection;
  VarMember: TPasVariable;
  PropertyMember: TPasProperty;
  ProcMember: TPasProcedure;
  Arg: TPasArgument;
  ServerClass: TPasClassType;
  Stream: TStream;
  ProcImpl: TPasProcedureImpl;
  Found: Boolean;
begin
  Module := TPasModule.Create(UnitName, nil);
  try
    InterfaceSection := TPasSection.Create('', Module);
    Module.InterfaceSection := InterfaceSection;
    ImplementationSection := TPasSection.Create('', Module);
    Module.ImplementationSection := ImplementationSection;
    InterfaceSection.AddUnitToUsesList('Classes');
    InterfaceSection.AddUnitToUsesList('XMLRPC');
    for i := 0 to RPCList.UsedModules.Count - 1 do
      InterfaceSection.AddUnitToUsesList(RPCList.UsedModules[i]);

    for i := 0 to RPCList.ServerClasses.Count - 1 do
      with TServerClass(RPCList.ServerClasses[i]) do
      begin
        ServerClass := TPasClassType.Create('T' + ImplName + 'XMLRPCServlet',
          InterfaceSection);
        InterfaceSection.Declarations.Add(ServerClass);
        ServerClass.ObjKind := okClass;
        ServerClass.AncestorType :=
          TPasUnresolvedTypeRef.Create('TXMLRPCServlet', ServerClass);

        // Create private field which holds the implementation instance
        VarMember := TPasVariable.Create('F' + ImplName, ServerClass);
        VarMember.Visibility := visPrivate;
        VarMember.VarType := TPasUnresolvedTypeRef.Create(Element.Name, VarMember);
        ServerClass.Members.Add(VarMember);

        // Create dispatcher method
        ProcMember := TPasProcedure.Create('Dispatch', ServerClass);
        ProcMember.Visibility := visProtected;
        ProcMember.IsOverride := True;
        ProcMember.ProcType := TPasProcedureType.Create('', ProcMember);
        ProcMember.ProcType.CreateArgument('AParser', 'TXMLRPCParser').
          Visibility := visPublic;
        ProcMember.ProcType.CreateArgument('AWriter', 'TXMLRPCWriter').
          Visibility := visPublic;
        ProcMember.ProcType.CreateArgument('APath', 'TStrings').
          Visibility := visPublic;
        ServerClass.Members.Add(ProcMember);

        // Create published property for implementation instance
        PropertyMember := TPasProperty.Create(ImplName, ServerClass);
        PropertyMember.Visibility := visPublished;
        PropertyMember.VarType := VarMember.VarType;
        VarMember.VarType.AddRef;
        PropertyMember.ReadAccessorName := 'F' + ImplName;
        PropertyMember.WriteAccessorName := 'F' + ImplName;
        ServerClass.Members.Add(PropertyMember);

        // Create dispatcher implementation
        ProcImpl := TPasProcedureImpl.Create('Dispatch', ServerClass);
        ImplementationSection.Declarations.Add(ProcImpl);
        ProcImpl.ProcType := ProcMember.ProcType;
        ProcMember.ProcType.AddRef;
        ProcImpl.ProcType.AddRef;
        WriteClassServerSource(Element, ImplementationSection, ProcImpl,
          ProcImpl, ImplName + '.', 0);
      end;

    for i := 0 to Engine.UsedModules.Count - 1 do
    begin
      Found := False;
      for j := 0 to RPCList.UsedModules.Count - 1 do
        if CompareText(RPCList.UsedModules[j],
          TPasModule(Engine.UsedModules[i]).Name) = 0 then
        begin
          Found := True;
          break;
        end;
      if not Found then
        ImplementationSection.AddUnitToUsesList(
          TPasModule(Engine.UsedModules[i]).Name);
    end;

    Stream := THandleStream.Create(StdOutputHandle);
    try
      WritePasFile(Module, Stream);
    finally
      Stream.Free;
    end;

    Stream := TFileStream.Create(OutputFilename, fmCreate);
    try
      WritePasFile(Module, Stream);
    finally
      Stream.Free;
    end;
  finally
    Module.Free;
  end;
end;


var
  i, j: Integer;
  s, Cmd, Arg: String;
  InputFiles, ClassList: TStringList;
begin
  InputFiles := TStringList.Create;
  ClassList := TStringList.Create;
  try
    for i := 1 to ParamCount do
    begin
      s := ParamStr(i);
      j := Pos('=', s);
      if j > 0 then
      begin
        Cmd := Copy(s, 1, j - 1);
        Arg := Copy(s, j + 1, Length(s));
      end else
      begin
        Cmd := s;
        SetLength(Arg, 0);
      end;
      if (Cmd = '-i') or (Cmd = '--input') then
        InputFiles.Add(Arg)
      else if Cmd = '--output' then
        OutputFilename := Arg
      else if Cmd = '--unitname' then
        UnitName := Arg
      else if Cmd = '--serverclass' then
        ClassList.Add(Arg)
      else
        WriteLn(StdErr, Format(SCmdLineInvalidOption, [s]));
    end;

    if ClassList.Count = 0 then
    begin
      WriteLn(StdErr, SNoServerClassNameProvided);
      Halt(2);
    end;

    if UnitName = '' then
    begin
      WriteLn(StdErr, SNoUnitNameProvided);
      Halt(2);
    end;

    Engine := TParserEngine.Create;
    try
      // Engine.SetPackageName('XMLRPC');
      for i := 0 to InputFiles.Count - 1 do
        ParseSource(Engine, InputFiles[i], '', '');

      RPCList := TRPCList.Create;
      try
        for i := 0 to ClassList.Count - 1 do
          RPCList.AddServerClass(ClassList[i]);
        WriteFPCServerSource;
      finally
        RPCList.Free;
      end;
    finally
      Engine.Free;
    end;
  finally
    InputFiles.Free;
    ClassList.Free;
  end;
end.
