Expand/Shrink

mappings

The following table lists examples of known mappings between Phix and JavaScript.

Mainly for reference, and partly to help clear up any confusion over what it should be doing.
Again, this is of no particular help to anyone actually using pwa/p2js.
Everything here should be either pretty obvious, or previously explained.

Phix JavaScript
sequence s = {1,2,3}
let /*sequence*/ s = ["sequence",1,2,3];
integer i [= ...]
atom a [= ...]
string s [= ...]
object o [= ...]
let /*integer*/ i [= ...];
let /*atom*/ a [= ...];
let /*string*/ s [= ...];
let /*object*/ o [= ...];
{a,b,c} = d
[,a,b,c] = d;  // (leading comma quite deliberate)
{} = f()
/*[] =*/ f();  // (special case, MDN sometimes lies...)
integer {i,j,k} = ...
let /*integer*/ [,i,j,k] = ...;
{string s, integer i} = ...
let [,/*string*/ s, /*integer*/ i] = ...;
=, :=, ==
=, ===, equal() // (depending on context, equal() is not reverted)
                // (in JavaScript, [1]===[1] yields false...)
                // (uses === when either side is atom or string, see note)
#7F -- (ditto 0b01111111)
0x7F; // (non-reversed, especially 0b)
00 -- (etc)
0 // (else JavaScript says octal literals not allowed)
'A'
0X41
"\#41"
0X41   // (JavaScript has no char type)
0x41   // (0XHH reversed, 0xHH not)
"\x41" // (JavaScript knows not \#, not reversed)
a & b
$conCat(a,b)  // (defined in p2js.js, documented here)
a = b & c
res = res & start
res &= start
a = $conCat(b, c[, true]);        // ie clone rqd
res = $conCat(res, start, false); // ie no clone rqd, since "res"==="res"
res = $conCat(res, start, false); // (reversed to above form[?])
s[i]
s[i..j]
s[g][h][i]
s[g][h][i..j]
$subse(s,i)
$subss(s,i,j)
$subse(s,i,["sequence",h,g])
$subss(s,i,j,["sequence",h,g])

(I am not overly adamant about "sequence", or innermost first)
s[i] = x
s[i..j] = x
s[g][h][i] = x
s[g][h][i..j] = x
s = $repe(s,i,x);
s = $repss(s,i,j,x);
s = $repe(s,i,x,["sequence",h,g]);
s = $repss(s,i,j,x,["sequence",h,g]);

(nb relies somewhat on JavaScript pass-by-sharing semantics...)
(inevitably += etc can get a tad messy/may not yet be fully supported)
s[i] op= x
s[] op= x
s[i+1][j+1][k+1] op= x
s = $repe(s,i,$subse(s,i) op x);
{ let i$dx = ; s = $repe(s,i$dx,$subse(s,i$dx) op x); }
{ let i$dx = k+1, i2$dx = j+1, i3$dx = i+1; 
  s = $repe(s,i$dx,$subse(s,i$dx,["sequence",i2$dx,i3$dx]) op x,["sequence",i2$dx,i3$dx]); }

An additional mini-scope is needed when any index is neither plain var nor literal integer, which is then used for all (rather than just those that actually need it). Note that (eg) i$dx is calculated once only, then used twice, which is why it must exist.
iff(a < b ? a : b)
(a < b) ? a : b
if a = b then
elsif c or d then
elsif e and not f then
elsif g xor h then
else
end if
if (a === b) {
} else if (c || d) {
} else if (e && !f) {
} else if (xor(g,h)) { // (xor() is defined in p2js.js)
} else {
}
for i=1 to 5 do end for
for i=1 to N do end for
for i=N to 1 by -1 do end for
for j=1 to N by p do end for
for k=N to 1 by -p do end for
for (let i=1; i<=5; i+=1) { }
for (let i=1, i$lim=N; i<=i$lim; i+=1) { }
for (let i=N; i>=1; i-=1) { }
for (let j=1, j$lim=N; j<=j$lim; j+=p) { } // (nb assumes p>0)
for (let k=N; k>=1; k-=p) { }              // (   "", ie -p<0)


The i$lim is needed because otherwise JavaScript would re-evaluate the expression on every iteration, whereas Phix does not. We can avoid introducing the i$lim variable when it is a fixed literal integer, but avoiding the same for 'N' would require careful analysis not just of the loop body but also any routines it might invoke, along with some messy scope considerations, and besides, anything like say length(s) is faster with the $lim approach. We can be certain that i$lim is a valid non-conflicting identifier as long as i was (and it checks that). The $lim versions are not (yet) reversible.

Be advised that when faced with "for i=init to lim by step", where init/lim and in particular step are dynamically defined, desktop/phix emits generic code that copes: pwa/p2js does not and never will (cope), except perhaps to get half or maybe only a quarter of them right by sheer luck. When step may or may not be negative, you will have to replace the construct with a more explicit while loop, or duplicated inside an if -ve/else. When step is omitted, both desktop and p2js equally assume +1 and <=.
for i,e in s do end for
for i,e in <expr> do end for
for e in <expr> do end for
for e in s do end for
for e in s [from <f>] [to <t>] do end for
for (let i=1, e$lim=length(s); i<=e$lim; i+=1) { let e = $subse(s,i); } [1]
  as [1] with e$seq=<expr> and then s -> e$seq (twice) [2]
  as [2] with i -> e$idx
  as [1] with i -> e$idx
  as above with (i or e$idx)=<f> and/or e$lim=<t>


