pas2js
Pas2js : What is it ?
Compiler
Pas2js is a pascal to javascript transpiler. It parses Object Pascal and emits Javascript. The javascript is currently of level ECMAScript 5 and should run in any browser or in nodejs. It is available in 3 forms:
- as a library
- as a command-line program
- as a webserver
It transpiles from actual Pascal source, it has no intermediate .ppu file. That means all sources must always be available.
Through external class definitions, the compiler can use Javascript classes:
- All classes available in the Javascript runtime, and in the browser are available
through import units (comparable to the windows or unix units for the native compiler). - For nodejs, basic support for the nodejs runtime environment is available.
- An import unit for jQuery is available (libjquery)
RTL
For the generated code to work, a small javascript file is needed: rtl.js. It defines an object rtl. This object will start the Object Pascal code if you include a call to rtl.run() in the html page.
<script type="application/javascript">
rtl.run()
</script>
pas2js can automatically include this file in the generated output, like this:
pas2js -Jc -Jirtl.js -Tbrowser hello.pas
For nodejs, the compiler will insert the call to rtl.run() automatically at the end of the generated javascript file.
There is a basic Object Pascal RTL, several units from the FPC Packages are also available
- system
- sysutils
- Math
- strutils
- rtlconst
- classes
- contnrs
- DB (yes, TDataset)
- fpcunit testsuite
- custapp
- restconnection
- js (javascript system objects)
- web (browser provided objects)
- libjquery (jquery is available too)
- nodejs (basic node runtime environment)
- typeinfo
- objpas
- browserconsole (support writeln)
- dateutils
- browserapp
- nodejsapp
Where to get it
The pas2js compiler is - naturally - open source and can be downloaded and used freely. The sources are currently the primary means to get it.
Compiler
- The Pas2js compiler is part of Free Pascal. As such, the compiler is present in the utils/pas2js directory of the
RTL
- The pas2js RTL is still in flux. To enable faster releases, the RTL and demos are in a separate repository in SVN:
https://svn.freepascal.org/svn/projects/pas2js/trunk
svn co https://svn.freepascal.org/svn/projects/pas2js/trunk pas2js
Snapshots
Till pas2js is integrated in the main FPC distribution, snapshots will be made available as time permits. They will contain binaries for Windows, Linux (64 bit) and Macos.
A beta snapshot can be found for the following versions:
How to use pas2js
The command-line arguments are kept mostly the same as the FPC command-line arguments. Error messages are also in the same format.
The compiler needs access to all sources, and so you need to specify the path to the sources of all used units.
As for the FPC compiler, a configuration file is supported, which has the same syntax as the FPC config file.
Basically, the command is the same as any FPC command line. The only thing that is different is the target: browser or nodeejs
Here is the complete list of command line arguments.
for the browser
Consider the classical:
program hello;
begin
Writeln('Hello, world!');
end.
Yes, writeln is supported. Here is how to compile it:
pas2js -Jc -jirtl.js -Tbrowser hello.pas
When compiled succesfully, the code can be run in the browser by opening a html file in the browser with the following content:
<html>
<head>
<meta charset="utf-8"/>
<script type="application/javascript" src="hello.js"></script>
</head>
<body>
<script type="application/javascript">
rtl.run();
</script>
</body>
</html>
The files that are needed are:
- hello.html
- hello.js
Whether hello.html is opened by double-clicking it in the explorer or put on a server and opened with an URL, is not relevant for the functioning.
The output is visible in the browser's web developer console. By including the browserconsole unit, it will be visible in the browser page:
program hello;
uses browserconsole;
begin
Writeln('Hello, world!');
end.
for NodeJS
pas2js -Tnodejs hello.pas
When compiled succesfully, the code can be run in node using the following command.
nodejs hello.js
Supported syntax elements
Basically, Delphi 7 syntax minus interfaces is supported. This includes RTTI. A more detailed list can be found in the translation.html file in the sources.
- Delphi and ObjFPC mode
- Program, Units
- unit initialization, but not finalization
- var, const, type
- string (unicodestring), char (widechar), boolean, double, byte, shortint, word, smallint, longword, longint, nativeint(in53), nativeuint(int52)
- resourcestrings
- pointer (as a reference to a class or array, but no pointer arithmetic)
- record (but no variant records)
- functions, procedures, nested
- function types, of object, reference to (closures)
- function arguments: default, const, var, out
- if-then-else
- for-do
- repeat-until
- while-do
- with-do
- try-finally
- try-except
- enums
- sets
- arrays static, dynamic, open, multi dimensionals
- class type, visibility, virtual, override, abstract, overload, properties, class properties, class var, class const, constructor, destructor
- class-of
- RTTI
- compiler directives (e.g. $ifdef, $if, $define)
There are some constructs that are naturally not supported and will never be supported:
- Anything involving memory pointers and pointer arithmetic.
- Variant records
Planned language features
Basically, the idea is to get the pas2js transpiler up to the same level as FPC or Delphi. That means the following needs to be added:
- More runtime/compile time checks
- Interfaces
- Generics
- Type helpers
- Anonymous functions
- Pointer of record
- Nested types in class
- Var Absolute modifier
Needless to say, anything requiring direct memory access is not going to be supported.
Other not implemented features
- Advanced records
- Array of const
- Attributes
- Currency
- Enumeration for..in..do
- Enums with custom values
- Global properties
- Futures
- Helpers for types, classes, records
- Inline
- Library
- Objects
- Operator overloading
- Pointer arithmetic
- Resources
- RTTI extended, $RTTI
- Runtime checks: Overflow -Co, $Q
- Runtime checks: Range -Cr, $R
- Runtime checks: Typecast -CR
- Scoped enums
- Type alias, e.g. type TTranslateString = type string;
- Variant records
- Variants
Lazarus integration of pas2js
Lazarus understands the concept of external classes as used by pas2js, so code completion will work.
pas2js comes with several lazarus packages that 'import' the pas2js rtl units: by doing so, the IDE will know which units to use.
- pas2js_rtl : the basic RTL
- fcl_base_pas2js: The custapp and other units
- fpcunit_pas2js: the fpcunit units
- pas2js_fcldb: the database units
A new project wizard is under construction, but deeper integration with lazarus is planned.
Importing Javascript classes
To import a javascript class, one writes a normal class definition that mimics the Javascript class. It is possible to use properties. Many examples can be found in the JS, web, nodejs and libjquery units.
Here is a simple example:
TJSFunction = class external name 'Function'(TJSObject)
private
Flength: NativeInt external name 'length';
Fprototyp: TJSFunction external name 'prototyp';
public
name: String;
property prototyp: TJSFunction read Fprototyp;
property length: NativeInt read Flength;
function apply(thisArg: TJSObject; const ArgArray: TJSValueDynArray): JSValue; varargs;
function bind(thisArg: TJSObject): JSValue; varargs;
function call(thisArg: TJSObject): JSValue; varargs;
end;
This declares the TJSFunction
object : in Javascript, functions are objects.
- The "
external name 'Function'
" means that you declare a Javascript class where the Javascript name of the class is 'Function'. - The
(TJSObject)
means it descends fromTJSObject
also an external class. There does not need to be an ancestor type. - Fields are declared just as in pascal.
- To declare read-only fields, a trick can be used: declare the field using an external name "thename" modifier, and declare a read-only property with the same name.
(see the length declaration) Varargs
can be used to indicate that a function accepts any number of arguments.JSValue
can be used to indicate an unknown type.
It is more or less equivalent to a Variant.
Debugging
The generated Javascript source code is of course visible and debuggable in the browser.
Moreover, the transpiler can generate a source map, which means that you will be able to see and debug the pascal code in the browser. (not everything will work, but many things do. This also depends on the browser)
A source map can be generated using the command-line parameter
-Jm
The easiest is to include the pascal sources in the source map
-Jminclude
You can tell the compiler to store all file names relative to a local base directory:
-Jmbasedir=DirName
And you can store an URL in the map, so the browser will use URL/above-relative-file-name to get the source:
-Jmsourceroot=URL