Browse Source

pastojs: nested classes

git-svn-id: trunk@38878 -
Mattias Gaertner 7 years ago
parent
commit
8100f9b222

+ 118 - 65
packages/pastojs/src/fppas2js.pp

@@ -2308,8 +2308,6 @@ end;
 procedure TPas2JSResolver.AddType(El: TPasType);
 begin
   inherited AddType(El);
-  if (El.Name<>'') and (TopScope is TPasClassScope) then
-    RaiseNotYetImplemented(20170608232534,El,'nested types');
 end;
 
 procedure TPas2JSResolver.ResolveImplAsm(El: TPasImplAsmStatement);
@@ -4446,7 +4444,11 @@ begin
   else if ThisPas=El then
     Result:='this'
   else
+    begin
     Result:=inherited GetLocalName(El);
+    if Result='this' then
+      Result:='';
+    end;
 end;
 
 function TFunctionContext.IndexOfLocalVar(const aName: string): integer;
@@ -4636,17 +4638,23 @@ end;
 
 procedure TConvertContext.WriteStack;
 {AllowWriteln}
+var
+  SelfCtx: TFunctionContext;
 
   procedure W(Index: integer; AContext: TConvertContext);
   begin
+    if AContext=SelfCtx then
+      writeln('  SelfContext:');
     AContext.DoWriteStack(Index);
     if AContext.Parent<>nil then
       W(Index+1,AContext.Parent);
   end;
 
 begin
-  writeln('TConvertContext.WriteStack: ');
+  SelfCtx:=GetSelfContext;
+  writeln('TConvertContext.WriteStack: START');
   W(1,Self);
+  writeln('TConvertContext.WriteStack: END');
 end;
 {AllowWriteln-}
 
@@ -10683,10 +10691,12 @@ begin
     Call.Expr:=CreateMemberExpression([FBuiltInNames[pbivnRTL],FnName]);
 
     // add parameter: owner. For top level class, the module is the owner.
-    if (El.Parent<>nil) and (El.Parent.ClassType=TImplementationSection) then
-      OwnerName:=AContext.GetLocalName(El.Parent)
+    if (El.Parent=nil)
+        or ((El.Parent is TPasSection)
+          and (El.Parent.ClassType<>TImplementationSection)) then
+      OwnerName:=AContext.GetLocalName(El.GetModule)
     else
-      OwnerName:=AContext.GetLocalName(El.GetModule);
+      OwnerName:=AContext.GetLocalName(El.Parent);
     if OwnerName='' then
       OwnerName:='this';
     Call.AddArg(CreatePrimitiveDotExpr(OwnerName,El));
@@ -10782,10 +10792,7 @@ begin
           else if C=TPasConst then
             NewEl:=ConvertConst(TPasConst(P),aContext)
           else if C=TPasProperty then
-            begin
-            NewEl:=ConvertProperty(TPasProperty(P),AContext);
-            if NewEl=nil then continue;
-            end
+            NewEl:=ConvertProperty(TPasProperty(P),AContext)
           else if C.InheritsFrom(TPasType) then
             NewEl:=CreateTypeDecl(TPasType(P),aContext)
           else if C.InheritsFrom(TPasProcedure) then
@@ -10794,9 +10801,8 @@ begin
             continue
           else
             RaiseNotSupported(P,FuncContext,20161221233338);
-          if NewEl=nil then
-            RaiseNotSupported(P,FuncContext,20170204223922);
-          AddToSourceElements(Src,NewEl);
+          if NewEl<>nil then
+            AddToSourceElements(Src,NewEl);
           end;
         end;
 
@@ -11810,35 +11816,40 @@ begin
       if ProcScope.ClassScope<>nil then
         begin
         // method or class method
-        FuncContext.ThisPas:=ProcScope.ClassScope.Element;
-        if bsObjectChecks in FuncContext.ScannerBoolSwitches then
+        if El.Parent is TProcedureBody then
           begin