We consistently use e$lim since that one must always exist (without any possible name clash), and/or use a literal constant when we can. I trust it is pretty obvious that by "s" I mean "a single variable name", and that e$seq is just a name to use for lack of anything else, which in no way attempts to achieve any kind of Phix-style copy-on-write semantics, except (perhaps) by virtue of being a hidden name that the programmer simply should not be able to accidentally typo-clobber.
integer i
for i ... end for
{let [e$seq=<expr>][, ][e$lim=length(s)]; 
  for (i=1; i<=e$lim; i+=1;) { [[let ]e=$subse(e[$seq],i);] ... }}


When the control variable is predefined, there is no let in the for itself, and should we need either e$seq or e$lim then we need a mini-wrapper-scope. Should e be predefined, it is simply a matter of omitting the inner "let", without causing such a container scope to be needed.

Be warned that desktop/Phix and pwa/p2js may have subtly different notions of [nested] scope: such "predefined" behaviour is only formally supported for declarations in the immediately surrounding scope. Nothing to do with JavaScript, that one’s fairly and squarely on me. It’s just that the older pmain.e of desktop\Phix is very conservative in calling increaseScope() and dropScope(), and prefers to handle the for loop variable(s) "by hand" in DoFor(), whereas the newer pwa\src\p2js_parse.e is generally much more eager to invoke add_scope() and drop_scope(). New/re-use may therefore differ.
Obviously if you hit an issue, I’ll happily investigate more, but I’m not about to make changes for changes sake when that perceived problem may simply never occur.
global procedure p(atom a)
end procedure
/*global*/ function p(/*atom*/ a) {
}

(reversal not yet - will depend on T_return scan/analysis)
(or maybe [/*global*/] /*procedure*/ function vs. /*function*/ function...)
function action(gdx /*h*/, integer i)
    return XPG_DEFAULT
end function
function action(/*gdx*/ /*h*/anon1, /*integer*/ i) {
    return XPG_DEFAULT;
}
type t(ptype pt) .. end type
function t(/*ptype*/ pt) { .. }

You can explicitly invoke the type as a function, but obviously you cannot add a type to the typeless language that is JavaScript, and should not expect any run-time typechecks.
?9/0
?e
crash("9/0"); // (non-reversed)
print(1,e);   //       ""
timedelta(hours:=4,seconds:=6)
timedelta(0,0,4,0,6) // (non-reversed)

(Named parameters are mapped to positional parameters, with defaults inserted)
constant a = {b:=2, "b"},
         c = {{d:=3, "d"}}
const b = 2, a = ["sequence",b, "b"], 
      d = 3, c = ["sequence", ["sequence",d, "d"]];

(irreversibly!)
try
    throw("bang")
catch e
    ?e
end try
try {
    throw("bang");
}
catch (e) {
    e = $catch(e);
    print(1, e);
}

NB: JavaScript does not typecheck (etc), this will only behave even semi-consistently for explicit throw() statements. It uses the JavaScript version of throw() rather than some translation of the desktop/Phix version, and the contents of an exception are not the same, to put it mildly, for instance:
desktop/Phix: {0,34279823,152,21,"-1","t68forin.exw",`C:\Program Files (x86)\Phix\test\`,"bang"}
pwa/p2js: {0,0,0,0,"","","","bang"}
function f()
    static [a,b]
    static a = 1
    constant b = 2
    nested function g()
    end nested function
    ...
end function
let %static1%a, %static2%b;
function f() {
    /*static*/ if (!%static1%a) { %static1%a = 1; }
    /*constant*/ if (!%static2%b) { %static2%b = 2; }
    /*nested*/ function g() {
    }
    ... (with matching renaming)
}

The if() tests prevent (re-)clobbering on every single (re-)invocation. Javascript handles nested functions anyway, but p2js actively tries to avoid creating closures, for reasons outlined in nested routines.
??? To be continued...


Within reason, most of the above should be reversible: while obviously secondary to the primary task of transpilation, and often lagging some steps behind, it has actually proved quite useful for testng and debugging purposes. However, feeding this some random snippet of JavaScript found elsewhere, as opposed to its own output, is quite likely to crash. I have some hopes for the IUP C examples (at least not crashing), but that’s a distant tertiary consideration.

The JavaScript % operator maps to the Phix remainder() function, rather than the mod() function, and both of those are defined in p2js.js. Also note that mapping remainder() to % would likely open up all manner of precedence issues, for instance remainder(a-b,7) is clearly not the same as a-b%7.

Aside: while investigating the peculiarities of the JavaScript === operator, one person said “it looks like arrays use reference equality which is what you’d expect. It’d be pretty awful if you couldn’t do that”, and it just struck me I have been coding in Phix and before that Euphoria for nearly 20 years and never once wanted that, apart from applying that idea as a petty optimisation a couple of times in the backend. It would in fact be pretty trivial for me to add a === operator to Phix, and pass it unaltered through transpilation to JavaScript, I simply cannot think of any good reason to... Besides, JavaScript, being JavaScript, does not use reference equality on strings, or floats, which means that operator would not be anything like as trivial as it seemed at first glance, plus it would make (mainly for testing) any round trip phix->js->phix completely impossible (except perhaps via [yet even more] ugly comment-hints).

One thing I have had to abandon is that "a"=={'a'} yields true, however equal("a",{'a'}) is fine. In other words you must use the equal() syntax in those rare cases where comparing a string against a dword-sequence is important, but you can still use the infix = operator for two strings or two dword-sequences, or indeed any two objects where neither is explicitly typed as string (a literal would be), which is probably not far off 99.9% of the time.

It turns out that it’s much more important and useful for desktop/Phix to baulk at "bad things" when under "with js" than it is for pwa/p2js.exw to "cope with everything", not entirely unlike the way the former makes it relatively straightforward to get ancient legacy (or 4.0+) Euphoria code (with sequence ops, etc) to work.