p2js

Translation programs from Delphi (Pascal) to javascript

Purpose of the project is to create a translator from the Pascal language (a variant of Delphi) to the javascript. The compiler must ensure the so-called "transparent" the translation of the source code.

Content

Conception

Before turning to the presentation of the implementation details needed to answer a number conceptual questions:

Back to top

Implementation

The solution was implemented on the basis of the Pascal language compiler.

Translation modules

Programs written in Pascal has a modular structure. This is not true for programs written in javascript. However, using objects, you can emulate this structure, as shown in the following table.

Pascal javascript
              Unit <unitname>;
              Interface
                 [interface section]
              Implementation
                 [implementation section]
              Initialization
                 [initialization section]
              End.
           
              pas.<unitname>={
               [interface section],
               impl: {
                 [implementation section],
                 $<UNITNAME>_init: function() {
                    [initialization section]
                 }
               }
              };
           
Of course, it is necessary to ensure the availability empty object pas. This and other helper objects can be found in the file rtl.js, within the project (
see also). In addition, it should be noted, that section finalization in the Pascal unit is not supported by.

The main module of the Pascal program (in case of "Delphi" is located in a file with extension "dpr") is translated in the following image

Pascal javascript
              Program <unitname>;
                 [implementation section],
              Begin
                 [main code]
              End.
           
              pas.<unitname>={
               impl: {
                 [implementation section],
                 $main:function() {
                    [main code]
                 }
               }
              }
           
Expected to that the function pas.<unitname>.impl.$main() is called after loading the index page of the application (for example, in the handler onload тэга body). To implement this, p2js offers option -Gi (see also), allowing to assign a template index page, where you can specify where to insert a call the main function of the program.

Before we show example should be noted that Pascal modules assembled and initialized in a certain order. To ensure such the order is necessary to use a helper function rtl.module (see also).

Example:

Pascal javascript
              Unit MyModule;
              Interface
              Uses windows,Sysutils;
                 var  iGlob:integer;
                      sGlob:string="abc";
              Implementation
              Uses Classes;
                 procedure implMyFunc;
                 begin
                 end;
              End.
           
              rtl.module("MyModule",["System","objpas","windows","sysutils"],
               function(uses, unit){
               this.iGlob=0; 
               this.sGlob="abc"; 
               var $impl={
                     $MYMODULE_init:function() {
                     }, 
                     implMyFunc:function() {
                     }
               };
               this.impl=$impl;
              },["Classes","Graphics","Provider","CommonDM"]);
           

It should be noted that the units System and objpas assembled implicitly compiler. Necessary to ensure their availability.

Back to top

Translation of variables

Variables transformed without specifying the type, because a clear indication of the types in javascript is not required. Specifying of the type is possible if the variable is assigned an initial value.

Example:

Pascal javascript
             Unit A1;
             Interface
             Uses Classes,Forms;

             const c1:integer=3;
                   c2 = 'abc';
                   c3 = 234;
                   c4 = 12.45;
                   c5 = nil;

             var   v1:string;
                   v2,v3:double;
                   v4:byte=0;
                   v5:TForm;
                   v6:TIdentMapEntry;

                   vv4:string='abcюжа';
                   vv5:set of byte=[0,1];
                   vv7:array[0..1] of byte=(1,2);
                   vv8:array of byte;
                   vv9:array[0..101] of string;

             Implementation
             End.

           
                   rtl.module("MyModule",["System","objpas","Classes","Forms"],
                   function(uses, unit){
                    this.c1=3; 
                    this.c2="abc"; 
                    this.c3=234; 
                    this.c4=12.45; 
                    this.c5=null; 
                    this.v1=""; 
                    this.v2=0.0; 
                    this.v3=0.0; 
                    this.v4=0; 
                    this.v5=undefined; 
                    this.v6=new pas.Classes.TIdentMapEntry(); 
                    this.vv4="abcюжа"; 
                    this.vv5=3; 
                    this.vv7=[1, 2]; 
                    this.vv8=[]; 
                    this.vv9=[]; 
                    var $impl={
                          $MYMODULE_init:function() {
                          }
                    };
                    this.impl=$impl;
                   },[]);
           

Back to top

Translation of types

In javascript type designs in a declarative form is absent, except for the types that can be attributed to the object type (in this case, use so-called prototypes). That's why all the derivatives from simple types of Pascal does not translate. Complex types of Pascal (classes, records (structures), or arrays) are translated into objects or arrays of javascript, respectively.

