Browse Source

pastojs: faster AnArray:=Concat(AnArray,...)

mattias 3 years ago
parent
commit
d3a2145e5a

+ 225 - 38
packages/pastojs/src/fppas2js.pp

@@ -558,6 +558,7 @@ type
     pbifnArray_Equal,
     pbifnArray_Equal,
     pbifnArray_Insert,
     pbifnArray_Insert,
     pbifnArray_Length,
     pbifnArray_Length,
+    pbifnArray_PushN,
     pbifnArray_Reference,
     pbifnArray_Reference,
     pbifnArray_SetLength,
     pbifnArray_SetLength,
     pbifnArray_Static_Clone,
     pbifnArray_Static_Clone,
@@ -749,6 +750,7 @@ const
     'arrayEq', // rtl.arrayEq          pbifnArray_Equal
     'arrayEq', // rtl.arrayEq          pbifnArray_Equal
     'arrayInsert', // rtl.arrayCopy      pbifnArray_Insert
     'arrayInsert', // rtl.arrayCopy      pbifnArray_Insert
     'length', // rtl.length    pbifnArray_Length
     'length', // rtl.length    pbifnArray_Length
+    'arrayPushN', // rtl.arrayPushN   pbifnArray_PushN
     'arrayRef', // rtl.arrayRef  pbifnArray_Reference
     'arrayRef', // rtl.arrayRef  pbifnArray_Reference
     'arraySetLength', // rtl.arraySetLength  pbifnArray_SetLength
     'arraySetLength', // rtl.arraySetLength  pbifnArray_SetLength
     '$clone', // pbifnArray_Static_Clone
     '$clone', // pbifnArray_Static_Clone
@@ -1681,6 +1683,7 @@ type
     function HasAnonymousFunctions(El: TPasImplElement): boolean;
     function HasAnonymousFunctions(El: TPasImplElement): boolean;
     function GetTopLvlProcScope(El: TPasElement): TPas2JSProcedureScope;
     function GetTopLvlProcScope(El: TPasElement): TPas2JSProcedureScope;
     function ProcCanBePrecompiled(DeclProc: TPasProcedure): boolean; virtual;
     function ProcCanBePrecompiled(DeclProc: TPasProcedure): boolean; virtual;
+    function IsDirectlyWritable(const ExprResolved: TPasResolverResult): boolean; virtual;
     function IsTObjectFreeMethod(El: TPasExpr): boolean; virtual;
     function IsTObjectFreeMethod(El: TPasExpr): boolean; virtual;
     function IsExternalBracketAccessor(El: TPasElement): boolean;
     function IsExternalBracketAccessor(El: TPasElement): boolean;
     function IsExternalClassConstructor(El: TPasElement): boolean;
     function IsExternalClassConstructor(El: TPasElement): boolean;
@@ -2027,6 +2030,7 @@ type
       AContext: TConvertContext): TJSElement; virtual;
       AContext: TConvertContext): TJSElement; virtual;
     Function CreateProcCallArgRef(El: TPasExpr; ResolvedEl: TPasResolverResult;
     Function CreateProcCallArgRef(El: TPasExpr; ResolvedEl: TPasResolverResult;
       TargetArg: TPasArgument;  AContext: TConvertContext): TJSElement; virtual;
       TargetArg: TPasArgument;  AContext: TConvertContext): TJSElement; virtual;
+    Function CreateArrayEl(El: TPasExpr; AContext: TConvertContext): TJSElement; virtual;
     Function CreateArrayEl(El: TPasExpr; JS: TJSElement; AContext: TConvertContext): TJSElement; virtual;
     Function CreateArrayEl(El: TPasExpr; JS: TJSElement; AContext: TConvertContext): TJSElement; virtual;
     Function CreateArgumentAccess(Arg: TPasArgument; AContext: TConvertContext;
     Function CreateArgumentAccess(Arg: TPasArgument; AContext: TConvertContext;
       PosEl: TPasElement): TJSElement; virtual;
       PosEl: TPasElement): TJSElement; virtual;
@@ -2218,6 +2222,8 @@ type
     Function ConvertBeginEndStatement(El: TPasImplBeginBlock; AContext: TConvertContext; NilIfEmpty: boolean): TJSElement; virtual;
     Function ConvertBeginEndStatement(El: TPasImplBeginBlock; AContext: TConvertContext; NilIfEmpty: boolean): TJSElement; virtual;
     Function ConvertStatement(El: TPasImplStatement; AContext: TConvertContext ): TJSElement; virtual;
     Function ConvertStatement(El: TPasImplStatement; AContext: TConvertContext ): TJSElement; virtual;
     Function ConvertAssignStatement(El: TPasImplAssign; AContext: TConvertContext): TJSElement; virtual;
     Function ConvertAssignStatement(El: TPasImplAssign; AContext: TConvertContext): TJSElement; virtual;
+    Function ConvertDirectAssignArrayStatement(El: TPasImplAssign; AssignContext: TAssignContext): TJSElement; virtual;
+    Function ConvertDirectAssignArrayConcat(El: TPasImplAssign; Params: TParamsExpr; AssignContext: TAssignContext): TJSElement; virtual;
     Function ConvertRaiseStatement(El: TPasImplRaise; AContext: TConvertContext ): TJSElement; virtual;
     Function ConvertRaiseStatement(El: TPasImplRaise; AContext: TConvertContext ): TJSElement; virtual;
     Function ConvertIfStatement(El: TPasImplIfElse; AContext: TConvertContext ): TJSElement; virtual;
     Function ConvertIfStatement(El: TPasImplIfElse; AContext: TConvertContext ): TJSElement; virtual;
     Function ConvertWhileStatement(El: TPasImplWhileDo; AContext: TConvertContext): TJSElement; virtual;
     Function ConvertWhileStatement(El: TPasImplWhileDo; AContext: TConvertContext): TJSElement; virtual;
@@ -7409,6 +7415,39 @@ begin
   until false;
   until false;
 end;
 end;
 
 
