Explorar o código

Merge branch source:main into main

Curtis Hamilton hai 1 mes
pai
achega
b2cb32e4f5
Modificáronse 40 ficheiros con 5312 adicións e 468 borrados
  1. 2 2
      compiler/aasmtai.pas
  2. 2 2
      compiler/fppu.pas
  3. 27 13
      compiler/htypechk.pas
  4. 1 0
      compiler/ncal.pas
  5. 1 1
      compiler/ncgrtti.pas
  6. 12 1
      compiler/pgenutil.pas
  7. 3 3
      compiler/pmodules.pas
  8. 6 2
      compiler/ptype.pas
  9. 11 0
      compiler/riscv/agrvgas.pas
  10. 1 1
      compiler/riscv/cpubase.pas
  11. 1 1
      compiler/riscv/itcpugas.pas
  12. 8 5
      packages/fcl-base/src/fpobserver.pp
  13. 58 40
      packages/fcl-passrc/src/pastree.pp
  14. 125 53
      packages/fcl-passrc/src/paswrite.pp
  15. 2 2
      packages/fcl-syntax/src/syntax.pascal.pp
  16. 834 0
      packages/vcl-compat/src/system.json.types.pp
  17. 2 0
      packages/vcl-compat/src/system.regularexpressionscore.pp
  18. 1 1
      packages/vcl-compat/tests/testcompat.lpr
  19. 843 0
      packages/vcl-compat/tests/utcjsontypes.pas
  20. 5 2
      packages/wasm-utils/src/wasm.pcrebridge.pas
  21. 9 1
      rtl/objpas/classes/classes.inc
  22. 117 293
      tests/test/units/linux/thwprobe.pp
  23. 19 0
      tests/webtbs/tw41503.pp
  24. 13 0
      tests/webtbs/tw41506a.pp
  25. 20 0
      tests/webtbs/tw41506b.pp
  26. 65 0
      tests/webtbs/tw41516a.pp
  27. 65 0
      tests/webtbs/tw41516b.pp
  28. 116 0
      tests/webtbs/tw41526.pp
  29. 11 0
      tests/webtbs/uw41506.pp
  30. 2 6
      utils/fpdoc/dglobals.pp
  31. 124 17
      utils/fpdoc/dw_basehtml.pp
  32. 2405 0
      utils/fpdoc/dw_newhtml.pp
  33. 146 18
      utils/fpdoc/dwriter.pp
  34. 8 1
      utils/fpdoc/examples/simple/testunit.pp
  35. 12 1
      utils/fpdoc/examples/simple/testunit.xml
  36. 6 2
      utils/fpdoc/fpdoc.lpi
  37. 1 0
      utils/fpdoc/fpdoc.pp
  38. 223 0
      utils/fpdoc/fpdocs.css
  39. 3 0
      utils/fpdoc/fpdocstrs.pp
  40. 2 0
      utils/fpdoc/fpmake.pp

+ 2 - 2
compiler/aasmtai.pas

@@ -308,11 +308,11 @@ interface
        ,top_asmlist
        ,top_callingconvention
 {$endif llvm}
-{$if defined(riscv32) or defined(riscv64)}
+{$if defined(riscv)}
        ,top_fenceflags
        ,top_roundingmode
        ,top_realconst
-{$endif defined(riscv32) or defined(riscv64)}
+{$endif defined(riscv)}
 {$ifdef wasm}
        ,top_functype
        ,top_single

+ 2 - 2
compiler/fppu.pas

@@ -2030,7 +2030,7 @@ var
          internalerror(200208187);
         deflist.count:=ppufile.header.deflistsize;
         symlist.count:=ppufile.header.symlistsize;
-        globalsymtable:=tglobalsymtable.create(modulename^,moduleid);
+        globalsymtable:=tglobalsymtable.create(realmodulename^,moduleid);
         tstoredsymtable(globalsymtable).ppuload(ppufile);
 
         if ppufile.readentry<>ibexportedmacros then
@@ -2082,7 +2082,7 @@ var
         { load implementation symtable }
         if mf_local_symtable in moduleflags then
           begin
-            localsymtable:=tstaticsymtable.create(modulename^,moduleid);
+            localsymtable:=tstaticsymtable.create(realmodulename^,moduleid);
             tstaticsymtable(localsymtable).ppuload(ppufile);
           end;
 

+ 27 - 13
compiler/htypechk.pas

@@ -2921,9 +2921,12 @@ implementation
            while (paraidx>=0) and (vo_is_hidden_para in tparavarsym(hp^.data.paras[paraidx]).varoptions) do
              dec(paraidx);
            pt:=tcallparanode(FParaNode);
-           while assigned(pt) and (paraidx>=0) do
+           while assigned(pt) and ((paraidx>=0) or (po_varargs in hp^.data.procoptions)) do
             begin
-              currpara:=tparavarsym(hp^.data.paras[paraidx]);
+              if paraidx<0 then
+                currpara:=nil
+              else
+                currpara:=tparavarsym(hp^.data.paras[paraidx]);
               { currpt can be changed from loadn to calln when a procvar
                 is passed. This is to prevent that the change is permanent }
               currpt:=pt;
@@ -2933,7 +2936,10 @@ implementation
               { retrieve current parameter definitions to compares }
               eq:=te_incompatible;
               def_from:=currpt.resultdef;