Back to top

Translation of a record type

Type of record is translated into a js object whose constructor forms the structure of the fields of this type. Example:

Pascal javascript
              Unit MyModule;
              Interface
                type
                     t3 = record
                        t3_f1 :integer;
                        t3_f2 :string;
                        t3_f3 :TDateTime;
                     end;

              Implementation
                var  r1:t3;
              Initialization
                r1.t3_f1 := 11;
              End.
           
              rtl.module("MyModule",["System","objpas"],function(uses, unit){
               this.t3=
                function() {
                 this.clean_fields = function() {
                 this.t3_f1=0; 
                 this.t3_f2=""; 
                 this.t3_f3=0.0; 
                 };
                 this.clean_fields();
                }; 
               var $impl={
                     $MYMODULE_init:function() {
                      $impl.r1.t3_f1=11; 
                     }, 
                     r1:new pas.MyModule.t3()
               };
               this.impl=$impl;
              },[]);
           
Should pay attention to that the declaration in Pascal variable of type record equivalent to creating a js object.

Back to top

Translation of array type

All types of arrays in Pascal (meaning both static and dynamic arrays) are translated into js array. In this case, control of the borders of the array is absent, becouse all arrays in the javascript are dynamic and also automatic variable (ie, automatically expanding their boundaries if necessary)

We present an example:

Pascal javascript
             Unit MyModule;
             Interface

             type
                   t1 = array [0..21] of byte;
                   t2 = array of TObject;
             var
                   a1: array of string;

             Implementation

             end.
           
             rtl.module("MyModule",["System","objpas"],function(uses, unit){
              this.t1=new Array(22); 
              this.t2=[]; 
              this.a1=[]; 
              var $impl={
                    $MYMODULE_init:function() {
                    }
              };
              this.impl=$impl;
             },[]);
           

Back to top

Translation of class type

Classes in the conventional sense of the word in javascript are not available. In a sense, replacement of the class is the concept prototype. To use the prototype to emulate the concept of classes will use the helper function rtl.extend (see also)

Example:

Pascal javascript
             Unit MyModule;
             Interface

             type
                    MyCls=class
                      FId:integer;
                      FZOrder:byte;
                      function getZOrder:byte;
                    end;

             Implementation

               function MyCls.getZOrder:byte;
               begin
                 Result:=FZOrder;
               end;

             end.
           
             rtl.module("MyModule",["System","objpas"],function(uses, unit){
              this.MyCls=rtl.extend(pas.System.TObject, {
                constructor: function() {
                  pas.System.TObject.apply(this, arguments);
                },
                FId:0, 
                FZOrder:0, 
                getZOrder:function() {
                 var $result=0; 
                 $result=this.FZOrder; 
                 return $result;
                }
               }); 
              var $impl={
                    $MYMODULE_init:function() {
                    }
              };
              this.impl=$impl;
             },[]);
           
For classes that implement interfaces, in the second argument of rtl.extend to pass an array of IDs these interfaces. Example:

Pascal javascript
     Unit MyModule;
     Interface

     type

        TMyObj=class(TObject,IUnknown)
         function QueryInterface(const guid: TGuid;out obj):LongInt; StdCall;
         function _AddRef:LongInt; StdCall;
         function _Release:LongInt; StdCall;
        end;

     Implementation
     Uses SysUtils;

        function TMyObj.QueryInterface(const guid: TGuid;out obj):LongInt; 
        begin

        end;

        function TMyObj._AddRef:LongInt; 
        begin

        end;

        function TMyObj._Release:LongInt;
        begin

        end;

     var  obj:TMyObj;
     initialization
       obj:=TMyObj.Create;
       if Supports(obj,IUnknown) then;
     end.
     
     rtl.module("MyModule",["System","objpas"],function(uses, unit){
      this.TMyObj=rtl.extend(pas.System.TObject, 
                ["{00000000-0000-0000-C000-000000000046}"], {
        constructor: function() {
          pas.System.TObject.apply(this, arguments);
        },
        QueryInterface:function(guid, obj) {
         var $result=0; 
         return $result;
        }, 
        _AddRef:function() {
         var $result=0; 
         return $result;
        }, 
        _Release:function() {
         var $result=0; 
         return $result;
        }
       }); 
      var $impl={
            $MYMODULE_init:function() {
             $impl.obj=new pas.MyModule.TMyObj(); 
             if(pas.sysutils.Supports$3.call(pas.sysutils,$impl.obj, 
                 "{00000000-0000-0000-C000-000000000046}")) ; 
            }, 
            obj:undefined
      };
      this.impl=$impl;
     },["sysutils","Graphics","Provider","windows","CommonDM"]);
           

