Expand/Shrink

Incompatibilities

Obviously there are significant restrictions on what can be run in the browser compared to a desktop application.
This page summarises the desktop-only features of Phix that cannot be used in pwa/p2js.

Lists such as the following usually come across as a catalog of doom and gloom, but in reality I am chuffed to bits this list is as short & clean as it is!

Please do not misinterpret my fastidious attention to detail as a warning or deterrent. Sure, I should manage expectations and say that, for instance, you cannot easily read and write files from a browser, but the list of rosettacode entries that work in pwa/p2js is steadily growing and even impresses me! (what? well over a thousand? That’s insane!)

One way to think of it: Surely it would be a foolish and never-ending strife to state all differences between two completely different programming languages, Phix and JavaScript, but this single page is effectively just that.

You must understand and accept the following before using pwa/p2js:
  1. true !== 1 and false !== 0, a very specific glitch. The JavaScript equality operators are simply weird1. In almost all other respects, true is 1 and false is 0, for instance 1+true evaluates to 2, just like Phix. It is only direct comparison for equality of booleans and numbers using an infix operator which fails, and that is simply something you just have to live with. Besides, it is not exactly very difficult or particularly unnatural to (manually) convert c==0, for any condition c, to not c or c==false, likewise c==1 to c or c==true.

    There is no such issue with equal() and compare(), which of course you can use, and pwa/p2js automatically maps to when needed, that is except for bool vs number, which is difficult because in Phix those are the same thing. In other words, explicit calls to those routines are always fine, it is the implicit mapping to them by the transpiler that may not (and quite simply cannot) occur. Thankfully, there are very few places anyone ever actually compares bools against 0 and 1 using an infix operator.

    In a similar vein, desktop/Phix sometimes goes a bit overboard to ensure that "fred" == {'f','r','e','d'}. I am pretty sure that is (usually/sometimes) broken under pwa/p2js, however I am not particularly keen to fix it, especially since replacing an infix operator with equal() or compare() almost certainly does the job. There is also a similar issue with null!=0, but thankfully there happens to be a perfectly valid workaround for that, described below. I have also just noticed that print(1,a=b) prints true or false under pwa/p2js whereas desktop/Phix prints 1 or 0: obviously it would be better if I could change the desktop but that is simply not possible, and I will simply not bother to "fix" the browser without a really good reason - besides using printf(1,"%d",a=b) instead would give a matching 1 or 0 on both anyway, and "%t" a matching true or false.

    Anyway, either apply equal() fanatically and slow everything down, for a handful of rare cases, or force you lot to cope, the latter easily won.

    The following example illustrates, and also emphasies the subtlety of the issue (no difference whatsoever if c, d, e, f are defined as bool):
                        for i=1 to 3 do
                            integer c = (i==2),         -- fine
                                    d = (c==1),         -- oops
                                    e = (c==true),      -- fine
                                    f = equal(c,1)      -- fine, ditto equal(c,true)
    
                            printf(1,"%d==2:%5t(%d) ==1:%5t, ==true:%5t, eq1:%5t\n",
                                      {i,     c, c,       d,          e,       f})
                        end for
    
            --
            -- output on desktop/Phix: 1==2:false(0) ==1:false, ==true:false, eq1:false
            --                         2==2: true(1) ==1: true, ==true: true, eq1: true
            --                         3==2:false(0) ==1:false, ==true:false, eq1:false
    
            --
            -- output on pwa/p2js:     1==2:false(0) ==1:false, ==true:false, eq1:false
            --                         2==2: true(1) ==1:false, ==true: true, eq1: true
            --                         3==2:false(0) ==1:false, ==true:false, eq1:false
            --
     
  2. pass by sharing is the underlying semantic nature of JavaScript, in contrast to the pass by reference with copy on write semantics of Phix. Faking either in the other would be utter madness, but thankfully also quite unnecessary. It turns out neither really depend on that sort of thing as much as we think they do, and banning both is not only perfectly reasonable, but also rather helpfully occurs on the exact same trigger points, so to speak.

    Consider sequence s=repeat(repeat(0,100),100). Phix/cow uses 800ish bytes and one refcount of 100, whereas JavaScript, and Phix after a bit more setup, uses 40,000ish bytes. However while that 800ish bytes might improve startup a fraction, it is otherwise a fake saving, and probably actually costs due to refcount updates, and that could even be measurable if the original drops out of cache and needs reloading up to 99 times for each copy/decref.
    Yes, it was a lovely idea, but I probably won’t miss it. [repeat() has been modified, but other benefits of pass by reference have been retained, fear not.]

    A different case is that someproc(s) can safely doodle in s, whereas similar JavaScript would scramble the original. Should it need to clone, JavaScript cannot, so what we want and need is to trigger a violation, and that way force the programmer to use a different approach.

    Simply adding "with js" (aka javascript[_semantics]) to a program disables both semantics equally.
    (I don’t expect anyone to type out "with javascript_semantics" when they can just use "with js", however searching for the latter on rosettacode is [not] going to be very productive.)
    The only real downside is the browser/JavaScript may fail silently, whereas desktop/Phix does a crash("p2js violation: relies on copy on write semantics"), that obviously you have to fix, and in so doing make the other stop failing too. Should you not need something to run in the browser, do not specify "with js", and then you won’t get any copy on write violations (that is, assuming I added no bugs). Likewise if you need help with a tricky violation, you can temporarily disable with js and at least continue developing on the desktop while waiting for a response.

    The whole of builtins/psqop.e needed a complete rewrite, and is all the better for it. In stark contrast and quite amazingly I have not found a single thing that needed changing in pGUI.e, not that it is ever transpiled itself, pGUI.js is a hand-written replacement, but it has been and will continue to get tested under with js quite a bit. (Full disclosure: I have added a few new p2js-related things to pGUI.e, eg IupTreeView, just not changed any old code.)
    Sometimes an inner repeat() suffices, other times a deep_copy() is needed (a new builtin). Using o = f(o) where o is a local variable may help.
    Conversely, "without js" triggers an error message when an attempt is made to transpile it, which could save wasted effort on an impossible task, such as trying to read/write files or invoke c_func or otherwise use a dll/so from within a browser (see note below regarding mpfr/gmp).

    Several consequences have arisen, where necessary all of these generate a p2js violation or transpilation error usually right on the offending statement, and most are therefore pretty straightforward to rectify:

    1. A statement such as s = {} was trying rather hard to use a shared constant, beginning with allconst in pmain.e/DoSequence(), which was the only thing actually changed, and finishing off in pEmit2.e and pbinary.e with a fair bit of careful collating and packaging.
      However, that caused far too many non-1 reference counts, so it now does an opMkSq: the code is a bit bigger, but on the plus side it is slightly more thread safe (for sequences of integer only, and not that threads actually have anything to do with p2js). A similar change applies to {} as a parameter default, but not to other parameter defaults. (no error there, just a bit of info)
    2. A knock-on effect is that you can no longer use nested sequences as parameter defaults - a shame, but unlikely to cause much pain, and the fix is as clear as day (compile procedure p(sequence s={{}}) to see). Note that predeclaring a constant for that {{}} and using that as a default is still perfectly fine, bar the facts that said constant might not yet be properly set up in some forward call situations, and that it will need a deep_copy() before it can be modified, as do all non-{} sequence defaults.
    3. There was a nice little refcount cleanup exposed in opMkSq, so it is now a bit more efficient due to these changes.
    4. There was a bug in parameter passing within a routine whereby it was incref’ing an unnamed temp rather than clearing it, which these changes also exposed, another worthwhile improvement.
    5. Another nice find/fix was that eg {a,b} = table[i] was creating an unnamed temp to hold table[i], but never releasing the reference count. Likewise length(table[i]). A similar thing was also occuring for table[i] = {a,b}, another very welcome fix. And another: b = append(tmp,atm) was correctly being replaced with b = tmp; b = append(b,atm) but when tmp&atm was similarly being replaced it was not getting quite the same treatment (was testing for =S_Tvar, now does !=S_Const).
    6. I also fixed a looong-standing issue whereby {s[i],s[j]} = {s[j],s[i]} <correction> {a,b} = {c,d} - it will still create and subscript a temp sequence if any subscripts are used or if any lhs var occurs on the right hand side </correction> was significantly slower than a longhand version using a temp, woo-hoo (see get_from_stack/rhs_stack in pmain.e).
    7. A mini optimisation that {} & p would just return p with an incref had to go, it now copies over the elements of p just as it does for a non-empty left-hand sequence. In the vast majority of cases that was always a very minor optimisation, and the case of p = {} & p is still properly optimised away to "do nothing" (that is when detected at runtime as opposed to when spotted during compilation). Likewise r = p[1..$] where r!=p now has to make a proper copy rather than returning an incref’d p, which probably made very little if any practical difference, and again p = p[1..$] is still optimised away to "do nothing" when detected at runtime, and p = p[i..j] will still clip in-situ when it can (not that p2js.js can manage or even attempts those particular feats).
    8. a = append(b,c) (ditto prepend) where a!=b may need to be replaced with (say) a = append(deep_copy(b),c) which is fairly rare, and thankfully it is usually pretty obvious what you need to do. You may also get a similar straightforward error with the likes of tbl[i] = append(tbl[i],x), unless tbl is local and pbr optimisation can be applied. I admit that may be a bit unnecessary but it is not exactly the end of the world, nor is it particularly common.
    9. return p & q is usually wrong, and typically needs to be replaced with either p &= q; return p or perhaps return deep_copy(p) & q.
    10. s[i][j] &= t, thankfully pretty rare, is invalid under with js. While s[i] &= x (ie the single index case) avoids the unnecessary incref (zeroing out s[i] over the append op) and is therefore compatible, the same is not true (/alas impractical) in the multiple index cases. If object o = s[i][j]; o &= t; triggers a cow-violation, so too will the above. (That is, if s[i][j] is not an atom, which w/could [just] be creating a brand new sequence-pair.) Replace it with eg s[i][j] = deep_copy(s[i][j]) & t, just like you would with o = deep_copy(s[i][j]); o &= t, or maybe break it down into four lines: sij = s[i][j]; s[i][j] = 0; sij &= t; s[i][j] = sij;.
    11. {s[i]} = {ch} becomes illegal when s is a string. JavaScript has some pretty decent Array destructuring operations, but also immutable strings and no char (see below), so in order to be able to use/emit faster and more elegant code it is not permitted to modify individual characters as part of a multiple assignment statement. When applicable, you might well consider using something like str = reinstate(str,{i,j,k},"abc") instead, since that doesn’t have any such issues.
      A crash("p2js violation: JavaScript does not support string subscript destructuring") occurs should such an invalid operation be attempted on desktop/Phix when with js is in force, which obviously you will have to fix, but of course s[i] = ch and similar are still all perfectly fine when not part of a destructuring operation, and handled differently behind the scenes.
    12. {ch1,ch2} = s likewise becomes illegal when s is a string under with js, fairly rare and easily replaced with ch1 = s[1]; ch2 = s[2], or perhaps (replacing troublesome syntax with hll calls often works) {ch1,ch2} = extract(s,{1,2},2).
      [aside: pretty sure transpiling to {ch1,ch2} = $charArray(s), with a minor tweak to that routine, would work just fine should it ever really be needed.]
    13. {s[i..j]} = {x} also becomes illegal, for both strings and dword sequences. JavaScript handles slices using Array.s[p]lice() which is not a valid destructuring target in JavaScript, nor would similar be valid on desktop/Phix, such as {a,slice(b),c} := xyz.
      A "p2js violation: JavaScript does not support slice destructuring" compilation error is emitted in desktop/Phix for attempts to perform slices during a destructuring operation when with js is in force.
      Again s[i..j] = x etc are all totally fine outside of multiple assignment.
    14. {s[$]} = x and in fact any other negative subscripts simply do not work in JavaScript Array destructuring operations, despite them otherwise being pretty decent.
      You can however still use $ and negative subscripts in non-destructuring operations, so eg
              sequence s = {1,2,3,4}
              --{s[1],s[$]} = {s[$],s[1]}     -- fail (4,2,3,4)
              integer l = length(s)
              {s[1],s[l]} = {s[$],s[1]}       -- good (4,2,3,1)
      
      is something else you’ll just have to learn to live with. Of course if you prefer to be consistent and replace the $ on the rhs as well, that’s perfectly fine and understandable.
      Note that while I can and do trap [$] and [-literal] during transpilation, there is no run-time checking for negative subscripts in [var] form during destructuring on either desktop/Phix or pwa/p2js, and the latter will just fail silently, again adding a proper message (at least that is to desktop/Phix) is probably best attempted alongside the planned ma_ip changes.
    15. {s[1],s[1]} = {1,2} assigns right-to-left on desktop/Phix, but left-to-right after it has been transpiled to JavaScript, so obviously s[1] ends up 1 on desktop/Phix but 2 in a browser, and unfortunately there is no way to detect or even warn about that. It would be fairly easy to change desktop/Phix to assign left-to-right, but again only at the cost of breaking a few legacy things with no proper warning, and there is very limited value to doing that. A better solution was found for Reduced_row_echelon_form, where this was first detected, by replacing
              -- nb m[i] is assigned before m[r], which matters when i=r:
              {m[r],m[i]} = {sq_div(m[i],m[i][lead]),m[r]}
      
      with
              object mr = sq_div(m[i],m[i][lead])
              m[i] = m[r]
              m[r] = mr
      
      which is obviously completely compatible, and both much clearer and slightly faster anyway.
    16. i & s sometimes needs to be replaced with deep_copy({i}) & s. One of those I cannot really adequately explain, but at least it moans about the precise statement it is unhappy with. It is probably due to that expression being optimised to prepend(i,s) and the latter being somehow re-optimised again at some later point in a js-unfriendly manner. Thankfully quite rare.
    17. {a,b} @= c forms of multiple assignment were deemed unnecessary and remain unsupported, and can normally be replaced with either an explicit {a,b} = repeat(c,2), or breaking it down into individual statements/declarations such as a = c [,] b = c or similar.
      In fact that construct is now deprecated in version 1.0.1 and later.
    18. tree[node+direction] = insertNode(tree[node+direction], key) from the rosettacode AVL task crashed under p2js. As said elsewhere you should never ever attempt to modify the same thing in the same line twice. Breaking it into atom tnd = insertNode(tree[node+direction], key) and tree[node+direction] = tnd fixed the issue easily enough, however it was by far the hardest thing in this list to track down, failing as it did mysteriously in the browser with a stack overflow that nearly brought the whole thing down, despite working perfectly on the desktop. Oddly almost the exact same code worked flawlessly in builtins\dict.e, but I’ve since applied the same "fix" to that code anyway.
    19. if s[i]=x then internally performs tmp:=s[i] immediately followed by a jump, which can sometimes leave an unwanted refcount for tmp, triggering a p2js violation. Two possible ways around this are: if deep_copy(s[i])=x then and if is_same(s,i,x) then with function is_same(sequence s, integer i, object x) return s[i]=x end function, which (both) work because the tmp used is discarded when the function returns. Thankfully this sort of problem is fairly and in fact quite surprisingly rather rare. Hopefully in some future release there will be a flag on or different version of opSubse (etc) that avoids the incref and instead of an actual tmp just leaves the result in (say) the eax/rax register...
    20. Also note that constant powers = 0&{1,2,3} applies an internal optimisation of mapping opConcat(atom,seq) to prepend(atom,seq), which triggers an internal clone and raises a p2js violation. Were it constant power1 = {1,2,3}, power0 = 0&power1, then it would perhaps make much more sense, if only a deep_copy(power1) actually helped, however you certainly cannot just get rid of that optimisation since it makes some desktop/Phix programs exponentially faster, including the compiler itself. Thankfully rare (7 months before first spotted) there is no easy fix, either in the form of a low-level backend patch or a foolproof/recommended hll code change, instead you must just massage away until that knot somehow eases itself out.. One small tip is that append()/prepend() may accept/honour deep_copy() a bit better than "&".
    21. s[i] = ch is significantly slower, especially on long strings. JavaScript strings are "immutable", and hence pwa/p2js.js/$repe() performs s[1..i-1] & ch & s[i+1..$] aka t = t.substring(0,idx-1) + String.fromCodePoint(x) + t.substring(idx); which for a 500x500 IupImage() pixel data would mean copying 250,000 bytes to set each pixel (this problem was only first noticed after some 18 months of using p2js). For that reason, repeat() now [1.0.2+] has an optional third parameter to prevent it from attempting to create (binary) strings.

    Everything (else) I have encountered so far suggests this approach is adequate and sufficient.
    The rosettacode entry for Cramer’s rule both relied on and proudly promoted the benefits of copy-on-write semantics. Admittedly it found a bug in the p2js.js version of deep_copy() but otherwise was trivial to get working under "with js" and in the browser.
    Even getting p2js itself to run under "with js" (just on the desktop, for now) was no big deal, despite the fact that over 90% of that was written before I had any idea at all that deep_copy() would be needed. The "with js" did its job perfectly and forced me to make around a dozen fairly trivial changes, all with clear no-nonsense messages, albeit ones I pre-understood. Two or three took me maybe three or four minutes, but that was about the worst of it.
    (That is not the same as saying that six or seven changes from the above table were not downright tricky, they were, but once done that was that, for them.)

    Example:
            O:={3}; P:=O;            O[1]:=5;   -- when transpiled to JavaScript: ARGH, P != {3} !!!
            O:={3}; P:=deep_copy(O); O[1]:=5;   -- when transpiled to JavaScript: ahhh, P == {3}
    
    Yes, that might sometimes be very painful, but far more often it should be really quite easy and simple.
    You would normally rely on desktop/Phix fatal messages to figure out where/when deep_copy() is needed.
    You should not expect things inadequately tested on desktop/Phix (under "with js") to work in the browser, in that regard.
    Fears this may carry an unacceptable performance hit have not yet materialised, but cannot be ruled out altogether.
    There has been a noticeable increase in running p -test, around 25% or 0.8s in total, which averages out at just over 0.01s per test file, mainly attributed as above to startup times of using opMkSq over shared constants and slightly larger code.

  3. {a, atom b} = x where a has already been declared raises the transpilation error "nested vardef error". (Ditto probably fixable.)
    Such statements w/could be transpiled to (say) let [,a, b] = x; but JavaScript (quite reasonably) complains that a has already been declared. Were it transpiled instead to [,a, let /*atom*/ b] = x; then JavaScript would (rather unhelpfully) raise an "Uncaught SyntaxError: Unexpected strict mode reserved word" error, so we can’t do that either. In short, such mid-lhs declarations are a Phix-only thing, easily fixed by replacing with (say) atom b; {a,b} = x. There are probably some other similar forms that are not yet properly caught. One I have spotted is {a, {atom b}} = x which now raises an "unexpected type" error, though I might yet have to remove that, should it start to trigger when not wanted.

  4. switch/exit is not supported, eg:
            for i=1 to length(s) do
                switch s[i] do
                    case ’!’: exit
                end switch
            end for
    
    In JavaScript the exit(/break) leaves the switch statement, whereas in Phix it exits the for loop. In Phix a break statement leaves the switch statement, but there is no exit statement in JavaScript and hence there is no similar distinction possible between exit and break. The transpiler raises an apppropriate error, as does desktop/Phix under with js. You will probably have to use a separate flag, and test it after the end switch. More details can be found in the switch docs.

  5. no include scope Apart from allowing multiple <script src="..."&></script> tags, JavaScript does not really do include files, and certainly does not grant a different scope to each file. Although pwa/p2js handles p2js.js, pGUI.js, and the autoincludes in pwa/builtins as separate script tags, it bundles all other sources into a single script tag. Any clashes are detected and reported as an error. Using multiple script tags would not solve anything. Of course JavaScript iframes do not share any scope or screen real estate and hence are not any kind of useful in this context either.

    One thing I have done is to automatically replace all (`base64_init`, `aleph`, `ccha`, `init_base64`) in pwa/builtins/base64.js with (`$base64_init`, `$aleph`, `$ccha`, `$init_base64`), along with checking that any such entries in p2js_depend.e do not clash with any other, and that way ensured that none of the builtins "leak" any of their private identifiers, similar to what I did manually in p2js.js and pGUI.js with eg $charArray and $storeAttr (see full list here).

    Rather belatedly I have realised that I could (build and) prefix a similar list of identifiers in user-land includes with say $N$, where N is a sequentially allocated number for each include file. Took me a while to figure that one out, it’s now on my list...

  6. no namespaces There is absolutely no support for namespaces in JavaScript, so any "as" on an include statement is bluntly rejected, likewise standalone namespace directives within an include file itself, along with any attempt to use namespaces anywhere else in the code.

    Note the above belatedly realised fake file scopes have zero overlap with namespaces, really absolutely none at all, and (indirect) sub-include handling grants a whole new level of meaning to the word "impossible" when everything gets lumped into a single file, and as I said above, multiple script tags would not solve anything. I mean there is a slim possibility that pwa/p2js could decipher all the namespace shenanigans and perform a frenetic renaming exercise on everything to keep JavaScript happy, but let’s just not go there.

  7. implicit forward calls are not supported, instead an explicit forward definition is required, eg:
            forward procedure Buzz(integer i)       -- <== i.e. this line...
            
            procedure Fizz(integer i)
                ...
                if mod(i,5)=0 then
                    Buzz(i/5)                       -- ...avoids an error here
                end if
                ...
            end procedure
            
            procedure Buzz(integer i)
                ...
                if mod(i,3)=0 then
                    Fizz(i/3)
                end if
                ...
            end procedure       
    
    This may be addressed in a future release but for now the effort (see p2js_scope.e) is focused on detecting clashes between different include files, keyword abuse, type inferencing and propagation, named parameter support, etc. Besides, any errors thrown up are trivial to fix manually.

  8. no typechecking There is the tiniest smigden of manual parameter typechecking within p2js.js itself, for instance puts(fn,x) begins with integer(fn,"fn");, where the optional second parameter to integer (in the browser only) makes it a fatal typecheck, however that is not widely applied.

    While you can declare user defined types and variables and invoke them explicitly, pwa/p2js simply maps them to the builtin types, and JavaScript will never invoke them implicitly (ie on assignment). You can still (mostly) use types to aid development on the desktop, but once a program is debugged and run through the transpiler, much of that is largely lost. If you are expecting run-time type checks in a typeless language, or even worse abusing types and expecting side effects on assignment, you will be sorely disappointed. However, if you are simply using types to make the code a bit more self-documenting, using them to control logic flow (in a potentially unsafe manner), or debug things (before they hit the browser), that’s fine.

  9. enum type is not supported, and must be manually replaced as follows (essentially what desktop/Phix does/did automatically):
    --enum type ternary T, M, F end type
    enum T, M, F
    type ternary(integer t) return find(t,{T,M,F}) end type
    
    I rather doubt that enum types are widely used enough to warrant wasting any effort on them, and besides you get a very clear compilation error on desktop/Phix under with js when it hits one.

  10. no delete_routine JavaScript does not (even) have class destructors, and trying to place any kind of event listener on a JavaScript object would require a reference, which would in turn prevent it from ever being garbage collected, which is of course the very event you would like/need to trap.

  11. file i/o is simply not possible from within a browser, or rather insanely difficult and triggers every stupid CORS restriction2 ever invented.
    Storage, database, security, and similar mechanisms have all yet to be devised. (Perhaps I could make pwa/p2js on desktop/Phix also act as a server...)
    Actually, I think this is where I should stick my hand up and say HELP!.

  12. allocate along with anything from Machine Level Interface and Calling C Functions is simply not possible from within a browser. [Eight that are available are int_to_bits(), int_to_bytes(), bits_to_int(), bytes_to_int(), atom_to_float32(), atom_to_float64(), float32_to_atom(), and float64_to_atom(), exceptions that prove the rule.]

  13. console input and everything else in Console Graphics is not supported. Likewise progress() would have no effect until the process goes idle, and in fact the GUI is not updated at all while JavaScript is running. Of course a browser is itself a GUI, and the idea of stopping JavaScript for input is at odds with the underlying event loop. While technically a browser has a console, it is hidden away at the bottom of the browser development tools. To cut a long story short, if you want any interaction, it has to be GUI not console style, in other words KEY or VALUE_CHANGED and similar, instead of getc(0) or gets(0).

  14. variable declaration reset does not occur in desktop/Phix. Surprisingly rare, eg:
    sequence s = {1,2,2,3,4,4,5}
    for i=1 to length(s) do
        integer curr = s[i], prev
        if i>1 and curr=prev then
            ?{i,s[i]}
        end if
        prev = curr
    end for
    -- desktop/Phix outputs {3,2} {6,4}, pwa/p2js outputs precisely nuthin.
    
    JavaScript actually resets prev at the start of every iteration, but desktop/Phix leaves it be (and I guess you could argue that’s a bug, not that I’ll listen).
    You need to declare prev outside the loop to prevent that sort of thing (ie reset every iteration in js) from happening.

  15. unicode handling is quite different. JavaScript handles utf8 natively, in effect an s[i] is the same as utf8_to_utf32(s)[i], though as per the notes in utfconv such routines are actually null-ops under p2js. Things will go wrong if you try to play with individual bytes rather than whole code points, but why would you? Likewise the kludge that is "unicode_align" in printf() is simply ignored under p2js.

  16. keywords. Must be extended to cover both Phix and JavaScript, so identifiers such as this, in, new, super, etc. must all be renamed. The transpiler should point ’em out, and I also added a table of them to the docs for you, and wrote a draft outline for a code minimiser, which if ever implemented w/could make that unnecessary.

  17. goto is not supported in JavaScript, so despite only recently adding goto to Phix, in fact in the same release as p2js, the latter does not support it.

  18. 20 year old code is not likely to run under pwa/p2js. Changes should be fairly minimal, however it is probably wise to consider p2js as effectively a brand new language and something that will only run brand new code written specifically for it, doubly so GUI-wise. Attempts to transpile legacy code without first testing it under "with js" on desktop/Phix deserve a very serious spanking!