-              def_to:=currpara.vardef;
+              if assigned(currpara) then
+                def_to:=currpara.vardef
+              else
+                def_to:=nil;
               if not(assigned(def_from)) then
                internalerror(200212091);
               if not(
@@ -2944,7 +2950,8 @@ implementation
                internalerror(200212092);
 
               { Convert tp procvars when not expecting a procvar }
-             if (currpt.left.resultdef.typ=procvardef) and
+             if assigned(def_to) and
+                (currpt.left.resultdef.typ=procvardef) and
                 not(def_to.typ in [procvardef,formaldef]) and
                 { if it doesn't require any parameters }
                 (tprocvardef(currpt.left.resultdef).minparacount=0) and
@@ -2968,7 +2975,8 @@ implementation
                returns a procdef we need to find the correct overloaded
                procdef that matches the expected procvar. The loadnode
                temporary returned the first procdef (PFV) }
-             if (
+             if assigned(def_to) and
+                (
                    (def_to.typ=procvardef) or
                    is_funcref(def_to)
                 ) and
@@ -2989,7 +2997,8 @@ implementation
 
              { same as above, but for the case that we have a proc-2-procvar
                conversion together with a load }
-             if (
+             if assigned(def_to) and
+                (
                    (def_to.typ=procvardef) or
                    is_funcref(def_to)
                 ) and
@@ -3026,7 +3035,8 @@ implementation
               else
               { for value and const parameters check if a integer is constant or
                 included in other integer -> equal and calc ordinal_distance }
-               if not(currpara.varspez in [vs_var,vs_out]) and
+               if assigned(currpara) and
+                  not(currpara.varspez in [vs_var,vs_out]) and
                   is_integer(def_from) and
                   is_integer(def_to) and
                   is_in_limit(def_from,def_to) then
@@ -3043,7 +3053,8 @@ implementation
               else
               { for value and const parameters check precision of real, give
                 penalty for loosing of precision. var and out parameters must match exactly }
-               if not(currpara.varspez in [vs_var,vs_out]) and
+               if assigned(currpara) and
+                  not(currpara.varspez in [vs_var,vs_out]) and
                   is_real_or_cextended(def_from) and
                   is_real_or_cextended(def_to) then
                  begin
@@ -3057,7 +3068,9 @@ implementation
               else
               { related object parameters also need to determine the distance between the current
                 object and the object we are comparing with. var and out parameters must match exactly }
-               if not(currpara.varspez in [vs_var,vs_out]) and
+               if assigned(currpara) and
+                  assigned(def_to) and
+                  not(currpara.varspez in [vs_var,vs_out]) and
                   (def_from.typ=objectdef) and
                   (def_to.typ=objectdef) and
                   (tobjectdef(def_from).objecttype=tobjectdef(def_to).objecttype) and
@@ -3080,7 +3093,7 @@ implementation
                  it has too little information. So we do explicitly a detailed comparisation,
                  see also bug #11288 (FK)
                }
-               else if (def_to.typ=setdef) and is_array_constructor(currpt.left.resultdef) then
+               else if assigned(def_to) and (def_to.typ=setdef) and is_array_constructor(currpt.left.resultdef) then
                  begin
                    n:=currpt.left.getcopy;
                    arrayconstructor_to_set(n);
@@ -3160,8 +3173,8 @@ implementation
               if (pt<>currpt) and (eq=te_exact) then
                 eq:=te_equal;
               { if var or out parameter type but paranode not is_valid_for_var }
-              if check_valid_var and (currpara.varspez in [vs_var,vs_out]) and not valid_for_var(currpt.left,false)
-                 and (def_to.typ<>formaldef) and not is_open_array(def_to) then
+              if check_valid_var and assigned(currpara) and (currpara.varspez in [vs_var,vs_out]) and not valid_for_var(currpt.left,false)
+                 and assigned(def_to) and (def_to.typ<>formaldef) and not is_open_array(def_to) then
                 eq:=te_incompatible;
 
               { increase correct counter }
@@ -3182,7 +3195,8 @@ implementation
 
 {$ifdef EXTDEBUG}
               { store equal in node tree for dump }
-              currpara.eqval:=eq;
+              if assigned(currpara) then
+                currpara.eqval:=eq;
 {$endif EXTDEBUG}
 
               { maybe release temp currpt }

+ 1 - 0
compiler/ncal.pas

@@ -4527,6 +4527,7 @@ implementation
 
            { Change loading of array of const to varargs }
            if assigned(left) and
+              (procdefinition.paras.count>0) and
               is_array_of_const(tparavarsym(procdefinition.paras[procdefinition.paras.count-1]).vardef) and
               (procdefinition.proccalloption in cdecl_pocalls) then
              convert_carg_array_of_const;

+ 1 - 1
compiler/ncgrtti.pas

@@ -652,7 +652,7 @@ implementation
            def:=tarraydef(def).elementdef;
          { name }
          if assigned(def.typesym) then
-           tcb.emit_shortstring_const(ttypesym(def.typesym).realname)
+           tcb.emit_shortstring_const(ttypesym(def.typesym).prettyname)
          else
            tcb.emit_shortstring_const('');
       end;

+ 12 - 1
compiler/pgenutil.pas

@@ -1528,7 +1528,7 @@ uses
           begin
             if not assigned(symowner) then
               internalerror(2022102101);
-            if not (symowner.symtabletype in [globalsymtable,recordsymtable]) then
+            if not (symowner.symtabletype in [globalsymtable,staticsymtable]) then
               internalerror(2022102102);
             hmodule:=find_module_from_symtable(symowner);
             if not assigned(hmodule) then
@@ -2777,6 +2777,7 @@ uses
       hmodule : tmodule;
       unitsyms : TFPHashObjectList;
       sym : tsym;
+      symtable : tsymtable;
       i : Integer;
       n : string;
 
@@ -2855,10 +2856,20 @@ uses
       unitsyms.free;
       if assigned(hmodule.globalsymtable) then
         symtablestack.push(hmodule.globalsymtable);
+      symtable:=genericdef.owner;
       { push the localsymtable if needed }
       if ((hmodule<>current_module) or not current_module.in_interface)
           and assigned(hmodule.localsymtable) then
         symtablestack.push(hmodule.localsymtable);
+      { also push the symtables of all owning types }
+      while assigned(symtable) and (symtable.symtabletype in [objectsymtable,recordsymtable]) do
+        begin
+          symtablestack.push(symtable);
+          if assigned(symtable.defowner) then
+            symtable:=symtable.defowner.owner
+          else
+            symtable:=nil;
+        end;
     end;
 
     procedure specialization_done(var state: tspecializationstate);

+ 3 - 3
compiler/pmodules.pas

@@ -1317,7 +1317,7 @@ type
         parse_only:=false;
 
         { create static symbol table }
-        curr.localsymtable:=tstaticsymtable.create(curr.modulename^,curr.moduleid);
+        curr.localsymtable:=tstaticsymtable.create(curr.realmodulename^,curr.moduleid);
 
 
         { Insert _GLOBAL_OFFSET_TABLE_ symbol if system uses it }
@@ -1976,7 +1976,7 @@ type
 
          { insert after the unit symbol tables the static symbol table }
          { of the program                                             }
-         curr.localsymtable:=tstaticsymtable.create(curr.modulename^,curr.moduleid);
+         curr.localsymtable:=tstaticsymtable.create(curr.realmodulename^,curr.moduleid);
 
          { ensure that no packages are picked up from the options }
          packagelist.clear;
@@ -2945,7 +2945,7 @@ type
 
          { insert after the unit symbol tables the static symbol table
            of the program                                              }
-         curr.localsymtable:=tstaticsymtable.create(curr.modulename^,curr.moduleid);
+         curr.localsymtable:=tstaticsymtable.create(curr.realmodulename^,curr.moduleid);
 
          { load system unit }
          load_ok:=loadsystemunit(curr);

+ 6 - 2
compiler/ptype.pas

@@ -295,7 +295,11 @@ implementation
                      symtablestack.pop(tabstractrecorddef(def).symtable);
                      symtablestack.free;
                      symtablestack:=oldsymtablestack;
-                     if isspecialize then
+                     if isspecialize or
+                         (
+                           (m_delphi in current_settings.modeswitches) and
+                           (token=_LSHARPBRACKET)
+                         ) then
                        begin
                          if not allowspecialization then
                            Message(parser_e_no_local_para_def);
@@ -335,7 +339,7 @@ implementation
                      structdefstack.add(structdef);
                      structdef:=tabstractrecorddef(structdef.owner.defowner);
                    end;
-                 parse_nested_types(def,isfowarddef,false,structdefstack);
+                 parse_nested_types(def,isfowarddef,true,structdefstack);
                  structdefstack.free;
                  result:=true;
                  exit;

+ 11 - 0
compiler/riscv/agrvgas.pas

@@ -180,6 +180,17 @@ unit agrvgas;
           end;
         top_roundingmode:
           getopstr:=roundingmode2str[o.roundingmode];
+        top_realconst:
+          case o.special_value of
+            ARSV_None:
+              str(o.val_real,getopstr);
+            ARSV_Nan:
+              getopstr:='nan';
+            ARSV_Min:
+              getopstr:='min';
+            ARSV_Inf:
+              getopstr:='inf';
+          end;
         else
           internalerror(2002070604);
       end;

+ 1 - 1
compiler/riscv/cpubase.pas

@@ -159,7 +159,7 @@ uses
 {$endif RISCV64}
 
         { Zfa extension }
-        A_FLI_S,
+        A_FLI_S,A_FLI_D,A_FLI_Q,A_FLI_H,
         A_FMINM_S,A_FMAXM_S,A_FMINM_D,A_FMAXM_D,A_FMINM_H,A_FMAXM_H,A_FMINM_Q,A_FMAXM_Q,
         A_FROUND_S,A_FROUNDNX_S,A_FROUND_D,A_FROUNDNX_D,A_FROUND_H,A_FROUNDNX_H,A_FROUND_Q,A_FROUNDNX_Q,
         A_FCVTMOD_W_D,

+ 1 - 1
compiler/riscv/itcpugas.pas

@@ -149,7 +149,7 @@ unit itcpugas;
 {$endif RISCV64}
 
         { Zfa extension }
-        'fli.s',
+        'fli.s','fli.d','fli.q','fli.h',
         'fminm.s','fmaxm.s','fminm.d','fmaxm.d','fminm.h','fmaxm.h','fminm.q','fmaxm.q',
         'fround.s','froundnx.s','fround.d','froundnx.d','fround.h','froundnx.h','fround.q','froundnx.q',
         'fcvtmod.w.d',

+ 8 - 5
packages/fcl-base/src/fpobserver.pp

@@ -151,11 +151,11 @@ Type
   { General-purpose of Mediating views. Can be used on any form/component }
 
   TComponentMediator = Class(TBaseMediator)
+  Private
     FViewComponent : TComponent;
   Protected
     function  GetView : TObject; override;
     procedure SetComponent(const AValue: TComponent);
-  Public
     procedure Notification(AComponent: TComponent;  Operation: TOperation); override;
   Published
     // General component which can be set in Object Inspector
@@ -390,9 +390,10 @@ implementation
 
 
 Resourcestring
-  SErrNotObserver = 'Instance of class %s is not an observer.';
-  SErrInvalidPropertyName = '%s is not a valid published property of class %s';
-  SErrObjectCannotBeObserved = 'Cannot observe an instance of class %d';
+  SErrNotObserver           = 'Instance of class %s is not an observer.';
+  SErrDuplicateObserver     = 'Cannot add an instance of class %s twice as observer';
+  SErrInvalidPropertyName   = '%s is not a valid published property of class %s';
+  SErrObjectCannotBeObserved = 'Cannot observe an instance of class %s';
   sErrInvalidFieldName      = 'No fieldname specified for column %d';
   sErrInvalidAlignmentChar  = 'Invalid alignment character "%s" specified for column %d';
   sErrInvalidWidthSpecifier = 'Invalid with "%s" specified for column %d';
@@ -492,7 +493,9 @@ begin
   If Not AObserver.GetInterface(SGUIDObserver,I) then
     Raise EObserver.CreateFmt(SErrNotObserver,[AObserver.ClassName]);
   If not Assigned(FObservers) then
-    FObservers:=TFPList.Create;
+    FObservers:=TFPList.Create
+  else if FObservers.IndexOf(aObserver)<>-1 then
+    Raise EObserver.CreateFmt(SErrDuplicateObserver,[AObserver.ClassName]);
   FObservers.Add(I);
 end;
 

+ 58 - 40
packages/fcl-passrc/src/pastree.pp

@@ -810,7 +810,7 @@ type
 
   TPasRecordType = class(TPasMembersType)
   private
-    procedure GetMembers(S: TStrings);
+    procedure GetMembers(S: TStrings; aSkipSection: Boolean=false);
   public
     constructor Create(const AName: TPasTreeString; AParent: TPasElement); override;
     destructor Destroy; override;
@@ -1166,7 +1166,8 @@ type
     procedure FreeChildren(Prepare: boolean); override;
     function ElementTypeName: TPasTreeString; override;
     function TypeName: TPasTreeString; override;
-    function GetDeclaration(full: Boolean): TPasTreeString; override;
+    function GetDeclaration(full: Boolean): TPasTreeString; override; overload;
+    function GetDeclaration(full, AddArgs, AddModifiers, AddParent: Boolean): TPasTreeString; virtual; overload;
     procedure GetModifiers(List: TStrings);
     procedure ForEachCall(const aMethodCall: TOnForEachPasElement;
       const Arg: Pointer); override;
@@ -1268,7 +1269,7 @@ type
     function ElementTypeName: TPasTreeString; override;
     function TypeName: TPasTreeString; override;
     function GetProcTypeEnum: TProcType; override;
-    function GetDeclaration (full : boolean) : TPasTreeString; override;
+    function GetDeclaration(full, AddArgs, AddModifiers, AddParent: Boolean): TPasTreeString; override;
     Property OperatorType : TOperatorType Read FOperatorType Write FOperatorType;
     // True if the declaration was using a token instead of an identifier
     Property TokenBased : Boolean Read FTokenBased Write FTokenBased;
@@ -2971,7 +2972,7 @@ begin
     Result:=Pred(Result);
 end;
 
-Function TPasOperator.NameSuffix : TPasTreeString;
+function TPasOperator.NameSuffix: TPasTreeString;
 
 Var
   I : Integer;
@@ -3514,7 +3515,7 @@ begin
     Result:=Result+': ('+sLineBreak;
     S:=TStringList.Create;
     try
-      Members.GetMembers(S);
+      Members.GetMembers(S,True);
       Result:=Result+S.Text;
     finally
       S.Free;
@@ -4610,7 +4611,7 @@ begin
   If IsPacked then
     Result := 'packed '+Result;      // 12/04/04 Dave - Added
   If Assigned(Eltype) then
-    Result:=Result+ElType.GetDeclaration(ElType is TPasUnresolvedTypeRef)
+    Result:=Result+ElType.GetDeclaration(Not (ElType is TPasUnresolvedTypeRef))
   else
     Result:=Result+'const';
 end;
@@ -4798,7 +4799,7 @@ end;
 
 { TPasRecordType }
 
-procedure TPasRecordType.GetMembers(S: TStrings);
+procedure TPasRecordType.GetMembers(S: TStrings; aSkipSection : Boolean = false);
 
 Var
   T : TStringList;
@@ -4818,7 +4819,7 @@ begin
     if E.Visibility<>CV then
       begin
       CV:=E.Visibility;
-      if CV<>visDefault then
+      if (CV<>visDefault) and not aSkipSection then
         S.Add(VisibilityNames[CV]);
       end;
     Temp:=E.GetDeclaration(True);
@@ -5186,14 +5187,14 @@ procedure TPasProcedure.GetModifiers(List: TStrings);
   end;
 
 begin
-  Doadd(IsVirtual,' Virtual');
-  DoAdd(IsDynamic,' Dynamic');
-  DoAdd(IsOverride,' Override');
-  DoAdd(IsAbstract,' Abstract');
-  DoAdd(IsOverload,' Overload');
-  DoAdd(IsReintroduced,' Reintroduce');
-  DoAdd(IsStatic,' Static');
-  DoAdd(IsMessage,' Message');
+  Doadd(IsVirtual,' virtual');
+  DoAdd(IsDynamic,' dynamic');
+  DoAdd(IsOverride,' override');
+  DoAdd(IsAbstract,' abstract');
+  DoAdd(IsReintroduced,' reintroduce');
+  DoAdd(IsOverload,' overload');
+  DoAdd(IsStatic,' static');
+  DoAdd(IsMessage,' message');
 end;
 
 procedure TPasProcedure.ForEachCall(const aMethodCall: TOnForEachPasElement;
@@ -5337,6 +5338,24 @@ begin
 end;
 
 function TPasProcedure.GetDeclaration(full: Boolean): TPasTreeString;
+
+begin
+  GetDeclaration(Full,True,Full,False);
+end;
+
+function TPasProcedure.GetDeclaration(full, AddArgs, AddModifiers, AddParent: Boolean): TPasTreeString;
+
+  function GetName(t : string) : String;
+  begin
+    Result:=T;
+    if Name='' then
+      exit;
+    Result:=Result+' ';
+    if addParent and (Parent is TPasType) then
+      Result:=Result+Parent.Name+'.';
+    Result:=Result+SafeName;
+  end;
+
 Var
   S : TStringList;
   T: TPasTreeString;
@@ -5344,31 +5363,29 @@ Var
 begin
   S:=TStringList.Create;
   try
-    If Full then
+    T:=TypeName;
+    If (NameParts=Nil) or not Full then
+      T:=GetName(T)
+    else
       begin
-      T:=TypeName;
-      if NameParts<>nil then
+      T:=T+' ';
+      for i:=0 to NameParts.Count-1 do
         begin
-        T:=T+' ';
-        for i:=0 to NameParts.Count-1 do
+        if i>0 then
+          T:=T+'.';
+        with TProcedureNamePart(NameParts[i]) do
           begin
-          if i>0 then
-            T:=T+'.';
-          with TProcedureNamePart(NameParts[i]) do
-            begin
-            T:=T+Name;
-            if Templates<>nil then
-              T:=T+GenericTemplateTypesAsString(Templates);
-            end;
+          T:=T+Name;
+          if Templates<>nil then
+            T:=T+GenericTemplateTypesAsString(Templates);
           end;
-        end
-      else if Name<>'' then
-        T:=T+' '+SafeName;
-      S.Add(T);
+        end;
       end;
+    S.Add(T);
     if Assigned(ProcType) then
       begin
-      ProcType.GetArguments(S);
+      if AddArgs then
+        ProcType.GetArguments(S);
       If (ProcType is TPasFunctionType)
           and Assigned(TPasFunctionType(Proctype).ResultEl) then
         With TPasFunctionType(ProcType).ResultEl.ResultType do
@@ -5380,14 +5397,17 @@ begin
             T:=T+GetDeclaration(False);
           S.Add(T);
           end;
-      GetModifiers(S); // needs proctype
+      if AddModifiers then
+        GetModifiers(S); // needs proctype
       end;
-    Result:=IndentStrings(S,Length(S[0]));
+    if s.Count>0 then
+      Result:=IndentStrings(S,Length(S[0]));
   finally
     S.Free;
   end;
 end;
 
+
 function TPasFunction.TypeName: TPasTreeString;
 begin
   Result:='function';
@@ -5415,7 +5435,7 @@ begin
     Result:=Result+TypeName+' '+OperatorTypeToOperatorName(OperatorType);
 end;
 
-function TPasOperator.GetDeclaration (full : boolean) : TPasTreeString;
+function TPasOperator.GetDeclaration(full, AddArgs, AddModifiers, AddParent: Boolean): TPasTreeString;
 
 Var
   S : TStringList;
@@ -5424,8 +5444,7 @@ Var
 begin
   S:=TStringList.Create;
   try
-    If Full then
-      S.Add(GetOperatorDeclaration(Full));
+    S.Add(GetOperatorDeclaration(Full));
     ProcType.GetArguments(S);
     If Assigned((Proctype as TPasFunctionType).ResultEl) then
       if Assigned(TPasFunctionType(ProcType).ResultEl.ResultType) then
@@ -5440,7 +5459,6 @@ begin
         end;
     GetModifiers(S);
     Result:=IndentStrings(S,Length(S[0]));
-
   finally
     S.Free;
   end;

+ 125 - 53
packages/fcl-passrc/src/paswrite.pp

@@ -45,8 +45,12 @@ type
                       woNoAsm,         // Do not allow asm block
                       woSkipPrivateExternals,  // Skip generation of external procedure declaration in implementation section
                       woAlwaysRecordHelper,     // Force use of record helper for type helper
-                      woSkipHints          // Do not add identifier hints
+                      woSkipHints,          // Do not add identifier hints
+                      woSparse,             // Generate sparse code, used to generate declarations suitable for documenation
+                      woDocHints            // When generating sparse code, additionally add documentation hints. (rw for properties etc.)
                       );
+  TElementFlag = (efSkipSection,efMember,efForceBody,efParent);
+  TElementFlags = set of TElementFlag;
   TPasWriterOptions = Set of TPasWriterOption;
 
   TOnUnitAlias = function(const UnitName : String) : String of Object;
@@ -61,6 +65,7 @@ type
     FLineNumberWidth: Integer;
     FOnUnitAlias: TOnUnitAlias;
     FOPtions: TPasWriterOptions;
+    FSkipVisibilities: TPasMemberVisibilities;
     FStream: TStream;
     FIndentSize : Integer;
     IsStartOfLine: Boolean;
@@ -70,9 +75,12 @@ type
     CurDeclSection: string;
     DeclSectionStack: TList;
     FInImplementation : Boolean;
+    procedure AddAsLines(aLines: String);
     procedure SetForwardClasses(AValue: TStrings);
     procedure SetIndentSize(AValue: Integer);
     function CheckUnitAlias(const AUnitName : String) : String;
+    procedure WriteProcDecl(AProc: TPasProcedure; ForceBody: Boolean=False; NamePrefix: String='');
+    procedure WriteProcDecl(AProc: TPasProcedure; aFlags: TElementFlags; NamePrefix: String='');
   protected
     procedure DisableHintsWarnings;
     procedure PrepareDeclSectionInStruct(const ADeclSection: string);
@@ -105,6 +113,7 @@ type
     procedure WriteResourceString(aStr: TPasResString); virtual;
     procedure WriteEnumType(AType: TPasEnumType); virtual;
     procedure WriteElement(AElement: TPasElement;SkipSection : Boolean = False);virtual;
+    procedure WriteElement(AElement: TPasElement; aFlags : TElementFlags);virtual;
     procedure WriteType(AType: TPasType; Full : Boolean = True);virtual;
     procedure WriteProgram(aModule : TPasProgram); virtual;
     Procedure WriteLibrary(aModule : TPasLibrary); virtual;
@@ -122,10 +131,10 @@ type
     Procedure WriteRecordType(AType : TPasRecordType); virtual;
     Procedure WriteArrayType(AType : TPasArrayType; Full : Boolean = True); virtual;
     procedure WriteProcType(AProc: TPasProcedureType);  virtual;
-    procedure WriteProcDecl(AProc: TPasProcedure; ForceBody: Boolean = False; NamePrefix : String = ''); virtual;
     procedure WriteProcImpl(AProc: TProcedureBody; IsAsm : Boolean = false); virtual;
     procedure WriteProcImpl(AProc: TPasProcedureImpl); virtual;
     procedure WriteProperty(AProp: TPasProperty); virtual;
+    procedure WriteProperty(AProp: TPasProperty; aFlags : TElementFlags); virtual;
     procedure WriteImplBlock(ABlock: TPasImplBlock);  virtual;
     procedure WriteImplElement(AElement: TPasImplElement; AAutoInsertBeginEnd: Boolean); virtual;
     procedure WriteImplCommand(ACommand: TPasImplCommand);virtual;
@@ -149,6 +158,7 @@ type
     procedure wrtln;overload; deprecated ;
     property Stream: TStream read FStream;
   Published
+    Property SkipVisibilities : TPasMemberVisibilities Read FSkipVisibilities Write FSkipVisibilities;
     Property OnUnitAlias : TOnUnitAlias Read FOnUnitAlias Write FOnUnitAlias;
     Property Options : TPasWriterOptions Read FOPtions Write FOptions;
     Property IndentSize : Integer Read FIndentSize Write SetIndentSize;
@@ -178,6 +188,7 @@ begin
   FForwardClasses:=TStringList.Create;
   FLineEnding:=sLineBreak;
   FLineNumberWidth:=4;
+  FSkipVisibilities:=[];
 end;
 
 destructor TPasWriter.Destroy;
@@ -248,16 +259,24 @@ begin
 end;
 
 procedure TPasWriter.WriteElement(AElement: TPasElement;SkipSection : Boolean = False);
+begin
+  if SkipSection then
+    WriteElement(aElement,[])
+  else
+    WriteElement(aElement,[efSkipSection])
+end;
+
+procedure TPasWriter.WriteElement(AElement: TPasElement; aFlags : TElementFlags);
 
 begin
-  if not SkipSection then
+  if not (efSkipSection in aFlags) then
     MaybeSetLineElement(AElement);
   if AElement.InheritsFrom(TPasModule) then
     WriteModule(TPasModule(AElement))
   else if AElement.InheritsFrom(TPasSection) then
     WriteSection(TPasSection(AElement))
   else if AElement.ClassType.InheritsFrom(TPasProperty) then
-    WriteProperty(TPasProperty(AElement))
+    WriteProperty(TPasProperty(AElement), aFlags)
   else if AElement.InheritsFrom(TPasConst) then
     WriteConst(TPasConst(AElement)) // Must be before variable
   else if AElement.InheritsFrom(TPasVariable) then
@@ -271,7 +290,7 @@ begin
   else if AElement.InheritsFrom(TPasProcedureImpl) then // This one must come before TProcedureBody/TPasProcedure
     WriteProcImpl(TPasProcedureImpl(AElement))
   else if AElement.InheritsFrom(TPasProcedure) then
-    WriteProcDecl(TPasProcedure(AElement))
+    WriteProcDecl(TPasProcedure(AElement),aFlags)
   else if AElement.InheritsFrom(TProcedureBody) then
     WriteProcImpl(TProcedureBody(AElement))
   else if AElement.InheritsFrom(TPasImplCommand) or AElement.InheritsFrom(TPasImplCommands) then
@@ -730,6 +749,8 @@ begin
     begin
     Member := TPasElement(aMembers[i]);
     CurVisibility := Member.Visibility;
+    if CurVisibility in SkipVisibilities then
+      Continue;
     if (CurVisibility <> LastVisibility) or ForceVisibility then
       begin
       DecIndent;
@@ -744,7 +765,7 @@ begin
       LastVisibility := CurVisibility;
       CurDeclSection := '';
       end;
-    WriteElement(Member);
+    WriteElement(Member,[efMember]);
     LastMember := Member;
     end;
 end;
@@ -818,8 +839,9 @@ begin
     PrepareDeclSectionInStruct('var');
   Add(aVar.SafeName + ': ');
   if Not Assigned(aVar.VarType) then
-    Raise EWriteError.CreateFmt('No type for variable %s',[aVar.SafeName]);
-  WriteType(aVar.VarType,False);
+    Add('unknown_type') // Raise EWriteError.CreateFmt('No type for variable %s',[aVar.SafeName]);
+  else
+    WriteType(aVar.VarType,False);
   if (aVar.AbsoluteExpr<>nil) then
     Add(' absolute %s',[aVar.AbsoluteExpr.ClassName])
   else if (aVar.LibraryName<>Nil) or Assigned (aVar.ExportName) then
@@ -877,10 +899,25 @@ begin
     Add(AType.Name)
 end;
 
+procedure TPasWriter.AddAsLines(aLines : String);
+var
+  L : TStrings;
+  aLine : string;
+begin
+  L:=TStringList.Create;
+  try
+    L.Text:=aLines;
+    For aLine in L do
+      AddLn(aLine);
+  finally
+    L.Free;
+  end;
+end;
+
 procedure TPasWriter.WriteRecordType(AType: TPasRecordType);
 
 Var
-  Temp : String;
+  Temp,TempVar : String;
   i : Integer;
   
 begin
@@ -911,7 +948,11 @@ begin
       temp:=temp+' of';
       AddLn(Temp);
       For I:=0 to AType.Variants.Count-1 do
-        AddLn(TPasVariant(AType.Variants[i]).GetDeclaration(True));
+        begin
+        INcIndent;
+        AddAsLines(TPasVariant(AType.Variants[i]).GetDeclaration(True));
+        DecIndent;
+        end;
       end;
   DecDeclSectionLevel;
   DecIndent;
@@ -934,6 +975,15 @@ end;
 
 
 procedure TPasWriter.WriteProcDecl(AProc: TPasProcedure; ForceBody : Boolean = False; NamePrefix : String = '');
+begin
+  if ForceBody then
+    WriteProcDecl(aProc,[efForceBody],NamePrefix)
+  else
+    WriteProcDecl(aProc,[],NamePrefix)
+end;
+
+procedure TPasWriter.WriteProcDecl(AProc: TPasProcedure; aFlags: TElementFlags; NamePrefix : String = '');
+
 
   Procedure EmptyBody;
 
@@ -946,38 +996,28 @@ procedure TPasWriter.WriteProcDecl(AProc: TPasProcedure; ForceBody : Boolean = F
 Var
   AddExternal : boolean;
   IsImpl : Boolean;
+  ShowArgs, ShowModifiers, IsMember : boolean;
 
 begin
-
   IsImpl:=AProc.Parent is TImplementationSection;
   if IsImpl then
-    PrepareDeclSection('');
+    PrepareDeclSection('')
+  else
+    PrepareDeclSectionInStruct('');
   if Not IsImpl then
     IsImpl:=FInImplementation;
-  if FInImplementation and not forcebody and (Assigned(AProc.LibraryExpr) or Assigned(AProc.LibrarySymbolName)) and HasOption(woSkipPrivateExternals)  then
+  if FInImplementation and not (efForcebody in aFlags) and (Assigned(AProc.LibraryExpr) or Assigned(AProc.LibrarySymbolName)) and HasOption(woSkipPrivateExternals)  then
     Exit;
-  Add(AProc.GetDeclaration(True));
+  IsMember:=(efMember in aFlags);
+  ShowArgs:=Not (Ismember and (woSparse in Options));
+  ShowModifiers:=Not (IsImpl or (woSparse in Options));
+  Add(AProc.GetDeclaration(Not IsMember,ShowArgs,ShowModifiers,efParent in aFlags));
   Add(';');
   // delphi compatible order for example: procedure foo; reintroduce; overload; static;
-  if not IsImpl and AProc.IsReintroduced then
-    Add(' reintroduce;');
   // if NamePrefix is not empty, we're writing a dummy for external class methods.
   // In that case, we must not write the 'overload'.
   if AProc.IsOverload and (NamePrefix='') and not IsImpl then
     Add(' overload;');
-  if not IsImpl then
-    begin
-    if AProc.IsVirtual then
-      Add(' virtual;');
-    if AProc.IsDynamic then
-      Add(' dynamic;');
-    if AProc.IsAbstract then
-      Add(' abstract;');
-    if AProc.IsOverride then
-      Add(' override;');
-    if AProc.IsStatic then
-      Add(' static;');
-    end;
   if (pmAssembler in AProc.Modifiers) and Not (woNoAsm in OPtions) then
     Add(' assembler;');
   if AProc.CallingConvention<>ccDefault then
@@ -1007,7 +1047,7 @@ begin
     else
       WriteProcImpl(AProc.Body,pmAssembler in AProc.Modifiers)
     end
-  else if ForceBody then
+  else if (efForceBody in aFlags) then
     EmptyBody;
 end;
 
@@ -1132,8 +1172,14 @@ begin
 end;
 
 procedure TPasWriter.WriteProperty(AProp: TPasProperty);
+begin
+  WriteProperty(aProp,[]);
+end;
+
+procedure TPasWriter.WriteProperty(AProp: TPasProperty; aFlags : TElementFlags);
 var
   i: Integer;
+  s : string;
 begin
   if AProp.IsClass then
     Add('class ');
@@ -1151,24 +1197,43 @@ begin
   end;
   if Assigned(AProp.VarType) then
   begin
-    Add(': ');
-    WriteType(AProp.VarType,False);
+    Add(': '+aProp.VarType.Name);
   end;
-  if AProp.IndexValue <> '' then
-    Add(' index ' + AProp.IndexValue); 
-  if AProp.ReadAccessorName <> '' then
-    Add(' read ' + AProp.ReadAccessorName);
-  if AProp.WriteAccessorName <> '' then
-    Add(' write ' + AProp.WriteAccessorName);
-  if AProp.StoredAccessorName <> '' then
-    Add(' stored ' + AProp.StoredAccessorName);
-  if AProp.DefaultValue <> '' then
-    Add(' default ' + AProp.DefaultValue);
-  if AProp.IsNodefault then
-    Add(' nodefault');
-  if AProp.IsDefault then
-    Add('; default');
-  AddLn(';');
+  if not ((woSparse in Options) and (efMember in aFlags)) then
+    begin
+    if not (woSparse in Options) then
+      begin
+      if AProp.IndexValue <> '' then
+        Add(' index ' + AProp.IndexValue);
+      if AProp.ReadAccessorName <> '' then
+        Add(' read ' + AProp.ReadAccessorName);
+      if AProp.WriteAccessorName <> '' then
+        Add(' write ' + AProp.WriteAccessorName);
+      end;
+    if AProp.StoredAccessorName <> '' then
+      Add(' stored ' + AProp.StoredAccessorName);
+    if AProp.DefaultValue <> '' then
+      Add(' default ' + AProp.DefaultValue);
+    if AProp.IsNodefault then
+      Add(' nodefault');
+    if AProp.IsDefault then
+      Add('; default');
+    end;
+  S:='';
+  if (woSparse in Options) and (efMember in aFlags) then
+    begin
+    if AProp.ReadAccessorName <> '' then
+      S:=S+'r';
+    if AProp.WriteAccessorName <> '' then
+      S:=S+'w';
+    if AProp.StoredAccessorName <> '' then
+      S:=S+'s';
+    if AProp.DefaultValue <> '' then
+      S:=S+'d';
+   if s<>'' then
+     S:=' {'+S+'}';
+   end;
+  AddLn(';'+S);
 end;
 
 procedure TPasWriter.WriteImplBlock(ABlock: TPasImplBlock);
@@ -1633,17 +1698,24 @@ end;
 
 procedure TPasWriter.PrepareDeclSectionInStruct(const ADeclSection: string);
 
+var
+  dodec : boolean;
 begin
   if Not SameText(ADeclSection,CurDeclSection) then
-  begin
-    if ADeclSection <> '' then
     begin
-      DecIndent;
+    if ADeclSection <> '' then
+      begin
+      DoDec:=Indent<>'';
+      if DoDec then
+        DecIndent;
       AddLn(ADeclSection);
-      IncIndent;
-    end;
+      if DoDec then
+        IncIndent;
+      end
+    else
+      DecIndent;
     CurDeclSection := ADeclSection;
-  end;
+    end;
 end;
 
 procedure TPasWriter.SetForwardClasses(AValue: TStrings);

+ 2 - 2
packages/fcl-syntax/src/syntax.pascal.pp

@@ -58,7 +58,7 @@ implementation
 
 const
   MaxKeywordLength = 15;
-  MaxKeyword = 60;
+  MaxKeyword = 61;
 
   KeywordTable: array[0..MaxKeyword] of string =
     ('AND', 'ARRAY', 'ASM', 'ASSEMBLER',
@@ -70,7 +70,7 @@ const
     'GOTO',
     'IF', 'IMPLEMENTATION', 'IN', 'INHERITED', 'INITIALIZATION', 'INTERFACE',
     'NIL', 'NOT',
-    'OBJECT', 'OF', 'ON', 'OR', 'OVERRIDE',
+    'OBJECT', 'OF', 'ON', 'OPERATOR', 'OR', 'OVERRIDE',
     'PACKED', 'PRIVATE', 'PROCEDURE', 'PROGRAM', 'PROPERTY', 'PROTECTED',
     'PUBLIC', 'PUBLISHED',
     'RAISE', 'RECORD', 'REPEAT', 'RESOURCESTRING',

+ 834 - 0
packages/vcl-compat/src/system.json.types.pp

@@ -0,0 +1,834 @@
+{
+    This file is part of the Free Pascal run time library.
+    Copyright (c) 2025 by the Free Pascal development team
+
+    Delphi compatibility unit: Various JSON structures & routines.
+
+    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.
+
+ **********************************************************************}
+unit System.JSON.Types;
+
+{$mode objfpc}
+{$modeswitch advancedrecords}
+{$h+}
+
+interface
+
+{$SCOPEDENUMS ON}
+
+uses
+  {$IFDEF FPC_DOTTEDUNITS}
+  System.SysUtils, System.Generics.Collections, System.Classes, System.StrUtils;
+  {$ELSE}
+  SysUtils, Generics.Collections, Classes, StrUtils;
+  {$ENDIF}
+
+const
+  JsonFalse            = 'false';
+  JsonNan              = 'NaN';
+  JsonNegativeInfinity = '-Infinity';
+  JsonNew              = 'new';
+  JsonNull             = 'null';
+  JsonPositiveInfinity = 'Infinity';
+  JsonTrue             = 'true';
+  JsonUndefined        = 'undefined';
+
+  JsonExtBinaryPropertyName     = '$binary';
+  JsonExtCodePropertyName       = '$code';
+  JsonExtDatePropertyName       = '$date';
+  JsonExtDbPropertyName         = '$db';
+  JsonExtDecimalPropertyName    = '$numberdecimal';
+  JsonExtIdPropertyName         = '$id';
+  JsonExtMaxKeyPropertyName     = '$maxkey';
+  JsonExtMinKeyPropertyName     = '$minkey';
+  JsonExtNumberLongPropertyName = '$numberlong';
+  JsonExtOidPropertyName        = '$oid';
+  JsonExtOptionsPropertyName    = '$options';
+  JsonExtRefPropertyName        = '$ref';
+  JsonExtRegexPropertyName      = '$regex';
+  JsonExtScopePropertyName      = '$scope';
+  JsonExtTypePropertyName       = '$type';
+  JsonExtUndefinedPropertyName  = '$undefined';
+
+  JsonExtMaxPropertyNameLen     = Length(JsonExtNumberLongPropertyName);
+
+  OidBytesCount = 12;
+
+type
+  TJsonToken = (
+    None, StartObject, StartArray, StartConstructor, PropertyName, Comment,
+    Raw, Integer, Float, &String, Boolean, Null, Undefined, EndObject,
+    EndArray, EndConstructor, Date, Bytes, Oid, RegEx, DBRef, CodeWScope,
+    MinKey, MaxKey, Decimal
+  );
+
+  
+  TJsonContainerType          = (None, &Object, &Array, &Constructor);
+  TJsonDateFormatHandling     = (Iso, Unix, FormatSettings);
+  TJsonDateParseHandling      = (None, DateTime);
+  TJsonDateTimeZoneHandling   = (Local, Utc);
+  TJsonDefaultValueHandling   = (Include, Ignore, Populate, IgnoreAndPopulate);
+  TJsonEmptyValueHandling     = (Empty, Null);
+  TJsonFloatFormatHandling    = (&String, Symbol, DefaultValue);
+  TJsonFormatting             = (None, Indented);
+  TJsonNullValueHandling      = (Include, Ignore);
+  TJsonObjectCreationHandling = (Auto, Reuse, Replace);
+  TJsonReferenceLoopHandling  = (Error, Ignore, Serialize);
+  TJsonStringEscapeHandling   = (Default, EscapeNonAscii, EscapeHtml);
+  TJsonTypeNameHandling       = (None, Objects, Arrays, All, Auto);
+
+const
+  JSONPrimitiveTokens = [
+    TJsonToken.Integer, TJsonToken.Float, TJsonToken.&String, TJsonToken.Boolean,
+    TJsonToken.Undefined, TJsonToken.Null, TJsonToken.Date, TJsonToken.Bytes,
+    TJsonToken.Oid, TJsonToken.RegEx, TJsonToken.DBRef, TJsonToken.CodeWScope,
+    TJsonToken.MinKey, TJsonToken.MaxKey
+    ];
+    
+  JSONStartTokens =  [TJsonToken.StartObject,TJsonToken.StartArray,TJsonToken.StartConstructor];
+  JSONEndTokens = [TJsonToken.EndObject, TJsonToken.EndArray, TJsonToken.EndConstructor];
+
+
+Type
+  
+  TJsonLineInfo = class
+  public
+    function GetLineNumber: Integer; virtual;
+    function GetLinePosition: Integer; virtual;
+    function HasLineInfo: Boolean; virtual;
+    property LineNumber: Integer read GetLineNumber;
+    property LinePosition: Integer read GetLinePosition;
+  end;
+  
+  TJsonExtendedJsonMode = (None, StrictMode, MongoShell);
+  
+  TJsonBinaryType = (
+    Generic = $00, 
+    &Function = $01, 
+    BinaryOld = $02,  
+    UUIDOld = $03, 
+    UUID = $04, 
+    MD5 = $05, 
+    UserDefined = $80
+  );
+
+ 
+  TJsonPosition = record
+  Public
+    ContainerType: TJsonContainerType;
+    Position: Integer;
+    PropertyName: string;
+    HasIndex: Boolean;
+    constructor Create(AType: TJsonContainerType); overload;
+    procedure Clear;
+    procedure WriteTo(const Sb: TStringBuilder);
+    function AsString(aInitialDot : Boolean) : String;
+    class function Create: TJsonPosition; overload; inline; static;
+    class function FormatMessage(const aLineInfo: TJsonLineInfo; const aPath, aMsg: string): string; static;
+  end;
+  TJsonPositionList = specialize TList<TJsonPosition>;
+
+  TEnumerablePositions = specialize TEnumerable<TJsonPosition>;
+  TJsonPositionHelper = record helper for TJsonPosition
+    class function BuildPath(const aPositions: TEnumerablePositions; aFromIndex: Integer = 0): string; static;
+  end;
+  
+  TJsonFiler = class(TJsonLineInfo)
+  private
+    function GetPath : String; 
+  protected
+    FStack: specialize TList<TJsonPosition>;
+    FCurrentPosition: TJsonPosition;
+    function GetPosition(ADepth: Integer): TJsonPosition;
+    function Peek: TJsonContainerType; inline;
+    function Pop: TJsonContainerType;
+    procedure Push(AValue: TJsonContainerType);
+    function GetInsideContainer: Boolean; virtual; abstract;
+  public
+    constructor Create;
+    destructor Destroy; override;
+    function GetPath(AFromDepth: Integer): string; overload;
+    procedure Rewind; virtual;
+    class function IsEndToken(aToken: TJsonToken): Boolean; static; inline;
+    class function IsPrimitiveToken(aToken: TJsonToken): Boolean; static; inline;
+    class function IsStartToken(aToken: TJsonToken): Boolean; static; inline;
+    property InsideContainer: Boolean read GetInsideContainer;
+    property Path: string read GetPath;
+  end;
+
+  EJsonException = class(Exception)
+  private
+    FInnerException: Exception;
+  public
+    constructor Create(const aMessage: string; const aInnerException: Exception); overload;
+    property InnerException: Exception read FInnerException;
+  end;
+
+  TOidBytes = Array[0..OidBytesCount-1] of Byte;
+  TJsonOid = record
+  private
+    function GetAsString: String;
+    procedure SetAsString(const aValue: String);
+    function GetAsBytes: TBytes;
+    procedure SetAsBytes(const aValue: TBytes);
+  public
+    Bytes: TOidBytes;
+    constructor Create(const aOid: TOidBytes); overload;
+    constructor Create(const aOid: TBytes); overload;
+    constructor Create(const aOid: String); overload;
+    property AsString: String read GetAsString write SetAsString;
+    property AsBytes: TBytes read GetAsBytes write SetAsBytes;
+  end;
+
+  TJsonCodeWScope = record
+  public type
+    TScopeItem = record
+      Ident: String;
+      Value: String;
+    end;
+  public
+    Code: String;
+    Scope: array of TScopeItem;
+    constructor Create(const aCode: String; aScope: TStrings);
+  end;
+  
+  TJsonDBRef = record
+  private
+    function GetAsString: String;
+    procedure SetAsString(const aValue: String);
+  public
+    DB: String;
+    Ref: String;
+    Id: TJsonOid;
+    constructor Create(const aDb, aRef, aId: String); overload;
+    constructor Create(const aDb, aRef: String; const aId: TJsonOid); overload;
+    constructor Create(const aRef, aId: String); overload;
+    constructor Create(const aRef: String; const aId: TJsonOid); overload;
+    property AsString: String read GetAsString write SetAsString;
+  end;
+
+  TJsonDecimal128 = record
+  private
+    function GetAsString: String;
+    procedure SetAsString(const aValue: String);
+    function GetAsExtended: Extended;
+    procedure SetAsExtended(const aValue: Extended);
+    function GetIsNan: Boolean;
+    function GetIsNegInfinity: Boolean;
+    function GetIsPosInfinity: Boolean;
+    function GetIsZero: Boolean;
+  public
+    type
+      TDecOidBytesCount8ToString = function (const aDec: TJsonDecimal128): string of object;
+      TStringToDecOidBytesCount8 = function (const aStr: string; var ADec: TJsonDecimal128): Boolean of object;
+    class var
+      FDecOidBytesCount8ToString: TDecOidBytesCount8ToString;
+      FStringToDecOidBytesCount8: TStringToDecOidBytesCount8;
+    const
+      MaxStrLen = 42;
+  public
+    lo, hi: UInt64;
+    constructor Create(const aValue: string); overload;
+    constructor Create(const aValue: Extended); overload;
+    property IsNan: Boolean read GetIsNan;
+    property IsPosInfinity: Boolean read GetIsPosInfinity;
+    property IsNegInfinity: Boolean read GetIsNegInfinity;
+    property IsZero: Boolean read GetIsZero;
+    property AsString: String read GetAsString write SetAsString;
+    property AsExtended: Extended read GetAsExtended write SetAsExtended;
+  end;
+
+  JsonNameAttribute = class(TCustomAttribute)
+  private
+    FValue: string;
+  public
+    constructor Create(const aValue: string);
+    property Value: string read FValue;
+  end;
+
+  TJsonRegEx = record
+  private
+    function GetAsString: String;
+    procedure SetAsString(const aValue: String);
+  public
+    RegEx: String;
+    Options: String;
+    constructor Create(const aRegEx, AOptions: String);
+    property AsString: String read GetAsString write SetAsString;
+  end;
+
+var
+  JSONFormatSettings: TFormatSettings;
+  JSONSerializationVersion: Integer = 36; // as defined in Delphi
+
+implementation
+
+{ ---------------------------------------------------------------------
+  Constants
+  ---------------------------------------------------------------------}
+
+
+const
+  SErrFormatMessagePath    = 'In %s';
+  SErrFormatMessageLinePos = 'at %d:%d';
+  SErrInvalidObjectId      = 'Invalid object ID';
+  SErrDecimalNotAvailable  = 'No decimal implementation available';
+  SErrInvalidDecimalString = 'Invalid decimal string value';
+
+const
+  HighOidBytesCount8Zero      = $3040000000000000;
+  HighOidBytesCount8PosInfity = $7800000000000000;
+  HighOidBytesCount8NegInfity = QWord($F800000000000000);
+  HighOidBytesCount8Nan       = $7C00000000000000;
+
+
+{ ---------------------------------------------------------------------
+  EJsonException
+  ---------------------------------------------------------------------}
+
+constructor EJsonException.Create(const aMessage: string; const aInnerException: Exception);
+
+begin
+  Create(aMessage);
+  FInnerException:=aInnerException;
+end;
+
+
+{ ---------------------------------------------------------------------
+  TJsonCodeWScope
+  ---------------------------------------------------------------------}
+
+constructor TJsonCodeWScope.Create(const aCode: String; aScope: TStrings);
+
+var
+  lLen,I: Integer;
+
+begin
+  Code:=aCode;
+  lLen:=0;
+  if (aScope<>nil) then
+    lLen:=aScope.Count;
+  SetLength(Self.Scope,lLen);
+  for I:=0 to lLen-1 do
+    With Self.Scope[i] do
+      begin
+      Ident:=aScope.Names[I];
+      Value:=aScope.ValueFromIndex[I];
+      end;
+end;
+
+{ ---------------------------------------------------------------------
+  TJsonDBRef
+  ---------------------------------------------------------------------}
+
+constructor TJsonDBRef.Create(const aDb,aRef,aId: String);
+begin
+  DB:=aDb;
+  Ref:=aRef;
+  Id.AsString:=aId;
+end;
+
+constructor TJsonDBRef.Create(const aDb, aRef: String; const aId: TJsonOid);
+
+begin
+  DB:=aDb;
+  Ref:=aRef;
+  Id:=aId;
+end;
+
+
+constructor TJsonDBRef.Create(const aRef, AId: String);
+
+begin
+  Self:=Create('',aRef,AId);
+end;
+
+
+constructor TJsonDBRef.Create(const aRef: String; const aId: TJsonOid);
+
+begin
+  Self:=Create('',ARef,AId);
+end;
+
+
+function TJsonDBRef.GetAsString: String;
+
+var
+  S : String;
+
+begin
+  S:=DB;
+  if S<>'' then
+    S:=S+'.';
+  S:=S+Ref+'.'+Id.AsString;
+  Result:=S;
+end;
+
+
+procedure TJsonDBRef.SetAsString(const aValue: String);
+
+var
+  lParts: TStringArray;
+
+begin
+  lParts:=SplitString(AValue,'.');
+  if Length(lParts)<2 then
+    Raise EJsonException.CreateFmt('Invalid DB ref: %s',[aValue]);
+  if Length(lParts)=2 then
+    begin
+    DB:='';
+    Ref:=lParts[0];
+    Id.AsString:=lParts[1];
+    end
+  else if Length(lParts)=3 then
+    begin
+    DB:=lParts[0];
+    Ref:=lParts[1];
+    Id.AsString:=lParts[2];
+    end;
+end;
+
+{ ---------------------------------------------------------------------
+  TJsonDecimal128
+  ---------------------------------------------------------------------}
+
+constructor TJsonDecimal128.Create(const aValue: Extended);
+
+begin
+  AsExtended:=AValue;
+end;
+
+
+constructor TJsonDecimal128.Create(const aValue: string);
+
+begin
+  AsString:=AValue;
+end;
+
+
+function TJsonDecimal128.GetAsString: String;
+
+begin
+  if Assigned(FDecOidBytesCount8ToString) then
+    Result:=FDecOidBytesCount8ToString(Self)
+  else  
+    raise EJsonException.Create(SErrDecimalNotAvailable);
+end;
+
+
+procedure TJsonDecimal128.SetAsString(const aValue: String);
+
+begin
+  if not Assigned(FStringToDecOidBytesCount8) then
+    raise EJsonException.Create(SErrDecimalNotAvailable);
+  if aValue='' then
+    begin
+    lo:=0;
+    Hi:=HighOidBytesCount8Zero
+    end
+  else if not FStringToDecOidBytesCount8(aValue, Self) then
+    raise EJsonException.CreateFmt(SErrInvalidDecimalString, [aValue]);
+end;
+
+
+function TJsonDecimal128.GetAsExtended: Extended;
+
+begin
+  if IsNan then
+    Result:=Extended.NaN
+  else if IsNegInfinity then
+    Result:=Extended.NegativeInfinity
+  else if IsPosInfinity then
+    Result:=Extended.PositiveInfinity
+  else if IsZero then
+    Result:=0.0
+  else if not TryStrToFloat(AsString,Result,JSONFormatSettings) then
+    Result:=Extended.NaN;
+end;
+
+
+procedure TJsonDecimal128.SetAsExtended(const aValue: Extended);
+
+begin
+  AsString:=FloatToStr(aValue, JSONFormatSettings);
+end;
+
+
+function TJsonDecimal128.GetIsNan: Boolean;
+
+begin
+  Result:=(lo=0) and (hi=HighOidBytesCount8Nan);
+end;
+
+
+function TJsonDecimal128.GetIsNegInfinity: Boolean;
+
+begin
+  Result:=(lo=0) and (hi=HighOidBytesCount8NegInfity);
+end;
+
+
+function TJsonDecimal128.GetIsPosInfinity: Boolean;
+
+begin
+  Result:=(lo=0) and (hi=HighOidBytesCount8PosInfity);
+end;
+
+
+function TJsonDecimal128.GetIsZero: Boolean;
+
+begin
+  Result:=(lo=0) and (hi=HighOidBytesCount8Zero);
+end;
+
+
+{ ---------------------------------------------------------------------
+  TJSONFiler
+  ---------------------------------------------------------------------}
+
+constructor TJsonFiler.Create;
+
+begin
+  inherited Create;
+  FStack:=Specialize TList<TJsonPosition>.Create;
+  FStack.Capacity:=10;
+  FCurrentPosition.Clear;
+end;
+
+
+destructor TJsonFiler.Destroy;
+
+begin
+  FStack.Free;
+  inherited Destroy;
+end;
+
+
+function TJsonFiler.GetPath: string;
+
+begin
+  Result:=GetPath(0);
+end;
+
+
+function TJsonFiler.GetPath(aFromDepth: Integer): string;
+
+var
+  I: Integer;
+
+begin
+  Result:='';
+  if FCurrentPosition.ContainerType=TJsonContainerType.None then
+    Exit;
+  if AFromDepth < 0 then
+    AFromDepth:=0;
+  for I:=AFromDepth to FStack.Count - 1 do
+    Result:=Result+FStack[I].AsString(Result<>'');
+  if InsideContainer and (AFromDepth <= FStack.Count) then
+    Result:=Result+FCurrentPosition.AsString(Result<>'');
+end;
+
+function TJsonFiler.GetPosition(aDepth: Integer): TJsonPosition;
+
+begin
+  if aDepth<FStack.Count then
+    Result:=FStack.List[aDepth]
+  else
+    Result:=FCurrentPosition;
+end;
+
+
+function TJsonFiler.Peek: TJsonContainerType;
+
+begin
+  Result:=FCurrentPosition.ContainerType;
+end;
+
+
+function TJsonFiler.Pop: TJsonContainerType;
+
+begin
+  Result:=FCurrentPosition.ContainerType;
+  if FStack.Count > 0 then
+    begin
+    FCurrentPosition:=FStack.List[FStack.Count - 1];
+    FStack.Delete(FStack.Count - 1);
+    end
+  else
+    begin
+    FCurrentPosition.Clear;
+    end;
+end;
+
+procedure TJsonFiler.Push(AValue: TJsonContainerType);
+
+begin
+  if FCurrentPosition.ContainerType <> TJsonContainerType.None then
+    FStack.Add(FCurrentPosition);
+  FCurrentPosition.Create(AValue);
+end;
+
+
+procedure TJsonFiler.Rewind;
+
+begin
+  FStack.Clear;
+  FCurrentPosition.Clear;
+end;
+
+class function TJsonFiler.IsPrimitiveToken(aToken: TJsonToken): Boolean;
+
+begin
+  Result:=aToken in JSONPrimitiveTokens;
+end;
+
+
+class function TJsonFiler.IsStartToken(aToken: TJsonToken): Boolean;
+
+begin
+  Result:=aToken in JSONStartTokens;
+end;
+
+
+class function TJsonFiler.IsEndToken(aToken: TJsonToken): Boolean;
+
+begin
+  Result:=aToken in JSONEndTokens
+end;
+
+
+{ ---------------------------------------------------------------------
+  TJsonLineInfo
+  ---------------------------------------------------------------------}
+
+
+function TJsonLineInfo.GetLineNumber: Integer;
+
+begin
+  Result:=0; 
+end;
+
+
+function TJsonLineInfo.GetLinePosition: Integer;
+
+begin
+  Result:=0; 
+end;
+
+
+function TJsonLineInfo.HasLineInfo: Boolean;
+
+begin
+  Result:=False; 
+end;
+
+
+{ ---------------------------------------------------------------------
+  JsonNameAttribute
+  ---------------------------------------------------------------------}
+
+constructor JsonNameAttribute.Create(const aValue: string);
+
+begin
+  inherited Create;
+  FValue:=aValue;
+end;
+
+
+{ ---------------------------------------------------------------------
+  TJsonOid
+  ---------------------------------------------------------------------}
+
+constructor TJsonOid.Create(const aOid: TOidBytes); overload;
+
+begin
+  Bytes:=aOid;
+end;
+
+constructor TJsonOid.Create(const aOid: TBytes);
+begin
+  AsBytes:=AOid;
+end;
+
+constructor TJsonOid.Create(const aOid: String);
+begin
+  AsString:=AOid;
+end;
+
+function TJsonOid.GetAsBytes: TBytes;
+begin
+  SetLength(Result,OidBytesCount);
+  Move(bytes[0],Result[0],OidBytesCount);
+end;
+
+procedure TJsonOid.SetAsBytes(const aValue: TBytes);
+begin
+  Case  Length(aValue) of
+    0 : Bytes:=Default(TOidBytes);
+    OidBytesCount: Move(aValue[0],Bytes[0],OidBytesCount)
+  else
+    raise EJsonException.Create(SErrInvalidObjectId);
+  end;
+end;
+
+function TJsonOid.GetAsString: String;
+var
+  LBytes, LText: TBytes;
+begin
+  LBytes:=AsBytes;
+  SetLength(LText,Length(LBytes)*2);
+  BinToHex(LBytes,0,LText,0,OidBytesCount);
+  Result:=TEncoding.ANSI.GetString(LText);
+end;
+
+procedure TJsonOid.SetAsString(const aValue: String);
+var
+  LText, LBytes: TBytes;
+begin
+  LText:=BytesOf(aValue);
+  SetLength(LBytes,Length(LText) div 2);
+  HexToBin(LText,0,LBytes,0,Length(LBytes));
+  SetAsBytes(LBytes);
+end;
+
+
+{ ---------------------------------------------------------------------
+  TJsonPosition
+  ---------------------------------------------------------------------}
+
+
+constructor TJsonPosition.Create(aType: TJsonContainerType);
+
+begin
+  ContainerType:=Atype;
+  Position:=-1;
+  PropertyName:='';
+  HasIndex:=(atype=TJsonContainerType.&Array) or (atype=TJsonContainerType.&Constructor);
+end;
+
+class function TJsonPosition.Create: TJsonPosition;
+begin
+  Result.Clear;
+end;
+
+procedure TJsonPosition.Clear;
+begin
+  ContainerType:=TJsonContainerType.None;
+  HasIndex:=False;
+  Position:=-1;
+  PropertyName:='';
+end;
+
+class function TJsonPosition.FormatMessage(const aLineInfo: TJsonLineInfo; const aPath, aMsg: string): string;
+var
+  S : String;
+begin
+  if aMsg.EndsWith(sLineBreak) then
+    S:=aMsg
+  else
+    begin
+    S:=Trim(aMsg);
+    if not S.EndsWith('.') then
+      S:=S+'. '
+    else
+      S:=S+' ';
+    end;
+  Result:=S+Format(SErrFormatMessagePath,[aPath]);
+  if Assigned(aLineInfo) then
+    With aLineInfo do
+      if HasLineInfo then
+        Result:=Result+Format(SErrFormatMessageLinePos,[LineNumber, LinePosition]);
+end;
+
+function TJsonPosition.AsString(aInitialDot : Boolean) : String;
+
+begin
+  Result:='';
+  case ContainerType of
+    TJsonContainerType.&Object:
+      begin
+      Result:=PropertyName;
+      if aInitialDot then
+        Result:='.'+Result;
+      end;
+    TJsonContainerType.&Array,
+    TJsonContainerType.&Constructor:
+      Result:='['+IntToStr(Position)+']';
+  end;
+end;
+
+procedure TJsonPosition.WriteTo(const Sb: TStringBuilder);
+var
+  S : String;
+begin
+  S:=AsString((ContainerType=TJsonContainerType.&Object) and (Sb.Length>0));
+  if (ContainerType<>TJsonContainerType.None) then
+    Sb.Append(S);
+end;
+
+{ ---------------------------------------------------------------------
+  TJsonPositionHelper
+  ---------------------------------------------------------------------}
+
+class function TJsonPositionHelper.BuildPath(const aPositions: TEnumerablePositions; aFromIndex: Integer=0): string;
+
+var
+  P : TJsonPosition;
+  I : Integer;
+
+begin
+  Result:='';
+  if AFromIndex<0 then
+    AFromIndex:=0;
+  i:=0;
+  for P in aPositions do
+    begin
+    if I>=aFromIndex then
+      Result:=Result+P.AsString(Result<>'');
+    inc(i);
+    end;
+end;
+
+{ ---------------------------------------------------------------------
+  TJsonRegEx
+  ---------------------------------------------------------------------}
+
+
+constructor TJsonRegEx.Create(const aRegEx, aOptions: String);
+begin
+  RegEx:=aRegEx;
+  Options:=aOptions;
+end;
+
+function TJsonRegEx.GetAsString: String;
+begin
+  Result:='/'+RegEx+'/'+Options;
+end;
+
+procedure TJsonRegEx.SetAsString(const aValue: String);
+var
+  lParts: TStringArray;
+begin
+  RegEx:='';
+  Options:='';
+  lParts:=SplitString(aValue,'/');
+  case Length(lParts) of
+  1 :  RegEx:=lParts[0];
+  2 :  RegEx:=lParts[1];
+  3 :
+    begin
+    RegEx:=lParts[1];
+    Options:=lParts[2];
+    end;
+  end;
+end;
+
+initialization
+  JSONFormatSettings:=TFormatSettings.Invariant;
+end.

+ 2 - 0
packages/vcl-compat/src/system.regularexpressionscore.pp

@@ -558,6 +558,7 @@ end;
 
 destructor TPerlRegEx.Destroy;
 begin
+  CleanUp;
   inherited Destroy;
 end;
 
@@ -644,6 +645,7 @@ var
   Buffer : Array[0..255] of ansichar;
 
 begin
+  FillChar(Buffer,SizeOf(Buffer),0);
   pcre2_get_error_message(ErrorNr,@Buffer,SizeOf(Buffer));
   Result:=strpas(@Buffer);
 end;

+ 1 - 1
packages/vcl-compat/tests/testcompat.lpr

@@ -7,7 +7,7 @@ uses
   Classes, consoletestrunner, tcnetencoding, tciotuils,
   utmessagemanager, utcdevices, utcanalytics, utcimagelist, 
   utcnotifications, utcjson, utcpush, utchash, utcregex, 
-  utcregexapi, utthreading, utccredentials;
+  utcjsontypes,  utcregexapi, utthreading, utccredentials;
 
 type
 

+ 843 - 0
packages/vcl-compat/tests/utcjsontypes.pas

@@ -0,0 +1,843 @@
+unit utcjsontypes;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, fpcunit, testutils, testregistry,
+  System.JSON.Types, Generics.Collections, Math;
+
+type
+
+  { TTestJsonLineInfo }
+
+  TTestJsonLineInfo = class(TTestCase)
+  private
+    FLineInfo: TJsonLineInfo;
+  protected
+    procedure SetUp; override;
+    procedure TearDown; override;
+  published
+    procedure TestGetLineNumber;
+    procedure TestGetLinePosition;
+    procedure TestHasLineInfo;
+    procedure TestLineNumberProperty;
+    procedure TestLinePositionProperty;
+  end;
+
+  { TTestJsonPosition }
+
+  TTestJsonPosition = class(TTestCase)
+  private
+    FPosition: TJsonPosition;
+  published
+    procedure TestCreateDefault;
+    procedure TestCreateWithType;
+    procedure TestClear;
+    procedure TestWriteToObject;
+    procedure TestWriteToArray;
+    procedure TestWriteToConstructor;
+    procedure TestBuildPathEmpty;
+    procedure TestBuildPathSingle;
+    procedure TestBuildPathMultiple;
+    procedure TestFormatMessage;
+  end;
+
+  { TTestJsonFiler }
+
+  TTestJsonFiler = class(TTestCase)
+  private
+    type
+      TTestJsonFilerImpl = class(TJsonFiler)
+      protected
+        function GetInsideContainer: Boolean; override;
+      end;
+    var
+      FFiler: TTestJsonFilerImpl;
+  protected
+    procedure SetUp; override;
+    procedure TearDown; override;
+  published
+    procedure TestCreateDestroy;
+    procedure TestPushPop;
+    procedure TestPeek;
+    procedure TestGetPath;
+    procedure TestRewind;
+    procedure TestIsEndToken;
+    procedure TestIsStartToken;
+    procedure TestIsPrimitiveToken;
+  end;
+
+  { TTestJsonOid }
+
+  TTestJsonOid = class(TTestCase)
+  private
+    FOid: TJsonOid;
+  published
+    procedure TestCreateFromBytes;
+    procedure TestCreateFromString;
+    procedure TestAsString;
+    procedure TestAsBytes;
+    procedure TestStringRoundTrip;
+    procedure TestBytesRoundTrip;
+    procedure TestInvalidStringLength;
+  end;
+
+  { TTestJsonRegEx }
+
+  TTestJsonRegEx = class(TTestCase)
+  private
+    FRegEx: TJsonRegEx;
+  published
+    procedure TestCreate;
+    procedure TestAsString;
+    procedure TestSetAsString;
+    procedure TestSetAsStringVariations;
+  end;
+
+  { TTestJsonDBRef }
+
+  TTestJsonDBRef = class(TTestCase)
+  private
+    FDBRef: TJsonDBRef;
+  published
+    procedure TestCreateWithDB;
+    procedure TestCreateWithoutDB;
+    procedure TestCreateWithOid;
+    procedure TestAsString;
+    procedure TestSetAsString;
+  end;
+
+  { TTestJsonCodeWScope }
+
+  TTestJsonCodeWScope = class(TTestCase)
+  private
+    FCodeWScope: TJsonCodeWScope;
+  published
+    procedure TestCreateEmpty;
+    procedure TestCreateWithScope;
+  end;
+
+  { TTestJsonDecimal128 }
+
+  TTestJsonDecimal128 = class(TTestCase)
+  private
+    FDecimal: TJsonDecimal128;
+  published
+    procedure TestCreateFromString;
+    procedure TestCreateFromExtended;
+    procedure TestIsZero;
+    procedure TestIsNan;
+    procedure TestIsPosInfinity;
+    procedure TestIsNegInfinity;
+    procedure TestAsExtended;
+    procedure TestAsString;
+  end;
+
+  { TTestJsonNameAttribute }
+
+  TTestJsonNameAttribute = class(TTestCase)
+  private
+    FAttribute: JsonNameAttribute;
+  protected
+    procedure TearDown; override;
+  published
+    procedure TestCreate;
+    procedure TestValue;
+  end;
+
+  { TTestEJsonException }
+
+  TTestEJsonException = class(TTestCase)
+  published
+    procedure TestCreateSimple;
+    procedure TestCreateWithInner;
+    procedure TestInnerException;
+  end;
+
+implementation
+
+{ TTestJsonLineInfo }
+
+procedure TTestJsonLineInfo.SetUp;
+begin
+  inherited SetUp;
+  FLineInfo := TJsonLineInfo.Create;
+end;
+
+procedure TTestJsonLineInfo.TearDown;
+begin
+  FLineInfo.Free;
+  inherited TearDown;
+end;
+
+procedure TTestJsonLineInfo.TestGetLineNumber;
+begin
+  AssertEquals('Default line number', 0, FLineInfo.GetLineNumber);
+end;
+
+procedure TTestJsonLineInfo.TestGetLinePosition;
+begin
+  AssertEquals('Default line position', 0, FLineInfo.GetLinePosition);
+end;
+
+procedure TTestJsonLineInfo.TestHasLineInfo;
+begin
+  AssertFalse('Default has no line info', FLineInfo.HasLineInfo);
+end;
+
+procedure TTestJsonLineInfo.TestLineNumberProperty;
+begin
+  AssertEquals('Line number property', 0, FLineInfo.LineNumber);
+end;
+
+procedure TTestJsonLineInfo.TestLinePositionProperty;
+begin
+  AssertEquals('Line position property', 0, FLineInfo.LinePosition);
+end;
+
+{ TTestJsonPosition }
+
+procedure TTestJsonPosition.TestCreateDefault;
+begin
+  FPosition := TJsonPosition.Create;
+  AssertEquals('Default container type', Ord(TJsonContainerType.None), Ord(FPosition.ContainerType));
+  AssertEquals('Default position', -1, FPosition.Position);
+  AssertEquals('Default property name', '', FPosition.PropertyName);
+  AssertFalse('Default has no index', FPosition.HasIndex);
+end;
+
+procedure TTestJsonPosition.TestCreateWithType;
+begin
+  FPosition := TJsonPosition.Create(TJsonContainerType.&Array);
+  AssertEquals('Array container type', Ord(TJsonContainerType.&Array), Ord(FPosition.ContainerType));
+  AssertTrue('Array has index', FPosition.HasIndex);
+  AssertEquals('Array position', -1, FPosition.Position);
+end;
+
+procedure TTestJsonPosition.TestClear;
+begin
+  FPosition.ContainerType := TJsonContainerType.&Object;
+  FPosition.Position := 5;
+  FPosition.PropertyName := 'test';
+  FPosition.Clear;
+  AssertEquals('Cleared container type', Ord(TJsonContainerType.None), Ord(FPosition.ContainerType));
+  AssertEquals('Cleared position', -1, FPosition.Position);
+  AssertEquals('Cleared property name', '', FPosition.PropertyName);
+end;
+
+procedure TTestJsonPosition.TestWriteToObject;
+var
+  Sb: TStringBuilder;
+begin
+  Sb := TStringBuilder.Create;
+  try
+    FPosition := TJsonPosition.Create(TJsonContainerType.&Object);
+    FPosition.PropertyName := 'test';
+    FPosition.WriteTo(Sb);
+    AssertEquals('Object path', 'test', Sb.ToString);
+
+    Sb.Clear;
+    Sb.Append('root');
+    FPosition.WriteTo(Sb);
+    AssertEquals('Object path with prefix', 'root.test', Sb.ToString);
+  finally
+    Sb.Free;
+  end;
+end;
+
+procedure TTestJsonPosition.TestWriteToArray;
+var
+  Sb: TStringBuilder;
+begin
+  Sb := TStringBuilder.Create;
+  try
+    FPosition := TJsonPosition.Create(TJsonContainerType.&Array);
+    FPosition.Position := 5;
+    FPosition.WriteTo(Sb);
+    AssertEquals('Array path', '[5]', Sb.ToString);
+  finally
+    Sb.Free;
+  end;
+end;
+
+procedure TTestJsonPosition.TestWriteToConstructor;
+var
+  Sb: TStringBuilder;
+begin
+  Sb := TStringBuilder.Create;
+  try
+    FPosition := TJsonPosition.Create(TJsonContainerType.&Constructor);
+    FPosition.Position := 3;
+    FPosition.WriteTo(Sb);
+    AssertEquals('Constructor path', '[3]', Sb.ToString);
+  finally
+    Sb.Free;
+  end;
+end;
+
+
+procedure TTestJsonPosition.TestBuildPathEmpty;
+var
+  Positions: TJsonPositionList;
+  Path: string;
+begin
+  Positions := TJsonPositionList.Create;
+  try
+    Path := TJsonPosition.BuildPath(Positions);
+    AssertEquals('Empty path', '', Path);
+  finally
+    Positions.Free;
+  end;
+end;
+
+procedure TTestJsonPosition.TestBuildPathSingle;
+var
+  Positions: TJsonPositionList;
+  Pos: TJsonPosition;
+  Path: string;
+begin
+  Positions := TJsonPositionList.Create;
+  try
+    Pos := TJsonPosition.Create(TJsonContainerType.&Object);
+    Pos.PropertyName := 'test';
+    Positions.Add(Pos);
+    Path := TJsonPosition.BuildPath(Positions);
+    AssertEquals('Single object path', 'test', Path);
+  finally
+    Positions.Free;
+  end;
+end;
+
+procedure TTestJsonPosition.TestBuildPathMultiple;
+var
+  Positions: TJsonPositionList;
+  Pos1, Pos2: TJsonPosition;
+  Path: string;
+begin
+  Positions := TJsonPositionList.Create;
+  try
+    Pos1 := TJsonPosition.Create(TJsonContainerType.&Object);
+    Pos1.PropertyName := 'root';
+    Positions.Add(Pos1);
+
+    Pos2 := TJsonPosition.Create(TJsonContainerType.&Array);
+    Pos2.Position := 0;
+    Positions.Add(Pos2);
+
+    Path := TJsonPosition.BuildPath(Positions);
+    AssertEquals('Multiple path', 'root[0]', Path);
+  finally
+    Positions.Free;
+  end;
+end;
+
+procedure TTestJsonPosition.TestFormatMessage;
+var
+  LineInfo: TJsonLineInfo;
+  Msg: string;
+begin
+  LineInfo := TJsonLineInfo.Create;
+  try
+    Msg := TJsonPosition.FormatMessage(LineInfo, 'test.path', 'Error occurred');
+    AssertTrue('Message contains error', Pos('Error occurred', Msg) > 0);
+    AssertTrue('Message contains path', Pos('test.path', Msg) > 0);
+  finally
+    LineInfo.Free;
+  end;
+end;
+
+{ TTestJsonFiler.TTestJsonFilerImpl }
+
+function TTestJsonFiler.TTestJsonFilerImpl.GetInsideContainer: Boolean;
+begin
+  Result := FCurrentPosition.ContainerType <> TJsonContainerType.None;
+end;
+
+{ TTestJsonFiler }
+
+procedure TTestJsonFiler.SetUp;
+begin
+  inherited SetUp;
+  FFiler := TTestJsonFilerImpl.Create;
+end;
+
+procedure TTestJsonFiler.TearDown;
+begin
+  FFiler.Free;
+  inherited TearDown;
+end;
+
+procedure TTestJsonFiler.TestCreateDestroy;
+begin
+  AssertNotNull('Filer created', FFiler);
+  AssertEquals('Empty path', '', FFiler.Path);
+end;
+
+procedure TTestJsonFiler.TestPushPop;
+begin
+  AssertEquals('Initial peek', Ord(TJsonContainerType.None), Ord(FFiler.Peek));
+
+  FFiler.Push(TJsonContainerType.&Object);
+  AssertEquals('After push object', Ord(TJsonContainerType.&Object), Ord(FFiler.Peek));
+
+  FFiler.Push(TJsonContainerType.&Array);
+  AssertEquals('After push array', Ord(TJsonContainerType.&Array), Ord(FFiler.Peek));
+
+  AssertEquals('Pop array', Ord(TJsonContainerType.&Array), Ord(FFiler.Pop));
+  AssertEquals('After pop array', Ord(TJsonContainerType.&Object), Ord(FFiler.Peek));
+
+  AssertEquals('Pop object', Ord(TJsonContainerType.&Object), Ord(FFiler.Pop));
+  AssertEquals('After pop object', Ord(TJsonContainerType.None), Ord(FFiler.Peek));
+end;
+
+procedure TTestJsonFiler.TestPeek;
+begin
+  AssertEquals('Initial peek', Ord(TJsonContainerType.None), Ord(FFiler.Peek));
+  FFiler.Push(TJsonContainerType.&Object);
+  AssertEquals('Peek object', Ord(TJsonContainerType.&Object), Ord(FFiler.Peek));
+  AssertEquals('Peek again', Ord(TJsonContainerType.&Object), Ord(FFiler.Peek));
+end;
+
+procedure TTestJsonFiler.TestGetPath;
+begin
+  AssertEquals('Empty path', '', FFiler.Path);
+  FFiler.Push(TJsonContainerType.&Object);
+  // Path building requires the position to be set up properly
+  // Since we haven't set any property names, the path should still be empty
+  AssertEquals('Path after push without properties', '', FFiler.Path);
+end;
+
+procedure TTestJsonFiler.TestRewind;
+begin
+  FFiler.Push(TJsonContainerType.&Object);
+  FFiler.Push(TJsonContainerType.&Array);
+  FFiler.Rewind;
+  AssertEquals('After rewind', Ord(TJsonContainerType.None), Ord(FFiler.Peek));
+end;
+
+procedure TTestJsonFiler.TestIsEndToken;
+begin
+  AssertTrue('EndObject is end', TJsonFiler.IsEndToken(TJsonToken.EndObject));
+  AssertTrue('EndArray is end', TJsonFiler.IsEndToken(TJsonToken.EndArray));
+  AssertTrue('EndConstructor is end', TJsonFiler.IsEndToken(TJsonToken.EndConstructor));
+  AssertFalse('StartObject is not end', TJsonFiler.IsEndToken(TJsonToken.StartObject));
+  AssertFalse('String is not end', TJsonFiler.IsEndToken(TJsonToken.&String));
+end;
+
+procedure TTestJsonFiler.TestIsStartToken;
+begin
+  AssertTrue('StartObject is start', TJsonFiler.IsStartToken(TJsonToken.StartObject));
+  AssertTrue('StartArray is start', TJsonFiler.IsStartToken(TJsonToken.StartArray));
+  AssertTrue('StartConstructor is start', TJsonFiler.IsStartToken(TJsonToken.StartConstructor));
+  AssertFalse('EndObject is not start', TJsonFiler.IsStartToken(TJsonToken.EndObject));
+  AssertFalse('String is not start', TJsonFiler.IsStartToken(TJsonToken.&String));
+end;
+
+procedure TTestJsonFiler.TestIsPrimitiveToken;
+begin
+  AssertTrue('Integer is primitive', TJsonFiler.IsPrimitiveToken(TJsonToken.Integer));
+  AssertTrue('Float is primitive', TJsonFiler.IsPrimitiveToken(TJsonToken.Float));
+  AssertTrue('String is primitive', TJsonFiler.IsPrimitiveToken(TJsonToken.&String));
+  AssertTrue('Boolean is primitive', TJsonFiler.IsPrimitiveToken(TJsonToken.Boolean));
+  AssertTrue('Null is primitive', TJsonFiler.IsPrimitiveToken(TJsonToken.Null));
+  AssertFalse('StartObject is not primitive', TJsonFiler.IsPrimitiveToken(TJsonToken.StartObject));
+  AssertFalse('EndObject is not primitive', TJsonFiler.IsPrimitiveToken(TJsonToken.EndObject));
+end;
+
+{ TTestJsonOid }
+
+procedure TTestJsonOid.TestCreateFromBytes;
+var
+  TestBytes: TBytes;
+begin
+  SetLength(TestBytes, 12);
+  TestBytes[0] := $01;
+  TestBytes[1] := $02;
+  TestBytes[11] := $0C;
+
+  FOid := TJsonOid.Create(TestBytes);
+  AssertEquals('First byte', $01, FOid.Bytes[0]);
+  AssertEquals('Second byte', $02, FOid.Bytes[1]);
+  AssertEquals('Last byte', $0C, FOid.Bytes[11]);
+end;
+
+procedure TTestJsonOid.TestCreateFromString;
+begin
+  FOid := TJsonOid.Create('0102030405060708090a0b0c');
+  AssertEquals('First byte from string', $01, FOid.Bytes[0]);
+  AssertEquals('Second byte from string', $02, FOid.Bytes[1]);
+  AssertEquals('Last byte from string', $0C, FOid.Bytes[11]);
+end;
+
+procedure TTestJsonOid.TestAsString;
+var
+  TestBytes: TBytes;
+begin
+  SetLength(TestBytes, 12);
+  TestBytes[0] := $01;
+  TestBytes[1] := $02;
+  TestBytes[11] := $0C;
+
+  FOid := TJsonOid.Create(TestBytes);
+  AssertEquals('String representation', '01020000000000000000000C', FOid.AsString.ToUpper);
+end;
+
+procedure TTestJsonOid.TestAsBytes;
+var
+  TestBytes, ResultBytes: TBytes;
+begin
+  SetLength(TestBytes, 12);
+  TestBytes[0] := $AB;
+  TestBytes[11] := $CD;
+
+  FOid := TJsonOid.Create(TestBytes);
+  ResultBytes := FOid.AsBytes;
+
+  AssertEquals('Byte array length', 12, Length(ResultBytes));
+  AssertEquals('First byte', $AB, ResultBytes[0]);
+  AssertEquals('Last byte', $CD, ResultBytes[11]);
+end;
+
+procedure TTestJsonOid.TestStringRoundTrip;
+const
+  TestString = '0123456789abcdef01234567';
+begin
+  FOid := TJsonOid.Create(TestString);
+  AssertEquals('String round trip', TestString.ToUpper, FOid.AsString.ToUpper);
+end;
+
+procedure TTestJsonOid.TestBytesRoundTrip;
+var
+  TestBytes, ResultBytes: TBytes;
+begin
+  SetLength(TestBytes, 12);
+  TestBytes[0] := $12;
+  TestBytes[5] := $34;
+  TestBytes[11] := $56;
+
+  FOid := TJsonOid.Create(TestBytes);
+  ResultBytes := FOid.AsBytes;
+
+  AssertEquals('Bytes round trip length', Length(TestBytes), Length(ResultBytes));
+  AssertEquals('Bytes round trip first', TestBytes[0], ResultBytes[0]);
+  AssertEquals('Bytes round trip middle', TestBytes[5], ResultBytes[5]);
+  AssertEquals('Bytes round trip last', TestBytes[11], ResultBytes[11]);
+end;
+
+procedure TTestJsonOid.TestInvalidStringLength;
+begin
+  try
+    FOid := TJsonOid.Create('invalid');
+    Fail('Should have raised exception for invalid string length');
+  except
+    on E: Exception do
+      AssertTrue('Correct exception type', E is EJsonException);
+  end;
+end;
+
+{ TTestJsonRegEx }
+
+procedure TTestJsonRegEx.TestCreate;
+begin
+  FRegEx := TJsonRegEx.Create('test.*', 'gi');
+  AssertEquals('RegEx pattern', 'test.*', FRegEx.RegEx);
+  AssertEquals('RegEx options', 'gi', FRegEx.Options);
+end;
+
+procedure TTestJsonRegEx.TestAsString;
+begin
+  FRegEx := TJsonRegEx.Create('test.*', 'gi');
+  AssertEquals('AsString format', '/test.*/gi', FRegEx.AsString);
+end;
+
+procedure TTestJsonRegEx.TestSetAsString;
+begin
+  FRegEx.AsString := '/test.*/gi';
+  AssertEquals('Set regex pattern', 'test.*', FRegEx.RegEx);
+  AssertEquals('Set regex options', 'gi', FRegEx.Options);
+end;
+
+procedure TTestJsonRegEx.TestSetAsStringVariations;
+begin
+  // Test single part
+  FRegEx.AsString := 'simple';
+  AssertEquals('Simple regex', 'simple', FRegEx.RegEx);
+  AssertEquals('Simple options', '', FRegEx.Options);
+
+  // Test two parts
+  FRegEx.AsString := '/pattern';
+  AssertEquals('Two part regex', 'pattern', FRegEx.RegEx);
+  AssertEquals('Two part options', '', FRegEx.Options);
+
+  // Test three parts (normal case)
+  FRegEx.AsString := '/pattern/flags';
+  AssertEquals('Three part regex', 'pattern', FRegEx.RegEx);
+  AssertEquals('Three part options', 'flags', FRegEx.Options);
+end;
+
+{ TTestJsonDBRef }
+
+procedure TTestJsonDBRef.TestCreateWithDB;
+begin
+  FDBRef := TJsonDBRef.Create('testdb', 'testcoll', '507f1f77bcf86cd799439011');
+  AssertEquals('DB name', 'testdb', FDBRef.DB);
+  AssertEquals('Collection name', 'testcoll', FDBRef.Ref);
+  AssertEquals('ID string', '507F1F77BCF86CD799439011', FDBRef.Id.AsString.ToUpper);
+end;
+
+procedure TTestJsonDBRef.TestCreateWithoutDB;
+begin
+  FDBRef := TJsonDBRef.Create('testcoll', '507f1f77bcf86cd799439011');
+  AssertEquals('Empty DB name', '', FDBRef.DB);
+  AssertEquals('Collection name', 'testcoll', FDBRef.Ref);
+  AssertEquals('ID string', '507F1F77BCF86CD799439011', FDBRef.Id.AsString.ToUpper);
+end;
+
+procedure TTestJsonDBRef.TestCreateWithOid;
+var
+  TestOid: TJsonOid;
+begin
+  TestOid := TJsonOid.Create('507f1f77bcf86cd799439011');
+  FDBRef := TJsonDBRef.Create('testdb', 'testcoll', TestOid);
+  AssertEquals('DB name with OID', 'testdb', FDBRef.DB);
+  AssertEquals('Collection name with OID', 'testcoll', FDBRef.Ref);
+  AssertEquals('ID from OID', TestOid.AsString.ToUpper, FDBRef.Id.AsString.ToUpper);
+end;
+
+procedure TTestJsonDBRef.TestAsString;
+begin
+  FDBRef := TJsonDBRef.Create('testdb', 'testcoll', '507f1f77bcf86cd799439011');
+  AssertEquals('Full string format', 'TESTDB.TESTCOLL.507F1F77BCF86CD799439011', FDBRef.AsString.ToUpper);
+
+  FDBRef := TJsonDBRef.Create('testcoll', '507f1f77bcf86cd799439011');
+  AssertEquals('No DB string format', 'TESTCOLL.507F1F77BCF86CD799439011', FDBRef.AsString.ToUpper);
+end;
+
+procedure TTestJsonDBRef.TestSetAsString;
+begin
+  FDBRef.AsString := 'testdb.testcoll.507f1f77bcf86cd799439011';
+  AssertEquals('Set DB from string', 'testdb', FDBRef.DB);
+  AssertEquals('Set collection from string', 'testcoll', FDBRef.Ref);
+
+  FDBRef.AsString := 'testcoll.507f1f77bcf86cd799439011';
+  AssertEquals('Set empty DB from string', '', FDBRef.DB);
+  AssertEquals('Set collection from short string', 'testcoll', FDBRef.Ref);
+end;
+
+{ TTestJsonCodeWScope }
+
+procedure TTestJsonCodeWScope.TestCreateEmpty;
+begin
+  FCodeWScope := TJsonCodeWScope.Create('function() { return 1; }', nil);
+  AssertEquals('Code value', 'function() { return 1; }', FCodeWScope.Code);
+  AssertEquals('Empty scope length', 0, Length(FCodeWScope.Scope));
+end;
+
+procedure TTestJsonCodeWScope.TestCreateWithScope;
+var
+  Scope: TStringList;
+begin
+  Scope := TStringList.Create;
+  try
+    Scope.Add('var1=value1');
+    Scope.Add('var2=value2');
+
+    FCodeWScope := TJsonCodeWScope.Create('function() { return var1 + var2; }', Scope);
+    AssertEquals('Code with scope', 'function() { return var1 + var2; }', FCodeWScope.Code);
+    AssertEquals('Scope length', 2, Length(FCodeWScope.Scope));
+    AssertEquals('First scope ident', 'var1', FCodeWScope.Scope[0].Ident);
+    AssertEquals('First scope value', 'value1', FCodeWScope.Scope[0].Value);
+    AssertEquals('Second scope ident', 'var2', FCodeWScope.Scope[1].Ident);
+    AssertEquals('Second scope value', 'value2', FCodeWScope.Scope[1].Value);
+  finally
+    Scope.Free;
+  end;
+end;
+
+{ TTestJsonDecimal128 }
+
+procedure TTestJsonDecimal128.TestCreateFromString;
+begin
+  // Basic test - actual implementation depends on assigned conversion functions
+  try
+    FDecimal := TJsonDecimal128.Create('123.45');
+    // If we get here, creation succeeded
+    AssertTrue('Created from string', True);
+  except
+    on EJsonException do
+      // Expected if conversion functions not implemented
+      AssertTrue('Expected exception for unimplemented decimal', True);
+  end;
+end;
+
+procedure TTestJsonDecimal128.TestCreateFromExtended;
+begin
+  try
+    FDecimal := TJsonDecimal128.Create(123.45);
+    AssertTrue('Created from extended', True);
+  except
+    on EJsonException do
+      AssertTrue('Expected exception for unimplemented decimal', True);
+  end;
+end;
+
+procedure TTestJsonDecimal128.TestIsZero;
+begin
+  FDecimal.lo := 0;
+  FDecimal.hi := $3040000000000000;
+  AssertTrue('Is zero', FDecimal.IsZero);
+
+  FDecimal.lo := 1;
+  AssertFalse('Not zero with lo=1', FDecimal.IsZero);
+end;
+
+procedure TTestJsonDecimal128.TestIsNan;
+begin
+  FDecimal.lo := 0;
+  FDecimal.hi := $7C00000000000000;
+  AssertTrue('Is NaN', FDecimal.IsNan);
+
+  FDecimal.hi := $7C00000000000001;
+  AssertFalse('Not NaN with different hi', FDecimal.IsNan);
+end;
+
+procedure TTestJsonDecimal128.TestIsPosInfinity;
+begin
+  FDecimal.lo := 0;
+  FDecimal.hi := $7800000000000000;
+  AssertTrue('Is positive infinity', FDecimal.IsPosInfinity);
+
+  FDecimal.hi := $7800000000000001;
+  AssertFalse('Not positive infinity with different hi', FDecimal.IsPosInfinity);
+end;
+
+procedure TTestJsonDecimal128.TestIsNegInfinity;
+begin
+  FDecimal.lo := 0;
+  FDecimal.hi := QWord($F800000000000000);
+  AssertTrue('Is negative infinity', FDecimal.IsNegInfinity);
+
+  FDecimal.hi := $7800000000000001;
+  AssertFalse('Not negative infinity with different hi', FDecimal.IsNegInfinity);
+end;
+
+procedure TTestJsonDecimal128.TestAsExtended;
+var
+  Result: Extended;
+begin
+  // Test zero
+  FDecimal.lo := 0;
+  FDecimal.hi := $3040000000000000;
+  Result := FDecimal.AsExtended;
+  AssertEquals('Zero as extended', 0.0, Result, 0.0001);
+
+  // Test NaN
+  FDecimal.lo := 0;
+  FDecimal.hi := $7C00000000000000;
+  Result := FDecimal.AsExtended;
+  AssertTrue('NaN as extended', IsNaN(Result));
+end;
+
+procedure TTestJsonDecimal128.TestAsString;
+begin
+  try
+    FDecimal.lo := 0;
+    FDecimal.hi := $3040000000000000;
+    // This will likely fail unless conversion functions are set up
+    FDecimal.AsString;
+    AssertTrue('String conversion succeeded', True);
+  except
+    on EJsonException do
+      AssertTrue('Expected exception for unimplemented string conversion', True);
+  end;
+end;
+
+{ TTestJsonNameAttribute }
+
+procedure TTestJsonNameAttribute.TearDown;
+begin
+  FAttribute.Free;
+  inherited TearDown;
+end;
+
+procedure TTestJsonNameAttribute.TestCreate;
+begin
+  FAttribute := JsonNameAttribute.Create('testName');
+  AssertNotNull('Attribute created', FAttribute);
+end;
+
+procedure TTestJsonNameAttribute.TestValue;
+begin
+  FAttribute := JsonNameAttribute.Create('testName');
+  AssertEquals('Attribute value', 'testName', FAttribute.Value);
+end;
+
+{ TTestEJsonException }
+
+procedure TTestEJsonException.TestCreateSimple;
+var
+  Ex: EJsonException;
+begin
+  Ex := EJsonException.Create('Test message');
+  try
+    AssertEquals('Simple message', 'Test message', Ex.Message);
+    AssertNull('No inner exception', Ex.InnerException);
+  finally
+    Ex.Free;
+  end;
+end;
+
+procedure TTestEJsonException.TestCreateWithInner;
+var
+  Inner: Exception;
+  Ex: EJsonException;
+begin
+  Inner := Exception.Create('Inner message');
+  try
+    Ex := EJsonException.Create('Outer message', Inner);
+    try
+      AssertEquals('Outer message', 'Outer message', Ex.Message);
+      AssertNotNull('Has inner exception', Ex.InnerException);
+      AssertSame('Same inner exception', Inner, Ex.InnerException);
+    finally
+      Ex.Free;
+    end;
+  finally
+    Inner.Free;
+  end;
+end;
+
+procedure TTestEJsonException.TestInnerException;
+var
+  Inner: Exception;
+  Ex: EJsonException;
+begin
+  Inner := Exception.Create('Inner message');
+  try
+    Ex := EJsonException.Create('Outer message', Inner);
+    try
+      AssertEquals('Inner exception message', 'Inner message', Ex.InnerException.Message);
+    finally
+      Ex.Free;
+    end;
+  finally
+    Inner.Free;
+  end;
+end;
+
+initialization
+  RegisterTests([
+    TTestJsonLineInfo,
+    TTestJsonPosition,
+    TTestJsonFiler,
+    TTestJsonOid,
+    TTestJsonRegEx,
+    TTestJsonDBRef,
+    TTestJsonCodeWScope,
+    TTestJsonDecimal128,
+    TTestJsonNameAttribute,
+    TTestEJsonException
+  ]);
+end.

+ 5 - 2
packages/wasm-utils/src/wasm.pcrebridge.pas

@@ -174,6 +174,7 @@ var
 
 begin
   Result:=Nil;
+  // Allocate memory for Move.
   SetLength(S,RegexLen);
   Move(Regexp^,S[1],SizeOf(Char)*RegexLen);
   F:=OptsToFlags(Opts);
@@ -182,7 +183,8 @@ begin
   except
     on E : Exception do
       begin
-      ErrorNr^:=PCRE2_ERROR_WASM; // Does not exist (yet)
+      if Assigned(ErrorNr) then
+        ErrorNr^:=PCRE2_ERROR_WASM; // Does not exist (yet)
       gLastError:=E.Message;
       ErrorPos^:=0;
       end;
@@ -287,7 +289,8 @@ begin
     CaptureOffset:=1+(I*EntryLen);
     NameOffset:=1+CharOffset+(I*EntryLen);
     Move(CaptureIdx,FNamesTable[CaptureOffset],SizeOf(Word));
-    Move(NS[1],FNamesTable[NameOffset],Length(NS)*SizeOf(Char));
+    if (NS<>'') then
+      Move(NS[1],FNamesTable[NameOffset],Length(NS)*SizeOf(Char));
     end;
 end;
 

+ 9 - 1
rtl/objpas/classes/classes.inc

@@ -909,7 +909,15 @@ begin
     as volatile, so the access won't be optimized away by the compiler. (KB) }
   for i:=1 to aIterations do
     begin
-      Inc(SpinWaitDummy); // SpinWaitDummy *MUST* be global
+    {$IF defined(CPUX86_64) or Defined(CPUI386)}
+    fpc_x86_pause;
+    {$ELSEIF defined(CPUARM)}
+    fpc_arm_yield;
+    {$ELSEIF defined(CPUAARCH64}
+    fpc_aarch64_yield;
+    {$ELSE}
+    Inc(SpinWaitDummy); // SpinWaitDummy *MUST* be global
+    {$ENDIF}
     end;
 end;
 

+ 117 - 293
tests/test/units/linux/thwprobe.pp

@@ -1,303 +1,127 @@
 { %target=linux }
 { %cpu=riscv32,riscv64 }
+{$mode objfpc}
 uses
   linux,sysutils;
 
+type
+  TRiscvExtension = record
+    mask: QWord;
+    name: string;
+  end;
+
+const
+  { Define all extensions to check }
+  RISCV_EXTENSIONS: array[0..58] of TRiscvExtension = (
+    { 0 } (mask: RISCV_HWPROBE_IMA_FD; name: 'F and D'),
+    { 1 } (mask: RISCV_HWPROBE_IMA_C; name: 'C'),
+    { 2 } (mask: RISCV_HWPROBE_IMA_V; name: 'V'),
+    { 3 } (mask: RISCV_HWPROBE_EXT_ZBA; name: 'ZBA'),
+    { 4 } (mask: RISCV_HWPROBE_EXT_ZBB; name: 'ZBB'),
+    { 5 } (mask: RISCV_HWPROBE_EXT_ZBS; name: 'ZBS'),
+    { 6 } (mask: RISCV_HWPROBE_EXT_ZICBOZ; name: 'ZICBOZ'),
+    { 7 } (mask: RISCV_HWPROBE_EXT_ZBC; name: 'ZBC'),
+    { 8 } (mask: RISCV_HWPROBE_EXT_ZBKB; name: 'ZBKB'),
+    { 9 } (mask: RISCV_HWPROBE_EXT_ZBKC; name: 'ZBKC'),
+    { 10 } (mask: RISCV_HWPROBE_EXT_ZBKX; name: 'ZBKX'),
+    { 11 } (mask: RISCV_HWPROBE_EXT_ZKND; name: 'ZKND'),
+    { 12 } (mask: RISCV_HWPROBE_EXT_ZKNE; name: 'ZKNE'),
+    { 13 } (mask: RISCV_HWPROBE_EXT_ZKNH; name: 'ZKNH'),
+    { 14 } (mask: RISCV_HWPROBE_EXT_ZKSED; name: 'ZKSED'),
+    { 15 } (mask: RISCV_HWPROBE_EXT_ZKSH; name: 'ZKSH'),
+    { 16 } (mask: RISCV_HWPROBE_EXT_ZKT; name: 'ZKT'),
+    { 17 } (mask: RISCV_HWPROBE_EXT_ZVBB; name: 'ZVBB'),
+    { 18 } (mask: RISCV_HWPROBE_EXT_ZVBC; name: 'ZVBC'),
+    { 19 } (mask: RISCV_HWPROBE_EXT_ZVKB; name: 'ZVKB'),
+    { 20 } (mask: RISCV_HWPROBE_EXT_ZVKG; name: 'ZVKG'),
+    { 21 } (mask: RISCV_HWPROBE_EXT_ZVKNED; name: 'ZVKNED'),
+    { 22 } (mask: RISCV_HWPROBE_EXT_ZVKNHA; name: 'ZVKNHA'),
+    { 23 } (mask: RISCV_HWPROBE_EXT_ZVKNHB; name: 'ZVKNHB'),
+    { 24 } (mask: RISCV_HWPROBE_EXT_ZVKSED; name: 'ZVKSED'),
+    { 25 } (mask: RISCV_HWPROBE_EXT_ZVKSH; name: 'ZVKSH'),
+    { 26 } (mask: RISCV_HWPROBE_EXT_ZVKT; name: 'ZVKT'),
+    { 27 } (mask: RISCV_HWPROBE_EXT_ZFH; name: 'ZFH'),
+    { 28 } (mask: RISCV_HWPROBE_EXT_ZFHMIN; name: 'ZFHMIN'),
+    { 29 } (mask: RISCV_HWPROBE_EXT_ZIHINTNTL; name: 'ZIHINTNTL'),
+    { 30 } (mask: RISCV_HWPROBE_EXT_ZVFH; name: 'ZVFH'),
+    { 31 } (mask: RISCV_HWPROBE_EXT_ZVFHMIN; name: 'ZVFHMIN'),
+    { 32 } (mask: RISCV_HWPROBE_EXT_ZFA; name: 'ZFA'),
+    { 33 } (mask: RISCV_HWPROBE_EXT_ZTSO; name: 'ZTSO'),
+    { 34 } (mask: RISCV_HWPROBE_EXT_ZACAS; name: 'ZACAS'),
+    { 35 } (mask: RISCV_HWPROBE_EXT_ZICOND; name: 'ZICOND'),
+    { 36 } (mask: RISCV_HWPROBE_EXT_ZIHINTPAUSE; name: 'ZIHINTPAUSE'),
+    { 37 } (mask: RISCV_HWPROBE_EXT_ZVE32X; name: 'ZVE32X'),
+    { 38 } (mask: RISCV_HWPROBE_EXT_ZVE32F; name: 'ZVE32F'),
+    { 39 } (mask: RISCV_HWPROBE_EXT_ZVE64X; name: 'ZVE64X'),
+    { 40 } (mask: RISCV_HWPROBE_EXT_ZVE64F; name: 'ZVE64F'),
+    { 41 } (mask: RISCV_HWPROBE_EXT_ZVE64D; name: 'ZVE64D'),
+    { 42 } (mask: RISCV_HWPROBE_EXT_ZIMOP; name: 'ZIMOP'),
+    { 43 } (mask: RISCV_HWPROBE_EXT_ZCA; name: 'ZCA'),
+    { 44 } (mask: RISCV_HWPROBE_EXT_ZCB; name: 'ZCB'),
+    { 45 } (mask: RISCV_HWPROBE_EXT_ZCD; name: 'ZCD'),
+    { 46 } (mask: RISCV_HWPROBE_EXT_ZCF; name: 'ZCF'),
+    { 47 } (mask: RISCV_HWPROBE_EXT_ZCMOP; name: 'ZCMOP'),
+    { 48 } (mask: RISCV_HWPROBE_EXT_ZAWRS; name: 'ZAWRS'),
+    { 49 } (mask: RISCV_HWPROBE_EXT_SUPM; name: 'SUPM'),
+    { 50 } (mask: RISCV_HWPROBE_EXT_ZFBFMIN; name: 'ZFBFMIN'),
+    { 51 } (mask: RISCV_HWPROBE_EXT_ZIHPM; name: 'ZIHPM'),
+    { 52 } (mask: RISCV_HWPROBE_EXT_ZFBMIN; name: 'ZFBMIN'),
+    { 53 } (mask: RISCV_HWPROBE_EXT_ZVFBFMIN; name: 'ZVFBFMIN'),
+    { 54 } (mask: RISCV_HWPROBE_EXT_ZVFBFWMA; name: 'ZVFBFWMA'),
+    { 55 } (mask: RISCV_HWPROBE_EXT_ZICBOM; name: 'ZICBOM'),
+    { 56 } (mask: RISCV_HWPROBE_EXT_ZAAMO; name: 'ZAAMO'),
+    { 57 } (mask: RISCV_HWPROBE_EXT_ZALRSC; name: 'ZALRSC'),
+    { 58 } (mask: RISCV_HWPROBE_EXT_ZABHA; name: 'ZABHA')
+  );
+
+procedure CheckExtension(value: QWord; ext: TRiscvExtension);
+  begin
+    if (value and ext.mask) <> 0 then
+      writeln(ext.name, ' extension supported')
+    else
+      writeln('  ', ext.name, ' extension not supported');
+  end;
+
+
+function GetAllTestedBits: QWord;
+  var
+    i: Integer;
+  begin
+    Result := 0;
+    for i := Low(RISCV_EXTENSIONS) to High(RISCV_EXTENSIONS) do
+      Result := Result or RISCV_EXTENSIONS[i].mask;
+  end;
+
+
 var
   ariscv_hwprobe: triscv_hwprobe;
+  i: Integer;
+  all_tested_bits: QWord;
+  untested_bits: QWord;
 
 begin
-  ariscv_hwprobe.key:=RISCV_HWPROBE_KEY_IMA_EXT_0;
-  riscv_hwprobe(@ariscv_hwprobe,1,0,nil,0);
-  writeln('Raw key value returned by RISCV_HWPROBE_KEY_IMA_EXT_0: %',Binstr(ariscv_hwprobe.value,64));
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_IMA_FD)<>0 then
-    writeln('F and D extensions supported')
-  else
-    writeln('  F and D extensions not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_IMA_V)<>0 then
-    writeln('V extension supported')
-  else
-    writeln('  V extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZBA)<>0 then
-    writeln('ZBA extension supported')
-  else
-    writeln('  ZBA extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZBB)<>0 then
-    writeln('ZBB extension supported')
-  else
-    writeln('  ZBB extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZBS)<>0 then
-    writeln('ZBS extension supported')
-  else
-    writeln('  ZBS extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZICBOZ)<>0 then
-    writeln('ZICBOZ extension supported')
-  else
-    writeln('  ZICBOZ extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZBC)<>0 then
-    writeln('ZBC extension supported')
-  else
-    writeln('  ZBC extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZBKB)<>0 then
-    writeln('ZBKB extension supported')
-  else
-    writeln('  ZBKB extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZBKC)<>0 then
-    writeln('ZBKC extension supported')
-  else
-    writeln('  ZBKC extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZBKX)<>0 then
-    writeln('ZBKX extension supported')
-  else
-    writeln('  ZBKX extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZKND)<>0 then
-    writeln('ZKND extension supported')
-  else
-    writeln('  ZKND extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZKNE)<>0 then
-    writeln('ZKNE extension supported')
-  else
-    writeln('  ZKNE extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZKNH)<>0 then
-    writeln('ZKNH extension supported')
-  else
-    writeln('  ZKNH extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZKSED)<>0 then
-    writeln('ZKSED extension supported')
-  else
-    writeln('  ZKSED extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZKSH)<>0 then
-    writeln('ZKSH extension supported')
-  else
-    writeln('  ZKSH extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZKT)<>0 then
-    writeln('ZKT extension supported')
-  else
-    writeln('  ZKT extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZVBB)<>0 then
-    writeln('ZVBB extension supported')
-  else
-    writeln('  ZVBB extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZVBC)<>0 then
-    writeln('ZVBC extension supported')
-  else
-    writeln('  ZVBC extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZVKB)<>0 then
-    writeln('ZVKB extension supported')
-  else
-    writeln('  ZVKB extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZVKG)<>0 then
-    writeln('ZVKG extension supported')
-  else
-    writeln('  ZVKG extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZVKNED)<>0 then
-    writeln('ZVKNED extension supported')
-  else
-    writeln('  ZVKNED extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZVKNHA)<>0 then
-    writeln('ZVKNHA extension supported')
-  else
-    writeln('  ZVKNHA extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZVKNHB)<>0 then
-    writeln('ZVKNHB extension supported')
-  else
-    writeln('  ZVKNHB extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZVKSED)<>0 then
-    writeln('ZVKSED extension supported')
-  else
-    writeln('  ZVKSED extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZVKSH)<>0 then
-    writeln('ZVKSH extension supported')
-  else
-    writeln('  ZVKSH extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZVKT)<>0 then
-    writeln('ZVKT extension supported')
-  else
-    writeln('  ZVKT extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZFH)<>0 then
-    writeln('ZFH extension supported')
-  else
-    writeln('  ZFH extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZFHMIN)<>0 then
-    writeln('ZFHMIN extension supported')
-  else
-    writeln('  ZFHMIN extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZIHINTNTL)<>0 then
-    writeln('ZIHINTNTL extension supported')
-  else
-    writeln('  ZIHINTNTL extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZVFH)<>0 then
-    writeln('ZVFH extension supported')
-  else
-    writeln('  ZVFH extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZVFHMIN)<>0 then
-    writeln('ZVFHMIN extension supported')
-  else
-    writeln('  ZVFHMIN extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZFA)<>0 then
-    writeln('ZFA extension supported')
-  else
-    writeln('  ZFA extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZTSO)<>0 then
-    writeln('ZTSO extension supported')
-  else
-    writeln('  ZTSO extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZACAS)<>0 then
-    writeln('ZACAS extension supported')
-  else
-    writeln('  ZACAS extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZICOND)<>0 then
-    writeln('ZICOND extension supported')
-  else
-    writeln('  ZICOND extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZIHINTPAUSE)<>0 then
-    writeln('ZIHINTPAUSE extension supported')
-  else
-    writeln('  ZIHINTPAUSE extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZVE32X)<>0 then
-    writeln('ZVE32X extension supported')
-  else
-    writeln('  ZVE32X extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZVE32F)<>0 then
-    writeln('ZVE32F extension supported')
-  else
-    writeln('  ZVE32F extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZVE64X)<>0 then
-    writeln('ZVE64X extension supported')
-  else
-    writeln('  ZVE64X extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZVE64F)<>0 then
-    writeln('ZVE64F extension supported')
-  else
-    writeln('  ZVE64F extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZVE64D)<>0 then
-    writeln('ZVE64D extension supported')
-  else
-    writeln('  ZVE64D extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZIMOP)<>0 then
-    writeln('ZIMOP extension supported')
-  else
-    writeln('  ZIMOP extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZCA)<>0 then
-    writeln('ZCA extension supported')
-  else
-    writeln('  ZCA extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZCB)<>0 then
-    writeln('ZCB extension supported')
-  else
-    writeln('  ZCB extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZCD)<>0 then
-    writeln('ZCD extension supported')
-  else
-    writeln('  ZCD extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZCF)<>0 then
-    writeln('ZCF extension supported')
-  else
-    writeln('  ZCF extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZCMOP)<>0 then
-    writeln('ZCMOP extension supported')
-  else
-    writeln('  ZCMOP extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZAWRS)<>0 then
-    writeln('ZAWRS extension supported')
-  else
-    writeln('  ZAWRS extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_SUPM)<>0 then
-    writeln('SUPM extension supported')
-  else
-    writeln('  SUPM extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZFBFMIN)<>0 then
-    writeln('ZFBFMIN extension supported')
-  else
-    writeln('  ZFBFMIN extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZVFBFMIN)<>0 then
-    writeln('ZVFBFMIN extension supported')
-  else
-    writeln('  ZVFBFMIN extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZVFBFWMA)<>0 then
-    writeln('ZVFBFWMA extension supported')
-  else
-    writeln('  ZVFBFWMA extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZVFBFMIN)<>0 then
-    writeln('ZVFBFMIN extension supported')
-  else
-    writeln('  ZVFBFMIN extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZVFBFWMA)<>0 then
-    writeln('ZVFBFWMA extension supported')
-  else
-    writeln('  ZVFBFWMA extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZICBOM)<>0 then
-    writeln('ZICBOM extension supported')
-  else
-    writeln('  ZICBOM extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZAAMO)<>0 then
-    writeln('ZAAMO extension supported')
-  else
-    writeln('  ZAAMO extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZALRSC)<>0 then
-    writeln('ZALRSC extension supported')
-  else
-    writeln('  ZALRSC extension not supported');
-
-  if (ariscv_hwprobe.value and RISCV_HWPROBE_EXT_ZABHA)<>0 then
-    writeln('ZABHA extension supported')
-  else
-    writeln('  ZABHA extension not supported');
+  ariscv_hwprobe.key := RISCV_HWPROBE_KEY_IMA_EXT_0;
+  riscv_hwprobe(@ariscv_hwprobe, 1, 0, nil, 0);
+  writeln('Raw key value returned by RISCV_HWPROBE_KEY_IMA_EXT_0: %',
+          Binstr(ariscv_hwprobe.value, 64));
+
+  { Check all extensions }
+  for i := Low(RISCV_EXTENSIONS) to High(RISCV_EXTENSIONS) do
+    CheckExtension(ariscv_hwprobe.value, RISCV_EXTENSIONS[i]);
+
+  { Verify all set bits are tested }
+  all_tested_bits := GetAllTestedBits;
+  untested_bits := ariscv_hwprobe.value and (not all_tested_bits);
+
+  if untested_bits <> 0 then
+  begin
+    writeln;
+    writeln('WARNING: The following bits are set but not tested:');
+    writeln('  Untested bits: %', Binstr(untested_bits, 64));
+  end
+  else
+  begin
+    writeln;
+    writeln('All set bits have been tested.');
+  end;
 end.

+ 19 - 0
tests/webtbs/tw41503.pp

@@ -0,0 +1,19 @@
+{ %NORUN }
+
+program tw41503;
+{$mode objFPC}
+
+procedure kek; public; cdecl;
+begin
+
+end;
+
+procedure kek0;              cdecl; varargs; external {'libkek'} name 'kek';
+procedure kek1(dummy: byte); cdecl; varargs; external {'libkek'} name 'kek';
+
+begin
+ kek1(0,1,2,3);  // OK \u2705
+ kek0;           // OK \u2705
+ kek0(0,1,2,3);  // IE \U0001f41e
+end.
+

+ 13 - 0
tests/webtbs/tw41506a.pp

@@ -0,0 +1,13 @@
+{ %NORUN }
+
+program tw41506a;
+{$mode objFPC}
+
+type
+  generic TGenClass<T> = class
+  end;
+  TCommon = tw41506a.specialize TGenClass<byte>;
+
+begin
+end.
+

+ 20 - 0
tests/webtbs/tw41506b.pp

@@ -0,0 +1,20 @@
+{ %NORUN }
+
+program tw41506b;
+{$mode ObjFPC}{$H+}
+
+uses uw41506;
+
+type
+  generic TGenClassCommon<T> = class F:T; end;
+
+  TCommon1 = specialize TGenClassCommon{declaration:TGenClassCommon}
+  <byte>;
+  TCommon2 = tw41506b.specialize TGenClassCommon{declaration:TGenClassCommon}
+  <byte>;
+  TCommon3 = uw41506.specialize TGenClassCommon{declaration:u_specialize_inline.TGenClassCommon}
+  <byte>;
+
+begin
+
+end.

+ 65 - 0
tests/webtbs/tw41516a.pp

@@ -0,0 +1,65 @@
+{ %NORUN }
+
+program tw41516a;{$Mode objfpc}
+type
+
+  TFoo = class
+    procedure       Foo2;
+  end;
+
+  generic TGen<T2: TObject> = class
+    procedure       Foo2;
+  end;
+
+  TWrap1 = class
+  public type
+    generic TTest4<T2: TObject> = class
+      procedure       Foo2;
+    end;
+
+  public
+    procedure         P1;
+  end;
+
+var
+  gTest4:  TWrap1.specialize TTest4<TObject>;
+
+{ TFoo }
+
+procedure TFoo.Foo2;
+var
+  v2:  TWrap1.specialize TTest4<TObject>;
+begin
+end;
+
+procedure TGen.Foo2;
+var
+  v2:  TWrap1.specialize TTest4<TObject>;
+begin
+end;
+
+// TWRAP1
+
+procedure TWrap1.TTest4.Foo2;
+var     // V2: project1.lpr(50,32) Error: Parameters or result types cannot contain local type definitions. Use a separate type definition in a type block.
+  v2:  TWrap1.specialize TTest4<TObject>;
+  v3:  specialize TTest4<TObject>;
+type
+  x2 =  TWrap1.specialize TTest4<TObject>;
+  x3 =  specialize TTest4<TObject>;
+begin
+end;
+
+procedure TWrap1.P1;
+var        // V2: project1.lpr(50,32) Error: Parameters or result types cannot contain local type definitions. Use a separate type definition in a type block.
+  v2:  TWrap1.specialize TTest4<TObject>;
+  v3:  specialize TTest4<TObject>;
+type
+  x2 =  TWrap1.specialize TTest4<TObject>;
+  x3 =  specialize TTest4<TObject>;
+begin
+end;
+
+begin
+end.
+

+ 65 - 0
tests/webtbs/tw41516b.pp

@@ -0,0 +1,65 @@
+{ %NORUN }
+
+program tw41516b;{$Mode delphi}
+type
+
+  TFoo = class
+    procedure       Foo2;
+  end;
+
+  TGen<T2: TObject> = class
+    procedure       Foo2;
+  end;
+
+  TWrap1 = class
+  public type
+    TTest4<T2: TObject> = class
+      procedure       Foo2;
+    end;
+
+  public
+    procedure         P1;
+  end;
+
+var
+  gTest4:  TWrap1.TTest4<TObject>;
+
+{ TFoo }
+
+procedure TFoo.Foo2;
+var
+  v2:  TWrap1.TTest4<TObject>;
+begin
+end;
+
+procedure TGen<T2>.Foo2;
+var
+  v2:  TWrap1.TTest4<TObject>;
+begin
+end;
+
+// TWRAP1
+
+procedure TWrap1.TTest4<T2>.Foo2;
+var     // V2: project1.lpr(50,32) Error: Parameters or result types cannot contain local type definitions. Use a separate type definition in a type block.
+  v2:  TWrap1.TTest4<TObject>;
+  v3:  TTest4<TObject>;
+type
+  x2 =  TWrap1.TTest4<TObject>;
+  x3 =  TTest4<TObject>;
+begin
+end;
+
+procedure TWrap1.P1;
+var        // V2: project1.lpr(50,32) Error: Parameters or result types cannot contain local type definitions. Use a separate type definition in a type block.
+  v2:  TWrap1.TTest4<TObject>;
+  v3:  TTest4<TObject>;
+type
+  x2 =  TWrap1.TTest4<TObject>;
+  x3 =  TTest4<TObject>;
+begin
+end;
+
+begin
+end.
+

+ 116 - 0
tests/webtbs/tw41526.pp

@@ -0,0 +1,116 @@
+program tw41526;
+
+{$IFDEF FPC}
+  {$MODE DELPHI}
+{$ENDIF}
+
+{$APPTYPE CONSOLE}
+
+uses
+  SysUtils,
+  Rtti,
+  TypInfo,
+  StrUtils;
+
+type
+  // Simple class
+  TMyClass = class
+  end;
+
+  // Simple record
+  TMyRecord = record
+    A, B: Integer;
+  end;
+
+  // Simple interface
+  IMyInterface = interface
+    ['{6BB98F7E-4B9E-4C88-8F76-90C2E47FB3D9}']
+    procedure DoSomething;
+  end;
+
+procedure CheckName(const aActual, aExpected: String);
+var
+  tmpactual, tmpexpected: String;
+begin
+  tmpactual := StringReplace(aActual, ' ', '', [rfReplaceAll]);
+  tmpexpected := StringReplace(aExpected, ' ', '', [rfReplaceAll]);
+  if not SameText(tmpactual, tmpexpected) then begin
+    Writeln('Name mismatch');
+    Writeln('   Expected: ', tmpexpected);
+    Writeln('   Actual: ', tmpactual);
+    ExitCode := 3;
+  end;
+end;
+
+var
+  Ctx: TRttiContext;
+  RType: TRttiType;
+  iname, sname: String;
+begin
+  Ctx := TRttiContext.Create;
+  try
+    RType := Ctx.GetType(TypeInfo(Integer));
+    if RType <> nil then
+      iname := RType.Name
+    else begin
+      Writeln('Integer RTTI not found');
+      Halt(1);
+    end;
+
+    RType := Ctx.GetType(TypeInfo(string));
+    if RType <> nil then
+      sname := RType.Name
+    else begin
+      Writeln('String RTTI not found');
+      Halt(2);
+    end;
+
+    // Dynamic array type (array of Integer)
+    RType := Ctx.GetType(TypeInfo(TArray<Integer>));
+    if RType <> nil then begin
+      Writeln('TArray<Integer> Name:    ', RType.Name);
+      CheckName(RType.Name, 'TArray<System.' + iname + '>');
+    end else
+      Writeln('TArray<Integer> RTTI not found');
+
+    // Dynamic array type (array of string)
+    RType := Ctx.GetType(TypeInfo(TArray<string>));
+    if RType <> nil then begin
+      Writeln('TArray<string> Name:    ', RType.Name);
+      CheckName(RType.Name, 'TArray<System.' + sname + '>');
+    end else
+      Writeln('TArray<string> RTTI not found');
+
+    // Dynamic array type (array of TMyClass)
+    RType := Ctx.GetType(TypeInfo(TArray<TMyClass>));
+    if RType <> nil then begin
+      Writeln('TArray<TMyClass> Name:    ', RType.Name);
+      CheckName(RType.Name, 'TArray<tw41526.TMyClass>');
+    end else
+      Writeln('TArray<TMyClass> RTTI not found');
+
+    // Dynamic array type (array of TMyRecord)
+    RType := Ctx.GetType(TypeInfo(TArray<TMyRecord>));
+    if RType <> nil then begin
+      Writeln('TArray<TMyRecord> Name:    ', RType.Name);
+      CheckName(RType.Name, 'TArray<tw41526.TMyRecord>');
+    end else
+      Writeln('TArray<TMyRecord> RTTI not found');
+
+    // Dynamic array type (array of IMyInterface)
+    RType := Ctx.GetType(TypeInfo(TArray<IMyInterface>));
+    if RType <> nil then begin
+      Writeln('TArray<IMyInterface> Name:    ', RType.Name);
+      CheckName(RType.Name, 'TArray<tw41526.IMyInterface>');
+    end else
+      Writeln('TArray<IMyInterface> RTTI not found');
+
+  finally
+    Ctx.Free;
+  end;
+
+  //Writeln;
+  //Writeln('Press ENTER to exit...');
+  //Readln;
+end.
+

+ 11 - 0
tests/webtbs/uw41506.pp

@@ -0,0 +1,11 @@
+unit uw41506;
+{$mode ObjFPC}{$H+}
+interface
+
+type
+
+  generic TGenClassCommon<T> = class F:T; end;
+implementation
+
+end.
+

+ 2 - 6
utils/fpdoc/dglobals.pp

@@ -1212,18 +1212,14 @@ end;
 function TFPDocEngine.FindAbsoluteLink(const AName: String): String;
 var
   LinkNode: TLinkNode;
-  P : String;
+
 begin
   // Writeln('Finding absolute link: ',aName);
   LinkNode := RootLinkNode.FindChild(AName);
   if Assigned(LinkNode) then
-    begin
-    Result := LinkNode.Link;
-    P:=LinkNode.Path;
-    end
+    Result := LinkNode.Link
   else
     SetLength(Result, 0);
-  // Writeln('Finding absolute link: ',aName,' (Node: ',P,') --> ',Result);
 end;
 
 function TFPDocEngine.ResolveLinkInPackages(AModule: TPasModule; const ALinkDest: String; Strict : Boolean = False): String;

+ 124 - 17
utils/fpdoc/dw_basehtml.pp

@@ -18,12 +18,23 @@ unit dw_basehtml;
 
 interface
 
-uses Classes, DOM, DOM_HTML, dGlobals, PasTree, dWriter;
+uses Classes, contnrs, DOM, DOM_HTML, dGlobals, PasTree, dWriter;
 
 
 type
 
-  { THTMLWriter }
+  { TLinkIdentifierMap }
+
+  TLinkIdentifierMap = class
+    Flist : TFPStringHashTable;
+    FWriter: TMultiFileDocWriter;
+  Public
+    constructor create(aWriter: TMultiFileDocWriter);
+    destructor destroy; override;
+    function GetLink(const aIdentifier : String) : String;
+    procedure AddLink(const aName, aLink : String);
+    procedure AddLink(const AElement : TPasElement);
+  end;
 
   { TBaseHTMLWriter }
 
@@ -38,6 +49,8 @@ type
     FCurOutputNode: TDOMNode;
     FDoPasHighlighting : Boolean;
     FHighlighterFlags: Byte;
+    FContentElementStack : Array of THTMLElement;
+    FContentElementCount : Integer;
   Protected
 
     Procedure SetContentElement(aElement : THTMLELement); virtual;
@@ -106,11 +119,14 @@ type
     procedure AppendKw(Parent: TDOMNode; const AText: AnsiString); virtual;
     procedure AppendKw(Parent: TDOMNode; const AText: DOMString); virtual;
     function  AppendPasSHFragment(Parent: TDOMNode; const AText: String; AShFlags: Byte): Byte; virtual;
+    function  AppendPasSHFragment(Parent: TDOMNode; const AText: String; AShFlags: Byte; aLinkIdentifierMap : TLinkIdentifierMap): Byte; virtual;
     procedure AppendFragment(aParentNode: TDOMElement; aStream: TStream); virtual;
     // FPDoc specifics
     procedure AppendSourceRef(aParent: TDOMElement; AElement: TPasElement);
     Procedure AppendSeeAlsoSection(AElement: TPasElement; DocNode: TDocNode); virtual;
+    Procedure AppendSeeAlsoSection(AElement: TPasElement; aParent : TDOMElement; DocNode: TDocNode); virtual;
     Procedure AppendExampleSection(AElement : TPasElement;DocNode : TDocNode); virtual;
+    Procedure AppendExampleSection(AElement : TPasElement;aParent : TDOMElement; DocNode : TDocNode); virtual;
     Procedure AppendShortDescr(Parent: TDOMNode; Element: TPasElement); virtual;
     procedure AppendShortDescr(AContext: TPasElement; Parent: TDOMNode; DocNode: TDocNode); virtual;
     procedure AppendShortDescrCell(Parent: TDOMNode; Element: TPasElement); virtual;
@@ -121,7 +137,8 @@ type
 
     // Helper functions for creating DOM elements
 
-    function CreateEl(Parent: TDOMNode; const AName: DOMString): THTMLElement; virtual;
+    function CreateEl(Parent: TDOMNode; const AName: DOMString): THTMLElement; virtual; overload;
+    function CreateEl(Parent: TDOMNode; const AName, aClass: DOMString): THTMLElement; virtual; overload;
     function CreatePara(Parent: TDOMNode): THTMLElement; virtual;
     function CreateH1(Parent: TDOMNode): THTMLElement; virtual;
     function CreateH2(Parent: TDOMNode): THTMLElement; virtual;
@@ -137,6 +154,11 @@ type
     function CreateCode(Parent: TDOMNode): THTMLElement; virtual;
     function CreateWarning(Parent: TDOMNode): THTMLElement; virtual;
 
+    // Push new content element. Returns the old content element.
+    function PushContentElement(aElement: THTMLELement) : THTMLElement;
+    // Pop content element, returns the old content element.
+    function PopContentElement : THTMLElement;
+
 
     // Some info
     Property ContentElement : THTMLELement Read FContentElement Write SetContentElement;
@@ -158,7 +180,7 @@ Function FixHTMLpath(const S : String) : STring;
 
 implementation
 
-uses fpdocstrs, xmlread, sysutils, sh_pas;
+uses fpdocstrs, xmlread, sysutils, sh_pas ;
 
 Function FixHTMLpath(const S : String) : STring;
 
@@ -166,6 +188,44 @@ begin
   Result:=StringReplace(S,'\','/',[rfReplaceAll]);
 end;
 
+{ TLinkIdentifierMap }
+
+constructor TLinkIdentifierMap.create(aWriter: TMultiFileDocWriter);
+begin
+  FList:=TFPStringHashTable.Create;
+  FWriter:=aWriter;
+end;
+
+destructor TLinkIdentifierMap.destroy;
+begin
+  Flist.Free;
+  inherited destroy;
+end;
+
+function TLinkIdentifierMap.GetLink(const aIdentifier: String): String;
+begin
+  Result:=FList.Items[Trim(LowerCase(aIdentifier))]
+end;
+
+procedure TLinkIdentifierMap.AddLink(const aName, aLink: String);
+begin
+  if (aName='') or (aLink='') then
+    exit;
+  FList.Add(LowerCase(aName),aLink);
+end;
+
+procedure TLinkIdentifierMap.AddLink(const AElement: TPasElement);
+var
+  lLink : String;
+begin
+  if aElement.Name<>'' then
+    begin
+    lLink:=FWriter.ResolveLinkID(aElement.FullName);
+    if lLink<>'' then
+      AddLink(aElement.Name,lLink);
+    end;
+end;
+
 constructor TBaseHTMLWriter.Create(APackage: TPasPackage; AEngine: TFPDocEngine);
 
 begin
@@ -181,7 +241,7 @@ begin
   inherited Destroy;
 end;
 
-Procedure TBaseHTMLWriter.SetContentElement(aElement : THTMLELement);
+procedure TBaseHTMLWriter.SetContentElement(aElement: THTMLELement);
 
 begin
   FContentElement:=aElement;
@@ -194,6 +254,12 @@ begin
   Parent.AppendChild(Result);
 end;
 
+function TBaseHTMLWriter.CreateEl(Parent: TDOMNode; const AName, aClass: DOMString): THTMLElement;
+begin
+  Result:=CreateEl(Parent,aName);
+  Result['class']:=aClass;
+end;
+
 function TBaseHTMLWriter.CreatePara(Parent: TDOMNode): THTMLElement;
 begin
   Result := CreateEl(Parent, 'p');
@@ -275,6 +341,26 @@ begin
   Result['class'] := 'warning';
 end;
 
+function TBaseHTMLWriter.PushContentElement(aElement: THTMLELement): THTMLElement;
+begin
+  if FContentElementCount=Length(FContentElementStack) then
+    SetLength(FContentElementStack,FContentElementCount+10);
+  Result:=FContentElement;
+  FContentElementStack[FContentElementCount]:=Result;
+  FContentElement:=aElement;
+  Inc(FContentElementCount);
+end;
+
+function TBaseHTMLWriter.PopContentElement: THTMLElement;
+begin
+  if FContentElementCount=0 then
+    Raise EFPDocWriterError.Create('Cannot pop content element, at bottom of stack');
+  Result:=FContentElement;
+  FContentElement:=FContentElementStack[FContentElementCount-1];
+  FContentElementStack[FContentElementCount-1]:=Nil;
+  Dec(FContentElementCount);
+end;
+
 procedure TBaseHTMLWriter.DescrEmitNotesHeader(AContext: TPasElement);
 begin
   AppendText(CreateH2(ContentElement), SDocNotes);
@@ -405,8 +491,12 @@ begin
 end;
 
 procedure TBaseHTMLWriter.DescrWriteVarEl(const AText: DOMString);
+var
+  NewEl: TDOMElement;
 begin
-  AppendText(CreateEl(CurOutputNode, 'var'), AText);
+  NewEl := CreateEl(CurOutputNode, 'span');
+  NewEl['class'] := 'identifier';
+  AppendText(NewEl, AText);
 end;
 
 procedure TBaseHTMLWriter.DescrBeginLink(const AId: DOMString);
@@ -704,6 +794,12 @@ end;
 function TBaseHTMLWriter.AppendPasSHFragment(Parent: TDOMNode;
   const AText: String; AShFlags: Byte): Byte;
 
+begin
+  Result:=AppendPasSHFragment(Parent,aText,AShFlags,Nil);
+end;
+
+function TBaseHTMLWriter.AppendPasSHFragment(Parent: TDOMNode; const AText: String; AShFlags: Byte;
+  aLinkIdentifierMap: TLinkIdentifierMap): Byte;
 
 var
   Line, Last, p: PChar;
@@ -780,8 +876,13 @@ begin
 end;
 
 
-procedure TBaseHTMLWriter.AppendSeeAlsoSection ( AElement: TPasElement;
-  DocNode: TDocNode ) ;
+
+procedure TBaseHTMLWriter.AppendSeeAlsoSection (AElement: TPasElement; DocNode: TDocNode) ;
+begin
+  AppendSeeAlsoSection(aElement,ContentElement,DocNode);
+end;
+
+procedure TBaseHTMLWriter.AppendSeeAlsoSection(AElement: TPasElement; aParent: TDOMElement; DocNode: TDocNode);
 
 var
   Node: TDOMNode;
@@ -801,8 +902,8 @@ begin
        if IsFirstSeeAlso then
          begin
          IsFirstSeeAlso := False;
-         AppendText(CreateH2(ContentElement), SDocSeeAlso);
-         TableEl := CreateTable(ContentElement);
+         AppendText(CreateH2(aParent), SDocSeeAlso);
+         TableEl := CreateTable(aParent);
          end;
        El:=TDOMElement(Node);
        TREl:=CreateTR(TableEl);
@@ -861,6 +962,12 @@ end;
 
 procedure TBaseHTMLWriter.AppendExampleSection ( AElement: TPasElement; DocNode: TDocNode ) ;
 
+begin
+  AppendExampleSection(AElement,ContentElement,DocNode);
+end;
+
+Procedure TBaseHTMLWriter.AppendExampleSection(AElement : TPasElement;aParent : TDOMElement; DocNode : TDocNode);
+
 var
   Node: TDOMNode;
   fn,s: String;
@@ -877,12 +984,12 @@ begin
       fn:=Engine.GetExampleFilename(TDOMElement(Node));
       If (fn<>'') then
         begin
-        AppendText(CreateH2(ContentElement), SDocExample);
+        AppendText(CreateH2(aParent), SDocExample);
         try
           Assign(f, FN);
           Reset(f);
           try
-            PushOutputNode(ContentElement);
+            PushOutputNode(aParent);
             DescrBeginCode(False, UTF8Encode(TDOMElement(Node)['highlighter']));
             while not EOF(f) do
               begin
@@ -1005,8 +1112,8 @@ var
 begin
   if Not Assigned(Element) then
     begin
-    Result := nil;
-    AppendText(CreateWarning(Parent), '<NIL>');
+    Result := CreateWarning(Parent);
+    AppendText(Result, '<NIL>');
     exit;
     end
   else if Element.InheritsFrom(TPasUnresolvedTypeRef) then
@@ -1066,11 +1173,11 @@ begin
     end
   else
     begin
-    Result := nil;
+    Result := CreateEl(Parent,'span');
     if  Element is TPasAliasType then
-      AppendText(Parent, TPasAliasType(Element).DestType.Name)
+      AppendText(Result, TPasAliasType(Element).DestType.Name)
     else
-      AppendText(Parent, Element.Name); // unresolved items
+      AppendText(Result, Element.Name); // unresolved items
     end;
 end;
 

+ 2405 - 0
utils/fpdoc/dw_newhtml.pp

@@ -0,0 +1,2405 @@
+{
+    FPDoc  -  Free Pascal Documentation Tool
+    Copyright (C) 2000 - 2005 by
+      Areca Systems GmbH / Sebastian Guenther, [email protected]
+
+    * HTML/XHTML output generator
+
+    See the file COPYING, 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.
+}
+{$mode objfpc}
+{$H+}
+
+unit dw_newhtml;
+{$WARN 5024 off : Parameter "$1" not used}
+interface
+
+uses Classes, DOM, DOM_HTML, dGlobals, PasTree, dWriter, dw_basehtml, PasWrite;
+
+
+type
+  { TNewHTMLWriter }
+
+  TNewHTMLWriter = class(TBaseHTMLWriter)
+  private
+    FCreateSideMenu: Boolean;
+    FHeadElement: TDomElement;
+    FOnTest: TNotifyEvent;
+    FCSSFile: String;
+    FCharSet : String;
+    FHeaderHTML,
+    FNavigatorHTML,
+    FFooterHTML: TStringStream;
+    FTitleElement: TDOMElement;
+    FIncludeDateInFooter : Boolean;
+    FUseMenuBrackets: Boolean;
+    FDateFormat: String;
+    FIndexColCount : Integer;
+    FSearchPage : String;
+    function GetVarDef(aElement: TPasVariable; aPrefixParent: Boolean): string;
+    procedure SetModuleInfo(aElement: TPasElement; ASubpageIndex: integer);
+    procedure SetOnTest(const AValue: TNotifyEvent);
+  protected
+    function CreateAllocator : TFileAllocator; override;
+    procedure WriteDocPage(const aFileName: String; aElement: TPasElement; aSubPageIndex: Integer);  override;
+    // General HTML creation
+    function CreateH1(Parent: TDOMNode): THTMLElement; override;
+    function CreateH2(Parent: TDOMNode): THTMLElement; override;
+    function CreateH3(Parent: TDOMNode): THTMLElement; override;
+
+    function CreateListColumn1(aParent: THTMLElement): THTMLElement;
+    function CreateListColumn2(aParent: THTMLElement): THTMLElement;
+    function CreateListColumns(aParent: THTMLElement): THTMLElement;
+    function CreateSection(aParent : THTMLElement) : THTMLElement; virtual;
+    procedure DescrWriteFileEl(const AText: DOMString); override;
+    procedure DescrWriteVarEl(const AText: DOMString); override;
+
+    function  AppendPasSHFragment(Parent: TDOMNode; const AText: String; AShFlags: Byte): Byte; override;
+    function  AppendPasSHFragment(Parent: TDOMNode; const AText: String; AShFlags: Byte; aLinkIdentifierMap : TLinkIdentifierMap): Byte; override;
+
+    procedure CreateCSSFile; virtual;
+
+    procedure AppendTitle(aParent: TDomElement; const AText: AnsiString; Hints : TPasMemberHints = []); virtual;
+    procedure AppendTitle(aParent: TDomElement; const AText: DOMString; Hints: TPasMemberHints=[]); virtual;
+    function AppendType(CodeEl: TDOMElement; Element: TPasType): TDOMElement; virtual;
+    function AppendProcType(CodeEl : TDOMElement;  Element: TPasProcedureType; Indent: Integer): TDOMElement; virtual;
+    procedure AppendProcExt(CodeEl: TDOMElement; Element: TPasProcedure); virtual;
+    procedure AppendProcDecl(CodeEl: TDOMElement; Element: TPasProcedureBase); virtual;
+    procedure AppendProcArgsSection(Parent: THTMLElement; Element: TPasProcedureType; SkipResult : Boolean = False); virtual;
+    procedure AppendShortDescrCell(Parent: TDOMNode; Element: TPasElement); override;
+    procedure AppendDescrSection(AContext: TPasElement; Parent: TDOMNode; DescrNode: TDOMElement; const ATitle: DOMString); override;
+    Procedure AppendSeeAlsoSection(AElement: TPasElement; aParent : TDOMElement; DocNode: TDocNode); override;
+
+    // Structural elements
+    procedure AppendMenuBar(ASubpageIndex: Integer);virtual;
+    procedure AppendTopicMenuBar(Topic : TTopicElement);virtual;
+    procedure FinishElementPage(AElement: TPasElement; aDescription: Boolean=True); virtual;
+    procedure AppendFooter;virtual;
+    procedure AppendSideMenuScript(aHead : THTMLElement);
+    procedure AppendSideMenu(aMenu: THTMLElement);
+
+    // Class
+    procedure CreateClassMainPage(aClass: TPasClassType);virtual;
+    procedure CreateClassInheritedSubpage(AClass: TPasClassType; aType: TClassMemberType);
+    procedure CreateClassSortedSubpage(AClass: TPasClassType; aType : TClassMemberType);virtual;
+    procedure CreateClassMemberList(aParent: THTMLElement; AClass: TPasClassType; DeclaredOnly: Boolean; AFilter: TMemberFilter);
+    procedure AppendMemberListSection(aParent: THTMLELement; aClass: TPasClassType; aMemberType : TClassMemberType; aDeclaredOnly: Boolean);
+    procedure AppendInheritanceTree(aParent: THTMLELement; aClass: TPasClassType);
+
+    // Package
+
+    procedure CreatePageBody(AElement: TPasElement; ASubpageIndex: Integer); virtual;
+    procedure CreatePackagePageBody;virtual;
+    procedure CreatePackageIndex;
+    procedure CreatePackageClassHierarchy;
+    procedure CreateClassHierarchyPage(AddUnit : Boolean);
+    procedure CreateIndexPage(aParent : THTMLElement; L : TStringList); virtual;
+    // Topic
+    Procedure CreateTopicPageBody(AElement : TTopicElement);
+    // Module
+    procedure CreateModuleMainPage(aModule: TPasModule);virtual;
+    procedure CreateModuleSimpleSubpage(aModule: TPasModule; ASubpageIndex: Integer; const ATitle: DOMString; AList: TFPList);virtual;
+    procedure CreateModuleResStringsPage(aModule: TPasModule);virtual;
+    procedure CreateModulePageBody(AModule: TPasModule; ASubpageIndex: Integer);
+    procedure CreateModuleIndexPage(AModule: TPasModule); virtual;
+    // Identifiers
+    procedure CreateConstPageBody(AConst: TPasConst);
+    procedure CreateTypePageBody(AType: TPasType);
+    procedure CreateClassPageBody(AClass: TPasClassType; ASubpageIndex: Integer);
+    procedure CreateClassMemberPageBody(AElement: TPasElement);
+    procedure CreateVarPageBody(AVar: TPasVariable);
+    procedure CreateProcPageBody(AProc: TPasProcedureBase);
+    Procedure CreateTopicLinks(aParent : THTMLElement; Node : TDocNode; PasElement : TPasElement);
+    // Type declarations
+    function GetElementCode(aElement: TPasElement; aSparse: boolean; aFlags: TElementFlags = []): String;
+    function AppendHighlightedCode(aParent: TDOMNode; aCode: String; const aLanguage: String=''; aMap: TLinkIdentifierMap = Nil): THTMLElement;
+    function CreateCodeLines(aLines: array of string): string;
+    procedure AppendTypeDecl(AType: TPasType);
+    procedure AppendAliasTypeDecl(aType: TPasAliasType);
+    procedure AppendClassOfTypeDecl(aType: TPasClassOfType);
+    function  AppendCodeBlock(aParent: TDOMNode; const aLanguage: String=''): THTMLElement;
+    procedure AppendEnumTypeDecl(aType: TPasEnumType);
+    procedure AppendPointerTypeDecl(aType: TPasPointerType);
+    procedure AppendProcedureTypeDecl(aType: TPasProcedureType);
+    procedure AppendRecordTypeDecl(aType: TPasRecordType);
+    procedure AppendSetTypeDecl(aType: TPasSetType);
+    procedure AppendTypeAliasTypeDecl(aType: TPasTypeAliasType);
+
+    //  Main documentation process
+    Procedure DoWriteDocumentation; override;
+
+    Property HeaderHTML : TStringStream Read FHeaderHTML;
+    Property NavigatorHTML : TStringStream read FNavigatorHTML;
+    Property FooterHTML : TStringStream read FFooterHTML;
+    Property CSSFile : String Read FCSSFile;
+    Property HeadElement : TDomElement Read FHeadElement;
+    Property TitleElement: TDOMElement Read FTitleElement;
+  public
+    // Creating all module hierarchy classes happens here !
+    constructor Create(APackage: TPasPackage; AEngine: TFPDocEngine); override;
+    // Overrides
+    Class Function FileNameExtension : String; override;
+    class procedure Usage(List: TStrings); override;
+    Class procedure SplitImport(var AFilename, ALinkPrefix: String); override;
+
+    Function InterPretOption(Const Cmd,Arg : String) : boolean; override;
+
+    // Single-page generation
+    function CreateHTMLPage(AElement: TPasElement; ASubpageIndex: Integer): TXMLDocument; virtual;
+
+    Property SearchPage: String Read FSearchPage Write FSearchPage;
+    Property IncludeDateInFooter : Boolean Read FIncludeDateInFooter Write FIncludeDateInFooter;
+    Property DateFormat : String Read FDateFormat Write FDateFormat;
+    property OnTest: TNotifyEvent read FOnTest write SetOnTest;
+    Property CharSet : String Read FCharSet Write FCharSet;
+    Property IndexColCount : Integer Read FIndexColCount write FIndexColCount;
+    Property CreateSideMenu : Boolean Read FCreateSideMenu Write FCreateSideMenu;
+  end;
+
+
+implementation
+
+uses fpdocstrs, SysUtils, HTMWrite, syntax.highlighter, syntax.pascal, fpdocclasstree;
+
+{$i newcss.inc}
+
+constructor TNewHTMLWriter.Create(APackage: TPasPackage; AEngine: TFPDocEngine);
+
+begin
+  inherited Create(APackage, AEngine);
+  // should default to true since this is the old behavior
+  CreateSideMenu:=True;
+  IndexColCount:=3;
+  Charset:='iso-8859-1';
+  FCSSFile:='fpdocs.css';
+end;
+
+procedure TNewHTMLWriter.AppendSideMenuScript(aHead: THTMLElement);
+
+Const
+  SFunc =
+    '  document.addEventListener("DOMContentLoaded", () => {'+sLinebreak+
+    '       const toggleButton = document.getElementById("menu-toggle");'+sLinebreak+
+    '       const sideMenu = document.getElementById("side-menu");'+sLinebreak+
+    '       const mainContent = document.getElementById("main-content");'+sLinebreak+
+    '       if (toggleButton && sideMenu && mainContent) {'+sLinebreak+
+    '           toggleButton.addEventListener("click", () => {'+sLinebreak+
+    '               sideMenu.classList.toggle("is-expanded");'+sLinebreak+
+    '               mainContent.classList.toggle("is-shifted");'+sLinebreak+
+    '           });'+sLinebreak+
+    '       }'+sLinebreak+
+    '   });';
+
+Var
+  SE : THTMLElement;
+
+begin
+  SE:=Doc.CreateElement('script');
+  aHead.AppendChild(SE);
+  AppendText(SE,SFunc);
+end;
+
+
+procedure TNewHTMLWriter.AppendSideMenu(aMenu: THTMLElement);
+
+  function AddLink(aParent : THTMLElement; ALinkSubpageIndex: Integer; const AName: String) : THTMLElement;
+
+  begin
+    Result:=CreateLink(aParent, ResolveLinkWithinPackage(Module, ALinkSubpageIndex));
+    AppendText(Result,aName);
+  end;
+
+  function AddPackageLink(aParent: THTMLElement; ALinkSubpageIndex: Integer; const AName: String) : THTMLElement;
+  var
+    lURL : String;
+  begin
+    lURL:=ResolveLinkWithinPackage(Package, ALinkSubpageIndex);
+    Result:=CreateLink(aParent,lURL);
+    AppendText(Result,aName);
+  end;
+
+
+var
+  lPara,lList,lItem : THTMLElement;
+  lModules : TStringList;
+  lModule : TPasModule;
+  I : Integer;
+
+begin
+  lPara:=CreateEl(aMenu,'p','menu-label');
+  AppendText(lPara,SDocPackageLinkTitle);
+  lList:=CreateEl(aMenu,'ul','menu-list');
+  lItem:=CreateEl(lList,'li');
+  if Assigned(Module) then
+  AddPackageLink(lItem,0, SDocReference);
+  //El:=AppendHyperlink(lItem, Package) as THTMLELement;
+  lItem:=CreateEl(lList,'li');
+  AddPackageLink(lItem,IndexSubIndex, SDocIdentifierIndex);
+  lItem:=CreateEl(lList,'li');
+  AddPackageLink(lItem,ClassHierarchySubIndex, SDocPackageClassHierarchy);
+  lPara:=CreateEl(aMenu,'p','menu-label');
+  AppendText(lPara,SDocUnits);
+  lList:=CreateEl(aMenu,'ul','menu-list');
+  lModules:=TStringList.Create;
+  try
+    For I:=0 to Package.Modules.Count-1 do
+      begin
+      lModule:=TPasModule(Package.Modules[I]);
+      lModules.AddObject(lModule.Name,lModule);
+      end;
+    lModules.Sort;
+    For I:=0 to lModules.Count-1 do
+      begin
+      lModule:=TPasModule(lModules.Objects[I]);
+      lItem:=CreateEl(lList,'li');
+      AppendHyperlink(lItem, lModule);
+      end;
+  finally
+    LModules.Free;
+  end;
+
+end;
+
+procedure TNewHTMLWriter.SetModuleInfo(aElement : TPasElement; ASubpageIndex : integer);
+
+var
+  i : integer;
+  Element : TPasElement;
+begin
+  CurDirectory := Allocator.GetFilename(AElement, ASubpageIndex);
+  i := Length(CurDirectory);
+  while (i > 0) and not (CurDirectory[i] in AllowDirectorySeparators) do
+    Dec(i);
+  CurDirectory := Copy(CurDirectory, 1, i);
+  BaseDirectory := Allocator.GetRelativePathToTop(AElement);
+  if aElement is TPasPackage then
+    Module:=Nil
+  else
+    begin
+    Element := AElement;
+    while (Element<>Nil) and (not (Element.ClassType.inheritsfrom(TPasModule))) do
+      Element := Element.Parent;
+    Module := TPasModule(Element);
+    end;
+end;
+
+function TNewHTMLWriter.CreateHTMLPage(AElement: TPasElement;
+  ASubpageIndex: Integer): TXMLDocument;
+var
+  HTMLEl: THTMLHtmlElement;
+  HeadEl: THTMLHeadElement;
+  LMain,lMenu,LContent,BodyElement : THTMLElement;
+  El: TDOMElement;
+
+begin
+
+  Result := THTMLDocument.Create;
+  SetHTMLDocument(THTMLDocument(Result));
+  Doc.AppendChild(Doc.Impl.CreateDocumentType('html','',''));
+
+  HTMLEl := Doc.CreateHtmlElement;
+  Doc.AppendChild(HTMLEl);
+
+  HeadEl := Doc.CreateHeadElement;
+  FHeadElement:=HeadEl;
+  HTMLEl.AppendChild(HeadEl);
+  El := Doc.CreateElement('meta');
+  HeadEl.AppendChild(El);
+  El['http-equiv'] := 'Content-Type';
+  El['content'] := 'text/html; charset=utf-8';
+
+  El := Doc.CreateElement('meta');
+  HeadEl.AppendChild(El);
+  El['name'] := 'viewport';
+  El['content'] := 'width=device-width, initial-scale=1';
+
+
+  FTitleElement := Doc.CreateElement('title');
+  HeadEl.AppendChild(TitleElement);
+
+  BodyElement := Doc.CreateElement('body');
+  BodyElement['class']:='has-navbar-fixed-top';
+  ContentElement:=BodyElement;
+  HTMLEl.AppendChild(BodyElement);
+  SetModuleInfo(aElement,ASubpageIndex);
+  AppendMenuBar(ASubpageIndex);
+  if CreateSideMenu then
+    begin
+    AppendSideMenuScript(HeadEl);
+    LMain:=CreateEl(ContentElement,'div');
+    LMain['id']:='main-layout';
+    lMenu:=CreateEl(lMain,'aside','is-expanded');
+    LMenu['id']:='side-menu';
+    AppendSideMenu(lMenu);
+    LContent:=CreateEl(lMain,'div','is-shifted');
+    LContent['id']:='main-content';
+    ContentElement:=lContent;
+    end;
+  CreatePageBody(AElement, ASubpageIndex);
+
+  AppendFooter;
+
+  El := Doc.CreateElement('link');
+  HeadEl.AppendChild(El);
+  El['rel'] := 'stylesheet';
+  El['type'] := 'text/css';
+  El['href'] := UTF8Decode(FixHtmlPath(UTF8Encode(Allocator.GetCSSFilename(AElement,'bulma'))));
+
+  El := Doc.CreateElement('link');
+  HeadEl.AppendChild(El);
+  El['rel'] := 'stylesheet';
+  El['type'] := 'text/css';
+  El['href'] := UTF8Decode(FixHtmlPath(UTF8Encode(Allocator.GetCSSFilename(AElement,'fpdocs'))));
+end;
+
+
+procedure TNewHTMLWriter.WriteDocPage(const aFileName: String; aElement: TPasElement; aSubPageIndex: Integer);
+
+Var
+  PageDoc: TXMLDocument;
+  FN : String;
+begin
+  PageDoc := CreateHTMLPage(aElement, aSubpageIndex);
+  try
+    FN:=GetFileBaseDir(Engine.Output)+aFilename;
+    //writeln('Element: ',Element.PathName, ' FileName: ', FN);
+    WriteHTMLFile(PageDoc, FN);
+  except
+    on E: Exception do
+      DoLog(SErrCouldNotCreateFile, [aFileName, e.Message]);
+  end;
+  PageDoc.Free;
+end;
+
+
+function TNewHTMLWriter.CreateH1(Parent: TDOMNode): THTMLElement;
+begin
+  Result:=inherited CreateH1(Parent);
+  Result['class']:='subtitle is-2'
+end;
+
+function TNewHTMLWriter.CreateH2(Parent: TDOMNode): THTMLElement;
+begin
+  Result:=inherited CreateH2(Parent);
+  Result['class']:='subtitle is-4'
+end;
+
+function TNewHTMLWriter.CreateH3(Parent: TDOMNode): THTMLElement;
+begin
+  Result:=inherited CreateH3(Parent);
+  Result['class']:='subtitle is-6'
+end;
+
+procedure TNewHTMLWriter.DoWriteDocumentation;
+
+
+begin
+  Inherited;
+  CreateCSSFile;
+end;
+
+procedure TNewHTMLWriter.CreateCSSFile;
+
+Var
+  TempStream: TMemoryStream;
+  Data : PByte;
+
+begin
+  TempStream := TMemoryStream.Create;
+  try
+    if (FCSSFile<>'') then
+      begin
+      if not FileExists(FCSSFile) then
+        begin
+        DoLog('Can''t find CSS file "%s"',[FCSSFILE]);
+        halt(1);
+        end;
+      TempStream.LoadFromFile(FCSSFile);
+      end
+    else
+      begin
+      DoLog('Using built-in CSS file',[]);
+      Data:=@DefaultNewCSS;
+      TempStream.WriteBuffer(Data^,SizeOf(DefaultNewCSS));
+      end;
+   TempStream.Position := 0;
+   TempStream.SaveToFile(Engine.output+'fpdocs.css');
+  finally
+    TempStream.Free;
+  end;
+end;
+
+
+{ Returns the new CodeEl, which will be the old CodeEl in most cases }
+function TNewHTMLWriter.AppendType(CodeEl: TDOMElement;  Element: TPasType): TDOMElement;
+
+Var
+  S : String;
+
+begin
+  Result := CodeEl;
+  S:=GetElementCode(Element, False);
+//  Writeln('Default code for "',Element.ClassName,'"(',Element.FullName,'):',S);
+  AppendHighlightedCode(ContentElement,S);
+end;
+
+function TNewHTMLWriter.AppendProcType(CodeEl: TDOMElement;
+  Element: TPasProcedureType; Indent: Integer): TDOMElement;
+
+var
+  i: Integer;
+  Arg: TPasArgument;
+  S : String;
+
+begin
+  if Element.Args.Count > 0 then
+  begin
+    AppendSym(CodeEl, '(');
+
+    for i := 0 to Element.Args.Count - 1 do
+    begin
+      Arg := TPasArgument(Element.Args[i]);
+      S:=AccessNames[Arg.Access];
+      if (S<>'') then
+        AppendKw(CodeEl,S);
+      AppendText(CodeEl, Arg.Name);
+      if Assigned(Arg.ArgType) then
+      begin
+        AppendSym(CodeEl, ': ');
+        CodeEl := AppendType(CodeEl, Arg.ArgType);
+      end;
+      if Length(Arg.Value) > 0 then
+        AppendPasSHFragment(CodeEl, ' = ' + Arg.Value, 0);
+      if i < Element.Args.Count - 1 then
+        AppendSym(CodeEl, ';');
+    end;
+
+    if Element.InheritsFrom(TPasFunctionType) or Element.IsOfObject then
+    begin
+      if Element.InheritsFrom(TPasFunctionType) then
+      begin
+        AppendSym(CodeEl, '):');
+        AppendHyperlink(CodeEl, TPasFunctionType(Element).ResultEl.ResultType);
+      end else
+        AppendSym(CodeEl, ')');
+      if Element.IsOfObject then
+      begin
+        AppendText(CodeEl, ' ');        // Don't remove
+        AppendKw(CodeEl, 'of object');
+      end;
+    end else
+      if Indent > 0 then
+        AppendSym(CodeEl, ')')
+      else
+      begin
+        AppendSym(CodeEl, ')');
+      end;
+  end
+  else
+    begin
+    { Procedure or function without arguments }
+    if Element.InheritsFrom(TPasFunctionType) then
+      begin
+      AppendSym(CodeEl, ': ');
+      AppendHyperlink(CodeEl, TPasFunctionType(Element).ResultEl.ResultType);
+      end;
+    if Element.IsOfObject then
+      AppendKw(CodeEl, ' of object');
+    end;
+  Result := CodeEl;
+end;
+
+procedure TNewHTMLWriter.AppendProcExt(CodeEl: TDOMElement;
+  Element: TPasProcedure);
+
+  procedure AppendExt(const Ext: String);
+  begin
+    AppendKw(CodeEl, ' ' + Ext);
+    AppendSym(CodeEl, ';');
+  end;
+
+begin
+  if Element.IsVirtual then
+    AppendExt('virtual');
+  if Element.IsDynamic then
+    AppendExt('dynamic');
+  if Element.IsAbstract then
+    AppendExt('abstract');
+  if Element.IsOverride then
+    AppendExt('override');
+  if Element.IsOverload then
+    AppendExt('overload');
+  if Element.IsMessage then
+    AppendExt('message');
+end;
+
+
+{ Used in two places:
+  - Page for the method of a class
+  - Page for a standalone procedure or function. }
+
+procedure TNewHTMLWriter.AppendProcDecl(CodeEl: TDOMElement;  Element: TPasProcedureBase);
+
+  procedure WriteVariant(AProc: TPasProcedure; SkipResult : Boolean);
+  begin
+    AppendHighlightedCode(CodeEl,GetElementCode(aProc,False,[efParent]));
+  end;
+
+var
+  i,fc: Integer;
+  P : TPasProcedure;
+begin
+  fc:=0;
+  if Element.ClassType = TPasOverloadedProc then
+    for i := 0 to TPasOverloadedProc(Element).Overloads.Count - 1 do
+    begin
+      P:=TPasProcedure(TPasOverloadedProc(Element).Overloads[i]);
+      if (P.ProcType is TPasFunctionType) then
+        Inc(fc);
+      WriteVariant(P,fc>1);
+    end
+  else
+    WriteVariant(TPasProcedure(Element),False);
+end;
+
+procedure TNewHTMLWriter.AppendProcArgsSection(Parent: THTMLElement;
+  Element: TPasProcedureType; SkipResult : Boolean = False);
+var
+  HasFullDescr, HaveArgDescr: Boolean;
+  ResultEl: TPasResultElement;
+  lColumns, lColumn: THTMLElement;
+  DocNode: TDocNode;
+  i: Integer;
+  Arg: TPasArgument;
+begin
+  HaveArgDescr:=False;
+  I:=0;
+  While (I<Element.Args.Count) and not HaveArgDescr do
+    begin
+    Arg := TPasArgument(Element.Args[i]);
+    HaveArgDescr:=Not IsDescrNodeEmpty(Engine.FindShortDescr(Arg));
+    inc(i);
+    end;
+  if HaveArgDescr then
+    begin
+    AppendText(CreateH2(Parent), SDocArguments);
+    for i := 0 to Element.Args.Count - 1 do
+      begin
+      lColumns := CreateListColumns(Parent);
+      Arg := TPasArgument(Element.Args[i]);
+      if IsDescrNodeEmpty(Engine.FindShortDescr(Arg)) then
+        continue;
+      lColumn:=CreateListColumn1(lColumns);
+      AppendText(lColumn, Arg.Name);
+      lColumn:=CreateListColumn2(lColumns);
+      AppendShortDescrCell(lColumn, Arg);
+      end;
+    end;
+  if (Element.ClassType = TPasFunctionType) and not SkipResult then
+  begin
+    ResultEl := TPasFunctionType(Element).ResultEl;
+    DocNode := Engine.FindDocNode(ResultEl);
+    HasFullDescr := Assigned(DocNode) and not IsDescrNodeEmpty(DocNode.Descr);
+    if HasFullDescr or
+      (Assigned(DocNode) and not IsDescrNodeEmpty(DocNode.ShortDescr)) then
+    begin
+      AppendText(CreateH2(Parent), SDocFunctionResult);
+      if HasFullDescr then
+        AppendDescr(ResultEl, Parent, DocNode.Descr, True)
+      else
+        AppendDescr(ResultEl, CreatePara(Parent), DocNode.ShortDescr, False);
+    end;
+  end;
+end;
+
+procedure TNewHTMLWriter.AppendTopicMenuBar(Topic : TTopicElement);
+
+  function AddLink(aParent : THTMLElement; ALinkSubpageIndex: Integer; const AName: String) : THTMLElement;
+
+  begin
+    Result:=CreateLink(aParent, ResolveLinkWithinPackage(Module, ALinkSubpageIndex));
+    Result['class']:='navbar-item';
+    AppendText(Result,aName);
+  end;
+
+  function AddPackageLink(aParent: THTMLElement; ALinkSubpageIndex: Integer; const AName: String) : THTMLElement;
+  begin
+    Result:=CreateLink(aParent, ResolveLinkWithinPackage(Package, ALinkSubpageIndex));
+    Result['class']:='navbar-item';
+    AppendText(Result,aName);
+  end;
+
+
+var
+  El,NavBrand, NavItem, NavEl, NavMenu, NavBar : THTMLElement;
+begin
+  NavEl := CreateEl(ContentElement, 'nav','navbar is-link is-fixed-top');
+  NavEl['role']:='navigation';
+  NavEl['aria-label']:='main navigation';
+  NavBrand:=CreateEl(NavEl,'div');
+  // We use the brand for the link to the overview
+  NavBrand['class']:='navbar-brand';
+  NavItem:=CreateEl(NavBrand,'a');
+  NavItem['class']:='navbar-item';
+  if Assigned(Module) then
+    begin
+    NavItem['href']:=UTF8Decode(ResolveLinkWithinPackage(Module, 0));
+    AppendText(NavItem,UTF8Decode(Module.Name));
+    end
+  else
+    begin
+    NavItem['href']:=UTF8Decode(ResolveLinkWithinPackage(Package, IndexSubIndex));
+    AppendText(NavItem,UTF8Decode(Package.Name));
+    end;
+  // Now the other items follow
+  NavMenu:=CreateEl(NavEl,'div');
+  NavMenu['class']:='navbar-menu';
+  NavBar:=CreateEl(NavMenu,'div');
+  NavBar['class']:='navbar-start';
+  if Assigned(Module) then
+    begin
+    // AddLink(NavBar,0, 'Unit '+Module.Name);
+    if Module.InterfaceSection.ResStrings.Count > 0 then
+      AddLink(NavBar,ResstrSubindex, SDocResStrings);
+    if Module.InterfaceSection.Consts.Count > 0 then
+      AddLink(NavBar,ConstsSubindex, SDocConstants);
+    if Module.InterfaceSection.Types.Count > 0 then
+      AddLink(NavBar,TypesSubindex, SDocTypes);
+    if Module.InterfaceSection.Classes.Count > 0 then
+      AddLink(NavBar,ClassesSubindex, SDocClasses);
+    if Module.InterfaceSection.Functions.Count > 0 then
+      AddLink(NavBar,ProcsSubindex, SDocProceduresAndFunctions);
+    if Module.InterfaceSection.Variables.Count > 0 then
+      AddLink(NavBar,VarsSubindex, SDocVariables);
+    AddLink(NavBar,IndexSubIndex,SDocIdentifierIndex);
+    AppendFragment(NavBar, NavigatorHTML);
+    end
+  else
+    begin
+    // Index
+    AddPackageLink(NavBar,IndexSubIndex, SDocIdentifierIndex);
+    // Class TObject tree
+    AddPackageLink(NavBar,ClassHierarchySubIndex, SDocPackageClassHierarchy);
+    AppendFragment(NavBar, NavigatorHTML)
+    end;
+  NavBar:=CreateEl(NavMenu,'div');
+  NavBar['class']:='navbar-end';
+
+  if Length(SearchPage) > 0 then
+    begin
+    El:=CreateLink(NavBar, SearchPage);
+    El['class']:='navbar-item';
+    AppendText(El, SDocSearch);
+    end;
+
+  if Assigned(Module) and Assigned(Package) then // Displays a Package page
+  begin
+    El:=AppendHyperlink(NavBar, Package) as THTMLELement;
+    El['class']:='navbar-item';
+  end;
+  AppendFragment(ContentElement,HeaderHTML);
+end;
+
+
+function TNewHTMLWriter.CreateAllocator: TFileAllocator;
+begin
+   Result:=TLongNameFileAllocator.Create('.html');
+end;
+
+procedure TNewHTMLWriter.AppendMenuBar(ASubpageIndex: Integer);
+
+  function AddLink(aParent : THTMLElement; ALinkSubpageIndex: Integer; const AName: String) : THTMLElement;
+
+  begin
+    Result:=CreateLink(aParent, ResolveLinkWithinPackage(Module, ALinkSubpageIndex));
+    Result['class']:='navbar-item';
+    AppendText(Result,aName);
+  end;
+
+  function AddPackageLink(aParent: THTMLElement; ALinkSubpageIndex: Integer; const AName: String) : THTMLElement;
+  begin
+    Result:=CreateLink(aParent, ResolveLinkWithinPackage(Package, ALinkSubpageIndex));
+    Result['class']:='navbar-item';
+    AppendText(Result,aName);
+  end;
+
+
+var
+  El,NavBrand, NavItem, NavEl, NavMenu, NavBar : THTMLElement;
+begin
+
+  NavEl := CreateEl(ContentElement, 'nav','navbar is-link is-fixed-top');
+  NavEl['role']:='navigation';
+  NavEl['aria-label']:='main navigation';
+  NavBrand:=CreateEl(NavEl,'div');
+  // We use the brand for the link to the overview
+  NavBrand['class']:='navbar-brand';
+  if CreateSideMenu then
+    begin
+    NavItem:=CreateEl(NavBrand,'a','navbar-item');
+    NavItem['role']:='button';
+    NavItem['id']:='menu-toggle';
+    NavItem['aria-label']:='menu';
+    NavItem['aria-expanded']:='false';
+    El:=CreateEl(NavItem,'span','burger-icon');
+    AppendText(El,#$2261); //
+    end;
+  NavItem:=CreateEl(NavBrand,'a');
+  NavItem['class']:='navbar-item';
+  if Assigned(Module) then
+    begin
+    NavItem['href']:=UTF8Decode(ResolveLinkWithinPackage(Module, 0));
+    AppendText(NavItem, UTF8Decode(Format(SDocUnitMenuTitle,[Module.Name])));
+    end
+  else
+    begin
+    NavItem['href']:=UTF8Decode(ResolveLinkWithinPackage(Package, IndexSubIndex));
+    AppendText(NavItem,UTF8Decode(Format(SDocPackageMenuTitle,[Package.Name])));
+    end;
+  // Now the other items follow
+  NavMenu:=CreateEl(NavEl,'div');
+  NavMenu['class']:='navbar-menu';
+  NavBar:=CreateEl(NavMenu,'div');
+  NavBar['class']:='navbar-start';
+  if Assigned(Module) then
+    begin
+    // AddLink(NavBar,0, 'Unit '+Module.Name);
+    if Module.InterfaceSection.ResStrings.Count > 0 then
+      AddLink(NavBar,ResstrSubindex, SDocResStrings);
+    if Module.InterfaceSection.Consts.Count > 0 then
+      AddLink(NavBar,ConstsSubindex, SDocConstants);
+    if Module.InterfaceSection.Types.Count > 0 then
+      AddLink(NavBar,TypesSubindex, SDocTypes);
+    if Module.InterfaceSection.Classes.Count > 0 then
+      AddLink(NavBar,ClassesSubindex, SDocClasses);
+    if Module.InterfaceSection.Functions.Count > 0 then
+      AddLink(NavBar,ProcsSubindex, SDocProceduresAndFunctions);
+    if Module.InterfaceSection.Variables.Count > 0 then
+      AddLink(NavBar,VarsSubindex, SDocVariables);
+    AddLink(NavBar,IndexSubIndex,SDocIdentifierIndex);
+    AppendFragment(NavBar, NavigatorHTML);
+    end
+  else
+    begin
+    // Index
+    AddPackageLink(NavBar,IndexSubIndex, SDocIdentifierIndex);
+    // Class TObject tree
+    AddPackageLink(NavBar,ClassHierarchySubIndex, SDocPackageClassHierarchy);
+    AppendFragment(NavBar, NavigatorHTML)
+    end;
+  NavBar:=CreateEl(NavMenu,'div');
+  NavBar['class']:='navbar-end';
+
+  if Length(SearchPage) > 0 then
+    begin
+    El:=CreateLink(NavBar, SearchPage);
+    El['class']:='navbar-item';
+    AppendText(El, SDocSearch);
+    end;
+
+  if Assigned(Module) and Assigned(Package) then // Displays a Package page
+  begin
+    El:=AppendHyperlink(NavBar, Package) as THTMLELement;
+    El['class']:='navbar-item';
+  end;
+  AppendFragment(ContentElement,HeaderHTML);
+end;
+
+
+procedure TNewHTMLWriter.AppendFooter;
+
+Var
+  S : String;
+  lContent,lDateEl,lFooter : TDomElement;
+begin
+  if not (Assigned(FooterHTML) or IncludeDateInFooter) then
+    exit;
+  lFooter:=CreateEl(ContentElement, 'footer','footer');
+  lContent:=CreateEl(lFooter, 'div','has-text-centered');
+  if Assigned(FooterHTML) then
+    AppendFragment(lContent, FooterHTML)
+  else if IncludeDateInFooter then
+    begin
+    lDateEl:=CreateEl(lContent,'span','footertext');
+    If (FDateFormat='') then
+      S:=DateToStr(Date)
+    else
+      S:=FormatDateTime(FDateFormat,Date);  
+    AppendText(lDateEl,Format(SDocDateGenerated,[S]));
+    end;
+end;
+
+function TNewHTMLWriter.CreateSection(aParent: THTMLElement): THTMLElement;
+begin
+  Result:=CreateEl(aParent,'section','section');
+end;
+
+procedure TNewHTMLWriter.DescrWriteFileEl(const AText: DOMString);
+var
+  NewEl: TDOMElement;
+begin
+  NewEl := CreateEl(CurOutputNode, 'span');
+  NewEl['class'] := 'fileref';
+  AppendText(NewEl, AText);
+end;
+
+procedure TNewHTMLWriter.DescrWriteVarEl(const AText: DOMString);
+var
+  NewEl: TDOMElement;
+begin
+  NewEl := CreateEl(CurOutputNode, 'span');
+  NewEl['class'] := 'identifier';
+  AppendText(NewEl, AText);
+end;
+
+function TNewHTMLWriter.AppendPasSHFragment(Parent: TDOMNode; const AText: String; AShFlags: Byte): Byte;
+begin
+  Result:=AppendPasSHFragment(Parent, AText, AShFlags,Nil);
+end;
+
+function TNewHTMLWriter.AppendPasSHFragment(Parent: TDOMNode; const AText: String; AShFlags: Byte;
+  aLinkIdentifierMap: TLinkIdentifierMap): Byte;
+
+var
+  El: TDOMElement;
+
+  Procedure OutputToken(aToken : TSyntaxToken);
+
+  Var
+    CurParent: TDomNode;
+    lLink : String;
+
+  begin
+    lLink:='';
+    If (aToken.Text='') then
+      exit;
+    If (el<>Nil) then
+      CurParent:=El
+    else
+      begin
+      CurParent:=Parent;
+      if (aToken.Kind=shDefault) and Assigned(aLinkIdentifierMap) then
+        lLink:=aLinkIdentifierMap.GetLink(aToken.Text);
+      end;
+    if lLink<>'' then
+      CurParent:=CreateLink(CurParent,lLink);
+    AppendText(CurParent,aToken.Text);
+    El:=Nil;
+  end;
+
+  Function NewEl(Const ElType,Attr,AttrVal : DOMString) : TDomElement;
+
+  begin
+    Result:=CreateEl(Parent,ElType);
+    Result[Attr]:=AttrVal;
+  end;
+
+  Function NewSpan(Const AttrVal : DOMString) : TDomElement;
+
+  begin
+    Result:=CreateEl(Parent,'span');
+    Result['class']:=AttrVal;
+  end;
+var
+  HL : TPascalSyntaxHighlighter;
+  Tokens : TSyntaxTokenArray;
+  T : TSyntaxToken;
+begin
+  Result:=0;
+  HL:=TPascalSyntaxHighlighter.Create;
+  Try
+    Tokens:=HL.Execute(aText);
+    For T in Tokens do
+      begin
+      case T.Kind  of
+        shDefault:    El:=Nil;
+        shInvalid:    El:=newel('font','color','red');
+        shSymbol :    El:=newspan('sym');
+        shKeyword:    El:=newspan('kw');
+        shComment:    El:=newspan('cmt');
+        shDirective:  El:=newspan('dir');
+        shNumbers:    El:=newspan('num');
+        shCharacters: El:=newspan('chr');
+        shStrings:    El:=newspan('str');
+        shAssembler:  El:=newspan('asm');
+      end;
+      OutputToken(T);
+      end;
+  finally
+    HL.Free;
+  end;
+end;
+
+procedure TNewHTMLWriter.AppendDescrSection(AContext: TPasElement; Parent: TDOMNode; DescrNode: TDOMElement; const ATitle: DOMString);
+
+var
+  lContent,lSection : THTMLElement;
+begin
+  if not IsDescrNodeEmpty(DescrNode) then
+  begin
+    lSection:=CreateSection(Parent as THTMLElement);
+    lContent:=CreateEl(lSection,'div','content');
+    If (ATitle<>'') then // Can be empty for topic.
+      AppendText(CreateH2(lContent), ATitle);
+    AppendDescr(AContext, lContent, DescrNode, True);
+  end;
+end;
+
+procedure TNewHTMLWriter.AppendSeeAlsoSection(AElement: TPasElement; aParent: TDOMElement; DocNode: TDocNode);
+
+  procedure GetSeeAlsoNodes(aList : TFPList);
+  var
+    Node : TDOMNode;
+  begin
+    // Get all nodes.
+    Node:=DocNode.SeeAlso.FirstChild;
+    While Assigned(Node) do
+      begin
+      if (Node.NodeType=ELEMENT_NODE) and (Node.NodeName='link') then
+        aList.Add(Node);
+      Node := Node.NextSibling;
+      end;
+  end;
+
+  procedure AppendSeeAlsoName(aParent : THTMLElement; El : TDOmElement);
+  var
+    NewEl : THTMLElement;
+    l,s,n : domstring;
+  begin
+    l:=El['id'];
+    // Create parent element for link text/id
+    if Assigned(Engine) and Engine.FalbackSeeAlsoLinks then
+      s:= ResolveLinkIDUnStrict(UTF8ENcode(l))
+    else
+      s:= ResolveLinkID(UTF8ENcode(l));
+    if Length(s)=0 then
+      begin
+      if assigned(module) then
+        s:=UTF8Decode(module.name)
+      else
+        s:='?';
+      if l='' then l:='<empty>';
+      if Assigned(AElement) then
+        N:=UTF8Decode(AElement.PathName)
+      else
+        N:='?';
+      DoLog(SErrUnknownLinkID, [s,N,l]);
+      LinkUnresolvedInc();
+      NewEl := CreateEl(aParent,'b')
+      end
+    else
+      NewEl := CreateLink(aParent,s);
+    // Append link
+    if Not IsDescrNodeEmpty(El) then
+      begin
+      PushOutputNode(NewEl);
+      Try
+        ConvertBaseShortList(AElement, El, True)
+      Finally
+        PopOutputNode;
+      end;
+      end
+    else
+      AppendText(NewEl,El['id']);
+  end;
+
+  Procedure AppendLinkShortDescr(aParent : THTMLElement; aDocEl: TDomElement);
+  var
+     l : domstring;
+     DescrEl : TDomElement;
+  begin
+     l:=aDocEl['id'];
+     DescrEl := Engine.FindShortDescr(AElement.GetModule,UTF8Encode(L));
+     if Not Assigned(DescrEl) then
+       exit;
+     aParent['class'] := aParent['class']+' cmt';
+     PushOutputNode(aParent);
+     try
+       ConvertShort(aElement, DescrEl);
+     finally
+       PopOutputNode;
+     end;
+  end;
+
+var
+  DocEl : TDOMElement;
+  lSection, lColumns, lColumn: THTMLElement;
+  i : integer;
+  List : TFPList;
+
+begin
+  if Not (Assigned(DocNode) and Assigned(DocNode.SeeAlso)) then
+    Exit;
+
+  List:=TFPList.Create;
+  try
+    GetSeeAlsoNodes(List);
+    if List.Count=0 then
+      exit;
+    lSection:=CreateSection(aParent as THTMLElement);
+    AppendText(CreateH2(lSection), SDocSeeAlso);
+    For I:=0 to List.Count-1 do
+      begin
+      DocEl:=TDOMElement(List[i]);
+      lColumns:=CreateListColumns(lSection);
+      // Name
+      lColumn:=CreateListColumn1(lColumns);
+      AppendSeeAlsoName(lColumn,DocEl);
+      lColumn:=CreateListColumn2(lColumns);
+      AppendLinkShortDescr(lColumn,DocEl);
+      end;
+  finally
+    List.Free;
+  end;
+end;
+
+procedure TNewHTMLWriter.FinishElementPage(AElement: TPasElement; aDescription : Boolean = True);
+
+var
+  DocNode: TDocNode;
+  lSection : THTMLElement;
+begin
+  DocNode := Engine.FindDocNode(AElement);
+  If Not Assigned(DocNode) then
+    exit;
+
+  // Description
+  if aDescription and Assigned(DocNode.Descr) then
+    AppendDescrSection(AElement, ContentElement, DocNode.Descr, UTF8Decode(SDocDescription));
+
+  // Append "Errors" section
+  if Assigned(DocNode.ErrorsDoc) then
+    AppendDescrSection(AElement, ContentElement, DocNode.ErrorsDoc, UTF8Decode(SDocErrors));
+
+  // Append Version info
+  if Assigned(DocNode.Version) then
+    AppendDescrSection(AElement, ContentElement, DocNode.Version, UTF8Decode(SDocVersion));
+
+  // Append "See also" section
+  AppendSeeAlsoSection(AElement,ContentElement,DocNode);
+
+  // Append examples, if present
+  lSection:=CreateSection(ContentElement);
+  AppendExampleSection(AElement,lSection, DocNode);
+  // Append notes, if present
+  ConvertNotes(AElement,DocNode.Notes);
+end;
+
+procedure TNewHTMLWriter.CreateTopicPageBody(AElement: TTopicElement);
+
+var
+  DocNode: TDocNode;
+  lSection : THTMLElement;
+
+begin
+  AppendTopicMenuBar(AElement);
+  DocNode:=AElement.TopicNode;
+  if Assigned(DocNode) then  // should always be true, but we're being careful.
+    begin
+    lSection:=CreateSection(ContentElement);
+    AppendShortDescr(AElement,CreateH2(lSection), DocNode);
+    if Assigned(DocNode.Descr) then
+       AppendDescrSection(AElement, lSection, DocNode.Descr, '');
+    AppendSeeAlsoSection(AElement,ContentElement,DocNode);
+    CreateTopicLinks(ContentElement,DocNode,AElement);
+    AppendExampleSection(AElement,DocNode);
+    ConvertNotes(AElement,DocNode.Notes);
+    end;
+end;
+
+procedure TNewHTMLWriter.CreateClassHierarchyPage(AddUnit : Boolean);
+
+type
+  TypeEN = (NPackage, NModule, NName);
+
+  function PushClassElement(aParent : THTMLElement; IsParent : Boolean) : THTMLElement;
+  Var
+    H : THTMLElement;
+  begin
+    H:=CreateEl(aParent, 'li');
+    if IsParent then
+      begin
+      H['class']:='parent expanded';
+      H['onclick']:='expandorcollapse(event)';
+      end;
+    Result:=H;
+  end;
+
+  Function PushClassList(aParent : THTMLELement) : THTMLElement;
+
+  begin
+    Result:=CreateEl(aParent, 'ul');
+    Result['class']:='treeview';
+  end;
+
+  function ExtractName(APathName: String; Tp: TypeEN):String;
+  var
+  l:TStringList;
+  begin
+    Result:= Trim(APathName);
+    if Result = '' then exit;
+    l:=TStringList.Create;
+    try
+      l.AddDelimitedText(Result, '.', True);
+      if l.Count=3 then
+        Result:= l.Strings[Integer(Tp)]
+      else
+        Result:='';
+    finally
+      l.free;
+    end;
+  end;
+
+  Procedure AppendClass(aParent : THTMLElement; EN : TPasElementNode);
+
+  Var
+    PE,PM : TPasElement;
+    I : Integer;
+    El, SubParent : THTMLELement;
+
+  begin
+    if not Assigned(EN) then exit;
+    PE:=EN.Element;
+    SubParent:=PushClassElement(aParent,EN.ChildCount>0);
+    if (PE<>Nil) then
+      begin
+      El:=CreateEl(SubParent, 'span');
+      AppendHyperLink(El,PE);
+      PM:=PE.GetModule();
+      if (PM<>Nil) then
+        begin
+        AppendText(El,' (');
+        AppendHyperLink(El,PM);
+        AppendText(el,')');
+        end
+      end
+    else
+      AppendText(El,EN.Element.Name);
+    if EN.ChildCount>0 then
+      begin
+      El:=PushClassList(SubParent);
+      For I:=0 to EN.ChildCount-1 do
+        AppendClass(El,EN.Children[i] as TPasElementNode);
+      end;
+  end;
+
+begin
+  AppendClass(PushClassList(ContentElement),TreeClass.RootNode);
+end;
+
+procedure TNewHTMLWriter.CreatePackageClassHierarchy;
+
+Const
+  SFunc = 'function expandorcollapse (event) { '+sLineBreak+
+          '  var el = event.target;'+sLineBreak+
+          '  if (el) { '+sLineBreak+
+          '    el.classList.toggle("expanded");'+sLineBreak+
+          '    event.stopPropagation();'+sLineBreak+
+          '  }'+sLineBreak+
+          '}';
+
+Var
+  S : String;
+  SE : THTMLElement;
+
+begin
+  SE := Doc.CreateElement('script');
+  AppendText(SE,SFunc);
+  HeadElement.AppendChild(SE);
+  S:=Package.Name;
+  If Length(S)>0 then
+    Delete(S,1,1);
+  AppendTitle(ContentElement,UTF8Decode(Format(SDocPackageClassHierarchy, [S])));
+  CreateClassHierarchyPage(True);
+end;
+
+procedure TNewHTMLWriter.CreatePageBody(AElement: TPasElement; ASubpageIndex: Integer);
+
+begin
+  if Module=nil then
+    begin
+    If (ASubPageIndex=0) then
+      CreatePackagePageBody
+    else if ASubPageIndex=IndexSubIndex then
+      CreatePackageIndex  
+    else if ASubPageIndex=ClassHierarchySubIndex then
+      CreatePackageClassHierarchy
+    end
+  else
+    begin
+    if AElement.ClassType.inheritsfrom(TPasModule) then
+      CreateModulePageBody(TPasModule(AElement), ASubpageIndex)
+    else if AElement.Parent.InheritsFrom(TPasClassType) then
+      CreateClassMemberPageBody(AElement)
+    else if AElement.ClassType = TPasConst then
+      CreateConstPageBody(TPasConst(AElement))
+    else if AElement.InheritsFrom(TPasClassType) then
+      CreateClassPageBody(TPasClassType(AElement), ASubpageIndex)
+    else if AElement.InheritsFrom(TPasType) then
+      CreateTypePageBody(TPasType(AElement))
+    else if AElement.ClassType = TPasVariable then
+      CreateVarPageBody(TPasVariable(AElement))
+    else if AElement.InheritsFrom(TPasProcedureBase) then
+      CreateProcPageBody(TPasProcedureBase(AElement))
+    else if AElement.ClassType = TTopicELement then
+      CreateTopicPageBody(TTopicElement(AElement))
+    else if AElement.ClassType = TPasProperty then
+      CreateClassMemberPageBody(TPasProperty(AElement))
+    else
+      writeln('Unknown classtype: ',AElement.classtype.classname);
+  end;
+end;
+
+procedure TNewHTMLWriter.CreateIndexPage(aParent: THTMLElement; L: TStringList);
+Var
+  Lists  : Array['A'..'Z'] of TStringList;
+  CL : TStringList;
+  lColumns, lColumn,  EL, el2: TDOMElement;
+  E : TPasElement;
+  I : Integer;
+  S : String;
+  C : Char;
+
+begin
+  For C:='A' to 'Z' do
+    Lists[C]:=Nil;
+  L.Sort;
+  Cl:=Nil;
+  // Divide over alphabet
+  For I:=0 to L.Count-1 do
+    begin
+    S:=L[i];
+    E:=TPasElement(L.Objects[i]);
+    If not (E is TPasUnresolvedTypeRef) then
+      begin
+      If (S<>'') then 
+        begin
+        C:=Upcase(S[1]);
+        If C='_' then
+          C:='A';
+        If (C in ['A'..'Z']) and (Lists[C]=Nil) then
+          begin
+          CL:=TStringList.Create;
+          Lists[C]:=CL;
+          end;
+        end;
+      if assigned(cl) then  
+        CL.AddObject(S,E);
+      end;  
+    end;  
+  Try
+  // Create a quick jump table to all available letters.
+  lColumns := CreateEl(aParent,'div','columns is-multiline');
+  for C:='A' to 'Z' do
+    If (Lists[C]<>Nil) then
+      begin
+      lColumn:=CreateEl(lColumns,'div','column is-narrow');
+      lColumn:=CreateLink(lColumn,UTF8Decode('#SECTION'+C));
+      lColumn['class']:='button is-link';
+      AppendText(lColumn,UTF8Decode(C));
+      end;
+  // Now emit all identifiers.    
+  For C:='A' to 'Z' do
+    begin
+    CL:=Lists[C];
+    If CL<>Nil then
+      begin
+      El:=CreateH2(aParent);
+      AppendText(El,UTF8Decode(C));
+      CreateAnchor(El,UTF8Decode('SECTION'+C));
+      El:=CreateEl(aParent,'div');
+      EL['style']:='display: block; column-count: 3';
+      for I:=0 to CL.Count-1 do
+        begin
+        E:=TPasElement(CL.Objects[I]);
+        El2:=AppendHyperlink(El,E);
+        if assigned(EL2) then
+          EL2['style']:='display: block;';
+        end;
+      end; // have List
+    end;  // For C:=
+  Finally
+    for C:='A' to 'Z' do
+      FreeAndNil(Lists[C]);
+  end;  
+end;
+
+procedure TNewHTMLWriter.CreatePackageIndex;
+
+Var
+  L : TStringList;
+  I : Integer;
+  M : TPasModule;
+  S : String;
+  lSection : THTMLElement;
+begin
+  L:=TStringList.Create;
+  try
+    L.Capacity:=PageInfos.Count; // Too much, but that doesn't hurt.
+    For I:=0 to Package.Modules.Count-1 do
+      begin
+      M:=TPasModule(Package.Modules[i]);
+      L.AddObject(M.Name,M);
+      AddModuleIdentifiers(M,L);
+      end;
+    S:=Package.Name;
+    If Length(S)>0 then
+      Delete(S,1,1);
+    lSection:=CreateSection(ContentElement);
+    AppendTitle(lSection,UTF8Decode(Format(SDocPackageIndex, [S])));
+    CreateIndexPage(lSection,L);
+  Finally
+    L.Free;
+  end;
+end;
+
+procedure TNewHTMLWriter.CreatePackagePageBody;
+
+var
+  DocNode: TDocNode;
+  lSection, lColumns, lColumn : THTMLElement;
+  TableEl, TREl: TDOMElement;
+  i: Integer;
+  ThisModule: TPasModule;
+  L : TStringList;
+
+begin
+  lSection:=CreateSection(ContentElement);
+  AppendTitle(lSection,Format(SDocPackageTitle, [Copy(Package.Name, 2, 256)]));
+  AppendShortDescr(CreatePara(lSection), Package);
+  AppendText(CreateH2(lSection), UTF8Encode(SDocUnits));
+  TableEl := CreateTable(lSection);
+  L:=TStringList.Create;
+  Try
+    L.Sorted:=True;
+    // Sort modules.
+    For I:=0 to Package.Modules.Count-1 do
+      L.AddObject(TPasModule(Package.Modules[i]).Name,TPasModule(Package.Modules[i]));
+    // Now create table.
+    for i:=0 to L.Count - 1 do
+      begin
+      ThisModule := TPasModule(L.Objects[i]);
+      lColumns:=CreateListColumns(lSection);
+      lColumn:=CreateListColumn1(lColumns);
+      AppendHyperlink(lColumn, ThisModule);
+      lColumn:=CreateListColumn2(lColumns);
+      AppendShortDescrCell(lColumn, ThisModule);
+      end;
+  Finally
+    L.Free;
+  end;
+  DocNode := Engine.FindDocNode(Package);
+  if Assigned(DocNode) then
+    begin
+    if Assigned(DocNode.Descr) then
+       AppendDescrSection(nil, ContentElement, DocNode.Descr, UTF8Decode(SDocDescription));
+    CreateTopicLinks(ContentElement, DocNode,Package);
+    end;
+end;
+
+function TNewHTMLWriter.CreateListColumns(aParent : THTMLElement) : THTMLElement;
+begin
+  Result:=CreateEl(aParent,'div','columns list');
+end;
+
+function TNewHTMLWriter.CreateListColumn1(aParent : THTMLElement) : THTMLElement;
+begin
+  Result:=CreateEl(aParent,'div','column is-2 list');
+  Result['style']:='overflow:hidden; text-overflow: ellipsis;';
+end;
+
+function TNewHTMLWriter.CreateListColumn2(aParent : THTMLElement) : THTMLElement;
+begin
+  Result:=CreateEl(aParent,'div','column is-10 list');
+end;
+
+
+procedure TNewHTMLWriter.CreateTopicLinks (aParent : THTMLElement; Node: TDocNode; PasElement: TPasElement) ;
+
+var
+  DocNode: TDocNode;
+  lSection,lColumns,lColumn: THTMLElement;
+  HaveTopics : Boolean;
+  ThisTopic: TPasElement;
+
+begin
+  DocNode:=Node.FirstChild;
+  HaveTopics:=False;
+  While Assigned(DocNode) and not HaveTopics do
+    begin
+    HaveTopics:=DocNode.TopicNode;
+    DocNode:=DocNode.NextSibling;
+    end;
+  if not HaveTopics then
+    exit;
+  lSection:=CreateSection(aParent);
+  AppendText(CreateH2(lSection), UTF8Decode(SDocRelatedTopics));
+  DocNode:=Node.FirstChild;
+  While Assigned(DocNode) do
+    begin
+    if DocNode.TopicNode then
+      begin
+      lColumns:=CreateListColumns(lSection);
+      lColumn:=CreateListColumn1(lColumns);
+      ThisTopic:=FindTopicElement(DocNode);
+      if Assigned(ThisTopic) then
+        AppendHyperlink(lColumn, ThisTopic);
+      lColumn:=CreateListColumn2(lColumns);
+      if Assigned(ThisTopic) then
+        AppendShortDescrCell(lColumn, ThisTopic);
+      end;
+    DocNode:=DocNode.NextSibling;
+    end;
+end;
+
+function TNewHTMLWriter.GetElementCode(aElement: TPasElement; aSparse : boolean; aFlags : TElementFlags =  []): String;
+var
+  W : TPasWriter;
+  S : TStringStream;
+  Vis : TPasMemberVisibilities;
+begin
+  Vis:=[];
+  if Engine.HidePrivate then
+    Vis:=Vis+[visPrivate,visStrictPrivate];
+  if Engine.HideProtected then
+    Vis:=Vis+[visProtected,visStrictProtected];
+  W:=nil;
+  S:=TStringStream.Create('');
+  try
+    W:=TPasWriter.Create(S);
+    if aSparse then
+      W.Options:=W.Options+[woSparse];
+    W.SkipVisibilities:=Vis;
+    W.WriteElement(aElement,[efSkipSection]+aFlags);
+    Result:=S.DataString;
+  finally
+    S.Free;
+    W.Free;
+  end;
+
+end;
+
+procedure TNewHTMLWriter.CreateModuleIndexPage(AModule: TPasModule);
+
+Var
+  L : TStringList;
+  lSection: THTMLElement;
+begin
+  L:=TStringList.Create;
+  try
+    AddModuleIdentifiers(AModule,L);
+    lSection:=CreateSection(ContentElement);
+    AppendTitle(lSection,Format(SDocModuleIndex, [AModule.Name]));
+    PushContentElement(lSection);
+    CreateIndexPage(lSection,L);
+    PopContentElement;
+  Finally
+    L.Free;
+  end;  
+end;
+
+procedure TNewHTMLWriter.CreateModuleMainPage(aModule : TPasModule);
+
+var
+  lContent,lSection,lColumns,lColumn: THTMLElement;
+  i: Integer;
+  UnitRef: TPasType;
+  DocNode: TDocNode;
+
+
+begin
+  lSection:=CreateSection(ContentElement);
+  AppendTitle(lSection,Format(SDocUnitTitle, [AModule.Name]),AModule.Hints);
+  lContent:=CreateEl(lSection,'div','content');
+  AppendShortDescr(lContent, AModule);
+
+  if AModule.InterfaceSection.UsesList.Count > 0 then
+  begin
+    AppendKw(CreateCode(CreatePara(lContent)), 'uses');
+    for i := 0 to AModule.InterfaceSection.UsesList.Count - 1 do
+    begin
+      lColumns:=CreateListColumns(lSection);
+      UnitRef := TPasType(AModule.InterfaceSection.UsesList[i]);
+      DocNode := Engine.FindDocNode(UnitRef);
+      if Assigned(DocNode) and DocNode.IsSkipped then
+        continue;
+      lColumn:=CreateListColumn1(lColumns);
+      AppendHyperlink(lColumn, UnitRef);
+      lColumn:=CreateListColumn2(lColumns);
+      AppendShortDescrCell(lColumn, UnitRef);
+    end;
+  end;
+
+  DocNode := Engine.FindDocNode(AModule);
+  if Assigned(DocNode) then
+    begin
+    if Assigned(DocNode.Descr) then
+      AppendDescrSection(AModule, ContentElement, DocNode.Descr, UTF8Decode(SDocOverview));
+    ConvertNotes(AModule,DocNode.Notes);
+    CreateTopicLinks(ContentElement, DocNode,AModule);
+    end;
+end;
+
+
+procedure TNewHTMLWriter.CreateModuleSimpleSubpage(aModule: TPasModule; ASubpageIndex: Integer; const ATitle: DOMString; AList: TFPList);
+
+var
+  lSection, ColumnsEl, ColumnEl, CodeEl: TDOMElement;
+  i, j: Integer;
+  Decl: TPasElement;
+  SortedList: TFPList;
+  DocNode: TDocNode;
+  S : String;
+
+begin
+  S:=UTF8Encode(ATitle);
+  lSection:=CreateSection(ContentElement);
+  AppendTitle(lSection,Format(SDocUnitTitle + ': %s', [AModule.Name, S]));
+  SortedList := TFPList.Create;
+  try
+    for i := 0 to AList.Count - 1 do
+    begin
+      Decl := TPasElement(AList[i]);
+      DocNode := Engine.FindDocNode(Decl);
+      if (not Assigned(DocNode)) or (not DocNode.IsSkipped) then
+      begin
+        j := 0;
+        while (j < SortedList.Count) and (CompareText(
+          TPasElement(SortedList[j]).PathName, Decl.PathName) < 0) do
+          Inc(j);
+        SortedList.Insert(j, Decl);
+      end;
+    end;
+
+    for i := 0 to SortedList.Count - 1 do
+    begin
+      ColumnsEl := CreateEl(lSection,'div','columns');
+      Decl := TPasElement(SortedList[i]);
+      ColumnEl := CreateEl(ColumnsEl,'div','column is-one-fifth list');
+      ColumnEl['style']:='overflow:hidden; text-overflow: ellipsis;';
+      CodeEl := CreateCode(ColumnEl);
+      AppendHyperlink(CodeEl, Decl);
+      ColumnEl := CreateEl(ColumnsEl,'div','column is-four-fifths list');
+      AppendShortDescrCell(ColumnEl, Decl);
+    end;
+  finally
+    SortedList.Free;
+  end;
+end;
+
+procedure TNewHTMLWriter.CreateModuleResStringsPage(aModule : TPasModule);
+var
+  lsection,lColumns,lColumn: THTMLElement;
+  i: Integer;
+  Decl: TPasResString;
+
+begin
+  lSection:=CreateSection(ContentElement);
+  AppendTitle(lSection,Format(SDocUnitTitle + ': %s', [AModule.Name, SDocResStrings]));
+
+  for i := 0 to AModule.InterfaceSection.ResStrings.Count - 1 do
+  begin
+    lColumns:=CreateListColumns(lSection);
+    Decl := TPasResString(AModule.InterfaceSection.ResStrings[i]);
+    lColumn:=CreateListColumn1(lColumns);
+    CreateEl(lColumn, 'a')['name'] := UTF8Decode(LowerCase(Decl.Name));
+    AppendText(lColumn, UTF8Decode(Decl.Name));
+    lColumn := CreateListColumn2(lColumns);
+    AppendText(lColumn, UTF8Decode(Decl.Expr.getDeclaration(true)));
+  end;
+end;
+
+
+procedure TNewHTMLWriter.CreateModulePageBody(AModule: TPasModule;
+  ASubpageIndex: Integer);
+
+begin
+  case ASubpageIndex of
+    0:
+      CreateModuleMainPage(aModule);
+    ResstrSubindex:
+      CreateModuleResStringsPage(aModule);
+    ConstsSubindex:
+      CreateModuleSimpleSubpage(aModule, ConstsSubindex,UTF8Decode(SDocConstants), AModule.InterfaceSection.Consts);
+    TypesSubindex:
+      CreateModuleSimpleSubpage(aModule, TypesSubindex,UTF8Decode(SDocTypes), AModule.InterfaceSection.Types);
+    ClassesSubindex:
+      CreateModuleSimpleSubpage(aModule, ClassesSubindex,UTF8Decode(SDocClasses), AModule.InterfaceSection.Classes);
+    ProcsSubindex:
+      CreateModuleSimpleSubpage(aModule, ProcsSubindex, UTF8Decode(SDocProceduresAndFunctions), AModule.InterfaceSection.Functions);
+    VarsSubindex:
+      CreateModuleSimpleSubpage(aModule, VarsSubindex,UTF8Decode(SDocVariables), AModule.InterfaceSection.Variables);
+    IndexSubIndex: 
+      CreateModuleIndexPage(AModule);
+  end;
+end;
+
+procedure TNewHTMLWriter.CreateConstPageBody(AConst: TPasConst);
+var
+  Section,CodeEl: THTMLElement;
+
+begin
+  Section:=CreateSection(ContentElement);
+  AppendTitle(Section,AConst.Name,AConst.Hints);
+  AppendShortDescr(CreatePara(Section), AConst);
+  Section:=CreateSection(ContentElement);
+  AppendText(CreateH2(Section), UTF8Decode(SDocDeclaration));
+  AppendSourceRef(Section,AConst);
+  CodeEl := AppendCodeBlock(Section);
+  AppendPasSHFragment(CodeEl, GetElementCode(aConst,False),0);
+  FinishElementPage(AConst);
+end;
+
+procedure TNewHTMLWriter.AppendShortDescrCell(Parent: TDOMNode;  Element: TPasElement);
+
+var
+  ParaEl: TDOMElement absolute Parent;
+
+begin
+  if Assigned(Engine.FindShortDescr(Element)) then
+  begin
+    if Parent is TDOMElement then
+      ParaEl['class'] := ParaEl['class'] +' cmt';
+    AppendShortDescr(Parent, Element);
+  end;
+
+end;
+
+function TNewHTMLWriter.AppendCodeBlock(aParent : TDOMNode; const aLanguage : String = ''): THTMLElement;
+var
+  lLanguage : String;
+
+begin
+  Result:=CreateEl(aParent,'pre','code');
+  lLanguage:=aLanguage;
+  if lLanguage='' then
+    lLanguage:='pascal';
+  lLanguage:='code-'+lLanguage;
+  Result:=CreateEl(Result,UTF8Decode('code'),UTF8Decode(lLanguage));
+end;
+
+function TNewHTMLWriter.AppendHighlightedCode(aParent: TDOMNode; aCode: String; const aLanguage: String; aMap: TLinkIdentifierMap
+  ): THTMLElement;
+begin
+//  Writeln('Code:',aCode);
+  Result:=AppendCodeBlock(aParent,aLanguage);
+  AppendPasSHFragment(Result, aCode, 0, aMap);
+end;
+
+function TNewHTMLWriter.CreateCodeLines(aLines: array of string): string;
+var
+  i : integer;
+begin
+  Result:='';
+  if Length(aLines)=0 then
+    exit;
+  Result:=aLines[0];
+  For I:=1 to Length(aLines)-1 do
+    Result:=Result+sLineBreak+aLines[i];
+end;
+
+procedure TNewHTMLWriter.AppendAliasTypeDecl(aType: TPasAliasType);
+begin
+  AppendHighlightedCode(ContentElement,CreateCodeLines(['type','  '+aType.GetDeclaration(True)+';']));
+end;
+
+procedure TNewHTMLWriter.AppendTypeAliasTypeDecl(aType: TPasTypeAliasType);
+
+var
+  CodeEl : THTMLElement;
+begin
+  CodeEl:=AppendHighlightedCode(ContentElement,CreateCodeLines(['type','  '+aType.GetDeclaration(True)+';']));
+  AppendHyperlink(CodeEl, TPasTypeAliasType(AType).DestType);
+end;
+
+
+procedure TNewHTMLWriter.AppendClassOfTypeDecl(aType: TPasClassOfType);
+var
+  CodeEl : THTMLElement;
+
+begin
+  CodeEl:=AppendHighlightedCode(ContentElement,CreateCodeLines(['type','  '+aType.GetDeclaration(True)+';']));
+  if Assigned(TPasClassOfType(AType).DestType) then
+    AppendHyperlink(CodeEl, TPasClassOfType(AType).DestType);
+end;
+
+procedure TNewHTMLWriter.AppendEnumTypeDecl(aType: TPasEnumType);
+var
+  S : String;
+  i : integer;
+  lColumns,lColumn : THTMLElement;
+  EnumValue: TPasEnumValue;
+begin
+  S:=aType.GetDeclaration(true);
+  AppendHighlightedCode(ContentElement,CreateCodeLines(['type','  '+S]));
+  AppendText(CreateH3(ContentElement),'Values');
+  for i := 0 to AType.Values.Count - 1 do
+  begin
+    lColumns := CreateListColumns(ContentElement);
+    EnumValue := TPasEnumValue(AType.Values[i]);
+    lColumn := CreateListColumn1(lColumns);
+    AppendText(lColumn,EnumValue.Name);
+    lColumn:= CreateListColumn2(lColumns);
+    AppendShortDescrCell(lColumn, EnumValue);
+  end;
+end;
+
+procedure TNewHTMLWriter.AppendPointerTypeDecl(aType: TPasPointerType);
+var
+  CodeEl : THTMLElement;
+begin
+  CodeEl:=AppendHighlightedCode(ContentElement,CreateCodeLines(['type','  '+aType.GetDeclaration(True)+';']));
+  if Assigned(AType.DestType) then
+    AppendHyperlink(CodeEl, AType.DestType)
+end;
+
+procedure TNewHTMLWriter.AppendProcedureTypeDecl(aType: TPasProcedureType);
+
+begin
+  AppendHighlightedCode(ContentElement,CreateCodeLines(['type','  '+aType.GetDeclaration(True)+';']));
+  AppendProcArgsSection(ContentElement, AType);
+end;
+
+
+procedure TNewHTMLWriter.AppendRecordTypeDecl(aType: TPasRecordType);
+var
+  lMap: TLinkIdentifierMap;
+  I : Integer;
+
+begin
+  lMap:=TLinkIdentifierMap.Create(Self);
+  try
+    for I:=0 to aType.Members.Count-1 do
+      lMap.AddLink(TPasElement(aType.Members[i]));
+    AppendHighlightedCode(ContentElement,GetElementCode(aType,True),'',lMap);
+  finally
+    lMap.Free;
+  end;
+end;
+
+
+procedure TNewHTMLWriter.AppendSetTypeDecl(aType: TPasSetType);
+var
+  dlEl,ddEl,dtEl,CodeEl : THTMLElement;
+  EnumType : TPasEnumType;
+  EnumValue : TPasEnumValue;
+  I : Integer;
+
+begin
+  CodeEl:=AppendHighlightedCode(ContentElement,CreateCodeLines(['type','  '+aType.GetDeclaration(True)+';']));
+  if AType.EnumType.ClassType = TPasEnumType then
+    begin
+    EnumType:=TPasEnumType(AType.EnumType);
+    AppendText(CreateH3(ContentElement),'Values');
+    dlEl := CreateEl(ContentElement,'div','columns list');
+    for i := 0 to EnumType.Values.Count - 1 do
+    begin
+      EnumValue := TPasEnumValue(EnumType.Values[i]);
+      dtEl := CreateEl(dlEl,'div','column is-2 list');
+      AppendText(dtEl,EnumValue.Name);
+      ddEl := CreateEl(dlEl,'div','column is-10 list');
+      AppendShortDescrCell(ddEl, EnumValue);
+    end;
+    end
+  else
+    begin
+    AppendHyperlink(CodeEl, TPasSetType(AType).EnumType);
+    AppendSym(CodeEl, ';');
+    end;
+end;
+
+procedure TNewHTMLWriter.AppendTypeDecl(AType: TPasType);
+
+begin
+  // Alias
+  if AType.ClassType = TPasAliasType then
+    AppendAliasTypeDecl(TPasAliasType(aType))
+  else if AType.ClassType = TPasClassOfType then
+    AppendClassOfTypeDecl(TPasClassOfType(AType))
+  else if AType.ClassType = TPasEnumType then
+    AppendEnumTypeDecl(TPasEnumType(AType))
+  else if AType.ClassType = TPasPointerType then
+    AppendPointerTypeDecl(TPasPointerType(aType))
+  else if AType.InheritsFrom(TPasProcedureType) then
+    AppendProcedureTypeDecl(TPasProcedureType(aType))
+  else if AType.ClassType = TPasRecordType then
+    AppendRecordTypeDecl(TPasRecordType(aType))
+ else if AType.ClassType = TPasSetType then
+   AppendSetTypeDecl(TPasSetType(aType))
+ else if AType.ClassType = TPasTypeAliasType then
+    AppendTypeAliasTypeDecl(TPasTypeAliasType(aType))
+ else
+  // Probably one of the simple types, which allowed in other places as wel...
+    AppendType(ContentElement, TPasType(AType));
+end;
+
+procedure TNewHTMLWriter.CreateTypePageBody(AType: TPasType);
+var
+  Section,CodeEl: THTMLElement;
+  DocNode: TDocNode;
+begin
+  Section:=CreateSection(ContentElement);
+  AppendTitle(Section,AType.Name,AType.Hints);
+  AppendShortDescr(CreatePara(section), AType);
+  Section:=CreateSection(ContentElement);
+  AppendText(CreateH2(section), UTF8Decode(SDocDeclaration));
+  AppendSourceRef(Section,AType);
+  DocNode := Engine.FindDocNode(AType);
+  If Assigned(DocNode) and
+     Assigned(DocNode.Node) and
+     (Docnode.Node['opaque']='1') then
+    begin
+    CodeEl := AppendCodeBlock(Section);
+    AppendKw(CodeEl, 'type ');
+    AppendText(CodeEl, UTF8Decode(AType.Name));
+    AppendSym(CodeEl, ' = ');
+    AppendText(CodeEl,UTF8Decode(SDocOpaque))
+    end
+  else
+    begin
+    PushContentElement(Section);
+    try
+      AppendTypeDecl(AType);
+    finally
+      PopContentElement;
+    end;
+    end;
+  FinishElementPage(AType);
+end;
+
+
+procedure TNewHTMLWriter.AppendTitle(aParent: TDomElement; const AText: AnsiString; Hints: TPasMemberHints);
+
+begin
+  AppendTitle(AParent,UTF8Decode(aText),Hints);
+end;
+
+procedure TNewHTMLWriter.AppendTitle(aParent : TDomElement; const AText: DOMString; Hints : TPasMemberHints = []);
+
+Var
+  T : UnicodeString;
+
+begin
+  T:=AText;
+  if (Hints<>[]) then
+    T:=T+' ('+UTF8Decode(Engine.HintsToStr(Hints))+')';
+  AppendText(TitleElement, AText);
+  AppendText(CreateH1(aParent), T)
+end;
+
+procedure TNewHTMLWriter.AppendMemberListSection(aParent: THTMLELement; aClass: TPasClassType; aMemberType: TClassMemberType;
+  aDeclaredOnly: Boolean);
+var
+   LinkEl,lSection: THTMLELement;
+   LinkAll : Boolean;
+begin
+  if not HasMembersToShow(aClass,True,GetMemberFilter(aMemberType)) then
+    exit;
+  linkAll:=aDeclaredOnly and Assigned(aClass.AncestorType) and Not aClass.AncestorType.InheritsFrom(TPasUnresolvedTypeRef);
+  lSection:=CreateSection(ContentElement);
+  AppendText(CreateH2(lSection),UTF8Decode(GetMemberDocName(aMemberType)));
+  CreateAnchor(lsection,UTF8Decode(GetAnchorName(aMemberType)));
+  CreateClassMemberList(lSection,AClass,True,GetMemberFilter(aMemberType));
+  if LinkAll then
+    begin
+    LinkEl:=CreateLink(lSection,FixHtmlPath(ResolveLinkWithinPackage(AClass,GetMemberSubIndex(aMemberType))));
+    AppendText(LinkEl,SSeeAll+' '+GetMemberDocName(aMemberType));
+    end;
+end;
+
+procedure TNewHTMLWriter.AppendInheritanceTree(aParent : THTMLELement; aClass : TPasClassType);
+
+  procedure AppendInterfaceInfo(ACodeEl : TDomElement ; AThisClass: TPasClassType);
+  var
+    i:Integer;
+    ThisInterface:TPasClassType;
+  begin
+  if Assigned(AThisClass) and (AThisClass.Interfaces.count>0) then
+    begin
+      for i:=0 to AThisClass.interfaces.count-1 do
+        begin
+          ThisInterface:=TPasClassType(AThisClass.Interfaces[i]);
+          AppendText(ACodeEl,',');
+          AppendHyperlink(ACodeEl, ThisInterface);
+        end;
+    end;
+  end;
+
+var
+  TableEl, TREl, TDEl, CodeEl: TDOMElement;
+  ThisClass, PrevClass: TPasType;
+  ThisTreeNode: TPasElementNode;
+begin
+  TableEl := CreateTable(aParent);
+  // Process tree class information
+  // First tree class link is to This class
+  PrevClass:= nil;
+  ThisClass:=aClass;
+  ThisClass := AClass; ThisTreeNode := Nil;
+  if AClass.ObjKind = okInterface then
+    ThisTreeNode := TreeInterface.GetPasElNode(AClass)
+  else
+    ThisTreeNode := TreeClass.GetPasElNode(AClass);
+  Repeat
+    TREl := CreateTR(TableEl);
+    TDEl := CreateTD_vtop(TREl);
+    TDEl['align'] := 'center';
+    CodeEl := CreateCode(CreatePara(TDEl));
+
+
+    // Show class item
+    AppendHyperlink(CodeEl, ThisClass);
+    if Assigned(PrevClass) and (PrevClass Is TPasClassType)  then // Interfaces from prevClass
+      AppendInterfaceInfo(CodeEl, TPasClassType(PrevClass));
+    TDEl := CreateTD_vtop(TREl);
+    AppendShortDescrCell(TDEl, ThisClass);
+
+    if Assigned(ThisTreeNode) then
+      if Assigned(ThisTreeNode.ParentNode) then
+        begin
+        TDEl := CreateTD(CreateTR(TableEl));
+        TDEl['align'] := 'center';
+        AppendText(TDEl, '|');
+        PrevClass:= ThisClass;
+        ThisClass := ThisTreeNode.ParentNode.Element;
+        ThisTreeNode := ThisTreeNode.ParentNode;
+        end
+      else
+        begin
+        ThisClass := nil;
+        ThisTreeNode:= nil;
+        PrevClass:= nil;
+        end
+   Until (ThisTreeNode=Nil);
+end;
+
+procedure TNewHTMLWriter.CreateClassMainPage(aClass : TPasClassType);
+
+var
+  lSection: THTMLElement;
+  ParaEl: TDOMElement;
+  lMap: TLinkIdentifierMap;
+  I : Integer;
+  DocNode: TDocNode;
+begin
+  // Menu bar
+  // Title, short description & navs
+  lSection:=CreateSection(ContentElement);
+  AppendTitle(lSection,AClass.Name,AClass.Hints);
+  ParaEl := CreatePara(lSection);
+  AppendShortDescr(CreatePara(lSection), AClass);
+  ParaEl:=CreateEl(lSection,'div','tabs');;
+  ParaEl:=CreateEl(ParaEl,'ul');
+  if HasMembersToShow(aClass,True,@PropertyFilter) then
+    AppendText(CreateLink(ParaEl,'#properties'),UTF8Decode(SDocProperties));
+  if HasMembersToShow(aClass,True,@MethodFilter) then
+    AppendText(CreateLink(ParaEl,'#methods'),UTF8Decode(SDocMethods));
+  if HasMembersToShow(aClass,True,@EventFilter) then
+    AppendText(CreateLink(ParaEl,'#events'),UTF8Decode(SDocEvents));
+  // Declaration
+  lSection:=CreateSection(ContentElement);
+  AppendText(CreateH2(lSection), UTF8Decode(SDocDeclaration));
+  AppendSourceRef(lSection,AClass);
+  lMap:=TLinkIdentifierMap.Create(Self);
+  try
+    if assigned(aClass.AncestorType) then
+      lMap.AddLink(aClass.AncestorType);
+    if assigned(aClass.Interfaces) then
+      for I:=0 to AClass.Interfaces.Count-1 do
+        lMap.AddLink(TPasElement(aClass.Interfaces[i]));
+    for I:=0 to AClass.Members.Count-1 do
+      lMap.AddLink(TPasElement(AClass.Members[i]));
+    AppendHighlightedCode(lSection,GetElementCode(aClass,True),'',lMap);
+  finally
+    lMap.Free;
+  end;
+  // Inheritance
+  lSection:=CreateSection(ContentElement);
+  AppendText(CreateH2(lSection), UTF8Decode(SDocInheritance));
+  AppendInheritanceTree(lSection,aClass);
+  // Description
+  DocNode := Engine.FindDocNode(aClass);
+  If Assigned(DocNode) and Assigned(DocNode.Descr) then
+    begin
+    lSection:=CreateSection(ContentElement);
+    AppendDescrSection(aClass,lSection,DocNode.Descr,UTF8Decode(SDocDescription));
+    end;
+  // Properties, methods and events
+  AppendMemberListSection(ContentElement,aClass,cmtProperty,True);
+  AppendMemberListSection(ContentElement,aClass,cmtMethod,True);
+  AppendMemberListSection(ContentElement,aClass,cmtEvent,True);
+  AppendMemberListSection(ContentElement,aClass,cmtField,True);
+  // The rest
+  FinishElementPage(aClass,False);
+end;
+
+
+procedure TNewHTMLWriter.CreateClassMemberList(aParent : THTMLElement; AClass: TPasClassType; DeclaredOnly : Boolean; AFilter: TMemberFilter);
+
+  Function GetMemberHints(aMember : TPasElement) : String;
+  var
+    S : String;
+
+  begin
+    S:='';
+    case aMember.Visibility of
+      visPrivate,
+      visStrictPrivate:
+        S:='pv';
+      visProtected,
+      visStrictProtected:
+        S:='pt';
+      visPublished:
+        S:='pl';
+    else
+      //
+    end;
+    if (aMember.ClassType = TPasProperty) and
+       ((TPasProperty(aMember).WriteAccessorName) = '') then
+      begin
+      if S<>'' then
+        S:=S+',';
+      S:=S+'ro';
+      end;
+    Result:=S;
+  end;
+
+var
+  List: TFPList;
+  ThisClass: TPasClassType;
+  i, j: Integer;
+  Member: TPasElement;
+  DlEl,DtEl,DdEl: TDOMElement;
+  S : String;
+
+begin
+  List := TFPList.Create;
+  try
+    ThisClass := AClass;
+    while Assigned(ThisClass) do
+    begin
+      for i := 0 to ThisClass.Members.Count - 1 do
+      begin
+        Member := TPasElement(ThisClass.Members[i]);
+        if Engine.ShowElement(Member) and AFilter(Member) then
+        begin
+          j := 0;
+          while (j < List.Count) and
+            (CompareText(TPasElement(List[j]).Name, Member.Name) < 0) do
+            Inc(j);
+          List.Insert(j, Member);
+        end;
+      end;
+      if DeclaredOnly or (Assigned(ThisClass.AncestorType) and not (ThisClass.AncestorType.inheritsfrom(TPasClassType))) then
+        ThisClass:=Nil
+      else
+        ThisClass := TPasClassType(ThisClass.AncestorType);
+    end;
+
+    for i := 0 to List.Count - 1 do
+    begin
+      dlEl := CreateEl(aParent,'div','columns list');
+      Member := TPasElement(List[i]);
+      dTEl:= CreateEl(dlEl,'div','column list is-2');
+      AppendHyperlink(dtEl, Member);
+      ddEl:= CreateEl(dlEl,'div','column is-10 list');
+      S:=GetMemberHints(Member);
+      if S<>'' then
+        begin
+        S:='{'+S+'}';
+        AppendNbSp(dtEl,1);
+        AppendText(CreateEl(dtEl,'span','cmt'),S);
+        end;
+      AppendShortDescr(DdEl,Member);
+    end;
+  finally
+    List.Free;
+  end;
+end;
+
+procedure TNewHTMLWriter.CreateClassSortedSubpage(AClass: TPasClassType; aType: TClassMemberType);
+
+var
+  lSection,TitleEl, linkEl: THTMLElement;
+
+begin
+  lSection:=CreateSection(ContentElement);
+  TitleEl:=CreateH1(lSection);
+  AppendText(TitleEl, GetMemberOverviewTitle(aType));
+  linkEl:=CreateLink(TitleEl,ResolveLinkWithinPackage(aClass,0));
+  AppendText(LinkEl, aClass.Name);
+  AppendMemberListSection(lSection,aClass,aType,False);
+end;
+
+procedure TNewHTMLWriter.CreateClassInheritedSubpage(AClass: TPasClassType; aType: TClassMemberType);
+
+var
+  lSection,TitleEl, linkEl: THTMLElement;
+begin
+  lSection:=CreateSection(ContentElement);
+  TitleEl:=CreateH1(lSection);
+  AppendText(TitleEl, GetMemberOverviewTitle(aType));
+  linkEl:=CreateLink(TitleEl,ResolveLinkWithinPackage(aClass,0));
+  AppendText(LinkEl, aClass.Name);
+  AppendMemberListSection(lSection,aClass,aType,False);
+end;
+
+
+procedure TNewHTMLWriter.CreateClassPageBody(AClass: TPasClassType; ASubpageIndex: Integer);
+
+begin
+  case ASubpageIndex of
+    0:
+      CreateClassMainPage(aClass);
+    PropertiesByInheritanceSubindex:
+      CreateClassInheritedSubpage(aClass,cmtProperty);
+    PropertiesByNameSubindex:
+      CreateClassSortedSubpage(aClass,cmtProperty);
+    MethodsByInheritanceSubindex:
+      CreateClassInheritedSubpage(aClass,cmtMethod);
+    MethodsByNameSubindex:
+      CreateClassSortedSubpage(aClass,cmtMethod);
+    EventsByInheritanceSubindex:
+      CreateClassInheritedSubpage(aClass,cmtEvent);
+    EventsByNameSubindex:
+      CreateClassSortedSubpage(aClass,cmtEvent);
+    FieldsByNameSubindex:
+      CreateClassSortedSubpage(aClass,cmtField);
+  end;
+end;
+
+
+function TNewHTMLWriter.GetVarDef(aElement : TPasVariable; aPrefixParent : Boolean) : string;
+
+begin
+  Result:=GetElementCode(aElement,False);
+end;
+
+procedure TNewHTMLWriter.CreateClassMemberPageBody(AElement: TPasElement);
+var
+  CodeBlock : TDOMElement;
+
+  procedure CreateVarPage(Element: TPasVariable);
+
+  var
+    S : String;
+
+  begin
+    S:=GetElementCode(Element,False);
+    AppendCodeBlock(ContentElement,S);
+  end;
+
+  procedure CreateTypePage(Element: TPasType);
+  var
+    S : String;
+
+  begin
+    S:=GetElementCode(Element,False);
+    AppendCodeBlock(ContentElement,S);
+  end;
+
+  procedure CreateConstPage(Element: TPasConst);
+  var
+    S : String;
+
+  begin
+    S:=GetElementCode(Element,False);
+    AppendCodeBlock(ContentElement,S);
+  end;
+
+  procedure CreatePropertyPage(Element: TPasProperty);
+  var
+    S : String;
+  begin
+    S:=GetElementCode(Element,True);
+    AppendCodeBlock(ContentElement,S);
+  end;
+
+var
+  s: String;
+
+begin
+  AppendTitle(ContentElement,AElement.FullName,AElement.Hints);
+  AppendShortDescr(CreatePara(ContentElement), AElement);
+
+  AppendText(CreateH2(ContentElement), SDocDeclaration);
+  AppendSourceRef(ContentElement,AElement);
+
+  CodeBlock := AppendCodeBlock(ContentElement);
+  if (Assigned(aElement.Parent) and aElement.Parent.InheritsFrom(TPasType)) and (AElement.Visibility<>visDefault) then
+    begin
+    s:=VisibilityNames[AElement.Visibility];
+    AppendKw(CodeBlock, s);
+    AppendText(CodeBlock, ' ');
+    end;
+  if AElement is TPasProperty then
+    CreatePropertyPage(TPasProperty(AElement))
+  else if AElement is TPasConst then
+    CreateConstPage(TPasConst(AElement))
+  else if (AElement is TPasVariable) then
+    CreateVarPage(TPasVariable(AElement))
+  else if AElement is TPasProcedureBase then
+    AppendProcDecl(CodeBlock,TPasProcedureBase(AElement))
+  else if AElement is TPasType then
+    CreateTypePage(TPasType(AElement))
+  else
+    AppendText(CreateWarning(ContentElement), '<' + AElement.ClassName + '>');
+
+  FinishElementPage(AElement);
+end;
+
+procedure TNewHTMLWriter.CreateVarPageBody(AVar: TPasVariable);
+var
+  lSection,CodeEl: TDOMElement;
+
+begin
+  lSection:=CreateSection(ContentElement);
+  AppendTitle(lSection,AVar.FullName,AVar.Hints);
+  AppendShortDescr(CreatePara(lSection), AVar);
+  AppendText(CreateH2(lSection), SDocDeclaration);
+  AppendSourceRef(lSection,AVar);
+  CodeEl := AppendCodeBlock(lSection);
+  AppendPasSHFragment(CodeEl, GetElementCode(aVar,False),0);
+  FinishElementPage(AVar);
+end;
+
+procedure TNewHTMLWriter.CreateProcPageBody(AProc: TPasProcedureBase);
+
+var
+  lSection,CodeEl: THTMLElement;
+
+begin
+  lSection:=CreateSection(ContentElement);
+  AppendTitle(lSection,AProc.Name,AProc.Hints);
+  AppendShortDescr(CreatePara(lSection), AProc);
+  lSection:=CreateSection(ContentElement);
+  AppendText(CreateH2(lSection), SDocDeclaration);
+  AppendSourceRef(lSection,AProc);
+  CodeEl := CreateCode(lSection);
+  AppendProcDecl(CodeEl, AProc);
+  if aProc is TPasProcedure then
+    if Assigned(TPasProcedure(aProc).ProcType) then
+      AppendProcArgsSection(lSection, TPasProcedure(aProc).ProcType);
+  FinishElementPage(AProc);
+end;
+
+function TNewHTMLWriter.InterPretOption ( const Cmd, Arg: String ) : boolean;
+
+  Function ReadFile(aFileName : string) : TstringStream;
+
+  begin
+    aFileName:= SetDirSeparators(aFileName);
+    try
+      if copy(aFileName,1,1)<>'@' then
+        Result:=TStringStream.Create(aFileName)
+      else
+        begin
+        Delete(aFileName,1,1);
+        Result:=TStringStream.Create('');
+        Result.LoadFromFile(aFileName);
+        Result.Position:=0;
+        end;
+    except
+      Result.Free;
+      Raise;
+    end;
+  end;
+
+begin
+  Result:=True;
+  if Cmd = '--html-search' then
+    SearchPage := Arg
+  else if Cmd = '--footer' then
+    FFooterHTML := ReadFile(Arg)
+  else if Cmd = '--header' then
+    FHeaderHTML := ReadFile(Arg)
+  else if Cmd = '--navigator' then
+    FNavigatorHTML := ReadFile(Arg)
+  else if Cmd = '--charset' then
+    CharSet := Arg
+  else if Cmd = '--index-colcount' then
+    IndexColCount := StrToIntDef(Arg,IndexColCount)
+  else if Cmd = '--image-url' then
+    BaseImageURL  := Arg
+  else if Cmd = '--css-file' then
+    FCSSFile := arg
+  else if Cmd = '--footer-date' then
+    begin
+    FIncludeDateInFooter:=True;
+    FDateFormat:=Arg;
+    end
+  else if Cmd = '--disable-menu-brackets' then
+    FUseMenuBrackets:=False
+  else
+    Result:=inherited InterPretOption(Cmd, Arg);
+end;
+
+
+class procedure TNewHTMLWriter.Usage(List: TStrings);
+begin
+  List.add('--header=file');
+  List.Add(SHTMLUsageHeader);
+  List.add('--footer=file');
+  List.Add(SHTMLUsageFooter);
+  List.add('--navigator=file');
+  List.Add(SHTMLUsageNavigator);
+  List.Add('--footer-date[=Fmt]');
+  List.Add(SHTMLUsageFooterDate);
+  List.Add('--charset=set');
+  List.Add(SHTMLUsageCharset);
+  List.Add('--html-search=pagename');
+  List.Add(SHTMLHtmlSearch);
+  List.Add('--index-colcount=N');
+  List.Add(SHTMLIndexColcount);
+  List.Add('--image-url=url');
+  List.Add(SHTMLImageUrl);
+  List.Add('--disable-menu-brackets');
+  List.Add(SHTMLDisableMenuBrackets);
+  inherited Usage(List);
+end;
+
+class procedure TNewHTMLWriter.SplitImport(var AFilename, ALinkPrefix: String);
+var
+  i: integer;
+begin
+  i := Pos(',', AFilename);
+  if i > 0 then
+    begin  //split into filename and prefix
+    ALinkPrefix := Copy(AFilename,i+1,Length(AFilename));
+    SetLength(AFilename, i-1);
+    end
+  else if ALinkPrefix = '' then
+    begin  //synthesize outdir\pgk.xct, ..\pkg
+    ALinkPrefix := '../' + ChangeFileExt(ExtractFileName(AFilename), '');
+    AFilename := ChangeFileExt(AFilename, '.xct');
+    end;
+end;
+
+class function TNewHTMLWriter.FileNameExtension: String;
+begin
+  result:='';
+end;
+
+// private methods
+
+
+procedure TNewHTMLWriter.SetOnTest(const AValue: TNotifyEvent);
+begin
+  if FOnTest=AValue then exit;
+    FOnTest:=AValue;
+end;
+
+
+initialization
+  // Do not localize.
+  RegisterWriter(TNewHTMLWriter,'newhtml','HTML output using fpdocs.css stylesheet.');
+
+finalization
+  UnRegisterWriter('newhtml');
+
+end.

+ 146 - 18
utils/fpdoc/dwriter.pp

@@ -54,6 +54,7 @@ type
       ASubindex: Integer): String; virtual; abstract;
     function GetRelativePathToTop(AElement: TPasElement): String; virtual;
     function GetCSSFilename(ARelativeTo: TPasElement): DOMString; virtual;
+    function GetCSSFilename(ARelativeTo: TPasElement; const aFileName : DOMstring): DOMString; virtual;
     property SubPageNames: Boolean read FSubPageNames write FSubPageNames;
   end;
 
@@ -67,6 +68,9 @@ type
     property Extension: String read FExtension;
   end;
 
+  // Member Filter Callback type
+  TMemberFilter = function(AMember: TPasElement): Boolean;
+  TClassMemberType = (cmtMethod,cmtProperty,cmtEvent,cmtField);
 
   TWriterLogEvent = Procedure(Sender : TObject; Const Msg : String) of object;
   TWriterNoteEvent = Procedure(Sender : TObject; Note : TDomElement; Var EmitNote : Boolean) of object;
@@ -95,6 +99,8 @@ type
     procedure Warning(AContext: TPasElement; const AMsg: String);
     procedure Warning(AContext: TPasElement; const AMsg: String;
       const Args: array of const);
+    procedure IndentLines(aLines: TStrings; aIndent: Word);
+    function IndentLines(aLines: String; aIndent: Word): String;
 
     // function FindShortDescr(const Name: String): TDOMElement;
 
@@ -104,16 +110,12 @@ type
     function ConvertShort(AContext: TPasElement; El: TDOMElement): Boolean;
     function ConvertNotes(AContext: TPasElement; El: TDOMElement): Boolean; virtual;
     function ConvertBaseShort(AContext: TPasElement; Node: TDOMNode): Boolean;
-    procedure ConvertBaseShortList(AContext: TPasElement; Node: TDOMNode;
-      MayBeEmpty: Boolean);
+    procedure ConvertBaseShortList(AContext: TPasElement; Node: TDOMNode;  MayBeEmpty: Boolean);
     procedure ConvertLink(AContext: TPasElement; El: TDOMElement);
     function ConvertExtShort(AContext: TPasElement; Node: TDOMNode): Boolean;
-    procedure ConvertDescr(AContext: TPasElement; El: TDOMElement;
-      AutoInsertBlock: Boolean);
-    function ConvertNonSectionBlock(AContext: TPasElement;
-      Node: TDOMNode): Boolean;
-    procedure ConvertExtShortOrNonSectionBlocks(AContext: TPasElement;
-      Node: TDOMNode);
+    procedure ConvertDescr(AContext: TPasElement; El: TDOMElement; AutoInsertBlock: Boolean);
+    function ConvertNonSectionBlock(AContext: TPasElement;  Node: TDOMNode): Boolean;
+    procedure ConvertExtShortOrNonSectionBlocks(AContext: TPasElement; Node: TDOMNode);
     function ConvertSimpleBlock(AContext: TPasElement; Node: TDOMNode): Boolean;
     Function FindTopicElement(Node : TDocNode): TTopicElement;
     Procedure ConvertImage(El : TDomElement);
@@ -197,6 +199,7 @@ type
     Procedure FPDocError(Fmt : String; Args : Array of Const);
     Function  ShowMember(M : TPasElement) : boolean;
     Procedure GetMethodList(ClassDecl: TPasClassType; List : TStringList);
+    function HasMembersToShow(AClass: TPasClassType; DeclaredOnly: Boolean; AFilter: TMemberFilter): boolean;
     Property EmitNotes : Boolean Read FEmitNotes Write FEmitNotes;
     Property BeforeEmitNote : TWriterNoteEvent Read FBeforeEmitNote Write FBeforeEmitNote;
   end;
@@ -225,6 +228,7 @@ const
   MethodsByNameSubindex = 14;
   EventsByInheritanceSubindex = 15;
   EventsByNameSubindex = 16;
+  FieldsByNameSubindex = 17;
 
 
 Type
@@ -260,11 +264,10 @@ Type
     function GetOutputPageNames: TStrings;
     function GetPageCount: Integer;
     function LinkFix(ALink:String):String;
+    procedure SetModule(AValue: TPasModule);
   Protected
     FAllocator: TFileAllocator;
     Procedure LinkUnresolvedInc();
-    // General resolving routine
-    function ResolveLinkID(const Name: String): DOMString;
     // Simplified resolving routine. Excluded last path after dot
     function ResolveLinkIDUnStrict(const Name: String): DOMString;
     function ResolveLinkIDInUnit(const Name,AUnitName: String): DOMString;
@@ -294,11 +297,13 @@ Type
   Public
     constructor Create(APackage: TPasPackage; AEngine: TFPDocEngine); override;
     Destructor Destroy; override;
+    // General resolving routine
+    function ResolveLinkID(const Name: String): DOMString;
     class procedure Usage(List: TStrings); override;
     function InterpretOption(const Cmd, Arg: String): boolean; override;
     property PageCount: Integer read GetPageCount;
     Property Allocator : TFileAllocator Read FAllocator;
-    Property Module: TPasModule  Read FModule Write FModule;
+    Property Module: TPasModule  Read FModule Write SetModule;
     Property CurDirectory: String Read FCurDirectory Write FCurDirectory;    // relative to curdir of process
     property BaseDirectory: String read FBaseDirectory Write FBaseDirectory; // relative path to package base directory
     Property OutputPageNames : TStrings Read GetOutputPageNames;
@@ -307,14 +312,17 @@ Type
   TFPDocWriterClass = Class of TFPDocWriter;
   EFPDocWriterError = Class(Exception);
 
-// Member Filter Callback type
-  TMemberFilter = function(AMember: TPasElement): Boolean;
 
 //  Filter Callbacks
 function PropertyFilter(AMember: TPasElement): Boolean;
 function MethodFilter(AMember: TPasElement): Boolean;
 function EventFilter(AMember: TPasElement): Boolean;
-
+function FieldFilter(AMember: TPasElement): Boolean;
+function GetMemberFilter(aMemberType : TClassMemberType) : TMemberFilter;
+Function GetMemberDocName(aMemberType : TClassMemberType) : String;
+Function GetAnchorName(aMemberType : TClassMemberType) : String;
+function GetMemberSubIndex(aMemberType : TClassMemberType) : Integer;
+Function GetMemberOverviewTitle(aMemberType : TClassMemberType) : String;
 
 // Register backend
 Procedure RegisterWriter(AClass : TFPDocWriterClass; Const AName,ADescr : String);
@@ -364,6 +372,63 @@ begin
     (Copy(AMember.Name, 1, 2) = 'On');
 end;
 
+function FieldFilter(AMember: TPasElement): Boolean;
+begin
+  Result := (AMember.ClassType = TPasVariable);
+end;
+
+function GetMemberFilter(aMemberType: TClassMemberType): TMemberFilter;
+begin
+  case aMemberType of
+    cmtProperty : Result:=@PropertyFilter;
+    cmtMethod : Result:=@MethodFilter;
+    cmtEvent : Result:=@EventFilter;
+    cmtField : Result:=@FieldFilter;
+  end;
+
+end;
+
+function GetMemberDocName(aMemberType: TClassMemberType): String;
+begin
+  case aMemberType of
+    cmtProperty : Result:=SDocProperties;
+    cmtMethod : Result:=SDocMethods;
+    cmtEvent : Result:=SDocEvents;
+    cmtField : Result:=SDocFields;
+  end;
+
+end;
+
+function GetAnchorName(aMemberType: TClassMemberType): String;
+begin
+  case aMemberType of
+    cmtProperty : Result:='properties';
+    cmtMethod : Result:='methods';
+    cmtEvent : Result:='events';
+    cmtField : Result:='fields';
+  end;
+end;
+
+function GetMemberSubIndex(aMemberType: TClassMemberType): Integer;
+begin
+  case aMemberType of
+    cmtProperty : Result:=PropertiesByNameSubIndex;
+    cmtMethod : Result:=MethodsByNameSubIndex;
+    cmtEvent : Result:=EventsByNameSubIndex;
+    cmtField : Result:=FieldsByNameSubIndex;
+  end;
+end;
+Function GetMemberOverviewTitle(aMemberType : TClassMemberType) : String;
+begin
+  Case aMemberType of
+    cmtField : Result:='Fields of ';
+    cmtProperty : Result:='Properties of ';
+    cmtMethod : Result:='Properties of ';
+    cmtEvent : Result:='Events of ';
+  end;
+end;
+
+
 { ---------------------------------------------------------------------
   Writer registration
   ---------------------------------------------------------------------}
@@ -498,6 +563,12 @@ begin
   Result:= res;
 end;
 
+procedure TMultiFileDocWriter.SetModule(AValue: TPasModule);
+begin
+  if FModule=AValue then Exit;
+  FModule:=AValue;
+end;
+
 { Used for:
   - <link> elements in descriptions
   - "see also" entries
@@ -625,9 +696,8 @@ begin
              and Engine.ShowElement(FPEl) then
                begin
                DocNode := Engine.FindDocNode(FPEl);
-               if Assigned(DocNode) then
-                 AddPage(FPEl, 0);
-               end;
+               AddPage(FPEl, 0)
+               end
           end;
         end;
       end;
@@ -656,6 +726,7 @@ begin
     AddPage(ClassEl, MethodsByNameSubindex);
     AddPage(ClassEl, EventsByInheritanceSubindex);
     AddPage(ClassEl, EventsByNameSubindex);
+    AddPage(ClassEl, FieldsByNameSubindex);
     for j := 0 to ClassEl.Members.Count - 1 do
       begin
       FPEl := TPasElement(ClassEl.Members[j]);
@@ -1051,7 +1122,12 @@ end;
 
 function TFileAllocator.GetCSSFilename(ARelativeTo: TPasElement): DOMString;
 begin
-  Result := Utf8Decode(GetRelativePathToTop(ARelativeTo)) + 'fpdoc.css';
+  Result := GetCSSFilename(ARelativeTo,'fpdoc');
+end;
+
+function TFileAllocator.GetCSSFilename(ARelativeTo: TPasElement; const aFileName: DOMString): DOMString;
+begin
+  Result := Utf8Decode(GetRelativePathToTop(ARelativeTo)) + aFileName+'.css';
 end;
 
 { ---------------------------------------------------------------------
@@ -1291,6 +1367,33 @@ begin
   Warning(AContext, Format(AMsg, Args));
 end;
 
+procedure TFPDocWriter.IndentLines(aLines: TStrings; aIndent: Word);
+
+var
+  lIndent : string;
+  I : Integer;
+begin
+  lIndent:=StringOfChar(' ',aIndent);
+  For I:=0 to aLines.Count-1 do
+    aLines[i]:=lIndent+aLines[i];
+end;
+
+function TFPDocWriter.IndentLines(aLines: String; aIndent: Word): String;
+var
+  l : TStringList;
+begin
+  L:=TStringList.Create;
+  try
+    L.Text:=aLines;
+    L.TrailingLineBreak:=False;
+    IndentLines(L,aIndent);
+    Result:=L.Text;
+  finally
+    L.Free;
+  end;
+end;
+
+
 function TFPDocWriter.IsDescrNodeEmpty(Node: TDOMNode): Boolean;
 var
   Child: TDOMNode;
@@ -2114,6 +2217,31 @@ begin
     Result:=Not ((M.Visibility=visProtected) and Engine.HideProtected)
 end;
 
+function TFPDocWriter.HasMembersToShow(AClass: TPasClassType; DeclaredOnly : Boolean; AFilter: TMemberFilter) : boolean;
+var
+  ThisClass: TPasClassType;
+  I : Integer;
+  Member: TPasElement;
+
+begin
+  Result:=False;
+  ThisClass := AClass;
+  while Assigned(ThisClass) do
+    begin
+    for i := 0 to ThisClass.Members.Count - 1 do
+      begin
+      Member := TPasElement(ThisClass.Members[i]);
+      if Engine.ShowElement(Member) and AFilter(Member) then
+        Exit(True);
+      end;
+    if DeclaredOnly or (Assigned(ThisClass.AncestorType) and not (ThisClass.AncestorType.inheritsfrom(TPasClassType))) then
+      ThisClass:=Nil
+    else
+      ThisClass := TPasClassType(ThisClass.AncestorType);
+    end;
+end;
+
+
 procedure TFPDocWriter.GetMethodList ( ClassDecl: TPasClassType;
   List: TStringList ) ;
 

+ 8 - 1
utils/fpdoc/examples/simple/testunit.pp

@@ -18,6 +18,10 @@ Const
   ARecordConst : TMethod = (Code:Nil;Data:Nil);
   ASetConst = [true,false];
   ADeprecatedConst = 1 deprecated;
+
+resourcestring
+  String1 = 'Resource string 1';
+  String2 = 'Resource string 2';
    
 Type
   TAnEnumType         = (one,two,three);
@@ -48,6 +52,7 @@ Type
   
   Private
     Const aconst = 123;
+  private  
     X22 : Integer;
     Procedure SetX(AValue : Integer);
     Function GetX : Integer;
@@ -60,7 +65,8 @@ Type
   end;
   TAExtRecordType        = Record
     Const X = 100;
-    operator assign(Y : Integer) : TAExtRecordType;
+  public  
+    class operator assign(Y : Integer) : TAExtRecordType;
   end;
                         
 Var
@@ -125,6 +131,7 @@ Type
     Procedure AStringMessageProc(Var Msg); Message '123';
     Procedure ADeprecatedProc; deprecated;
     Procedure APlatformProc; Platform;
+    function MyFunc : Integer; 
     Property IntProp : Integer Read FI Write Fi;
     Property IntROProp : Integer Read FI;
     Property GetIntProp : Integer Read ReadI Write WriteI;

+ 12 - 1
utils/fpdoc/examples/simple/testunit.xml

@@ -17,6 +17,15 @@ We have here a description
   <note>Unit note</note>
 </notes>
 
+<topic name="MyTopic">
+<short>A short topic</short>
+<descr>Longer text for the topic</descr>
+<seealso>
+<link id="AnIntegerConst"/>
+</seealso>
+</topic>
+
+
 <!-- constant Visibility: default -->
 <element name="AnIntegerConst">
 <short>Aha this is not me</short>
@@ -107,6 +116,7 @@ Appears in 2.0
 <element name="TAnEnumType">
 <short></short>
 <descr>
+Some longer description of the type.
 </descr>
 <version>
 Appears in 2.0
@@ -144,8 +154,9 @@ Appears in 2.0
 
 <!-- array type Visibility: default -->
 <element name="TAnArrayType">
-<short></short>
+<short>Short description</short>
 <descr>
+We have a longer description.
 </descr>
 <seealso>
 </seealso>

+ 6 - 2
utils/fpdoc/fpdoc.lpi

@@ -40,7 +40,7 @@
         <PackageName Value="FCL"/>
       </Item1>
     </RequiredPackages>
-    <Units Count="21">
+    <Units Count="22">
       <Unit0>
         <Filename Value="fpdoc.pp"/>
         <IsPartOfProject Value="True"/>
@@ -132,6 +132,10 @@
         <Filename Value="fpdocstrs.pp"/>
         <IsPartOfProject Value="True"/>
       </Unit20>
+      <Unit21>
+        <Filename Value="dw_newhtml.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit21>
     </Units>
   </ProjectOptions>
   <CompilerOptions>
@@ -141,7 +145,7 @@
     </Target>
     <SearchPaths>
       <IncludeFiles Value="$(ProjOutDir)"/>
-      <OtherUnitFiles Value="../../packages/fcl-passrc/src"/>
+      <OtherUnitFiles Value=".;../../packages/fcl-passrc/src"/>
       <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
     </SearchPaths>
     <Linking>

+ 1 - 0
utils/fpdoc/fpdoc.pp

@@ -32,6 +32,7 @@ uses
   dw_XML,    // XML writer
   dw_dxml,   // Delphi XML doc.
   dw_HTML,   // HTML writer
+  dw_newhtml,   // HTML writer
   dw_chm,    // CHM Writer
   dw_markdown, // Markdown writer
   dw_ipflin, // IPF writer (new linear output)

+ 223 - 0
utils/fpdoc/fpdocs.css

@@ -0,0 +1,223 @@
+/*
+  $Id: fpdoc2.css,v 1.1 2003/03/17 23:03:20 michael Exp $
+
+  Default style sheet for FPDoc reference documentation
+  by Sebastian Guenther, [email protected]
+
+  Feel free to use this file as a template for your own style sheets.
+*/
+
+.section {
+  padding: 1rem 1rem; !important
+}
+
+div.code, tt, span.kw, pre, div.code a {
+  font-family: Courier, monospace
+}
+
+.column.list {
+  padding-top: 0px;
+  padding-bottom: 0px;
+}
+
+/* source fragments */
+span.code {
+  white-space: nowrap
+}
+
+/* symbols in source fragments */
+span.sym {
+  color: darkred
+}
+
+/* keywords in source fragments */
+span.kw {
+  font-weight: bold
+}
+
+/* comments in source fragments */
+span.cmt {
+  color: darkcyan;
+  font-style: italic
+}
+
+/* directives in source fragments */
+span.dir {
+  color: darkyellow;
+  font-style: italic
+}
+
+/* numbers in source fragments */
+span.num {
+  color: darkmagenta
+}
+
+/* characters (#...) in source fragments */
+span.chr {
+  color: darkcyan
+}
+
+/* strings in source fragments */
+span.str {
+  color: blue
+}
+
+/* assembler passages in source fragments */
+span.asm {
+  color: green
+}
+
+p.cmt {
+  color: gray
+}
+
+span.warning {
+  color: red;
+  font-weight: bold
+}
+
+/* !!!: How should we define this...? */
+span.file {
+  color: darkgreen
+}
+
+span.footer {
+  font-style: italic;
+  color: darkblue
+}
+
+span.toggletreeclose {
+    background: url(minus.png) center left no-repeat;
+    padding-left: 20px;
+}
+span.toggletreeopen {
+    background: url(plus.png) center left no-repeat;
+    padding-left: 20px;
+}
+
+span.identifier {
+  font-style: italic;
+}
+
+
+
+/*  --------------------------------------------------------------------
+ *  Side menu styling 
+ *  -------------------------------------------------------------------- */
+
+.burger-icon {
+    font-size: 1.5rem; 
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    line-height: 1; 
+}
+
+/* Ensure the main container accounts for the navbar height */
+#main-layout {
+    display: flex;
+    min-height: calc(100vh - 52px); /* 52px is the standard Bulma navbar height */
+    position: relative;
+}
+
+/* 2. Side Menu (Off-Canvas/Collapsed State) */
+#side-menu {
+    /* Position the menu absolutely so it doesn't displace content in the flow */
+    position: absolute; 
+    top: 0;
+    left: 0;
+    height: 100%; /* Full height of the main-layout container */
+    width: 10em; 
+    padding: 10px;
+    background-color: #f5f5f5; /* Bulma's 'bis' color for contrast */
+    box-shadow: 2px 0 3px rgba(0, 0, 0, 0.1);
+    
+    /* Hidden off-screen */
+    transform: translateX(-10em); 
+    transition: transform 0.3s ease-in-out;
+    z-index: 20; /* Ensure it is above the main content, but below navbar (navbar uses 30) */
+    overflow-y: scroll
+}
+
+/* 3. Side Menu (Expanded State) */
+#side-menu.is-expanded {
+    /* Slide it into view */
+    transform: translateX(0);
+}
+
+/* 4. Main Content Area (Initial/Collapsed State) */
+#main-content {
+    flex-grow: 1; 
+    width: 100%; /* Ensures it fills the remaining space */
+    
+    /* Start with no padding on the left */
+    padding-left: 0 !important; 
+    transition: padding-left 0.3s ease-in-out; 
+}
+
+/* 5. Main Content Area (Shifted State) */
+#main-content.is-shifted {
+    /* When the menu is open, shift the content to the right to reveal the menu */
+    padding-left: 200px !important;
+}
+
+
+/*  --------------------------------------------------------------------
+ *  Class inheritance tree styling 
+ *  -------------------------------------------------------------------- */
+   
+.treeview, .treeview ul {
+    list-style: none; /* Remove default bullets */
+    padding-left: 15px;
+    margin: 0;
+}
+
+.treeview li {
+    line-height: 1.5;
+    cursor: pointer;
+    position: relative; /* Needed for positioning the pseudo-element */
+    padding-left: 5px; /* Add some space for the icon */
+}
+
+/* --- Nested List Visibility --- */
+
+/* Hide all nested ULs by default */
+.treeview ul {
+    display: none;
+}
+
+/* Show the nested UL when the parent LI has the 'expanded' class */
+.treeview li.expanded > ul {
+    display: block;
+}
+
+/* --- Icon Logic using ::before Pseudo-Element --- */
+
+/* 1. Default/Collapsed State for Parent Nodes */
+.treeview li.parent::before {
+    /* Right-pointing triangle: &#9656; or \25B8 */
+    content: '\25B8'; 
+    margin-right: 5px;
+    color: #007bff;
+    display: inline-block;
+    font-weight: bold;
+    font-size: 18px; /* Adjust size to look neat */
+}
+
+/* 2. Expanded State for Parent Nodes */
+.treeview li.expanded.parent::before {
+    /* Down-pointing triangle: &#9662; or \25BE */
+    content: '\25BE';
+}
+
+/* 3. Styling for Leaf Nodes (No Expansion) */
+/* The element without the 'parent' class is a leaf node */
+.treeview li:not(.parent)::before {
+    /* Black circle/dot for a simple leaf icon: &#9679; or \25CF */
+    content: '\25CF'; 
+    margin-right: 5px;
+    color: gray;
+    font-size: 0.8em;
+    cursor: default; 
+}
+

+ 3 - 0
utils/fpdoc/fpdocstrs.pp

@@ -6,6 +6,7 @@ interface
 
 resourcestring
   // Output strings
+  SDocReference              = 'Reference';
   SDocPackageTitle           = 'Reference for package ''%s''';
   SDocPackageMenuTitle       = 'Package ''%s''';
   SDocPackageLinkTitle       = 'Package';
@@ -62,6 +63,7 @@ resourcestring
   SDocProperties             = 'Properties';
   SDocMethods                = 'Methods';
   SDocEvents                 = 'Events';
+  SDocFields                 = 'Fieldss';
   SDocByName                 = 'by Name';
   SDocByInheritance          = 'By inheritance';
   SDocValue                  = 'Value';
@@ -73,6 +75,7 @@ resourcestring
   SDocVisibility             = 'Visibility';
   SDocOpaque                 = 'Opaque type';
   SDocDateGenerated          = 'Documentation generated on: %s';
+  SSeeAll                    = 'See all';
   // The next line requires leading/trailing space due to XML comment layout:
   SDocGeneratedByComment     = ' Generated using FPDoc - (c) 2000-2021 FPC contributors and Sebastian Guenther, [email protected] ';
   SDocNotes                  = 'Notes';

+ 2 - 0
utils/fpdoc/fpmake.pp

@@ -37,6 +37,7 @@ begin
     P.Dependencies.Add('fcl-xml');
     P.Dependencies.Add('fcl-passrc');
     P.Dependencies.Add('fcl-process');
+    P.Dependencies.Add('fcl-syntax');
     P.Dependencies.Add('chm');
     P.Dependencies.Add('univint',[darwin,iphonesim,ios]);
 
@@ -109,6 +110,7 @@ begin
     if Bin2Obj <> '' then
       begin
       P.Commands.AddCommand(Bin2Obj,'-o $(DEST) -c DefaultCSS $(SOURCE)','css.inc','fpdoc.css');
+      P.Commands.AddCommand(Bin2Obj,'-o $(DEST) -c DefaultNewCSS $(SOURCE)','newcss.inc','fpdocs.css');
       P.Commands.AddCommand(Bin2Obj,'-o $(DEST) -c PlusImageData $(SOURCE)','plusimage.inc','images/plus.png');
       P.Commands.AddCommand(Bin2Obj,'-o $(DEST) -c MinusImageData $(SOURCE)','minusimage.inc','images/minus.png');
       end;