Back to top

Translation of the enumerated types and sets

Enumerated type create field in the unit. The names of these fields correspond to the names of elements of an enumerated type, and value - the item's position in the announcement of this type. For example:

Pascal javascript
           Unit MyModule;
           Interface
           type 
                t1 = (t31,t32,t33);
           Implementation
           End.
           
           rtl.module("MyModule",["System","objpas"],function(uses, unit){
            this.t1=rtl.enumtype('t31','t32','t33'); 
            var $impl={
                  $MYMODULE_init:function() {
                  }
            };
            this.impl=$impl;
           },[]);
           

After this, in the unit appears fields with names pas.MyModule.t31 fields, etc. and the corresponding values

The sets are represented by a integer number or an array of integers. Operations on sets are substituted. For example

Pascal javascript
           Unit MyModule;
           Interface

           Implementation

             var  abc:set of byte;
                  r1 :byte;
                  r2 :byte;

             function f1:boolean;
             begin
               abc := [1,5,25];
               r1:=0;r2:=1;
               Result:=1 in [r1,r2];
             end;

           initialization
           end.
           
           rtl.module("MyModule",["System","objpas"],function(uses, unit){
            var $impl={
                  $MYMODULE_init:function() {
                  }, 
                  abc:undefined, 
                  r1:0, 
                  r2:0, 
                  f1:function() {
                   var $result=false; 
                   $impl.abc=[33554466,131072,512,2]; 
                   $impl.r1=0; 
                   $impl.r2=1; 
                   $result=rtl.inset((([] | ($impl.r1<<1)) | ($impl.r2<<1)), 1); 
                   return $result;
                  }
            };
            this.impl=$impl;
           },[]);
           

Back to top

Translation cycles

The translation the while and repeat cycles nothing much is different. This does not apply to the translation of the loop for. Question is that the javascript the termination condition for loop is evaluated at every cycleю. In Pascal language it is not that. In Pascal language the termination condition of the cycle is evaluated before its execution, and therefore can not be changed during the loop. To implement this feature p2js creates an helper variable, whose value is computed before the loop. Example:

Pascal javascript
           Unit MyModule;
           Interface

           Implementation

             var  arr :array of string;

             function f1:string;
             var i:integer;
             begin
               i:=0;
               while i<10 do 
               begin
                arr[i]:='abc';
                i:=i+1;
               end;
               repeat 
                arr[i]:='dce';
                i:=i+1;
               until i>=12;
             end;

             procedure f2;
             var i:integer;
             begin
               for i:=0 to 12 do arr[i]:='abc';
               for i:=12 downto 0 do begin 
                  arr[i]:='dce';
                  if(i=12) then arr[i]:='fgt';
               end;
             end;

           end.
           
           rtl.module("MyModule",["System","objpas"],function(uses, unit){
            var $impl={
                  $MYMODULE_init:function() {
                  }, 
                  arr:[], 
                  f1:function() {
                   var i=0; 
                   var $result=""; 
                   i=0; 
                   while ((i < 10)) {
                    $impl.arr[i]="abc"; 
                    i=(i + 1); 
                   }; 
                   do {
                    $impl.arr[i]="dce"; 
                    i=(i + 1); 
                   } while (!(i >= 12)); 
                   return $result;
                  }, 
                  f2:function() {
                   var i=0; 
                   {i=0; for(var $efor_176_5=12; i<=$efor_176_5; i++) {
                    $impl.arr[i]="abc"; 
                   }
                   }; 
                   {i=12; for(var $efor_177_5=0; i>=$efor_177_5; i--) {
                    $impl.arr[i]="dce"; 
                    if((i == 12)) {
                     $impl.arr[i]="fgt"; 
                    }; 
                   }
                   }; 
                  }
            };
            this.impl=$impl;
           },[]);
           

Back to top

Features of the translation of other operators

Translation of many operators Pascal language has no features. Therefore, this paragraph will be referred to only those operators, which translation is not obvious, or has its nuances.

Back to top

Operator try ... except

In the javascript statement catch (analog except) need to determine the name of the variable that will contain the exception object That name in p2js is the exceptObject. The name was not chosen by chance. It matches with the function name in the Pascal, which returns a pointer to an object of type Exception. Accordingly, all calls to this function are replaced by reference to the variable exceptObject. Example:

