├── .gitignore ├── Makefile ├── README.md ├── benchmark ├── naiverev.js ├── naiverev.pl ├── naiverev_benchmark.js ├── naiverev_benchmark.pl ├── queens.js ├── queens.pl └── queens_benchmark.js ├── examples ├── append.js ├── append.pl └── append_test.js ├── runtime.js ├── src ├── compiler.pl ├── input.pl ├── output.pl └── transform.pl └── util.js /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | .buildpath 3 | .project 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: compile 2 | 3 | SRC = benchmark/naiverev.pl benchmark/queens.pl examples/append.pl 4 | JS = benchmark/naiverev.js benchmark/queens.js examples/append.js 5 | 6 | %.js: %.pl 7 | swipl -q -f src/compiler.pl -t transpile $< 8 | js-beautify -r $@ 9 | 10 | compile: $(JS) 11 | 12 | profile: benchmark/naiverev.js 13 | node --prof benchmark/naiverev_benchmark.js 14 | node-tick-processor 15 | 16 | clean: 17 | rm -f $(JS) 18 | rm -f v8.log 19 | 20 | check: 21 | jshint runtime.js 22 | 23 | .PHONY: clean check -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | prolog-target-js 2 | ================ 3 | 4 | Simple Prolog to JS transpiler. The project is at rather incomplete state at the moment. 5 | 6 | Example input: 7 | 8 | ```prolog 9 | append([], Ys, Ys). 10 | append([X|Xs], Ys, [X|Zs]):- append(Xs, Ys, Zs). 11 | ``` 12 | 13 | Example output: 14 | 15 | ```javascript 16 | function append_3_0($0, $1, $2, s, cb) { 17 | var $3 = new Var(); 18 | var _i = s.length; 19 | s.push(function() { 20 | return append_3_1($0, $1, $2, s, cb); 21 | }); 22 | if (!_u(s, $0, '[]')) return _b(s); 23 | if (!_u(s, $1, $3)) return _b(s); 24 | if (!_u(s, $2, $3)) return _b(s); 25 | return cb; 26 | } 27 | function append_3_1($0, $1, $2, s, cb) { 28 | var $3 = new Var(); 29 | var $4 = new Var(); 30 | var $5 = new Var(); 31 | var $6 = new Var(); 32 | var _i = s.length; 33 | if (!_u(s, $0, new Struct('.', $3, $4))) return _b(s); 34 | if (!_u(s, $1, $5)) return _b(s); 35 | if (!_u(s, $2, new Struct('.', $3, $6))) return _b(s); 36 | return append_3_0($4, $5, $6, s, cb); 37 | } 38 | exports.append_3 = append_3_0; 39 | ``` 40 | 41 | Calling the predicate 42 | --------------------- 43 | 44 | ```javascript 45 | var runtime = require('../runtime'); 46 | var util = require('../util'); 47 | var append = require('./append'); 48 | 49 | var list1 = util.array2List([1, 2, 3, 4, 5]); 50 | var list2 = util.array2List([6, 7, 8, 9, 10]); 51 | 52 | var result = new runtime.Var(); 53 | 54 | runtime.run(append.append_3(list1, list2, result, [], null)); 55 | 56 | console.log('Result is: ' + runtime.toString(result)); 57 | ``` 58 | 59 | -------------------------------------------------------------------------------- /benchmark/naiverev.js: -------------------------------------------------------------------------------- 1 | var runtime = require("../runtime"); 2 | var Var = runtime.Var; 3 | var Struct = runtime.Struct; 4 | var _u = runtime.unification; 5 | var _b = runtime.backtrack; 6 | var _is = runtime.is; 7 | var _inequal = runtime.inequal; 8 | var _less = runtime.less; 9 | var _cut = runtime.cut; 10 | 11 | function naiverev_2_0($0, $1, s, cb) { 12 | var _i = s.length; 13 | s.push(function() { 14 | return naiverev_2_1($0, $1, s, cb); 15 | }); 16 | if (!_u(s, $0, '[]')) return _b(s); 17 | if (!_u(s, $1, '[]')) return _b(s); 18 | return cb; 19 | } 20 | function naiverev_2_1($0, $1, s, cb) { 21 | var $2 = new Var(); 22 | var $3 = new Var(); 23 | var $4 = new Var(); 24 | var $5 = new Var(); 25 | var _i = s.length; 26 | if (!_u(s, $0, new Struct('.', $2, $3))) return _b(s); 27 | if (!_u(s, $1, $4)) return _b(s); 28 | return naiverev_2_0($3, $5, s, function() { 29 | return append_3_0($5, new Struct('.', $2, '[]'), $4, s, cb); 30 | }); 31 | } 32 | exports.naiverev_2 = naiverev_2_0; 33 | 34 | function append_3_0($0, $1, $2, s, cb) { 35 | var $3 = new Var(); 36 | var _i = s.length; 37 | s.push(function() { 38 | return append_3_1($0, $1, $2, s, cb); 39 | }); 40 | if (!_u(s, $0, '[]')) return _b(s); 41 | if (!_u(s, $1, $3)) return _b(s); 42 | if (!_u(s, $2, $3)) return _b(s); 43 | return cb; 44 | } 45 | function append_3_1($0, $1, $2, s, cb) { 46 | var $3 = new Var(); 47 | var $4 = new Var(); 48 | var $5 = new Var(); 49 | var $6 = new Var(); 50 | var _i = s.length; 51 | if (!_u(s, $0, new Struct('.', $3, $4))) return _b(s); 52 | if (!_u(s, $1, $5)) return _b(s); 53 | if (!_u(s, $2, new Struct('.', $3, $6))) return _b(s); 54 | return append_3_0($4, $5, $6, s, cb); 55 | } 56 | exports.append_3 = append_3_0; 57 | -------------------------------------------------------------------------------- /benchmark/naiverev.pl: -------------------------------------------------------------------------------- 1 | append([], Ys, Ys). 2 | append([X|Xs], Ys, [X|Zs]):- 3 | append(Xs, Ys, Zs). 4 | 5 | naiverev([], []). 6 | naiverev([H|T], R) :- 7 | naiverev(T, RevT), 8 | append(RevT, [H], R). -------------------------------------------------------------------------------- /benchmark/naiverev_benchmark.js: -------------------------------------------------------------------------------- 1 | var util = require('../util'); 2 | var runtime = require('../runtime'); 3 | var naiverev = require('./naiverev'); 4 | var Var = runtime.Var; 5 | 6 | var array = []; 7 | for (var i = 0; i < 1000; i++) { 8 | array.push(i); 9 | } 10 | 11 | var list = util.array2List(array); 12 | var reverse = new Var(); 13 | 14 | var top = naiverev.naiverev_2(list, reverse, [], null); 15 | 16 | console.time('reverse'); 17 | runtime.run(top); 18 | console.timeEnd('reverse'); 19 | 20 | //console.log('Reversed is: ' + runtime.toString(reverse)); 21 | -------------------------------------------------------------------------------- /benchmark/naiverev_benchmark.pl: -------------------------------------------------------------------------------- 1 | :- include('naiverev.pl'). 2 | 3 | run:- 4 | findall(E, between(1, 1000, E), Es), 5 | profile(naiverev(Es, _)). -------------------------------------------------------------------------------- /benchmark/queens.js: -------------------------------------------------------------------------------- 1 | var runtime = require("../runtime"); 2 | var Var = runtime.Var; 3 | var Struct = runtime.Struct; 4 | var _u = runtime.unification; 5 | var _b = runtime.backtrack; 6 | var _is = runtime.is; 7 | var _inequal = runtime.inequal; 8 | var _less = runtime.less; 9 | var _cut = runtime.cut; 10 | var _s = runtime.toString; 11 | 12 | function range_3_0($0, $1, $2, s, cb) { 13 | var $3 = new Var(); 14 | var _i = s.length; 15 | s.push(function() { 16 | return range_3_1($0, $1, $2, s, cb); 17 | }); 18 | if (!_u(s, $0, $3)) return _b(s); 19 | if (!_u(s, $1, $3)) return _b(s); 20 | if (!_u(s, $2, new Struct('.', $3, '[]'))) return _b(s); 21 | _cut(s, _i); 22 | return cb; 23 | } 24 | function range_3_1($0, $1, $2, s, cb) { 25 | var $3 = new Var(); 26 | var $4 = new Var(); 27 | var $5 = new Var(); 28 | var $6 = new Var(); 29 | var _i = s.length; 30 | if (!_u(s, $0, $3)) return _b(s); 31 | if (!_u(s, $1, $4)) return _b(s); 32 | if (!_u(s, $2, new Struct('.', $3, $5))) return _b(s); 33 | return _less($3, $4, s, function() { 34 | return _is($6, new Struct('+', $3, 1), s, function() { 35 | return range_3_0($6, $4, $5, s, cb); 36 | }); 37 | }); 38 | } 39 | exports.range_3 = range_3_0; 40 | 41 | function select_3_0($0, $1, $2, s, cb) { 42 | var $3 = new Var(); 43 | var $4 = new Var(); 44 | var _i = s.length; 45 | s.push(function() { 46 | return select_3_1($0, $1, $2, s, cb); 47 | }); 48 | if (!_u(s, $0, new Struct('.', $3, $4))) return _b(s); 49 | if (!_u(s, $1, $4)) return _b(s); 50 | if (!_u(s, $2, $3)) return _b(s); 51 | return cb; 52 | } 53 | function select_3_1($0, $1, $2, s, cb) { 54 | var $3 = new Var(); 55 | var $4 = new Var(); 56 | var $5 = new Var(); 57 | var $6 = new Var(); 58 | var _i = s.length; 59 | if (!_u(s, $0, new Struct('.', $3, $4))) return _b(s); 60 | if (!_u(s, $1, new Struct('.', $3, $5))) return _b(s); 61 | if (!_u(s, $2, $6)) return _b(s); 62 | return select_3_0($4, $5, $6, s, cb); 63 | } 64 | exports.select_3 = select_3_0; 65 | 66 | function not_attack_3_0($0, $1, $2, s, cb) { 67 | var $3 = new Var(); 68 | var $4 = new Var(); 69 | var _i = s.length; 70 | s.push(function() { 71 | return not_attack_3_1($0, $1, $2, s, cb); 72 | }); 73 | if (!_u(s, $0, '[]')) return _b(s); 74 | if (!_u(s, $1, $3)) return _b(s); 75 | if (!_u(s, $2, $4)) return _b(s); 76 | _cut(s, _i); 77 | return cb; 78 | } 79 | function not_attack_3_1($0, $1, $2, s, cb) { 80 | var $3 = new Var(); 81 | var $4 = new Var(); 82 | var $5 = new Var(); 83 | var $6 = new Var(); 84 | var $7 = new Var(); 85 | var _i = s.length; 86 | if (!_u(s, $0, new Struct('.', $3, $4))) return _b(s); 87 | if (!_u(s, $1, $5)) return _b(s); 88 | if (!_u(s, $2, $6)) return _b(s); 89 | return _inequal($5, new Struct('+', $3, $6), s, function() { 90 | return _inequal($5, new Struct('-', $3, $6), s, function() { 91 | return _is($7, new Struct('+', $6, 1), s, function() { 92 | return not_attack_3_0($4, $5, $7, s, cb); 93 | }); 94 | }); 95 | }); 96 | } 97 | exports.not_attack_3 = not_attack_3_0; 98 | 99 | function not_attack_2_0($0, $1, s, cb) { 100 | var $2 = new Var(); 101 | var $3 = new Var(); 102 | var _i = s.length; 103 | if (!_u(s, $0, $2)) return _b(s); 104 | if (!_u(s, $1, $3)) return _b(s); 105 | return not_attack_3_0($2, $3, 1, s, cb); 106 | } 107 | exports.not_attack_2 = not_attack_2_0; 108 | 109 | function queens_3_0($0, $1, $2, s, cb) { 110 | var $3 = new Var(); 111 | var _i = s.length; 112 | s.push(function() { 113 | return queens_3_1($0, $1, $2, s, cb); 114 | }); 115 | if (!_u(s, $0, '[]')) return _b(s); 116 | if (!_u(s, $1, $3)) return _b(s); 117 | if (!_u(s, $2, $3)) return _b(s); 118 | return cb; 119 | } 120 | function queens_3_1($0, $1, $2, s, cb) { 121 | var $3 = new Var(); 122 | var $4 = new Var(); 123 | var $5 = new Var(); 124 | var $6 = new Var(); 125 | var $7 = new Var(); 126 | var _i = s.length; 127 | if (!_u(s, $0, $3)) return _b(s); 128 | if (!_u(s, $1, $4)) return _b(s); 129 | if (!_u(s, $2, $5)) return _b(s); 130 | return select_3_0($3, $6, $7, s, function() { 131 | return not_attack_2_0($4, $7, s, function() { 132 | return queens_3_0($6, new Struct('.', $7, $4), $5, s, cb); 133 | }); 134 | }); 135 | } 136 | exports.queens_3 = queens_3_0; 137 | 138 | function queens_2_0($0, $1, s, cb) { 139 | var $2 = new Var(); 140 | var $3 = new Var(); 141 | var $4 = new Var(); 142 | var _i = s.length; 143 | if (!_u(s, $0, $2)) return _b(s); 144 | if (!_u(s, $1, $3)) return _b(s); 145 | return range_3_0(1, $2, $4, s, function() { 146 | return queens_3_0($4, '[]', $3, s, cb); 147 | }); 148 | } 149 | exports.queens_2 = queens_2_0; 150 | -------------------------------------------------------------------------------- /benchmark/queens.pl: -------------------------------------------------------------------------------- 1 | queens(N,Qs) :- 2 | range(1,N,Ns), 3 | queens(Ns,[],Qs). 4 | 5 | queens([],Qs,Qs). 6 | queens(UnplacedQs,SafeQs,Qs) :- 7 | select(UnplacedQs,UnplacedQs1,Q), 8 | not_attack(SafeQs,Q), 9 | queens(UnplacedQs1,[Q|SafeQs],Qs). 10 | 11 | not_attack(Xs,X) :- 12 | not_attack(Xs,X,1). 13 | 14 | not_attack([],_,_) :- !. 15 | not_attack([Y|Ys],X,N) :- 16 | X =\= Y+N, X =\= Y-N, 17 | N1 is N+1, 18 | not_attack(Ys,X,N1). 19 | 20 | select([X|Xs],Xs,X). 21 | select([Y|Ys],[Y|Zs],X) :- select(Ys,Zs,X). 22 | 23 | range(N,N,[N]) :- !. 24 | range(M,N,[M|Ns]) :- 25 | M < N, 26 | M1 is M+1, 27 | range(M1,N,Ns). -------------------------------------------------------------------------------- /benchmark/queens_benchmark.js: -------------------------------------------------------------------------------- 1 | var util = require('../util'); 2 | var runtime = require('../runtime'); 3 | var queens = require('./queens'); 4 | var Var = runtime.Var; 5 | 6 | var stack = []; 7 | var placement = new Var(); 8 | var top = queens.queens_2(11, placement, stack, null); 9 | 10 | console.time('queens'); 11 | runtime.run(top); 12 | 13 | //console.log('Placement is: ' + runtime.toString(placement)); 14 | 15 | while (runtime.retry(stack)) { 16 | //console.log('Another solution: ' + runtime.toString(placement)); 17 | } 18 | 19 | console.timeEnd('queens'); -------------------------------------------------------------------------------- /examples/append.js: -------------------------------------------------------------------------------- 1 | var runtime = require("../runtime"); 2 | var Var = runtime.Var; 3 | var Struct = runtime.Struct; 4 | var _u = runtime.unification; 5 | var _b = runtime.backtrack; 6 | var _is = runtime.is; 7 | var _inequal = runtime.inequal; 8 | var _less = runtime.less; 9 | var _cut = runtime.cut; 10 | 11 | function append_3_0($0, $1, $2, s, cb) { 12 | var $3 = new Var(); 13 | var _i = s.length; 14 | s.push(function() { 15 | return append_3_1($0, $1, $2, s, cb); 16 | }); 17 | if (!_u(s, $0, '[]')) return _b(s); 18 | if (!_u(s, $1, $3)) return _b(s); 19 | if (!_u(s, $2, $3)) return _b(s); 20 | return cb; 21 | } 22 | function append_3_1($0, $1, $2, s, cb) { 23 | var $3 = new Var(); 24 | var $4 = new Var(); 25 | var $5 = new Var(); 26 | var $6 = new Var(); 27 | var _i = s.length; 28 | if (!_u(s, $0, new Struct('.', $3, $4))) return _b(s); 29 | if (!_u(s, $1, $5)) return _b(s); 30 | if (!_u(s, $2, new Struct('.', $3, $6))) return _b(s); 31 | return append_3_0($4, $5, $6, s, cb); 32 | } 33 | exports.append_3 = append_3_0; 34 | -------------------------------------------------------------------------------- /examples/append.pl: -------------------------------------------------------------------------------- 1 | append([], Ys, Ys). 2 | append([X|Xs], Ys, [X|Zs]):- append(Xs, Ys, Zs). 3 | -------------------------------------------------------------------------------- /examples/append_test.js: -------------------------------------------------------------------------------- 1 | var runtime = require('../runtime'); 2 | var util = require('../util'); 3 | var append = require('./append'); 4 | 5 | var list1 = util.array2List([1, 2, 3, 4, 5]); 6 | var list2 = util.array2List([6, 7, 8, 9, 10]); 7 | 8 | var result = new runtime.Var(); 9 | 10 | runtime.run(append.append_3(list1, list2, result, [], null)); 11 | 12 | console.log('Result is: ' + runtime.toString(result)); 13 | -------------------------------------------------------------------------------- /runtime.js: -------------------------------------------------------------------------------- 1 | // Constructor for variables. 2 | 3 | function Var() { 4 | this.ref = this; 5 | } 6 | 7 | // Constructor for compound terms. 8 | 9 | function Struct() { 10 | this.arguments = arguments; 11 | } 12 | 13 | Struct.prototype.toString = function() { 14 | var args = Array.prototype.slice.call(this.arguments, 1); 15 | return this.arguments[0] + '(' + args.map(toString).join(', ') + ')'; 16 | }; 17 | 18 | exports.Var = Var; 19 | exports.Struct = Struct; 20 | 21 | // Dereferences given term. 22 | 23 | function deref(term) { 24 | while (term instanceof Var) { 25 | if (term === term.ref) { 26 | return term; 27 | } else { 28 | term = term.ref; 29 | } 30 | } 31 | return term; 32 | } 33 | 34 | // Runs the goal. 35 | 36 | function run(cb) { 37 | try { 38 | while (cb = cb()) {}; 39 | return true; 40 | } catch (e) { 41 | return false; 42 | } 43 | } 44 | 45 | exports.run = run; 46 | 47 | // Retries the goal. 48 | 49 | exports.retry = function(s) { 50 | var cb = backtrack(s); 51 | if (cb) { 52 | return run(cb); 53 | } else { 54 | return false; 55 | } 56 | }; 57 | 58 | // Runs backtracking. 59 | // Undoes variables, returns next goal. 60 | // Ignores cut choices. 61 | 62 | function backtrack(s) { 63 | var top; 64 | while (top = s.pop()) { 65 | if (typeof top === 'function') { 66 | if (top.cut) { 67 | continue; 68 | } else { 69 | return top; 70 | } 71 | } else { 72 | top.ref = top; 73 | } 74 | } 75 | throw new Error('No more choices'); 76 | } 77 | 78 | exports.backtrack = backtrack; 79 | 80 | // Marks all choices cut down 81 | // to the given index in the stack. 82 | 83 | function cut(s, index) { 84 | var current = s.length; 85 | while (current > index) { 86 | var e = s[current]; 87 | if (typeof e === 'function') { 88 | e.cut = true; 89 | } 90 | current--; 91 | } 92 | } 93 | 94 | exports.cut = cut; 95 | 96 | function toString(term) { 97 | var term = deref(term); 98 | if (term instanceof Var) { 99 | return '_'; 100 | } else { 101 | return term.toString(); 102 | } 103 | } 104 | 105 | exports.toString = toString; 106 | 107 | // Low-level unification. Does not undo bindings 108 | // itself. 109 | 110 | function unification(stack, a, b) { 111 | var ad = deref(a); 112 | var bd = deref(b); 113 | 114 | if (ad instanceof Var) { 115 | ad.ref = bd; 116 | stack.push(ad); 117 | } else if (bd instanceof Var) { 118 | bd.ref = ad; 119 | stack.push(bd); 120 | } else if (ad instanceof Struct) { 121 | if (bd instanceof Struct) { 122 | var aas = ad.arguments; 123 | var bas = bd.arguments; 124 | if (aas[0] === bas[0] && aas.length === bas.length) { 125 | for (var i = aas.length - 1; i >= 1; i--) { 126 | if (!unification(stack, aas[i], bas[i])) { 127 | return false; 128 | } 129 | } 130 | } else { 131 | return false; 132 | } 133 | } else { 134 | return false; 135 | } 136 | } else { 137 | return ad === bd; 138 | } 139 | 140 | return true; 141 | } 142 | 143 | // Helper to evaluate arithmetical expressions. 144 | 145 | function calc(exp) { 146 | var exp = deref(exp); 147 | 148 | if (typeof exp === 'number') { 149 | return exp; 150 | } else if (exp instanceof Struct) { 151 | var op = exp.arguments[0]; 152 | var left = calc(exp.arguments[1]); 153 | var right = calc(exp.arguments[2]); 154 | if (op === '+') { 155 | return left + right; 156 | } else if (op === '-') { 157 | return left - right; 158 | } else if (op === '*') { 159 | return left * right; 160 | } else if (op === '/') { 161 | return left / right; 162 | } else { 163 | throw new Error('Unknown artithmetical operator ' + op); 164 | } 165 | } else { 166 | throw new Error('Invalid arithmetical expression ' + exp); 167 | } 168 | } 169 | 170 | // Implements is/2 predicate. 171 | 172 | function is(v, expression, s, cb) { 173 | var v = deref(v); 174 | 175 | if (!(v instanceof Var)) { 176 | throw new Error('Left side of is/2 is not a variable'); 177 | } 178 | 179 | v.ref = calc(expression); 180 | s.push(v); 181 | 182 | return cb; 183 | } 184 | 185 | // Implements =\=/2 predicate. 186 | 187 | function inequal(left, right, s, cb) { 188 | if (calc(left) !== calc(right)) { 189 | return cb; 190 | } else { 191 | return backtrack(s); 192 | } 193 | } 194 | 195 | // Implements Choice = yes ; Choice = no), !, 47 | write_clause(Fd, Clause, Nth, Choice), 48 | NthNext is Nth + 1, 49 | write_clauses(Fd, Clauses, NthNext, NumChoices). 50 | 51 | %% write_clause(Fd, Head-Calls, Nth, Choice) is det. 52 | % 53 | % Transpiles single clause. Choice is 'yes' or 'no' 54 | % and sets whether the clause will push choice point or not. 55 | 56 | write_clause(Fd, Head-Calls, Nth, Choice):- 57 | functor(Head, Name, Arity), 58 | clause_variables(Head-Calls, ArgNames, LocalNames), 59 | atomic_list_concat(ArgNames, ', ', ArgString), 60 | atomic_list_concat([Name, Arity, Nth], '_', Fun), 61 | format(Fd, 'function ~w(~w, s, cb) {', [Fun, ArgString]), 62 | write_locals(Fd, LocalNames), 63 | format(Fd, 'var _i = s.length;', []), 64 | NthNext is Nth + 1, 65 | push_choice(Fd, Choice, Name, Arity, ArgString, NthNext), 66 | write_calls(Fd, Calls), 67 | format(Fd, '}', []). 68 | 69 | %% write_calls(+Fd, +Calls) is det. 70 | % 71 | % Writes predicate calls into JavaScript 72 | % constructs. 73 | 74 | write_calls(Fd, []):- 75 | format(Fd, 'return cb;', []). 76 | 77 | write_calls(Fd, [Call|Calls]):- 78 | Call =.. [Name|Args], 79 | length(Args, Arity), 80 | maplist(termname, Args, ArgTerms), 81 | atomic_list_concat(ArgTerms, ', ', ArgTermString), 82 | write_call(Fd, Name, Arity, ArgTermString, Calls). 83 | 84 | write_call(Fd, '=', 2, ArgTermString, Calls):- !, 85 | format(Fd, 'if (!_u(s, ~w)) return _b(s);', [ArgTermString]), 86 | write_calls(Fd, Calls). 87 | 88 | write_call(Fd, 'is', 2, ArgTermString, Calls):- !, 89 | format(Fd, 'return _is(~w, s, function() {', [ArgTermString]), 90 | write_calls(Fd, Calls), 91 | format(Fd, '});', []). 92 | 93 | write_call(Fd, '=\\=', 2, ArgTermString, Calls):- !, 94 | format(Fd, 'return _inequal(~w, s, function() {', [ArgTermString]), 95 | write_calls(Fd, Calls), 96 | format(Fd, '});', []). 97 | 98 | write_call(Fd, '<', 2, ArgTermString, Calls):- !, 99 | format(Fd, 'return _less(~w, s, function() {', [ArgTermString]), 100 | write_calls(Fd, Calls), 101 | format(Fd, '});', []). 102 | 103 | write_call(Fd, '!', 0, _, Calls):- !, 104 | format(Fd, '_cut(s, _i);', []), 105 | write_calls(Fd, Calls). 106 | 107 | write_call(Fd, 'writeln', 1, ArgTermString, Calls):- !, 108 | format(Fd, 'console.log(_s(~w));', [ArgTermString]), 109 | write_calls(Fd, Calls). 110 | 111 | write_call(Fd, Name, Arity, ArgTermString, []):- !, 112 | atomic_list_concat([Name, Arity], '_', Fun), 113 | format(Fd, 'return ~w_0(~w, s, cb);', [Fun, ArgTermString]). 114 | 115 | write_call(Fd, Name, Arity, ArgTermString, Calls):- 116 | atomic_list_concat([Name, Arity], '_', Fun), 117 | format(Fd, 'return ~w_0(~w, s, function() {', [Fun, ArgTermString]), 118 | write_calls(Fd, Calls), 119 | format(Fd, '});', []). 120 | 121 | %% push_choice(+Fd, +Choice, +Name, +Arity, +ArgString, +NthNext) is det. 122 | % 123 | % Pushes choicepoint into the stack when needed (the clause 124 | % is not the last one for the predicate). The choice is 125 | % just a function that returns the next clause. 126 | 127 | push_choice(_, no, _, _, _, _). 128 | 129 | push_choice(Fd, yes, Name, Arity, ArgString, NthNext):- 130 | Params = [Name, Arity, NthNext, ArgString], 131 | format(Fd, 's.push(function() { return ~w_~w_~w(~w, s, cb); });', Params). 132 | 133 | %% write_locals(+Fd, +LocalNames) is det. 134 | % 135 | % Writes statement for local variable initialization. 136 | 137 | write_locals(_, []):- !. 138 | 139 | write_locals(Fd, LocalNames):- 140 | maplist(init_local, LocalNames, LocalInits), 141 | atomic_list_concat(LocalInits, '; ', LocalString), 142 | format(Fd, '~w;', [LocalString]). 143 | 144 | %% init_local(+LocalName, -Init) is det. 145 | % 146 | % Turns local variable into initialization 147 | % expression. 148 | 149 | init_local(LocalName, Init):- 150 | format(atom(Init), 'var ~w = new Var()', [LocalName]). 151 | 152 | %% clause_variables(+Head-Calls, -ArgNames, -LocalNames) is det. 153 | % 154 | % Extracts variables from the clause and turn them 155 | % into JavaScript variables. The naming scheme must 156 | % match predargs/2. 157 | 158 | clause_variables(Head-Calls, ArgNames, LocalNames):- 159 | term_variables(Head-Calls, Vars), 160 | numbervars(Head-Calls, 0, _), 161 | clause_arguments(Head, ArgNames), 162 | maplist(varname, Vars, AllNames), 163 | findall(LocalName, ( 164 | member(LocalName, AllNames), 165 | \+ member(LocalName, ArgNames) 166 | ), LocalNames). 167 | 168 | %% clause_arguments(+Head, -ArgNames) is det. 169 | % 170 | % Turns clause arguments into JavaScript variables. 171 | 172 | clause_arguments(Head, ArgNames):- 173 | Head =.. [_|Args], 174 | maplist(varname, Args, ArgNames). 175 | 176 | %% termname(+Term, -Atom) is det. 177 | % 178 | % Turns Prolog term into JavaScript object. 179 | 180 | termname(Term, Atom):- 181 | var_number(Term, _), !, 182 | varname(Term, Atom). 183 | 184 | termname(Term, Term):- 185 | number(Term), !. 186 | 187 | termname([], '\'[]\''):- !. 188 | 189 | termname(Term, Atom):- 190 | atom(Term), !, 191 | format(atom(Atom), '~q', [Term]). 192 | 193 | termname(Term, Atom):- 194 | Term =.. [Name|Args], 195 | maplist(termname, Args, ArgTerms), 196 | atomic_list_concat(ArgTerms, ', ', ArgTermString), 197 | format(atom(Atom), 'new Struct(\'~w\', ~w)', [Name, ArgTermString]). 198 | 199 | %% Gives variable name for JavaScript. 200 | % 201 | % Assumes that numbervars/3 has been called 202 | % on the term containing the variable. 203 | 204 | varname(Var):- 205 | nonvar(Var), !, 206 | throw(error(varname_for_nonvar(Var))). 207 | 208 | varname(Var, Name):- 209 | var_number(Var, Num), !, 210 | atomic_concat('$', Num, Name). -------------------------------------------------------------------------------- /src/transform.pl: -------------------------------------------------------------------------------- 1 | :- module(transform, [ 2 | exargs/3, 3 | pred_calls/2 4 | ]). 5 | 6 | %% exargs(+Head, -NewHead, -Calls) is det. 7 | % 8 | % Extracts non-variable arguments from the 9 | % predicate head and replaces them with unification calls. 10 | % Throws error when the head cannot be handled. 11 | % Example call: exargs(pred([X|Xs]), pred(V1), [V1=[X|Xs]]). 12 | 13 | exargs(Head, Head, []):- 14 | atom(Head), !. 15 | 16 | exargs(HeadIn, HeadOut, Calls):- 17 | compound(HeadIn), !, 18 | HeadIn =.. [Name|ArgsIn], 19 | newargs(ArgsIn, ArgsOut, Calls), 20 | HeadOut =.. [Name|ArgsOut]. 21 | 22 | exargs(Head, _, _):- 23 | throw(error(invalid_head(Head))). 24 | 25 | %% newargs(+Terms, -Vars, -Calls) is det. 26 | % 27 | % Takes list of terms and turns them into 28 | % unifications Var=Term where Var is a fresh variable. 29 | 30 | newargs([], [], []). 31 | 32 | newargs([Term|Terms], [Var|Vars], CallsOut):- 33 | CallsOut = [Unification|CallsIn], 34 | Unification = '='(Var, Term), 35 | newargs(Terms, Vars, CallsIn). 36 | 37 | %% body_calls(+Body, +CallsIn, -CallsOut) is det. 38 | % 39 | % Turns predicate body into a list of predicate calls. 40 | % Throws error when Body contains non-supported predicate call. 41 | 42 | body_calls(Body, CallsIn, CallsOut):- 43 | Body = ','(Left, Right), !, 44 | body_calls(Left, CallsIn, CallsLeft), 45 | body_calls(Right, CallsLeft, CallsOut). 46 | 47 | body_calls(Body, CallsIn, CallsOut):- 48 | atom(Body), !, 49 | append(CallsIn, [Body], CallsOut). 50 | 51 | body_calls(Body, _, _):- 52 | functor(Body, Name, Arity), 53 | not_supported_call(Name/Arity), 54 | throw(error(not_supported_call(Name/Arity))). 55 | 56 | body_calls(Body, CallsIn, CallsOut):- 57 | append(CallsIn, [Body], CallsOut). 58 | 59 | %% clause_calls(+Clause, -Head-Calls) is det. 60 | % 61 | % Turns predicate clause (rule) into a list 62 | % of calls. 63 | 64 | clause_calls(HeadIn :- Body, HeadOut-CallsOut):- !, 65 | exargs(HeadIn, HeadOut, CallsHead), 66 | body_calls(Body, CallsHead, CallsOut). 67 | 68 | clause_calls(HeadIn, HeadOut-Calls):- 69 | exargs(HeadIn, HeadOut, Calls). 70 | 71 | %% pred_calls(Functor-Clauses, Functor-ClauseCalls) is det. 72 | % 73 | % Applies clause_calls/2 for each clause of the predicate. 74 | 75 | pred_calls(Functor-Clauses, Functor-ClauseCalls):- 76 | maplist(clause_calls, Clauses, ClauseCalls). 77 | 78 | %% not_supported_call(+Functor) is det. 79 | % 80 | % Some calls are currently not supported. 81 | % These should be listed here. 82 | 83 | not_supported_call(Functor):- 84 | memberchk(Functor, [ 85 | '!'/0, 86 | ';'/2, 87 | '->'/2, 88 | '\='/2, 89 | 'call'/_, 90 | 'not'/1, 91 | '\+'/1 92 | ]). -------------------------------------------------------------------------------- /util.js: -------------------------------------------------------------------------------- 1 | var runtime = require('./runtime'); 2 | var Struct = runtime.Struct; 3 | 4 | exports.array2List = function(array) { 5 | var tail = '[]'; 6 | for (var i = array.length - 1; i >= 0; i--) { 7 | tail = new Struct('.', array[i], tail); 8 | } 9 | return tail; 10 | } --------------------------------------------------------------------------------