The following workarounds are used in the transpiler (selected highlights of far too many to mention, I guess):
  1. null !== 0. Much like the true !== 1 issue above, however in this case there is a trivial workaround: the transpiler maps null to NULL, and explicitly defines the uppercase variant as 0. The big difference is we are in complete control of which routines return null, and can map them all, whereas unfortunately we cannot guarantee the same for routines/tests returning true and false. In fact the transpiler maps TRUE and True to true, and FALSE and False to false, the other way around, otherwise that aforementioned glitch would just be even more inconsistent and confusing, since without that mapping c=true would work but c=TRUE would not. Not entirely surprisingly, attempting to define the lowercase variant(s) triggers a syntax error in JavaScript. Just for info, desktop/Phix defines the uppercase variant (NULL) as an alias of the lowercase one (null) in psym.e/syminit(), and of course some minor glitches may occur because some bit of code in p2js.js or pGUI.js incorrectly/accidentally uses a null instead of NULL, or perhaps vice versa, hopefully easily fixable, once actually detected and reproduced that is.

  2. no char. You do not have to look very far to find JavaScript that looks like it is using characters, there is even some in p2js.js, but trust me it’s all strings. In Phix, '2'+1 is '3', in JavaScript it is '21'. The transpiler converts eg '2' to 0X32 to neatly sidestep this issue, rather successfully I might add.

  3. mutable strings are provided seamlessly. While technically JavaScript has immutable strings, the transpiler converts eg s[i] = x to s = $repe(s,i,x) which handles both strings and dword sequences seamlessly, even with their 0-based and 1-based subscripts respectively. The same can also be said for append() and prepend(), and the infix & operator, which is transpiled to $conCat() calls, which also takes care of that messy '+' operator overloading business of JavaScript. It also relies on desktop/Phix crashing should a '+' operator be attempted on a string when sq_add() should have been used. Whereas previous versions of desktop/Phix would sometimes substitute a '+' with sq_add(etc) and issue a warning, that is now treated as a compilation error whenever with js is in force.

  4. named parameters are not supported by JavaScript, therefore we map them to positional arguments inside p2js, eg timedelta(days:=1) ==> timedelta(0,1);. Some of the work that was needed for the builtins might well deserve to be back-ported to desktop/Phix.

  5. enum is not supported by JavaScript <Sybil Fawlty>Oh I know, .. yes I know</Sybil Fawlty>, therefore p2js translates them to standard constants.

  6. nested constant declarations are not supported by JavaScript, therefore p2js extracts them into standard constant definitions (recursively! irreversibly!).

  7. unescaped strings like Phix backticks or treble quotes are not supported by JavaScript, so p2js valiantly tries to correct for that on output, though I may have missed a trick or two, especially with some of the nuances of leading whitespace.

  8. hexadecimal strings such as x"123_456" are automatically mapped to a JavaScript-compatible format, in this case "\x12\x03\x45\x06", applying the equivalent nibble-rules to desktop/Phix.

