Pascal to JavaScript: A Report


FPC trunk includes the fppas2js.pp unit for converting Pascal source to JavaScript source. The file currently stands at over 12K lines of code and clearly represents a significant effort over the last several years.

I was curious about several things: (1) What does the JavaScript code emitted by the converter look like? (2) How do you use that code with external JavaScript libraries? and (3) Is it practical to write JavaScript in Pascal?

As a test, I took this snippet of JavaScript from the BoxCast example app described here:

Figure 1. JavaScript from boxcast.js.

var map = new mapboxgl.Map({
    container: "map",
    style: "mapbox://styles/mapbox/satellite-streets-v10",
    center: [-107.74, 37.92],
    zoom: 9.0,
    logoPosition: "bottom-right" });
This is typical JavaScript code. It creates a Map object by calling its constructor, passing in a JavaScript object that specifies various things such as the HTML element to associate with the map object, the location the map should display, and so on. All very readable and concise. You can read about the Map class and its options here. Its source is here.

So how would you do this in Pascal? Can the converter create the functional equivalent of this code from Pascal?

Since Map is in the mapbox-gl.js file, we need a way of referencing code that's external to what the converter will produce. This is roughly analogous to using an external dynamic library currently, where a Pascal interface unit provides declarations for the functions that the library exports. With a dynamic library, you usually run a parser on the library's header file (.h) to create the interface unit more or less automatically. Since JavaScript doesn't have header files, I just created a rudimentary interface unit manually. It looks like this:

Figure 2. Pascal interface unit for mapbox-gl.js

unit MapboxGL_Intf;

interface

{$modeswitch externalclass}

type
  TMapOptions = record
    container : string;
    style : string;
    doubleClickZoom : Boolean;
//    center : array [0..1] of Double;  //not supported yet
    zoom : Double;
    logoPosition : string;
  end;

  TMap = class external name 'mapboxgl.Map'
    constructor New(Options : TMapOptions);
  end;

implementation

end.
Note that instead of declaring functions as external, the way you would with a dynamic library, it declares a JavaScript class as external.

Also, the converter does not appear to support the array declaration yet, so that's commented out for now.

In order to convert this to JS, I hacked FPC's testpas2js.pp program, specifically the tcmodules.pas unit, and added a few additional test suites. Here's what the converter generates for Figure 2's Pascal code:

Figure 3. Interface unit converted to JavaScript.

rtl.module("MapboxGL_Intf", ["System"], function () {
  var $mod = this;
  this.TMapOptions = function (s) {
    if (s) {
      this.container = s.container;
      this.style = s.style;
      this.doubleClickZoom = s.doubleClickZoom;
      this.zoom = s.zoom;
      this.logoPosition = s.logoPosition;
    } else {
      this.container = "";
      this.style = "";
      this.doubleClickZoom = false;
      this.zoom = 0.0;
      this.logoPosition = "";
    };
    this.$equal = function (b) {
      return (this.container === b.container) && ((this.style === b.style) && ((this.doubleClickZoom === b.doubleClickZoom) && ((this.zoom === b.zoom) && (this.logoPosition === b.logoPosition))));
    };
  };
});
No real surprises here. It uses rtl.module to register the module. Looking around I found rtl.js in FPC trunk too (under utils/pas2js/dist). Obviously you'll need to use rtl.js with anything the converter generates.

The TMapOptions record converts to a JavaScript object, with a constructor that either creates a new blank object or initializes it from an object passed. There's also a function for comparing whether the object is equal to another object of the same type. Since TMap was declared external, no additional code was generated for it.

So how to use the interface unit? Here's what I came up with as the Pascal equivalent of Figure 1's JavaScript:

Figure 4. Pascal that uses interface unit.

program testmb1;
uses
  MapboxGL_Intf;

var
  options : TMapOptions;
  map : TMap;

begin
  options.container := 'map';
  options.style := 'mapbox://styles/mapbox/satellite-streets-v10';
//  options.center[0] := -107.74;  //longitude
//  options.center[1] := 37.92;  //latitude
  options.zoom := 9.0;
  options.logoPosition := 'bottom-right';

  map := TMap.New(options);
end.
Converted to JavaScript, it looks like this:

Figure 5. Pascal that uses interface unit converted to JavaScript.

rtl.module("program", ["System", "MapboxGL_Intf"], function () {
  var $mod = this;
  this.options = new pas.MapboxGL_Intf.TMapOptions();
  this.map = null;
  $mod.$main = function () {
    $mod.options.container = "map";
    $mod.options.style = "mapbox:\/\/styles\/mapbox\/satellite-streets-v10";
    $mod.options.zoom = 9.0;
    $mod.options.logoPosition = "bottom-right";
    $mod.map = new mapboxgl.Map(new pas.MapboxGL_Intf.TMapOptions($mod.options));
  };
});
Note the use of "pas". This is an object in rtl.js that allows you to reference your registered modules.

Finally, does the generated JavaScript still work?

First I added the new JavaScript files to BoxCast's index.html file (including a dummy system.js that I created):

Figure 6. Detail of BoxCast's modified index.html file.

<script src="rtl.js"></script>
<script src="system.js"></script>
<script src="mapboxgl_intf.js"></script>
<script src="testmb1.js"></script>
Here's the new calling code to replace Figure 1's "native" JavaScript:

Figure 7. New boxcast.js calling code.

rtl.run("program");
var map = pas.program.map;  //so remaining BoxCast code can be used as is
map.setCenter([-107.74, 37.92]);  //workaround since can't set yet in Pascal
With the modified index.html loaded into a browser, BoxCast still works as before.

Questions and comments

  1. The JavaScript object that the TMapOptions record converts to is probably not exactly what we want. With the JavaScript options object, only name-value pairs that you pass get set; unspecified options are assigned default values in the Map constructor. For example, Map's doubleClickZoom default is true, but since we did not assign it a value, its initial value of false (set in our TMapOptions constructor) is passed to the constructor.

  2. Some of the Map options can be more than one type. For example, container can be either an HTMLElement or a string, style can be either an Object or a string, and so on. This is very common in JavaScript. To support more than one type in Pascal, the TMapOptions record may need to declare these as Variants or something.

  3. How do the Pascal interface units get written? With dynamic libraries, these units can be generated by parsers. For example, I use Ryan's parser to create interface units for dynamic libraries and macOS frameworks that have C or Objective C header files. But no such tool exists for JavaScript. If you had a JavaScript parser, you might be able to create such a tool for generating interface units directly from the JavaScript source (for example, from map.js).

  4. What about documentation, example code, articles, tutorials, FAQs, demos, discussions, etc. for the JavaScript libraries that you'll need to use even if you write your JavaScript app in Pascal? Everything in that list will assume JavaScript, not Pascal, so you will still need to know some JavaScript in order to use the Pascal interface units.

  5. Is converting from Pascal easier than writing in JavaScript in the first place? Can you produce better code, or can you produce equivalent code faster? Even assuming that "someone else" creates interface units for the JavaScript libraries you'll use, the complete lack of support and documentation for using these libraries via Pascal is balanced largely by the argument of familiarity with Pascal.

Copyright 2017 by Phil Hess.

macpgmr (at) icloud (dot) com

First posted July 30, 2017.