Pascal javascript
           Interface

           Implementation
           Uses SysUtils,Dialogs;

             function f2(a:integer):double;
             var s:string;
             begin
               try
                 Result:=10/a;
               except
                 on E:EZeroDivide do result:=0;
                 on E:EOverflow do result:=0;
                 else 
                   begin
                    s:=(ExceptObject as Exception).Message;
                    raise Exception.Create('abort');
                   end;
               end;
             end;

           end.
           
           rtl.module("MyModule",["System","objpas"],function(uses, unit){
            var $impl={
                  $MYMODULE_init:function() {
                  }, 
                  f2:function(a) {
                   var s=""; 
                   var $result=0.0; 
                   try {
                    $result=(10 / a); 
                   }
                   catch(exceptObject){
                    var E=exceptObject; 
                    if(E instanceof pas.sysutils.EZeroDivide) {
                     $result=0; 
                    } else {
                     {
                      if(E instanceof pas.sysutils.EOverflow) {
                       $result=0; 
                      } else {
                       {
                        s=exceptObject.fmessage; 
                        throw new pas.sysutils.Exception("abort"); 
                       }; 
                      }; 
                     }; 
                    }; 
                   }; 
                   return $result;
                  }
            };
            this.impl=$impl;
           },["sysutils","Dialogs","Graphics","Provider","windows","CommonDM"]);
           

Back to top

Call an event handler

The event handler (object field a procedural type) should be invoked from the object to which it belongs. In other words, the field of Self within the handler should to specify to the owner of this method. For this purpose, in p2js all handlers are objects that must be created by rtl.createEvent(). When you explicitly call p2js handler translate it into a function call handler.callback(...). For set the value of handler must use the function handler.set(self,callback). Example:

Pascal javascript
           Unit MyModule;
           Interface

           Implementation

           Type
                 TMyFuncObj=function(a:integer):boolean of object;

             TObj=class
              FMyFunc:TMyFuncObj;
              function handler(a:integer):boolean;
             end;
            
             function TObj.handler(a:integer):boolean;
             begin
             end;

           Var  Obj:TObj;

             procedure mySet;
             begin
               Obj.FMyFunc:=Obj.handler;
             end;

             function myCall:boolean;
             begin
               Result:=Obj.FMyFunc(20);
             end;

           end.
           
           rtl.module("MyModule",["System","objpas"],function(uses, unit){
            var $impl={
                  $MYMODULE_init:function() {
                  }, 
                  TMyFuncObj:function($self, a){}, 
                  TObj:rtl.extend(pas.System.TObject, {
                    constructor: function() {
                      pas.System.TObject.apply(this, arguments);
                      this.FMyFunc=rtl.createEvent();
                    },
                    FMyFunc:undefined, 
                    handler:function(a) {
                     var $result=false; 
                     return $result;
                    }
                   }), 
                  Obj:undefined, 
                  mySet:function() {
                   $impl.Obj.FMyFunc.set($impl.Obj, $impl.Obj.handler); 
                  }, 
                  myCall:function() {
                   var $result=false; 
                   $result=$impl.Obj.FMyFunc.callback(20); 
                   return $result;
                  }
            };
            this.impl=$impl;
           },[]);
           

Back to top

Passing by reference

It is known that in javascript you can not pass data from a function by reference. Therefore, translation arguments to functions / methods with modifiers var / out requires special processing techniques. Consider the example:

Pascal javascript
              procedure f1(var b:byte);
              begin
                b:=-1;
              end;
           
              f1:function(b) {
                   b.set(-1);
              }
           
Here we assume, that instead the argument a simple type is passed of object with the method set. When you call the function f, with the help of p2js the following happens
Pascal javascript
              function f2:byte;
              begin
                f1(Result);
              end;
           
              f2:function() {
               var $result;
               this.f1({get:function(){return $result;},set:function(v){$result=v;}});
               return $result;
              }
           
It uses a closure. After all, the method set, called from the body f1 will be executed in the context of f2, what is equivalent to passing the value of the argument to a higher level of control.

Back to top

Refactoring modal dialogs

Modal dialog with the user are not supported by most browsers. This causes a problem in the translation of programs written in Pascal, who use modality. Will be mentioned here about the true modality. The true modality we call the opportunity implementation in anywhere in the program closed the message loop that allows the programmer to write code like this:

             {a}
             if f.ShowModal=mrOk then {b}
                                 else {c};
             {d}
           