-          // rtl.checkMethodCall(this,<class>)
-          Call:=CreateCallExpression(PosEl);
-          AddBodyStatement(Call,PosEl);
-          Call.Expr:=CreateMemberExpression([FBuiltInNames[pbivnRTL],
-                                          FBuiltInNames[pbifnCheckMethodCall]]);
-          Call.AddArg(CreatePrimitiveDotExpr('this',PosEl));
-          ClassPath:=CreateReferencePath(ProcScope.ClassScope.Element,AContext,rpkPathAndName);
-          Call.AddArg(CreatePrimitiveDotExpr(ClassPath,PosEl));
-          end;
-
-        if ImplProc.Body.Functions.Count>0 then
-          begin
-          // has nested procs -> add "var self = this;"
-          FuncContext.AddLocalVar(FBuiltInNames[pbivnSelf],FuncContext.ThisPas);
-          SelfSt:=CreateVarStatement(FBuiltInNames[pbivnSelf],
-                              CreatePrimitiveDotExpr('this',ImplProc),ImplProc);
-          AddBodyStatement(SelfSt,PosEl);
-          if ImplProcScope.SelfArg<>nil then
-            begin
-            // redirect Pascal-Self to JS-Self
-            FuncContext.AddLocalVar(FBuiltInNames[pbivnSelf],ImplProcScope.SelfArg);
-            end;
+          // nested sub procedure  ->  no 'this'
+          FuncContext.ThisPas:=nil;
           end
         else
           begin
-          if ImplProcScope.SelfArg<>nil then
+          FuncContext.ThisPas:=ProcScope.ClassScope.Element;
+          if bsObjectChecks in FuncContext.ScannerBoolSwitches then
+            begin
+            // rtl.checkMethodCall(this,<class>)
+            Call:=CreateCallExpression(PosEl);
+            AddBodyStatement(Call,PosEl);
+            Call.Expr:=CreateMemberExpression([FBuiltInNames[pbivnRTL],
+                                            FBuiltInNames[pbifnCheckMethodCall]]);
+            Call.AddArg(CreatePrimitiveDotExpr('this',PosEl));
+            ClassPath:=CreateReferencePath(ProcScope.ClassScope.Element,AContext,rpkPathAndName);
+            Call.AddArg(CreatePrimitiveDotExpr(ClassPath,PosEl));
+            end;
+
+          if ImplProc.Body.Functions.Count>0 then
+            begin
+            // has nested procs -> add "var self = this;"
+            FuncContext.AddLocalVar(FBuiltInNames[pbivnSelf],FuncContext.ThisPas);
+            SelfSt:=CreateVarStatement(FBuiltInNames[pbivnSelf],
+                                CreatePrimitiveDotExpr('this',ImplProc),ImplProc);
+            AddBodyStatement(SelfSt,PosEl);
+            if ImplProcScope.SelfArg<>nil then
+              begin
+              // redirect Pascal-Self to JS-Self
+              FuncContext.AddLocalVar(FBuiltInNames[pbivnSelf],ImplProcScope.SelfArg);
+              end;
+            end
+          else if ImplProcScope.SelfArg<>nil then
             begin
             // no nested procs ->  redirect Pascal-Self to JS-this
             FuncContext.AddLocalVar('this',ImplProcScope.SelfArg);
