Pas2js : What is it ?
- as a library
- as a command-line program
- as a webserver
It transpiles from actual Pascal source, it has no intermediate .ppu files. That means all sources must always be available.
through import units (comparable to the windows or unix units for the native compiler).
- For Node.js, basic support for the nodejs runtime environment is available.
- An import unit for jQuery is available (libjquery)
pas2js can automatically include this file in the generated output, like this:
pas2js -Jc -Jirtl.js -Tbrowser hello.pas
There is a basic Object Pascal RTL, several units from the FPC Packages are also available
- DB (yes, TDataset)
- fpcunit testsuite
- web (browser provided objects)
- libjquery (jquery is available too)
- nodejs (basic node runtime environment)
- browserconsole (support writeln)
Where to get it
The pas2js compiler and RTL are - naturally - open source and can be downloaded and used freely.
The snapshots contain binaries for Windows (32 and 64bit), Linux (64 bit) and MacOS.
The snapshots are uploaded to
Every version has a directory with the version number. A list of changes can be found on the changelog page Pas2JS Version Changes
svn co https://svn.freepascal.org/svn/projects/pas2js/trunk pas2js
You need FPC 3.0.4 or better to compile it.
Change to the directory and build it with:
make clean all
This creates compiler/utils/pas2js/pas2js (Windows: compiler\utils\pas2js\pas2js.exe)
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. Note that the snapshots and svn version already contains a default pas2js.cfg with unit search paths (-Fu) for the rtl and fcl.
Basically, the command is the same as any FPC command line. The only thing that is different is the target: -Tbrowser or -Tnodeejs
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:
The files that are needed are:
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.
pas2js -Tnodejs hello.pas
When compiled succesfully, the code can be run in node using the following command.
Note: on MacOS it is "node hello.js"
Supported syntax elements
- Delphi and ObjFPC mode
- Program, Units, namespaces
- unit initialization, but not finalization
- Var, Const, Type
- string (unicodestring), char (widechar), Boolean, Double, Byte, Shortint, Word, Smallint, longword, Longint, nativeint(int53), nativeuint(int52), currency
- Pointer (as a reference to a class, array, record, pointer of record, interface, but no pointer arithmetic)
- Record (but no variant records)
- Functions, Procedures, nested, anonymous functions
- function types, of object, reference to (closures)
- function arguments: default, const, var, out
- arrays static, dynamic, open, multi dimensionals
- String like array operations: a:=[1,2,3]+[1,1];
- class type, visibility, virtual, override, abstract, overload, properties, class properties, class var, class const, constructor, destructor
- nested classes
- interfaces: CORBA, COM, delegations, method resolution, reference counting, TVirtualInterface
- external classes, vars, const
- Enumeration for..in..do
- Type alias, e.g. type TTranslateString = type string;
- compiler directives (e.g. $ifdef, $if, $define, $modeswitch, $R+)
- compile time and run time range checking
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:
- Advanced records
- Runtime checks: Overflow -Co, $Q
- Type helpers
- Array of const
Needless to say, anything requiring direct memory access is not going to be supported.
Other not implemented features
- Enums with custom values
- Global properties
- Class constructor, destructor
- array of interface
- Record field interface
- Helpers for types, classes, records
- Operator overloading
- Pointer arithmetic
- RTTI extended, $RTTI
- Variant records
Lazarus integration of pas2js
Lazarus understands the concept of external classes as used by pas2js, so code completion will work.
Since Lazarus 1.9 the IDE can use pas2js.exe as a normal compiler.
The integration is described here: lazarus pas2js integration. It is still under construction, but deep integration with lazarus is planned.
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
- The "
(TJSObject)means it descends from
TJSObjectalso 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)
Varargscan be used to indicate that a function accepts any number of arguments.
JSValuecan be used to indicate an unknown type.
It is more or less equivalent to a Variant.
Create simple JS objects with the new function
Some JS-framework functions expect an JS object as parameter. Here is how to do that in Pascal using the new function from unit JS:
// Pascal; DoIt(new(['name','Fred', 'id',3, 'size',4.3]));
You can nest it to create sub objects:
// Pascal; DoIt(new(['name','Fred', 'size',new(['width',3, 'height',2])]));
You can use TJSArray._of to create JS arrays on the fly:
// Pascal; DoIt(new(['numbers',TJSArray._of(1,2,3)]));
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 depends on the browser too.)
A source map can be generated using the command-line parameter
The easiest is to include the Pascal sources in the source map
You can tell the compiler to store all file names relative to a local base directory:
And you can store an URL in the map, so the browser will use URL/above-relative-file-name to get the source:
Please report bugs in the FPC bugtracker with category pas2js: http://bugs.freepascal.org
- Time Tracking Application: https://www.devstructor.com/demos/pas2js-time/source.zip (sources: https://www.devstructor.com/demos/pas2js-time/source.zip)
- Drawing and animation on canvas: http://ragnemalm.se/images/santa/santa.html (sources: http://ragnemalm.se/images/santa/)
- WebGL: https://github.com/genericptr/Pas2JS-WebGL#pas2js-webgl
The ultimate goal is of course to have the LCL running in the web. Discussions on this topic are delegated to a separate page. pas2js_widgetsets
Why is a simple hello world program so big?
This is mainly due to the used rtl.js. The rtl.js contains code for Pascal modules, classes, RTTI, sets, range checks, etc and is written with big WebApps in mind, not for scripts with a few lines of code.
- You can create your own minified rtl.js by removing all functions you don't need. Eventually this will be done automatically by pas2js.
Why are asm blocks bad?
Asm blocks are useful for things you cannot do with pas2js. But there are some downsides: pas2js does not parse the JS. Neither does it check the syntax, nor does it know what Pascal identifiers the code is referencing. That means any identifier only accessed by the asm block will be removed by the pas2js' optimizer.
Therefore always try to do it in Pascal. Remember you can typecast values to JSValue, objects to TJSObject, arrays to TJSArray, strings to TJSString, etc to use almost all JS features.
Why not parse asm blocks?
Any compiletime JS parser can only do a syntax check and parse only simple JS. But since simple JS can be better written in Pascal, it is somewhat pointless and has therefore low priority.
What about optimization X?
See here for Pas2js optimizations