Once the user to input data in a modal dialog box and leave a message loop in the program will be two alternative ways: {b, d} or {c, d}. The simplest refactoring method is to convert the source code in the following form:
             {a}
             f.ShowModal( function($modalresult) 
                          {
                            if $modalresult=mrOk then {b}
                                                 else {c};
                            {d}
                          }
                         );
           
So far we have deliberately not taken into account a number of nuances that would arise later. So the modal call is modeless, and all possible outcomes ({b, d} or {c, d}) are enclosed in a function that will call f.ShowModal after the user data entry. Such a function will be called the reminder. In the argument of the function $modalresult will be passed a value that describes the result of a modal dialog (For example, if the user presses the Ok button in the $modalresult will pass the mrOk).

If you have any branches in the program is much more complicated situation. Here is an example:

             if {x} then
             begin
               {a}
               if f.ShowModal=mrOk then {b}
                                   else {c};
               {d}
             end else
               {y};
             {z};
           
Possible branches are {x, y, z}, {x, a, b, d, z}, {x, a, c, d, z}. Note that {z} is included in all possible branches, among which is the branch with a modal dialog, and without it. This means that there are two conflicting requirements: move {z} in the function of the reminder and leave it in place. This and many other features accounted in the implementation of the function rtl.modal_if. And statement if in the translation is replaced by a call rtl.modal_if. For example:

Pascal javascript
           Unit MyModule;
           Interface

           Implementation
           Uses SysUtils,Dialogs;

             function f2(a:integer):double;
             begin
               if a<0 then ShowMessage('А value is less than zero')
                      else Result:=sqrt(a);
             end;

           end.
           
           rtl.module("MyModule",["System","objpas"],function(uses, unit){
            var $impl={
                  $MYMODULE_init:function() {
                  }, 
                  f2:function(a, $scope, $callback, $topLevel) {
                   var $result=0.0; 
                   rtl.modal_topLevel(this, function($context){
                    $context(function(){
                     rtl.modal_if($context, (a < 0), function($context){
                      $context(function(){
                       pas.Dialogs.ShowMessage("А value is less than zero", this, 
                        function($modalresult){
                        $context(function(){
                         $context.ret(); 
                        }); 
                       }, $context); 
                      }); 
                     }, function($context){
                      $context(function(){
                       $result=Math.sqrt(a); 
                       $context.ret(); 
                      }); 
                     }, function($context){
                      $context(function(){
                       $context.ret(); 
                      }); 
                     }); 
                     
                    }); 
                   }, $scope, $callback, function(){
                    return $result; 
                   }, $topLevel); 
                   return $result;
                  }
            };
            this.impl=$impl;
           },["sysutils","Dialogs","Graphics","Provider","windows","CommonDM"]);
           

It looks scary. By the way, note that the body a modal functions is placed in the specialized function rtl.modal_topLevel. This function is necessary for the correct transfer of control at the exit of the modal dialog.

Next, you need to take into account that the functions are nested into each other. Function, which has a modal function call will also be modal. Example:

             function X:byte;
             begin
               {a0}
               if f.ShowModal=mrOk then {b0}
                                   else {c0};
               {d0}
             end;

             procedure Y;
             begin
               {a1}
               if X=0 then {b1}
                      else {c1}
               {d1}
             end;
           
Consider the following translation:
                  function X($scope, $callback, $topLevel) {
                   var $result=0; 
                   rtl.modal_topLevel(this, function($context){
                    $context(function(){
                     {a0}; 
                     $impl.f.ShowModal(this, function($modalresult){
                      $context(function(){
                       if(($modalresult == 1)) {
                        {b0}; 
                       } else {
                        {c0}; 
                       }; 
                       {d0}; 
                       $context.ret(); 
                      }); 
                     }, $context); 
                    }); 
                   }, $scope, $callback, function(){
                    return $result; 
                   }, $topLevel); 
                   return $result;
                  };
            
                  function Y($scope, $callback, $topLevel) {
                   rtl.modal_topLevel(this, function($context){
                    $context(function(){
                     {a1}; 
                     $impl.X(this, function($modalresult){
                      $context(function(){
                       if(($modalresult == 0)) {
                        {b1}; 
                       } else {
                        {c1}; 
                       }; 
                       {d1}; 
                       $context.ret(); 
                      }); 
                     }, $context); 
                    }); 
                   }, $scope, $callback, function(){
                    return; 
                   }, $topLevel); 
                  };
           