@@ -16267,12 +16278,41 @@ function TPasToJSConverter.CreateReferencePath(El: TPasElement;
     Result:=CreateReferencePath(AbsolResolved.IdentEl,AContext,Kind,Full,Ref);
   end;
 
+  function ImplToDecl(El: TPasElement): TPasElement;
+  var
+    ProcScope: TPasProcedureScope;
+  begin
+    Result:=El;
+    if El.CustomData is TPasProcedureScope then
+      begin
+      // proc: always use the declaration, not the body
+      ProcScope:=TPasProcedureScope(El.CustomData);
+      if ProcScope.DeclarationProc<>nil then
+        Result:=ProcScope.DeclarationProc;
+      end;
+  end;
+
+  function IsA(SrcType, DstType: TPasType): boolean;
+  begin
+    while SrcType<>nil do
+      begin
+      if SrcType=DstType then exit(true);
+      if SrcType.ClassType=TPasClassType then
+        SrcType:=TPas2JSClassScope(SrcType.CustomData).DirectAncestor
+      else if (SrcType.ClassType=TPasAliasType)
+          or (SrcType.ClassType=TPasTypeAliasType) then
+        SrcType:=TPasAliasType(SrcType).DestType
+      else
+        exit(false);
+      end;
+    Result:=false;
+  end;
+
 var
   FoundModule: TPasModule;
   ParentEl: TPasElement;
   Dot: TDotContext;
   WithData: TPas2JSWithExprScope;
-  ProcScope: TPasProcedureScope;
   ShortName: String;
   SelfContext: TFunctionContext;
   ElClass: TClass;
@@ -16346,7 +16386,7 @@ begin
     end
   else if (ElClass=TPasClassType) and TPasClassType(El).IsExternal then
     begin
-    // an external var -> use the literal
+    // an external class -> use the literal
     Result:=TPasClassType(El).ExternalName;
     exit;
     end
@@ -16355,24 +16395,12 @@ begin
     // need full path
     if El.Parent=nil then
       RaiseNotSupported(El,AContext,20170201172141,GetObjName(El));
-    if (El.CustomData is TPasProcedureScope) then
-      begin
-      // proc: always use the declaration, not the body
-      ProcScope:=TPasProcedureScope(El.CustomData);
-      if ProcScope.DeclarationProc<>nil then
-        El:=ProcScope.DeclarationProc;
-      end;
+    El:=ImplToDecl(El);
 
     ParentEl:=El.Parent;
     while ParentEl<>nil do
       begin
-      if (ParentEl.CustomData is TPasProcedureScope) then
-        begin
-        // proc: always use the the declaration, not the body
-        ProcScope:=TPasProcedureScope(ParentEl.CustomData);
-        if ProcScope.DeclarationProc<>nil then
-          ParentEl:=ProcScope.DeclarationProc;
-        end;
+      ParentEl:=ImplToDecl(ParentEl);
 
       // check if there is a local var
       ShortName:=AContext.GetLocalName(ParentEl);
@@ -16410,37 +16438,62 @@ begin
           Prepend(Result,ParentEl.Name)
         else
           begin
-          // Pascal and JS have similar scoping rules (we are not in a dotscope),
-          // so 'this' can be used.
+          // Not in a Pascal dotscope and accessing a class member.
+          // Possible results: this.v, module.path.path.v, this.path.v
+          //    In nested proc 'this' can have another name, e.g. '$Self'
           SelfContext:=AContext.GetSelfContext;
           if ShortName<>'' then
-            Result:=ShortName
-          else if AContext.GetFunctionContext.ThisPas<>nil then
-            Result:='this'
-          else if SelfContext<>nil then
-            Result:=SelfContext.GetLocalName(SelfContext.ThisPas)
+            Prepend(Result,ShortName)
+          else if (El.Parent<>ParentEl) or (El is TPasType) then
+            Prepend(Result,ParentEl.Name)
+          else if (SelfContext<>nil)
+              and IsA(TPasType(SelfContext.ThisPas),TPasType(ParentEl)) then
+            begin
+            ShortName:=SelfContext.GetLocalName(SelfContext.ThisPas);
+            Prepend(Result,ShortName);
+            end
           else
+            begin
+            // missing JS var for Self
+            {$IFDEF VerbosePas2JS}
+            writeln('TPasToJSConverter.CreateReferencePath missing JS var for Self: El=',El.FullName,':',El.ClassName,' CurParentEl=',ParentEl.FullName,':',ParentEl.ClassName,' AContext:');
+            AContext.WriteStack;
+            {$ENDIF}
             RaiseNotSupported(El,AContext,20180125004049);
-          if (SelfContext<>nil) and not IsClassFunction(SelfContext.PasElement) then
+            end;
+          if (El.Parent=ParentEl) and (SelfContext<>nil)
+              and not IsClassFunction(SelfContext.PasElement) then
             begin
             // inside a method -> Self is a class instance
             if El is TPasVariable then
               begin
               //writeln('TPasToJSConverter.CreateReferencePath class var ',GetObjName(El),' This=',GetObjName(This));
+              // Note: reading a class var does not need accessing the class
+              //   For example: read v   ->  this.v
+              //                write v  ->  this.$class.v
               if (ClassVarModifiersType*TPasVariable(El).VarModifiers<>[])
                   and (AContext.Access=caAssign) then
                 begin
-                  Append_GetClass(El); // writing a class var
+                Append_GetClass(El); // writing a class var
                 end;
               end
             else if IsClassFunction(El) then
               Append_GetClass(El); // accessing a class function
             end;
-          break;
+          if ShortName<>'' then
+            break;
           end;
         end
       else if ParentEl.ClassType=TPasEnumType then
-        Prepend(Result,ParentEl.Name);
+        begin
+        if (ShortName<>'') and not Full then
+          begin
+          Prepend(Result,ShortName);
+          break;
+          end
+        else
+          Prepend(Result,ParentEl.Name);
+        end;
       ParentEl:=ParentEl.Parent;
       end;
     end;

+ 184 - 9
packages/pastojs/tests/tcmodules.pas

@@ -441,7 +441,9 @@ type
     Procedure TestClassOf_Const;
 
     // nested class
-    Procedure TestNestedClass_Fail;
+    Procedure TestNestedClass_Alias;
+    Procedure TestNestedClass_Record;
+    Procedure TestNestedClass_Class;
 
     // external class
     Procedure TestExternalClass_Var;
@@ -10523,12 +10525,12 @@ begin
     '      Self.SetSize(Self.GetSize() + 8);',
     '    };',
     '    Sub();',
-    '    this.Key = this.Key + 12;',
+    '    Self.Key = Self.Key + 12;',
     '    Self.Key = Self.Key + 13;',
-    '    this.$class.State = this.State + 14;',
+    '    Self.$class.State = Self.State + 14;',
     '    Self.$class.State = Self.State + 15;',
     '    $mod.TObject.State = $mod.TObject.State + 16;',
-    '    this.SetSize(this.GetSize() + 17);',
+    '    Self.SetSize(Self.GetSize() + 17);',
     '    Self.SetSize(Self.GetSize() + 18);',
     '  };',
     '});',
@@ -11470,18 +11472,191 @@ begin
     '']));
 end;
 