+function TPas2JSResolver.IsDirectlyWritable(const ExprResolved: TPasResolverResult
+  ): boolean;
+var
+  C: TClass;
+  IdentEl, Setter: TPasElement;
+  Prop: TPasProperty;
+begin
+  if not (rrfWritable in ExprResolved.Flags) then exit;
+  Result:=false;
+  IdentEl:=ExprResolved.IdentEl;
+  if ExprResolved.BaseType=btContext then
+    begin
+    if IdentEl<>nil then
+      begin
+      C:=IdentEl.ClassType;
+      if (C=TPasVariable) or (C=TPasConst) or (C=TPasResultElement) then
+        exit(true)
+      else if (C=TPasArgument) then
+        begin
+        if TPasArgument(IdentEl).Access=argDefault then
+          exit(true);
+        end
+      else if (C=TPasProperty) then
+        begin
+        Prop:=TPasProperty(IdentEl);
+        Setter:=GetPasPropertySetter(Prop);
+        if Setter is TPasVariable then
+          exit(true);
+        end;
+      end;
+    end;
+end;
+
 function TPas2JSResolver.IsTObjectFreeMethod(El: TPasExpr): boolean;
 function TPas2JSResolver.IsTObjectFreeMethod(El: TPasExpr): boolean;
 var
 var
   Ref: TResolvedReference;
   Ref: TResolvedReference;
