Combining Prolog and Javascript into a Programming Language
Towards Higher Level Syntax for Programming Languages 2024-12-09
Below, is a snippet of working code from a project (several years ago), that combines Prolog exhaustive search (unification) abilities with Javascript string-interpolation abilities:
# layer bounding boxes
## parameters
ID
X
Y
Right
Bottom
## imports
shapes
onSameDiagram
inside
names
ports
contains
## query
diagram_fact(x,ID,X)
diagram_fact(y,ID,Y)
diagram_fact(width,ID,W)
diagram_fact(height,ID,H)
Right is X+W
Bottom is Y+H
## display
das_fact(bbL,${ID},${X}).
das_fact(bbT,${ID},${Y}).
das_fact(bbR,${ID},${Right}).
das_fact(bbB,${ID},${Bottom}).
Firstly, note the markdown-ish syntax (“#” and “##”). My text editor (emacs, in markdown mode) gave me code elision for free, with this syntax.
The Prolog-ish stuff consists of the “## parameters”, “## imports” and “## query” sections. The Javascript-ish code is in the “## display” section.
[Aside: the goal was to pattern-match against a factbase containing facts of the form “diagram_fact(...)” and to transform the matches into a higher level form of factbase with facts of the form “das_fact(...)”.]
The “## imports” section just names various bits of support Prolog (.pl) code that needed to be included in the executable.
The parameters - all Upper case - declare Prolog “logic variables” that would be assigned in Prolog then passed over to the Javascript code. Note that the same variables are used in, both, the “## query” and “## display” sections (Prolog code and Javascript code, respectively).
The “compiler” for this nano-DSL, created 3 files
Prolog code
Javascript code
A Bash pipeline that joined the two pieces of code together into an executable.
The information calculated by the Prolog side was converted into JSON and inhaled into the Javascript side using JSON.parse(...).
Generated Code
Prolog
?- consult("fb.pl").
?- consult("/Users/paultarvydas/projects/das/das2f/shapes.pl").
?- consult("/Users/paultarvydas/projects/das/das2f/onSameDiagram.pl").
?- consult("/Users/paultarvydas/projects/das/das2f/inside.pl").
?- consult("/Users/paultarvydas/projects/das/das2f/names.pl").
?- consult("/Users/paultarvydas/projects/das/das2f/ports.pl").
?- consult("/Users/paultarvydas/projects/das/das2f/contains.pl").
query_helper(ID,X,Y,Right,Bottom):-
diagram_fact(x,ID,X),
diagram_fact(y,ID,Y),
diagram_fact(width,ID,W),
diagram_fact(height,ID,H),
Right is X+W,
Bottom is Y+H,
true.
query:-
(setof([ID,X,Y,Right,Bottom],query_helper(ID,X,Y,Right,Bottom),Set),
json_write(user_output,Set,[width(128)])
)
;
json_write(user_output,[],[width(123)]).
Javascript
const fs = require ('fs');
var rawText = fs.readFileSync ('/dev/fd/0');
var parameters = JSON.parse(rawText);
parameters.forEach (p => {
var ID = p [0];
var X = p [1];
var Y = p [2];
var Right = p [3];
var Bottom = p [4];
if (true) { console.log (`das_fact(bbL,${ID},${X}).
das_fact(bbT,${ID},${Y}).
das_fact(bbR,${ID},${Right}).
das_fact(bbB,${ID},${Bottom}).`);};
});
Bash
temp=temp${RANDOM}
# layer bounding boxes
cat >${temp}.pl <<'~~~'
:- use_module(library(http/json)).
?- consult("fb.pl").
?- consult("/Users/paultarvydas/projects/das/das2f/shapes.pl").
?- consult("/Users/paultarvydas/projects/das/das2f/onSameDiagram.pl").
?- consult("/Users/paultarvydas/projects/das/das2f/inside.pl").
?- consult("/Users/paultarvydas/projects/das/das2f/names.pl").
?- consult("/Users/paultarvydas/projects/das/das2f/ports.pl").
?- consult("/Users/paultarvydas/projects/das/das2f/contains.pl").
query_helper(ID,X,Y,Right,Bottom):-
diagram_fact(x,ID,X),
diagram_fact(y,ID,Y),
diagram_fact(width,ID,W),
diagram_fact(height,ID,H),
Right is X+W,
Bottom is Y+H,
true.
query:-
(setof([ID,X,Y,Right,Bottom],query_helper(ID,X,Y,Right,Bottom),Set),
json_write(user_output,Set,[width(128)])
)
;
json_write(user_output,[],[width(123)]).
~~~
cat >${temp}.js <<'~~~'
const fs = require ('fs');
var rawText = fs.readFileSync ('/dev/fd/0');
var parameters = JSON.parse(rawText);
parameters.forEach (p => {
var ID = p [0];
var X = p [1];
var Y = p [2];
var Right = p [3];
var Bottom = p [4];
if (true) { console.log (`das_fact(bbL,${ID},${X}).
das_fact(bbT,${ID},${Y}).
das_fact(bbR,${ID},${Right}).
das_fact(bbB,${ID},${Bottom}).`);};
});
~~~
swipl -g "consult(${temp})." -g 'query.' -g 'halt.' | node ${temp}.js
rm -f ${temp}.pl
rm -f ${temp}.js
The Bash code simply includes the Prolog code and the Javascript code in “here documents” (delimited by “~~~”, in this case). This code is written, using the ‘cat’ command, to temporary files with the appropriate extensions, .pl and .js. The name of the files is created by using Bash’s ${RANDOM} operator and prefixing the names with “temp”.
At the bottom of the bash code (3rd line from the bottom), is a simple pipeline that runs SWIPL[1] (a free, modern Prolog) and pipes the results to node.js.
The last two lines of the bash script just do some cleanup, by deleting the temporary files.
Repository
The code for the above example can be found in the das/das2f repository[2].
Bibliography
[1] SWIPL from https://www.swi-prolog.org
[2] DAS - Diagrams As Syntax Code Repository from https://github.com/guitarvydas/das
See Also
References: https://guitarvydas.github.io/2024/01/06/References.html
Blog: https://www.guitarvydas.github.io
Videos: https://www.youtube.com/@programmingsimplicity2980
Discord: https://discord.gg/65YZUh6Jpq
Leanpub: [WIP] https://leanpub.com/u/paul-tarvydas
Gumroad: https://tarvydas.gumroad.com
Twitter: @paul_tarvydas