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.
Before turning to the presentation of the implementation details needed to answer a number conceptual questions:
Due to the rapid development the web browsers as a runtime environment, have a real opportunity run the classic client/server applications from under these environments At the same time, users want that these programs worked in desktop version The task staged this way, in fact, equivalent to the creation of technologies to develop cross-platform applications with the use of the Pascal language and RAD tools such as Delphi.
The solution was implemented on the basis of the Pascal language compiler.
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]
}
}
};
|
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]
}
}
}
|
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.
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;
},[]);
|
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.
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;
},[]);
|
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;
},[]);
|
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;
},[]);
|
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"]);
|
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;
},[]);
|
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;
},[]);
|
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.
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"]);
|
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;
},[]);
|
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);
}
|
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;
}
|
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.
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;
...
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- 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)
-vg verbose information
-vm information output of the used modules
Dictionary is a text file with fields separated by tabs. Record dictionary has the following fields:
Dictionary substitution has additional syntax elements.
In the last two fields, after specifying the name of the method we can write [= As the index file can be any text file template. For example:
Forms TApplication Hint FHint=getAlt() SetHint
Generate an index file
<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-APPNAME Application name (Delphi project)
$SUBS-VERSION Version 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-MAIN The command calls the main function of the project. In most cases, it appears as <application name>.impl.$main()
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)
Simple formSource code: sample_source_1.zip
A working example: Run
|
![]() |
![]() |
||
Modal dialogSource code: sample_source_2.zip
A working example: Run
|
![]() |
![]() |
||
ListSource 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.
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:
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.
| Tariff | Base | Advanced | Special |
|---|---|---|---|
| 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, €/мес. | 20 | 60 | 140 |
* - 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.