The general principle of translation on based on the structure of the program is as follows: the function of reminder a certain level of control (in this example, Y) is transmitted to the underlying level of control (in this example, X) and is called after reminder function of the current level. In this way, the these functions form a the correct order of calls. It should be noted that due to the "closures", implemented in the javascript language, function of reminder called in the right context (ie, that which was conceived in the original program in Pascal).

Any function or method can be modal. For this p2js extends the syntax of Pascal and uses the modifier modal. By example:

             ...
             procedure MyModal;modal;
             ...
           

Back to top

Command line options

The command line is as follows

p2js [options] <inputfile>

, where inputfile - the file name of the source code in Pascal language, options - optional values. List of options:

  • -G determines the output mode
    • -G- to turn off the generation of javascript
    • -Ga if true, add the generated code to an existing file (default - off)
    • -Gt<x> <x> sets as output file name (default "<inputfile>.js")
    • -Gi<x> <x> sets in the name of the index html-file. Plays a role in the generation of the project file (*. dpr).
    • -Gs establishes a regime for the generation of stubs (without the implementation section of the module) (default - off)
    • -Gd<x> sets <x> as the directory in which an output
    • -Gu<x> <x> sets the file name, which will be printed dictionaries (*. dict and *. udict) (default - dictionaries are not formed)
    • -Gp<x> <x> sets the file name that is used as a dictionary substitution
    • -Ge<x> <x> sets as a list of names to be excluded from the result of the modules
    • -Gm sets the mode of rebuilding the program code associated with modal dialogs (default mode is off)
  • -v determines the mode of informing the process of generating
    • -vg verbose information
    • -vm information output of the used modules

Back to top

Dictionaries

There are three types of dictionaries:

Dictionary is a text file with fields separated by tabs. Record dictionary has the following fields:

Records are separated a chars a newline and carriage return.

Dictionary substitution has additional syntax elements. In the last two fields, after specifying the name of the method we can write [= [()]]. When parentheses are specified, then the substitution is a method, otherwise an appeal to the field. For example:

           Forms	TApplication	Hint	FHint=getAlt()	SetHint
           

Back to top

Generate an index file

As the index file can be any text file template. For example:

<html>
            <head>
              <script type="text/javascript" src="ext/ext-all.js"></script>
              <script type="text/javascript" src="?app=$SUBS-APPNAME?r=jsproject.js?v=$SUBS-VERSION"></script>
              <script type="text/javascript">
                 Ext.onReady(function(){$SUBS-MAIN});
              </script>
            </head>
           <body>
           </body>
           </html>
It should be borne in mind that the template can use the following substitution variables:
The variable name Description
$SUBS-APPNAMEApplication name (Delphi project)
$SUBS-VERSIONVersion of the project. The value will be generated by finding the nearby with project file (*. dpr) file of name c ver.rc in which is placed the source code of resource Windows
$SUBS-MAINThe command calls the main function of the project. In most cases, it appears as <application name>.impl.$main()

Back to top

Javascript helpers

To demonstrate the capabilities of p2js send us any project consisting of a single module. The only limitation of the project is that it can only use standard components for VCL. This limitation is not a limitation of technology. It is a limitation of the runtime javascript in the demo. (e.g. Application.MainFormOnTaskbar not support. This line must be removed from the DPR)

Examples

Simple form

Source code: sample_source_1.zip
A working example: Run

Modal dialog

Source code: sample_source_2.zip
A working example: Run

List

Source code: sample_source_2.zip
A working example: Run

The p2js team is ready to help to create cross-platform applications. Write to us p2js@mail.ru. Language of communication - English or Russian.

Licensing and use restrictions

p2js produced under license GNU General Public License. You can download it and use it in all its developments, including commercial.

Third-party software used in the project:

Services

The project team provides services in supporting users p2js. In order to get support, you need to get acquainted with its terms, choose the tariff and to pay for services.

TariffBaseAdvancedSpecial
List of Services
Advice on e-mail*
Inform the customer about the release of updates. Inform the customer to expand the functionality of the updated versions.
Elimination of bugs on request within 5 working days
Immediate elimination of bugs on request
The maximum response time for handling customer support - 40 hours 16 hours
Accept for proposals for revision of functionality. The calculation of the cost and timing.
Price, €/мес.2060140

* - address technical support p2js@mail.ru. Language of communication English or Russian.

Billing period of service is a calendar month. Once payment is received on the account of the support service begins to operate in the current calendar month. Technical Support will provide the services requested from the email address specified in the payment.