-procedure TTestModule.TestNestedClass_Fail;
+procedure TTestModule.TestNestedClass_Alias;
 begin
+  Converter.Options:=Converter.Options-[coNoTypeInfo];
   StartProgram(false);
   Add([
   'type',
   '  TObject = class',
-  '    type TNested = longint;',
+  '    type TNested = type longint;',
   '  end;',
-  'begin']);
-  SetExpectedPasResolverError('not yet implemented: TNested:TPasAliasType [20170608232534] nested types',
-    nNotYetImplemented);
+  'type TAlias = type tobject.tnested;',
+  'var i: tobject.tnested = 3;',
+  'var j: TAlias = 4;',
+  'begin',
+  '  if typeinfo(TAlias)=nil then ;',
+  '  if typeinfo(tobject.tnested)=nil then ;',
+  '']);
   ConvertProgram;
+  CheckSource('TestNestedClass_Alias',
+    LinesToStr([ // statements
+    'rtl.createClass($mod, "TObject", null, function () {',
+    '  $mod.$rtti.$inherited("TObject.TNested", rtl.longint, {});',
+    '  this.$init = function () {',
+    '  };',
+    '  this.$final = function () {',
+    '  };',
+    '});',
+    '$mod.$rtti.$inherited("TAlias", $mod.$rtti["TObject.TNested"], {});',
+    'this.i = 3;',
+    'this.j = 4;',
+    '']),
+    LinesToStr([ // $mod.$main
+    'if ($mod.$rtti["TAlias"] === null) ;',
+    'if ($mod.$rtti["TObject.TNested"] === null) ;',
+    '']));
+end;
+
+procedure TTestModule.TestNestedClass_Record;
+begin
+  Converter.Options:=Converter.Options-[coNoTypeInfo];
+  StartProgram(false);
+  Add([
+  'type',
+  '  TObject = class',
+  '    type TPoint = record',
+  '       x,y: byte;',
+  '    end;',
+  '    procedure DoIt(t: TPoint);',
+  '  end;',
+  'procedure tobject.DoIt(t: TPoint);',
+  'var p: TPoint;',
+  'begin',
+  '  t.x:=t.y;',
+  '  p:=t;',
+  'end;',
+  'var',
+  '  p: tobject.tpoint = (x:2; y:4);',
+  '  o: TObject;',
+  'begin',
+  '  p:=p;',
+  '  o.doit(p);',
+  '']);
+  ConvertProgram;
+  CheckSource('TestNestedClass_Record',
+    LinesToStr([ // statements
+    'rtl.createClass($mod, "TObject", null, function () {',
+    '  this.TPoint = function (s) {',
+    '    if (s) {',
+    '      this.x = s.x;',
+    '      this.y = s.y;',
+    '    } else {',
+    '      this.x = 0;',
+    '      this.y = 0;',
+    '    };',
+    '    this.$equal = function (b) {',
+    '      return (this.x === b.x) && (this.y === b.y);',
+    '    };',
+    '  };',
+    '  $mod.$rtti.$Record("TObject.TPoint", {}).addFields("x", rtl.byte, "y", rtl.byte);',
+    '  this.$init = function () {',
+    '  };',
+    '  this.$final = function () {',
+    '  };',
+    '  this.DoIt = function (t) {',
+    '    var p = new this.TPoint();',
+    '    t.x = t.y;',
+    '    p = new this.TPoint(t);',
+    '  };',
+    '});',
+    'this.p = new $mod.TObject.TPoint({',
+    '  x: 2,',
+    '  y: 4',
+    '});',
+    'this.o = null;',
+    '']),
+    LinesToStr([ // $mod.$main
+    '$mod.p = new $mod.TObject.TPoint($mod.p);',
+    '$mod.o.DoIt(new $mod.TObject.TPoint($mod.p));',
+    '']));
+end;
+
+procedure TTestModule.TestNestedClass_Class;
+begin
+  Converter.Options:=Converter.Options-[coNoTypeInfo];
+  StartProgram(false);
+  Add([
+  'type',
+  '  TObject = class end;',
+  '  TBird = class',
+  '    type TLeg = class',
+  '      FId: longint;',
+  '      constructor Create;',
+  '      function Create(i: longint): TLeg;',
+  '    end;',
+  '    function DoIt(b: TBird): Tleg;',
+  '  end;',
+  'constructor tbird.tleg.create;',
+  'begin',
+  '  FId:=3;',
+  'end;',
+  'function tbird.tleg.Create(i: longint): TLeg;',
+  'begin',
+  '  Create;',
+  '  Result:=TLeg.Create;',
+  '  Result:=TBird.TLeg.Create;',
+  '  Result:=Create(3);',
+  '  FId:=i;',
+  'end;',
+  'function tbird.DoIt(b: tbird): tleg;',
+  'begin',
+  '  Result.Create;',
+  '  Result:=TLeg.Create;',
+  '  Result:=TBird.TLeg.Create;',
+  '  Result:=Result.Create(3);',
+  'end;',
+  'var',
+  '  b: Tbird.tleg;',
+  'begin',
+  '  b.Create;',
+  '  b:=TBird.TLeg.Create;',
+  '  b:=b.Create(3);',
+  '']);
+  ConvertProgram;
+  CheckSource('TestNestedClass_Class',
+    LinesToStr([ // statements
+    'rtl.createClass($mod, "TObject", null, function () {',
+    '  this.$init = function () {',
+    '  };',
+    '  this.$final = function () {',
+    '  };',
+    '});',
+    'rtl.createClass($mod, "TBird", $mod.TObject, function () {',
+    '  rtl.createClass(this, "TLeg", $mod.TObject, function () {',
+    '    this.$init = function () {',
+    '      $mod.TObject.$init.call(this);',
+    '      this.FId = 0;',
+    '    };',
+    '    this.Create = function () {',
+    '      this.FId = 3;',
+    '    };',
+    '    this.Create$1 = function (i) {',
+    '      var Result = null;',
+    '      this.Create();',
+    '      Result = $mod.TBird.TLeg.$create("Create");',
+    '      Result = $mod.TBird.TLeg.$create("Create");',
+    '      Result = this.Create$1(3);',
+    '      this.FId = i;',
+    '      return Result;',
+    '    };',
+    '  });',
+    '  this.DoIt = function (b) {',
+    '    var Result = null;',
+    '    Result.Create();',
+    '    Result = this.TLeg.$create("Create");',
+    '    Result = $mod.TBird.TLeg.$create("Create");',
+    '    Result = Result.Create$1(3);',
+    '    return Result;',
+    '  };',
+    '});',
+    'this.b = null;',
+    '']),
+    LinesToStr([ // $mod.$main
+    '$mod.b.Create();',
+    '$mod.b = $mod.TBird.TLeg.$create("Create");',
+    '$mod.b = $mod.b.Create$1(3);',
+    '']));
 end;
 
 procedure TTestModule.TestExternalClass_Var;

+ 1 - 2
utils/pas2js/docs/translation.html

@@ -1518,7 +1518,7 @@ function(){
     <li>Supported: constructor, destructor, private, protected, public,
       strict private, strict protected, class vars, class methods, external methods,
       virtual, override, abstract, call inherited, assigned(), type cast,
-      overloads, reintroduce, sealed class</li>
+      overloads, reintroduce, sealed class, nested types.</li>
     <li>Not supported: class constructor/destructor</li>
     <li>Property:
       <ul>
@@ -2863,7 +2863,6 @@ End.
     <li>Helpers for types, classes, records</li>
     <li>Inline</li>
     <li>Library</li>
-    <li>Nested classes</li>
     <li>Objects</li>
     <li>Operator overloading</li>
     <li>Pointer arithmetic</li>