@@ -14475,7 +14514,7 @@ function TPasToJSConverter.ConvertBuiltIn_ConcatArray(El: TParamsExpr;
 var
 var
   Params: TPasExprArray;
   Params: TPasExprArray;
   ParamResolved: TPasResolverResult;
   ParamResolved: TPasResolverResult;
-  Param0, Param: TPasExpr;
+  Param0: TPasExpr;
   ArrayType: TPasArrayType;
   ArrayType: TPasArrayType;
   i: Integer;
   i: Integer;
   Call: TJSCallExpression;
   Call: TJSCallExpression;
@@ -14520,9 +14559,7 @@ begin
     try
     try
       for i:=0 to length(Params)-1 do
       for i:=0 to length(Params)-1 do
         begin
         begin
-        Param:=Params[i];
-        JS:=ConvertExpression(Param,AContext);
-        JS:=CreateArrayEl(Param,JS,AContext);
+        JS:=CreateArrayEl(Params[i],AContext);
         Call.AddArg(JS);
         Call.AddArg(JS);
         end;
         end;
       Result:=Call;
       Result:=Call;
@@ -18968,14 +19005,11 @@ function TPasToJSConverter.CreateArrayInit(ArrayType: TPasArrayType;
     var
     var
       i: Integer;
       i: Integer;
       JS: TJSElement;
       JS: TJSElement;
-      Param: TPasExpr;
     begin
     begin
       Result:=TJSArrayLiteral(CreateElement(TJSArrayLiteral,El));
       Result:=TJSArrayLiteral(CreateElement(TJSArrayLiteral,El));
       for i:=0 to length(ExprArray)-1 do
       for i:=0 to length(ExprArray)-1 do
         begin
         begin
-        Param:=ExprArray[i];
-        JS:=ConvertSubExpr(Param);
-        JS:=CreateArrayEl(Param,JS,AContext);
+        JS:=CreateArrayEl(ExprArray[i],AContext);
         Result.Elements.AddElement.Expr:=JS;
         Result.Elements.AddElement.Expr:=JS;
         end;
         end;
     end;
     end;
@@ -22678,7 +22712,7 @@ var
     if not lRightIsTempValid then
     if not lRightIsTempValid then
       begin
       begin
       lRightIsTempValid:=true;
       lRightIsTempValid:=true;
-      lRightIsTemp:=IsExprTemporaryVar(El.right);
+      lRightIsTemp:=IsExprTemporaryVar(El.Right);
       end;
       end;
     Result:=lRightIsTemp;
     Result:=lRightIsTemp;
   end;
   end;
@@ -22689,7 +22723,7 @@ var
     if not lLeftIsConstSetterValid then
     if not lLeftIsConstSetterValid then
       begin
       begin
       lLeftIsConstSetterValid:=true;
       lLeftIsConstSetterValid:=true;
-      lLeftIsConstSetter:=IsExprPropertySetterConst(El.left,AContext);
+      lLeftIsConstSetter:=IsExprPropertySetterConst(El.Left,AContext);
       end;
       end;
     Result:=lLeftIsConstSetter
     Result:=lLeftIsConstSetter
   end;
   end;
@@ -22714,8 +22748,8 @@ var
       Call.AddArg(AssignSt);
       Call.AddArg(AssignSt);
       Result:=Call;
       Result:=Call;
       end;
       end;
-    Call.AddArg(CreateLiteralNumber(El.right,MinVal));
-    Call.AddArg(CreateLiteralNumber(El.right,MaxVal));
+    Call.AddArg(CreateLiteralNumber(El.Right,MinVal));
+    Call.AddArg(CreateLiteralNumber(El.Right,MaxVal));
   end;
   end;
 
 
   function ApplyRangeCheck_Type(AssignSt: TJSElement; aType: TPasType): TJSElement;
   function ApplyRangeCheck_Type(AssignSt: TJSElement; aType: TPasType): TJSElement;
@@ -22770,7 +22804,7 @@ begin
   try
   try
     if aResolver<>nil then
     if aResolver<>nil then
       begin
       begin
-      aResolver.ComputeElement(El.left,AssignContext.LeftResolved,[rcNoImplicitProc]);
+      aResolver.ComputeElement(El.Left,AssignContext.LeftResolved,[rcNoImplicitProc]);
       Flags:=[];
       Flags:=[];
       LeftIsProcType:=aResolver.IsProcedureType(AssignContext.LeftResolved,false);
       LeftIsProcType:=aResolver.IsProcedureType(AssignContext.LeftResolved,false);
       if LeftIsProcType then
       if LeftIsProcType then
@@ -22780,7 +22814,7 @@ begin
         else
         else
           Include(Flags,rcNoImplicitProcType);
           Include(Flags,rcNoImplicitProcType);
         end;
         end;
-      aResolver.ComputeElement(El.right,AssignContext.RightResolved,Flags);
+      aResolver.ComputeElement(El.Right,AssignContext.RightResolved,Flags);
       {$IFDEF VerbosePas2JS}
       {$IFDEF VerbosePas2JS}
       writeln('TPasToJSConverter.ConvertAssignStatement Left={',GetResolverResultDbg(AssignContext.LeftResolved),'} Right={',GetResolverResultDbg(AssignContext.RightResolved),'}');
       writeln('TPasToJSConverter.ConvertAssignStatement Left={',GetResolverResultDbg(AssignContext.LeftResolved),'} Right={',GetResolverResultDbg(AssignContext.RightResolved),'}');
       {$ENDIF}
       {$ENDIF}
@@ -22790,7 +22824,7 @@ begin
         begin
         begin
         // Delphi allows assigning a proc without @: proctype:=proc
         // Delphi allows assigning a proc without @: proctype:=proc
         LeftTypeEl:=AssignContext.LeftResolved.LoTypeEl;
         LeftTypeEl:=AssignContext.LeftResolved.LoTypeEl;
-        AssignContext.RightSide:=CreateCallback(El.right,
+        AssignContext.RightSide:=CreateCallback(El.Right,
              AssignContext.RightResolved,
              AssignContext.RightResolved,
              TPasProcedureType(LeftTypeEl).CallingConvention=ccSafeCall,
              TPasProcedureType(LeftTypeEl).CallingConvention=ccSafeCall,
              AContext);
              AContext);
@@ -22800,7 +22834,7 @@ begin
         if aResolver.IsArrayType(AssignContext.LeftResolved) then
         if aResolver.IsArrayType(AssignContext.LeftResolved) then
           begin
           begin
           // array:=nil -> array:=[]
           // array:=nil -> array:=[]
-          AssignContext.RightSide:=TJSArrayLiteral(CreateElement(TJSArrayLiteral,El.right));
+          AssignContext.RightSide:=TJSArrayLiteral(CreateElement(TJSArrayLiteral,El.Right));
           end;
           end;
         end
         end
       else if AssignContext.LeftResolved.BaseType=btContext then
       else if AssignContext.LeftResolved.BaseType=btContext then
@@ -22809,7 +22843,7 @@ begin
         if (LeftTypeEl.ClassType=TPasRecordType)
         if (LeftTypeEl.ClassType=TPasRecordType)
             and (AssignContext.RightResolved.BaseType in btAllStrings) then
             and (AssignContext.RightResolved.BaseType in btAllStrings) then
           begin
           begin
-          if aResolver.GetAssignGUIDString(TPasRecordType(LeftTypeEl),El.right,GUID) then
+          if aResolver.GetAssignGUIDString(TPasRecordType(LeftTypeEl),El.Right,GUID) then
             begin
             begin
             // guidvar:='{...}';  ->  convert string to GUID object { D1:x12345678, D2:0x1234,...}
             // guidvar:='{...}';  ->  convert string to GUID object { D1:x12345678, D2:0x1234,...}
             // Note: the "guidvar.$assign()" is done by left side
             // Note: the "guidvar.$assign()" is done by left side
@@ -22819,12 +22853,20 @@ begin
           else
           else
             RaiseNotSupported(El,AContext,20180415101516);
             RaiseNotSupported(El,AContext,20180415101516);
           end;
           end;
-        if (LeftTypeEl.ClassType=TPasArrayType) and (El.Kind<>akDefault) then
-          aResolver.RaiseMsg(20201028212754,nIllegalQualifier,sIllegalQualifier,[AssignKindNames[El.Kind]],El);
+        if (LeftTypeEl.ClassType=TPasArrayType) then
+          begin
+          if (El.Kind<>akDefault) then
+            aResolver.RaiseMsg(20201028212754,nIllegalQualifier,sIllegalQualifier,[AssignKindNames[El.Kind]],El);
+          if aResolver.IsDirectlyWritable(AssignContext.LeftResolved) then
+            begin
+            Result:=ConvertDirectAssignArrayStatement(El,AssignContext);
+            if Result<>nil then exit;
+            end;
+          end;
         end;
         end;
       end;
       end;
     if AssignContext.RightSide=nil then
     if AssignContext.RightSide=nil then
-      AssignContext.RightSide:=ConvertExpression(El.right,AContext);
+      AssignContext.RightSide:=ConvertExpression(El.Right,AContext);
 
 
     if (AssignContext.RightResolved.BaseType in [btSet,btArrayOrSet])
     if (AssignContext.RightResolved.BaseType in [btSet,btArrayOrSet])
         and (AssignContext.RightResolved.IdentEl<>nil) then
         and (AssignContext.RightResolved.IdentEl<>nil) then
@@ -22834,7 +22876,7 @@ begin
       //writeln('TPasToJSConverter.ConvertAssignStatement SET variable Right={',GetResolverResultDbg(AssignContext.RightResolved),'} AssignContext.RightResolved.IdentEl=',GetObjName(AssignContext.RightResolved.IdentEl));
       //writeln('TPasToJSConverter.ConvertAssignStatement SET variable Right={',GetResolverResultDbg(AssignContext.RightResolved),'} AssignContext.RightResolved.IdentEl=',GetObjName(AssignContext.RightResolved.IdentEl));
       {$ENDIF}
       {$ENDIF}
       // create  rtl.refSet(right)
       // create  rtl.refSet(right)
-      AssignContext.RightSide:=CreateReferencedSet(El.right,AssignContext.RightSide);
+      AssignContext.RightSide:=CreateReferencedSet(El.Right,AssignContext.RightSide);
       end
       end
     else if AssignContext.LeftResolved.BaseType=btCurrency then
     else if AssignContext.LeftResolved.BaseType=btCurrency then
       begin
       begin
@@ -22874,7 +22916,7 @@ begin
         if AssignContext.LeftResolved.LoTypeEl is TPasArrayType then
         if AssignContext.LeftResolved.LoTypeEl is TPasArrayType then
           begin
           begin
           // AnArray:=aString  -> AnArray:=aString.split("")
           // AnArray:=aString  -> AnArray:=aString.split("")
-          AssignContext.RightSide:=CreateDotSplit(El.right,AssignContext.RightSide);
+          AssignContext.RightSide:=CreateDotSplit(El.Right,AssignContext.RightSide);
           end;
           end;
         end;
         end;
       end
       end
@@ -22892,7 +22934,7 @@ begin
             {$IFDEF VerbosePas2JS}
             {$IFDEF VerbosePas2JS}
             writeln('TPasToJSConverter.ConvertAssignStatement STATIC ARRAY variable Right={',GetResolverResultDbg(AssignContext.RightResolved),'} AssignContext.RightResolved.IdentEl=',GetObjName(AssignContext.RightResolved.IdentEl));
             writeln('TPasToJSConverter.ConvertAssignStatement STATIC ARRAY variable Right={',GetResolverResultDbg(AssignContext.RightResolved),'} AssignContext.RightResolved.IdentEl=',GetObjName(AssignContext.RightResolved.IdentEl));
             {$ENDIF}
             {$ENDIF}
-            AssignContext.RightSide:=CreateCloneStaticArray(El.right,
+            AssignContext.RightSide:=CreateCloneStaticArray(El.Right,
                    TPasArrayType(RightTypeEl),AssignContext.RightSide,AContext);
                    TPasArrayType(RightTypeEl),AssignContext.RightSide,AContext);
             end;
             end;
           end
           end
@@ -22910,7 +22952,7 @@ begin
                 and (not LeftIsConstSetter) then
                 and (not LeftIsConstSetter) then
               begin
               begin
               // DynArrayA := DynArrayB  ->  DynArrayA = rtl.arrayRef(DynArrayB)
               // DynArrayA := DynArrayB  ->  DynArrayA = rtl.arrayRef(DynArrayB)
-              AssignContext.RightSide:=CreateArrayRef(El.right,AssignContext.RightSide);
+              AssignContext.RightSide:=CreateArrayRef(El.Right,AssignContext.RightSide);
               end;
               end;
             end;
             end;
           end;
           end;
@@ -22958,10 +23000,10 @@ begin
                 begin
                 begin
                 // IntfVar:=ClassInstVar
                 // IntfVar:=ClassInstVar
                 if TPasClassType(RightTypeEl).IsExternal then
                 if TPasClassType(RightTypeEl).IsExternal then
-                  RaiseNotSupported(El.right,AContext,20180327210004,'external class instance');
+                  RaiseNotSupported(El.Right,AContext,20180327210004,'external class instance');
                 if AssignContext.LeftResolved.LoTypeEl=nil then
                 if AssignContext.LeftResolved.LoTypeEl=nil then
-                  RaiseNotSupported(El.right,AContext,20180327204021);
-                Call:=CreateCallExpression(El.right);
+                  RaiseNotSupported(El.Right,AContext,20180327204021);
+                Call:=CreateCallExpression(El.Right);
                 case TPasClassType(LeftTypeEl).InterfaceType of
                 case TPasClassType(LeftTypeEl).InterfaceType of
                 // COM: $ir.ref(id,rtl.queryIntfT(ClassInstVar,IntfVarType))
                 // COM: $ir.ref(id,rtl.queryIntfT(ClassInstVar,IntfVarType))
                 citCom:
                 citCom:
@@ -23015,22 +23057,22 @@ begin
         LeftTypeEl:=AssignContext.LeftResolved.LoTypeEl;
         LeftTypeEl:=AssignContext.LeftResolved.LoTypeEl;
         if (LeftTypeEl is TPasProcedureType)
         if (LeftTypeEl is TPasProcedureType)
             and (TPasProcedureType(AssignContext.LeftResolved.LoTypeEl).CallingConvention=ccSafeCall)
             and (TPasProcedureType(AssignContext.LeftResolved.LoTypeEl).CallingConvention=ccSafeCall)
-            and (El.right is TUnaryExpr)
-            and (TUnaryExpr(El.right).OpCode=eopAddress) then
+            and (El.Right is TUnaryExpr)
+            and (TUnaryExpr(El.Right).OpCode=eopAddress) then
           begin
           begin
           // aSafeCall:=@Proc
           // aSafeCall:=@Proc
-          AssignContext.RightSide:=CreateSafeCallback(El.right,AssignContext.RightSide,AContext);
+          AssignContext.RightSide:=CreateSafeCallback(El.Right,AssignContext.RightSide,AContext);
           end;
           end;
         end;
         end;
       end;
       end;
     // convert left side
     // convert left side
-    LHS:=ConvertExpression(El.left,AssignContext);
+    LHS:=ConvertExpression(El.Left,AssignContext);
 
 
     if AssignContext.Call<>nil then
     if AssignContext.Call<>nil then
       begin
       begin
       // left side is a Setter -> RightSide was already inserted as parameter
       // left side is a Setter -> RightSide was already inserted as parameter
       if AssignContext.RightSide<>nil then
       if AssignContext.RightSide<>nil then
-        RaiseInconsistency(20170207215544,El.left);
+        RaiseInconsistency(20170207215544,El.Left);
       Result:=LHS;
       Result:=LHS;
       end
       end
     else
     else
@@ -23077,7 +23119,7 @@ begin
           if LeftTypeEl is TPasUnresolvedSymbolRef then
           if LeftTypeEl is TPasUnresolvedSymbolRef then
             begin
             begin
             if not aResolver.GetIntegerRange(AssignContext.LeftResolved.BaseType,MinVal,MaxVal) then
             if not aResolver.GetIntegerRange(AssignContext.LeftResolved.BaseType,MinVal,MaxVal) then
-              RaiseNotSupported(El.left,AContext,20180119154120);
+              RaiseNotSupported(El.Left,AContext,20180119154120);
             Result:=CreateRangeCheck(Result,MinVal,MaxVal,pbifnRangeCheckInt);
             Result:=CreateRangeCheck(Result,MinVal,MaxVal,pbifnRangeCheckInt);
             end
             end
           else if LeftTypeEl.ClassType=TPasRangeType then
           else if LeftTypeEl.ClassType=TPasRangeType then
@@ -23114,6 +23156,142 @@ begin
   end;
   end;
 end;
 end;
 
 
+function TPasToJSConverter.ConvertDirectAssignArrayStatement(
+  El: TPasImplAssign; AssignContext: TAssignContext): TJSElement;
+// AnArrayVar:=
+var
+  RightExpr, FuncExpr: TPasExpr;
+  Ref: TResolvedReference;
+  Decl: TPasElement;
+  BuiltInProc: TResElDataBuiltInProc;
+  Params: TParamsExpr;
+begin
+  Result:=nil;
+
+  RightExpr:=El.Right;
+  if RightExpr.Kind=pekFuncParams then
+    begin
+    Params:=TParamsExpr(RightExpr);
+    FuncExpr:=Params.Value;
+    if FuncExpr.CustomData is TResolvedReference then
+      begin
+      Ref:=TResolvedReference(FuncExpr.CustomData);
+      Decl:=Ref.Declaration;
+      if Decl.CustomData is TResElDataBuiltInProc then
+        begin
+        BuiltInProc:=TResElDataBuiltInProc(Decl.CustomData);
+        {$IFDEF VerbosePas2JS}
+        writeln('TPasToJSConverter.ConvertDirectAssignArrayStatement BuiltInProc ',Decl.Name,' ',ResolverBuiltInProcNames[BuiltInProc.BuiltIn]);
+        {$ENDIF}
+        case BuiltInProc.BuiltIn of
+        bfConcatArray:
+          Result:=ConvertDirectAssignArrayConcat(El,Params,AssignContext);
+        end;
+
+        end;
+      end;
+    end;
+end;
+
+function TPasToJSConverter.ConvertDirectAssignArrayConcat(El: TPasImplAssign;
+  Params: TParamsExpr; AssignContext: TAssignContext): TJSElement;
+// AnArrayVar:=Concat()
+var
+  Param1, LeftExpr, Param2: TPasExpr;
+  LeftRef, ParamRef: TResolvedReference;
+  SubParams: TParamsExpr;
+  Call: TJSCallExpression;
+  DotExpr: TJSDotMemberExpression;
+  i: Integer;
+  ParentContext: TConvertContext;
+  JS: TJSElement;
+  CondExpr: TJSConditionalExpression;
+  ArrLit: TJSArrayLiteral;
+  ok: Boolean;
+begin
+  Result:=nil;
+  LeftExpr:=El.Left;
+  if not (LeftExpr.CustomData is TResolvedReference) then exit;
+  LeftRef:=TResolvedReference(LeftExpr.CustomData);
+
+  Param1:=Params.Params[0];
+  if Param1.CustomData is TResolvedReference then
+    begin
+    ParamRef:=TResolvedReference(Param1.CustomData);
+    if LeftRef.Declaration=ParamRef.Declaration then
+      begin
+      {$IFDEF VerbosePas2JS}
+      writeln('TPasToJSConverter.ConvertDirectAssignArrayConcat A:=Concat(A,...)');
+      {$ENDIF}
+      ParentContext:=AssignContext.Parent;
+      if length(Params.Params)=1 then
+        begin
+        // A:=Concat(A)  ->  A;
+        Result:=ConvertExpression(Param1,ParentContext);
+        exit;
+        end;
+      // A:=Concat(A,...)  ->  append to array
+      if length(Params.Params)=2 then
+        begin
+        Param2:=Params.Params[1];
+        if (Param2.Kind=pekSet) then
+          begin
+          // A:=Concat(A,[b,c,...])
+          SubParams:=TParamsExpr(Param2);
+          if length(SubParams.Params)=0 then
+            begin
+            // A:=Concat(A,[])  ->  A;
+            Result:=ConvertExpression(Param1,ParentContext);
+            exit;
+            end;
+          ok:=false;
+          try
+            if length(SubParams.Params)=1 then
+              begin
+              // A:=Concat(A,[b])  ->  A?A.push(b):[b];
+              CondExpr:=TJSConditionalExpression(CreateElement(TJSConditionalExpression,El));
+              Result:=CondExpr;
+
+              // A?
+              CondExpr.A:=ConvertExpression(Param1,ParentContext);
+
+              // A.push(b)
+              Call:=CreateCallExpression(El);
+              CondExpr.B:=Call;
+              DotExpr:=TJSDotMemberExpression(CreateElement(TJSDotMemberExpression,El));
+              Call.Expr:=DotExpr;
+              DotExpr.MExpr:=ConvertExpression(Param1,ParentContext);
+              DotExpr.Name:='push';
+
+              // [b]
+              ArrLit:=TJSArrayLiteral(CreateElement(TJSArrayLiteral,El));
+              CondExpr.C:=ArrLit;
+              ArrLit.AddElement(CreateArrayEl(SubParams.Params[0],ParentContext));
+              end
+            else
+              begin
+              // A:=Concat(A,[b,c])  ->  rtl.arrayPushN(A,b,c);
+              Call:=CreateCallExpression(El);
+              Result:=Call;
+              Call.Expr:=CreatePrimitiveDotExpr(GetBIName(pbivnRTL)+'.'+GetBIName(pbifnArray_PushN),El);
+              Call.AddArg(ConvertExpression(Param1,ParentContext));
+              end;
+            for i:=0 to length(SubParams.Params)-1 do
+              begin
+              JS:=CreateArrayEl(SubParams.Params[i],ParentContext);
+              Call.AddArg(JS);
+              end;
+            ok:=true;
+          finally
+            if not ok then
+              Result.Free;
+          end;
+          end;
+        end;
+      end;
+    end;
+end;
+
 function TPasToJSConverter.ConvertIfStatement(El: TPasImplIfElse;
 function TPasToJSConverter.ConvertIfStatement(El: TPasImplIfElse;
   AContext: TConvertContext): TJSElement;
   AContext: TConvertContext): TJSElement;
 Var
 Var
@@ -25357,15 +25535,15 @@ begin
   RightBT:=AssignContext.RightResolved.BaseType;
   RightBT:=AssignContext.RightResolved.BaseType;
 
 
   if not aResolver.GetIntegerRange(LeftBT,LeftMinVal,LeftMaxVal) then
   if not aResolver.GetIntegerRange(LeftBT,LeftMinVal,LeftMaxVal) then
-    RaiseNotSupported(El.left,AssignContext,20210815195159);
+    RaiseNotSupported(El.Left,AssignContext,20210815195159);
   if not aResolver.GetIntegerRange(RightBT,RightMinVal,RightMaxVal) then
   if not aResolver.GetIntegerRange(RightBT,RightMinVal,RightMaxVal) then
-    RaiseNotSupported(El.right,AssignContext,20210815195228);
+    RaiseNotSupported(El.Right,AssignContext,20210815195228);
   if (LeftMinVal<=RightMinVal) and (LeftMaxVal>=RightMaxVal) then
   if (LeftMinVal<=RightMinVal) and (LeftMaxVal>=RightMaxVal) then
     exit; // right is subset of left
     exit; // right is subset of left
 
 
   // right might not fit into left
   // right might not fit into left
 
 
-  Value:=aResolver.Eval(El.right,[]);
+  Value:=aResolver.Eval(El.Right,[]);
   try
   try
     if Value<>nil then
     if Value<>nil then
       begin
       begin
@@ -25394,7 +25572,7 @@ begin
       revkExternal:
       revkExternal:
         exit;
         exit;
       else
       else
-        RaiseNotSupported(El.right,AssignContext,20210815204203,'right='+Value.AsDebugString);
+        RaiseNotSupported(El.Right,AssignContext,20210815204203,'right='+Value.AsDebugString);
       end;
       end;
 
 
       case LeftBT of
       case LeftBT of
@@ -25434,7 +25612,7 @@ begin
         end;
         end;
       if AssignContext.RightSide<>nil then
       if AssignContext.RightSide<>nil then
         AssignContext.RightSide.Free;
         AssignContext.RightSide.Free;
-      AssignContext.RightSide:=CreateLiteralNumber(El.right,IntValue);
+      AssignContext.RightSide:=CreateLiteralNumber(El.Right,IntValue);
       end;
       end;
   finally
   finally
     ReleaseEvalValue(Value);
     ReleaseEvalValue(Value);
@@ -26873,6 +27051,15 @@ begin
   end;
   end;
 end;
 end;
 
 
+function TPasToJSConverter.CreateArrayEl(El: TPasExpr; AContext: TConvertContext
+  ): TJSElement;
+var
+  JS: TJSElement;
+begin
+  JS:=ConvertExpression(El,AContext);
+  Result:=CreateArrayEl(El,JS,AContext);
+end;
+
 function TPasToJSConverter.CreateArrayEl(El: TPasExpr; JS: TJSElement;
 function TPasToJSConverter.CreateArrayEl(El: TPasExpr; JS: TJSElement;
   AContext: TConvertContext): TJSElement;
   AContext: TConvertContext): TJSElement;
 // call this function for every element of an array literal
 // call this function for every element of an array literal

+ 4 - 4
packages/pastojs/src/pas2jsfiler.pp

@@ -4916,8 +4916,8 @@ begin
   WritePasElement(Obj,El,aContext);
   WritePasElement(Obj,El,aContext);
   if El.Kind<>akDefault then
   if El.Kind<>akDefault then
     Obj.Add('Kind',PCUAssignKind[El.Kind]);
     Obj.Add('Kind',PCUAssignKind[El.Kind]);
-  WriteExpr(Obj,El,'Left',El.left,aContext);
-  WriteExpr(Obj,El,'Right',El.right,aContext);
+  WriteExpr(Obj,El,'Left',El.Left,aContext);
+  WriteExpr(Obj,El,'Right',El.Right,aContext);
 end;
 end;
 
 
 procedure TPCUWriter.WriteImplSimple(Obj: TJSONObject; El: TPasImplSimple;
 procedure TPCUWriter.WriteImplSimple(Obj: TJSONObject; El: TPasImplSimple;
@@ -9883,8 +9883,8 @@ begin
     else
     else
       RaiseMsg(20200105200423,El,s);
       RaiseMsg(20200105200423,El,s);
     end;
     end;
-  El.left:=ReadExpr(Obj,El,'Left',aContext);
-  El.right:=ReadExpr(Obj,El,'Right',aContext);
+  El.Left:=ReadExpr(Obj,El,'Left',aContext);
+  El.Right:=ReadExpr(Obj,El,'Right',aContext);
 end;
 end;
 
 
 procedure TPCUReader.ReadImplSimple(Obj: TJSONObject; El: TPasImplSimple;
 procedure TPCUReader.ReadImplSimple(Obj: TJSONObject; El: TPasImplSimple;

+ 2 - 2
packages/pastojs/tests/tcconverter.pas

@@ -1445,8 +1445,8 @@ function TTestConverter.CreateAssignStatement(LHS: String; RHS: String
 
 
 begin
 begin
   Result:=TPasImplAssign(CreateElement(TPasImplAssign));
   Result:=TPasImplAssign(CreateElement(TPasImplAssign));
-  Result.left:=CreateIdent(LHS);
-  Result.right:=CreateIdent(RHS);
+  Result.Left:=CreateIdent(LHS);
+  Result.Right:=CreateIdent(RHS);
 end;
 end;
 
 
 function TTestConverter.CreateParamsExpr(Kind: TPasExprKind;
 function TTestConverter.CreateParamsExpr(Kind: TPasExprKind;

+ 2 - 2
packages/pastojs/tests/tcfiler.pas

@@ -2165,8 +2165,8 @@ end;
 procedure TCustomTestPrecompile.CheckRestoredImplAssign(const Path: string;
 procedure TCustomTestPrecompile.CheckRestoredImplAssign(const Path: string;
   Orig, Rest: TPasImplAssign; Flags: TPCCheckFlags);
   Orig, Rest: TPasImplAssign; Flags: TPCCheckFlags);
 begin
 begin
-  CheckRestoredElement(Path+'.left',Orig.left,Rest.left,Flags);
-  CheckRestoredElement(Path+'.right',Orig.right,Rest.right,Flags);
+  CheckRestoredElement(Path+'.left',Orig.Left,Rest.Left,Flags);
+  CheckRestoredElement(Path+'.right',Orig.Right,Rest.Right,Flags);
 end;
 end;
 
 
 procedure TCustomTestPrecompile.CheckRestoredImplSimple(const Path: string;
 procedure TCustomTestPrecompile.CheckRestoredImplSimple(const Path: string;

+ 122 - 28
packages/pastojs/tests/tcmodules.pas

@@ -497,6 +497,7 @@ type
     Procedure TestArray_ArrayOfCharAssignString;
     Procedure TestArray_ArrayOfCharAssignString;
     Procedure TestArray_ConstRef;
     Procedure TestArray_ConstRef;
     Procedure TestArray_Concat;
     Procedure TestArray_Concat;
+    Procedure TestArray_Concat_Append;
     Procedure TestArray_Copy;
     Procedure TestArray_Copy;
     Procedure TestArray_InsertDelete;
     Procedure TestArray_InsertDelete;
     Procedure TestArray_DynArrayConstObjFPC;
     Procedure TestArray_DynArrayConstObjFPC;
@@ -11271,6 +11272,96 @@ begin
 end;
 end;
 
 
 procedure TTestModule.TestArray_Concat;
 procedure TTestModule.TestArray_Concat;
+begin
+  StartProgram(false);
+  Add([
+  'type',
+  '  integer = longint;',
+  '  TFlag = (big,small);',
+  '  TFlags = set of TFlag;',
+  '  TRec = record',
+  '    i: integer;',
+  '  end;',
+  '  TArrInt = array of integer;',
+  '  TArrRec = array of TRec;',
+  '  TArrFlag = array of TFlag;',
+  '  TArrSet = array of TFlags;',
+  '  TArrJSValue = array of jsvalue;',
+  'var',
+  '  ArrInt1, ArrInt2: tarrint;',
+  '  ArrRec1, ArrRec2: tarrrec;',
+  '  ArrFlag1, ArrFlag2: tarrflag;',
+  '  ArrSet1, ArrSet2: tarrset;',
+  '  ArrJSValue1, ArrJSValue2: tarrjsvalue;',
+  'begin',
+  '  arrint1:=concat(arrint2);',
+  '  arrint1:=concat(arrint2,arrint2);',
+  '  arrint1:=concat(arrint2,arrint2,arrint2);',
+  '  arrrec1:=concat(arrrec2);',
+  '  arrrec1:=concat(arrrec2,arrrec2);',
+  '  arrrec1:=concat(arrrec2,arrrec2,arrrec2);',
+  '  arrset1:=concat(arrset2);',
+  '  arrset1:=concat(arrset2,arrset2);',
+  '  arrset1:=concat(arrset2,arrset2,arrset2);',
+  '  arrjsvalue1:=concat(arrjsvalue2);',
+  '  arrjsvalue1:=concat(arrjsvalue2,arrjsvalue2);',
+  '  arrjsvalue1:=concat(arrjsvalue2,arrjsvalue2,arrjsvalue2);',
+  '  arrint1:=concat([1],arrint2);',
+  '  arrflag1:=concat([big]);',
+  '  arrflag1:=concat([big],arrflag2);',
+  '  arrflag1:=concat(arrflag2,[small]);',
+  '']);
+  ConvertProgram;
+  CheckSource('TestArray_Concat',
+    LinesToStr([ // statements
+    'this.TFlag = {',
+    '  "0": "big",',
+    '  big: 0,',
+    '  "1": "small",',
+    '  small: 1',
+    '};',
+    'rtl.recNewT(this, "TRec", function () {',
+    '  this.i = 0;',
+    '  this.$eq = function (b) {',
+    '    return this.i === b.i;',
+    '  };',
+    '  this.$assign = function (s) {',
+    '    this.i = s.i;',
+    '    return this;',
+    '  };',
+    '});',
+    'this.ArrInt1 = [];',
+    'this.ArrInt2 = [];',
+    'this.ArrRec1 = [];',
+    'this.ArrRec2 = [];',
+    'this.ArrFlag1 = [];',
+    'this.ArrFlag2 = [];',
+    'this.ArrSet1 = [];',
+    'this.ArrSet2 = [];',
+    'this.ArrJSValue1 = [];',
+    'this.ArrJSValue2 = [];',
+    '']),
+    LinesToStr([ // $mod.$main
+    '$mod.ArrInt1 = rtl.arrayRef($mod.ArrInt2);',
+    '$mod.ArrInt1 = rtl.arrayConcatN($mod.ArrInt2, $mod.ArrInt2);',
+    '$mod.ArrInt1 = rtl.arrayConcatN($mod.ArrInt2, $mod.ArrInt2, $mod.ArrInt2);',
+    '$mod.ArrRec1 = rtl.arrayRef($mod.ArrRec2);',
+    '$mod.ArrRec1 = rtl.arrayConcat($mod.TRec, $mod.ArrRec2, $mod.ArrRec2);',
+    '$mod.ArrRec1 = rtl.arrayConcat($mod.TRec, $mod.ArrRec2, $mod.ArrRec2, $mod.ArrRec2);',
+    '$mod.ArrSet1 = rtl.arrayRef($mod.ArrSet2);',
+    '$mod.ArrSet1 = rtl.arrayConcat("refSet", $mod.ArrSet2, $mod.ArrSet2);',
+    '$mod.ArrSet1 = rtl.arrayConcat("refSet", $mod.ArrSet2, $mod.ArrSet2, $mod.ArrSet2);',
+    '$mod.ArrJSValue1 = rtl.arrayRef($mod.ArrJSValue2);',
+    '$mod.ArrJSValue1 = rtl.arrayConcatN($mod.ArrJSValue2, $mod.ArrJSValue2);',
+    '$mod.ArrJSValue1 = rtl.arrayConcatN($mod.ArrJSValue2, $mod.ArrJSValue2, $mod.ArrJSValue2);',
+    '$mod.ArrInt1 = rtl.arrayConcatN([1], $mod.ArrInt2);',
+    '$mod.ArrFlag1 = [$mod.TFlag.big];',
+    '$mod.ArrFlag1 = rtl.arrayConcatN([$mod.TFlag.big], $mod.ArrFlag2);',
+    '$mod.ArrFlag1 = rtl.arrayConcatN($mod.ArrFlag2, [$mod.TFlag.small]);',
+    '']));
+end;
+
+procedure TTestModule.TestArray_Concat_Append;
 begin
 begin
   StartProgram(false);
   StartProgram(false);
   Add([
   Add([
@@ -11292,26 +11383,28 @@ begin
   '  ArrFlag: tarrflag;',
   '  ArrFlag: tarrflag;',
   '  ArrSet: tarrset;',
   '  ArrSet: tarrset;',
   '  ArrJSValue: tarrjsvalue;',
   '  ArrJSValue: tarrjsvalue;',
+  '  r: TRec;',
+  '  f: TFlags;',
   'begin',
   'begin',
+  '  // append',
   '  arrint:=concat(arrint);',
   '  arrint:=concat(arrint);',
-  '  arrint:=concat(arrint,arrint);',
-  '  arrint:=concat(arrint,arrint,arrint);',
+  '  arrint:=concat(arrint,[2]);',
+  '  arrint:=concat(arrint,[3,4]);',
   '  arrrec:=concat(arrrec);',
   '  arrrec:=concat(arrrec);',
-  '  arrrec:=concat(arrrec,arrrec);',
-  '  arrrec:=concat(arrrec,arrrec,arrrec);',
+  '  arrrec:=concat(arrrec,[r]);',
+  '  arrrec:=concat(arrrec,[r,r]);',
   '  arrset:=concat(arrset);',
   '  arrset:=concat(arrset);',
-  '  arrset:=concat(arrset,arrset);',
-  '  arrset:=concat(arrset,arrset,arrset);',
+  '  arrset:=concat(arrset,[f]);',
+  '  arrset:=concat(arrset,[f,f]);',
   '  arrjsvalue:=concat(arrjsvalue);',
   '  arrjsvalue:=concat(arrjsvalue);',
-  '  arrjsvalue:=concat(arrjsvalue,arrjsvalue);',
-  '  arrjsvalue:=concat(arrjsvalue,arrjsvalue,arrjsvalue);',
-  '  arrint:=concat([1],arrint);',
-  '  arrflag:=concat([big]);',
-  '  arrflag:=concat([big],arrflag);',
+  '  arrjsvalue:=concat(arrjsvalue,[11]);',
+  '  arrjsvalue:=concat(arrjsvalue,[12,13]);',
+  '  arrflag:=concat(arrflag);',
   '  arrflag:=concat(arrflag,[small]);',
   '  arrflag:=concat(arrflag,[small]);',
+  '  arrflag:=concat(arrflag,[small,big]);',
   '']);
   '']);
   ConvertProgram;
   ConvertProgram;
-  CheckSource('TestArray_Concat',
+  CheckSource('TestArray_Concat_Append',
     LinesToStr([ // statements
     LinesToStr([ // statements
     'this.TFlag = {',
     'this.TFlag = {',
     '  "0": "big",',
     '  "0": "big",',
@@ -11334,24 +11427,25 @@ begin
     'this.ArrFlag = [];',
     'this.ArrFlag = [];',
     'this.ArrSet = [];',
     'this.ArrSet = [];',
     'this.ArrJSValue = [];',
     'this.ArrJSValue = [];',
+    'this.r = this.TRec.$new();',
+    'this.f = {};',
     '']),
     '']),
     LinesToStr([ // $mod.$main
     LinesToStr([ // $mod.$main
-    '$mod.ArrInt = rtl.arrayRef($mod.ArrInt);',
-    '$mod.ArrInt = rtl.arrayConcatN($mod.ArrInt, $mod.ArrInt);',
-    '$mod.ArrInt = rtl.arrayConcatN($mod.ArrInt, $mod.ArrInt, $mod.ArrInt);',
-    '$mod.ArrRec = rtl.arrayRef($mod.ArrRec);',
-    '$mod.ArrRec = rtl.arrayConcat($mod.TRec, $mod.ArrRec, $mod.ArrRec);',
-    '$mod.ArrRec = rtl.arrayConcat($mod.TRec, $mod.ArrRec, $mod.ArrRec, $mod.ArrRec);',
-    '$mod.ArrSet = rtl.arrayRef($mod.ArrSet);',
-    '$mod.ArrSet = rtl.arrayConcat("refSet", $mod.ArrSet, $mod.ArrSet);',
-    '$mod.ArrSet = rtl.arrayConcat("refSet", $mod.ArrSet, $mod.ArrSet, $mod.ArrSet);',
-    '$mod.ArrJSValue = rtl.arrayRef($mod.ArrJSValue);',
-    '$mod.ArrJSValue = rtl.arrayConcatN($mod.ArrJSValue, $mod.ArrJSValue);',
-    '$mod.ArrJSValue = rtl.arrayConcatN($mod.ArrJSValue, $mod.ArrJSValue, $mod.ArrJSValue);',
-    '$mod.ArrInt = rtl.arrayConcatN([1], $mod.ArrInt);',
-    '$mod.ArrFlag = [$mod.TFlag.big];',
-    '$mod.ArrFlag = rtl.arrayConcatN([$mod.TFlag.big], $mod.ArrFlag);',
-    '$mod.ArrFlag = rtl.arrayConcatN($mod.ArrFlag, [$mod.TFlag.small]);',
+    '$mod.ArrInt;',
+    '($mod.ArrInt ? $mod.ArrInt.push(2) : [2]);',
+    'rtl.arrayPushN($mod.ArrInt, 3, 4);',
+    '$mod.ArrRec;',
+    '($mod.ArrRec ? $mod.ArrRec.push($mod.TRec.$clone($mod.r)) : [$mod.TRec.$clone($mod.r)]);',
+    'rtl.arrayPushN($mod.ArrRec, $mod.TRec.$clone($mod.r), $mod.TRec.$clone($mod.r));',
+    '$mod.ArrSet;',
+    '($mod.ArrSet ? $mod.ArrSet.push(rtl.refSet($mod.f)) : [rtl.refSet($mod.f)]);',
+    'rtl.arrayPushN($mod.ArrSet, rtl.refSet($mod.f), rtl.refSet($mod.f));',
+    '$mod.ArrJSValue;',
+    '($mod.ArrJSValue ? $mod.ArrJSValue.push(11) : [11]);',
+    'rtl.arrayPushN($mod.ArrJSValue, 12, 13);',
+    '$mod.ArrFlag;',
+    '($mod.ArrFlag ? $mod.ArrFlag.push($mod.TFlag.small) : [$mod.TFlag.small]);',
+    'rtl.arrayPushN($mod.ArrFlag, $mod.TFlag.small, $mod.TFlag.big);',
     '']));
     '']));
 end;
 end;
 
 

+ 8 - 0
utils/pas2js/dist/rtl.js

@@ -1044,6 +1044,14 @@ var rtl = {
     return a;
     return a;
   },
   },
 
 
+  arrayPushN: function(a){
+    if(a==null) a=[];
+    for (var i=1; i<arguments.length; i++){
+      a.push(arguments[i]);
+    }
+    return a;
+    },
+
   arrayCopy: function(type, srcarray, index, count){
   arrayCopy: function(type, srcarray, index, count){
     // type: see rtl.arrayClone
     // type: see rtl.arrayClone
     // if count is missing, use srcarray.length
     // if count is missing, use srcarray.length