Improvements warranted
  1. error handling is generally pretty poor, often a plain crash (independently of and in addition to the constant CRASH at the top of p2js.exw). Note that pwa/p2js copes with several things on the assumption they are inside a platform()!=JS test or similar, and (at least in the fullness of time) is expected to deal with Phix, JavaScript, HTML, CSS, C, etc. None of $repe(), $repss(), $subse(), or $subss() perform bounds checking yet, but they should and will. Note however that https://rosettacode.org/wiki/Law_of_cosines_-_triples#Phix relies on a "sparse array" trick and will crash if we actually initialise an array of 100,000,000 items or apply stricter bounds checking. It is the only such one I know of though, and could probably be modified to use a dictionary on js, then again is bounds checking in js actually useful?.
    Right now the target is simply to deal with bug-and-syntax-error-free code, and it would be unwise to expect too much in the way of proper error handling, or warnings that you are trying to use a feature that a browser will never support, etc, though of course I’m sure that will gradually improve.

  2. structs/classes ... at the moment I do not even try to parse such things. JavaScript classes cannot be abstract and have no specific support for public/private or any kind of destructor, and also there is a constructor() which must be present and any super() must always be called. Fields must be set in constructor() rather than declared and class methods have no "function". Obviously there is no support for c_structs, and all JavaScript classes are what Phix would dub "dynamic". Overall I would say there probably is a fairly easy and straightforward mapping, but no promises.
    Update: I had to disable test\t64structs.e during p2js testing and builtins\structs.e will need to disable "with js" using #ilASM{ xor eax,eax; call :%pWithJS; mov [rest_js],ecx } followed by #ilASM{ mov eax,[rest_js]; call :%pWithJS } to re-enable it, and pHeap.e changed to set e/rcx. Messy but at least that part of it is doable.

  3. pGUI.js is still somewhat at the proof of concept stage though the number of working examples is steadily growing. The biggest difficulty is getting a consistent mapping between the GAP/PADDING/MARGIN/SIZE/EXPAND/SHRINK etc attributes of the IUP container model and the required CSS settings of the browser box model, not easy but I cannot think of anything else that would be. Most of the elements/containers that have even been started have been fairly easy, apart from the spacing/layout aspects, which have in contrast been quite frustrating.
    Update: work has started on xpGUI.

    Also note that more complex controls such as IupMatrix() and IupPlot() are never going to be supported, see those links for suggested alternatives. It might be fairly reasonable to add new controls, as per IupTable() and IupGraph(), that behave consistently under pwa/p2js and desktop/Phix, but utter madness to try and replicate every little nuance and foible of those existing and frankly terrifying tyrannosaurii.

  4. multithreading in JavaScript is done with webworkers, a completely different interface to the existing multithreaded capabilities of desktop/Phix. The fact that we need separate files for JavaScript (or Blob doobries with a separate scope) strongly suggests that we would be far better off adding a webworker compatible api to desktop/Phix than trying to emulate create_thread() and friends in the browser, but beyond that simple thought I got nothing much. One possibility is that "w = webworker filename.e" would be hauntingly similar to "include filename.e" except for granting it a new separate clean and empty scope, or a new (just like class) "webworker w ... end webworker" construct to much the same effect, the critical point being that desktop/Phix would not have any access to variables or routines that a JavaScript webworker as transpiled from that code would not. Aside: the ma_ip flag added to pRepeN.e and pSubseN.e is not thread safe and would need migrating to the opcode/stack.

    I will also state that attempting to emulate multitasking in the browser is almost certainly a terrible idea: were it in any way reasonably possible there would be dozens of js libraries offering that functionality by now and I have never heard of any. Besides which all of that critical section locking stuff goes out the window with a webworker api anyway, which would be the only reason anyone would ever use tasks over threads in the first place.

  5. mpfr/gmp dlls cannot be used directly, however (and in preference to all that WASM nonsense) JavaScript now has a native BigInt in nearly 90% of all browsers in use (see https://caniuse.com/?search=bigint, and note that several of those nice green boxes are 2021), so a replacement mpfr.js has been manually crafted as a drop-in replacement.
    Likewise there may or may not be alternatives we ("we", huh...) can cobble together for the likes of database/pSQLite, libcurl, librsvg, and sockets, but I would be very surprised if there is anything for ipc, LiteZip, pipeio, or of course cffi. Just to complete the picture, I don’t see any problems with using the existing base64, complex, dict, json, ordinal, ppp, pqueue, regex, sets, timedate, unit_test, utfconv, or xml components, which should all be usable right now, not very well tested mind you.

  6. 1.23MB all in for the (first, broken, self-host of the) transpiler (813K) and all other support files (416K), which includes excessive comments and no attempt at any kind of minimisation. A simple zip of the 290K builtins was 90K. For comparison, the median fully optimised sizes for Vue/React/Angular pages are 460K..1.14MB. A hello world Blazor WASM app is apparently 1.6MB. I have also had the joy of downloading and running 75MB WASM files once or twice, never that again please. Obviously it is generally speaking utterly ridiculous to compare my little 3-bit toy against those... and those median sizes probably contain several images... Beyond that, I have absolutely no idea what the final tallies will be, and not particularly worried: it will not be free or near zero, and far more likely to go down than start bloating from now on. Of course smaller is better and faster, but in all honesty these days even a previously unthinkable 10MB is actually acceptable, just about and not that I plan on ever getting anywhere near that.

  7. Inline JavaScript might very well be handy. Just as inline assembly uses a #ilASM{} construct with embedded guards [32], [64], [PE], [ELF] (and pairings) to emit the right assembly for each target architecture, we could have a [JS] guard around embedded inline javascript. Admittedly it is slightly odd to lump javascript in with assembly, but probably the right thing to do. Just as most people (for good reason) steer well clear of inline assembly, I would expect most to steer well clear of inline javascript as well.

  8. Generating C code instead of JavaScript is probably reasonably straightforward, however things totally outside my comfort zone include installing a C compiler and applying the estimated 1047 compiler flags and configuration settings actually needed in order to make it work, along with a manually crafted p2js.c equivalent for p2js.js, and for the exceptionally brave and stupid, using IUP and mpfr C sources directly instead of pre-built dlls. Happy to help where I can, as long as I’m not put in charge.

As stated above, that list might look pretty dreadful, but actually there aren’t any really big show-stoppers in there, and plenty not-mentioned works just fine.

Performance seems pretty decent, that is in the browser, in fact Attractive_numbers is actually (reported as) faster in the browser than on the desktop, though of course it is usually the other way round, with some around 20 times slower in the browser, for instance one particular and hopefully fairly rare bad case is Extra_primes.
Calls to deep_copy() may not be the overhead you think they are: sure there is an additional function call but the actual work it does, at least on a flat sequence or when you decide it is ok to limit the depth appropriately, is (often) exactly the same work it always had to do quietly behind the scenes for you anyway.
The only real difference may be in making that explicit for the benefit of JavaScript.

Footrants
1 (JavaScript equality operators are simply weird) For instance 2==="2" yields false, whereas 2=="2" yields true (and 2="2" is an invalid assignment statement).
Fairly obviously Phix could never ever work with those crazy == rules, and so we use/p2js always emits the === form.
JavaScript supporters are prone to saying things like "it's not weird, you just don't understand it".
When I read section 11.9.3 (2011), or [update] better yet section 7.2.15 (2020), I say “When I see an equality comparison, or any other operator, I want a pin-sharp crystal-clear image in my mental model of how it works, not a half-baked hodge-podge of ~16 edge conditions I have to consider on each and every frakkin’ statement...” In truth, "weird" is the kind way of saying it. To be fair, the Phix equality operators, albeit arguably more logically consistent, are not exactly simple either, and I am more than confident that someone somewhere will say “what? "fred"=={'f','r','e','d'}??? utter madness!” (which was always a compatibility fudge for Euphoria more that anything else, let’s be honest)

2 (CORS) Of course I get why we need it, just don’t necessarily agree with all of it, or the implementation of it. My ignorance on these matters is as follows:
Mapping file: to the server, because they could not possibly get away without doing that, but adamantly and arrogantly asserting that it cannot be mapped to localhost, because it breaks the exact same rule, is what I classify as cretinous. To be clear: it is not necessarily the CORS specification itself, for missing out that all important "unless they resolve to the same thing" clause, but every browser implementation, or jobsworth-types kicking up a fuss3. I know, I can just run a server, and then reconfigure and restart it 20 times or so. Sure, downloading some random html/js and running it locally might be deemed a security risk, but then saying that uploading that exact same file to your server and running it there is all suddenly somehow not a security risk, do me a favour!

3 (workaround for localhost) It seems like every browser had one at least once, that presumably triggered some ridiculous backlash from idiots thinking that they knew better, and consequently been removed. Or maybe Google cannot make quite as much money if developers can test their work offline. The Firefox browser is streets ahead (or behind in the removal process) of all the other players in these silly little games, but still far from perfect.
Update: of the two features I relied on in Firefox, one remains, the other was rudely ripped out in mid-March 2021. Sigh. Of course what probably happens is they get a serious security breach, so someone disables the localhost loophole, doesn’t make the blindest bit of difference, and four or five days later when they fix the real problem either no-one asks about re-enabling it or gets shouted at or fired when they do...