josh - Standalone JavaScript to pure portable shell transpiler and interpterer

42 min read Original article ↗
#!/bin/sh # ISC License # Copyright (c) 2026 Alexandre Gomes Gaigalas <alganet@gmail.com> # Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # josh.sh — standalone ES6 interpreter. # Generated bundle: core/header + io + err + ast/engine + lang/es6/{parser,runtime,compile} + core/footer + driver. # Usage: # josh.sh <file.js> run the file # josh.sh read JS from stdin set -euf LC_ALL=C IFS='' _EOL=' ' _TAB=" " # Clear PATH: no external commands needed PATH= # Shell compat: aliases in bash, POSIX-like in zsh shopt -s expand_aliases >/dev/null 2>&1 || : setopt sh_word_split null_glob glob_subst \ no_glob no_multios no_equals 2>/dev/null || : # ksh/mksh fallback: 'local' may not exist, use 'typeset' instead command -v local >/dev/null 2>&1 || alias local=typeset >/dev/null 2>&1 || : # Fallback: if 'test' is not a builtin, wrap it via command -p if ! command -v test >/dev/null 2>&1; then test () { command -p test "$@"; }; fi # --- Output helpers --- # Detect best available output primitive: # printf > print > echo > command -p printf # _printn1: print string without newline _printr1: print string with newline if command -v printf >/dev/null 2>&1 then _printn1 () { printf '%s' "$1"; } _printr1 () { printf '%s\n' "$@"; } elif command -v print >/dev/null 2>&1 then _printn1 () { echo -n -E "$1"; } _printr1 () { echo -E "$@"; } elif command -v echo >/dev/null 2>&1 then _printn1 () { echo -n "$1"; } _printr1 () { echo "$@"; } else # yash fallback _printn1 () { command -p printf '%s' "$1"; } _printr1 () { command -p printf '%s\n' "$@"; } fi # _printesc1: write string interpreting backslash escapes (no trailing newline) # _OCT_PFX: octal escape prefix for building escape strings # printf uses \NNN (_OCT_PFX=), echo uses \0NNN (_OCT_PFX=0) if command -v printf >/dev/null 2>&1; then _printesc1 () { printf "$1"; } _OCT_PFX= elif command -v echo >/dev/null 2>&1; then _printesc1 () { echo -n "$1"; } _OCT_PFX=0 else _printesc1 () { command -p printf "$1"; } _OCT_PFX= fi # Read all of stdin into REPLY (replaces /bin/cat for eval "$(io_readall)"). io_readall () { REPLY= while IFS='' read -r _l; do REPLY="$REPLY$_l$_EOL"; done REPLY="$REPLY$_l" } # --- Input feeding --- # Read from stdin into IO_FEED_BUF buffer, one line at a time. # Lines > 128 chars are split into chunks before appending. # io_feed_read: read one line, chunk-split into IO_FEED_BUF # io_feed_fill: refill when IO_FEED_BUF < 16 chars (tight loop, most branches) # io_feed_more: refill when IO_FEED_BUF < 1024 chars (bulk reads for names/keywords) alias io_feed_read='if IFS= read -r IO_FEED_LINE then IO_FEED_RD=$((IO_FEED_RD + 1)); eval "IO_FEED_SRC$IO_FEED_RD=\"\$IO_FEED_LINE\"" while test ${#IO_FEED_LINE} -gt 128; do str_repeat_questn 128 _b="${IO_FEED_LINE#${REPLY}}"; _a="${IO_FEED_LINE%"$_b"}" IO_FEED_BUF="$IO_FEED_BUF$_a"; IO_FEED_LINE="$_b" done IO_FEED_BUF="$IO_FEED_BUF$IO_FEED_LINE$_EOL" else IO_FEED_EOF=1; IO_FEED_BUF="$IO_FEED_BUF$IO_FEED_LINE" case "$IO_FEED_LINE" in ?*) IO_FEED_RD=$((IO_FEED_RD + 1)); eval "IO_FEED_SRC$IO_FEED_RD=\"\$IO_FEED_LINE\"";; esac fi' alias io_feed_fill='if test ${#IO_FEED_BUF} -lt 16 -a $IO_FEED_EOF -eq 0; then io_feed_read; fi' alias io_feed_more=' while test ${#IO_FEED_BUF} -lt 1024 -a $IO_FEED_EOF -eq 0 do io_feed_read; done' # --- Position tracking --- # Count newlines in a consumed/skipped string, update IO_FEED_LN/IO_FEED_COL. # No-newline fast path: just adds string length to IO_FEED_COL. io_feed_track_nl () { local _s="$1" _h case "$_s" in *"$_EOL"*) while :; do _h="${_s#*"$_EOL"}" case "$_h" in *"$_EOL"*) IO_FEED_LN=$((IO_FEED_LN+1)); _s="$_h";; *) IO_FEED_LN=$((IO_FEED_LN+1)) IO_FEED_COL=$((${#_h} + 1)) return;; esac done;; *) IO_FEED_COL=$((IO_FEED_COL + ${#_s}));; esac } # --- Character-level operations --- # io_feed_consume - move 1-4 chars from IO_FEED_BUF into IO_FEED_CONSUMED # io_feed_skip - discard 1-3 chars or whitespace from IO_FEED_BUF # _io_feed_xfer - base: append stripped prefix to IO_FEED_CONSUMED # _io_feed_xbulk - base: append IO_FEED_REST to IO_FEED_CONSUMED, advance IO_FEED_BUF alias _io_feed_xfer='IO_FEED_CONSUMED="$IO_FEED_CONSUMED${IO_FEED_BUF%"$IO_FEED_REST"}"; IO_FEED_BUF="$IO_FEED_REST"' alias _io_feed_xbulk='IO_FEED_CONSUMED="$IO_FEED_CONSUMED$IO_FEED_REST"; IO_FEED_BUF="${IO_FEED_BUF#"$IO_FEED_REST"}"' alias io_feed_consume='IO_FEED_REST="${IO_FEED_BUF#?}"; _io_feed_xfer; IO_FEED_COL=$((IO_FEED_COL+1))' alias io_feed_consume2='IO_FEED_REST="${IO_FEED_BUF#??}"; _io_feed_xfer; IO_FEED_COL=$((IO_FEED_COL+2))' alias io_feed_consume3='IO_FEED_REST="${IO_FEED_BUF#???}"; _io_feed_xfer; IO_FEED_COL=$((IO_FEED_COL+3))' alias io_feed_consume4='IO_FEED_REST="${IO_FEED_BUF#????}"; _io_feed_xfer; IO_FEED_COL=$((IO_FEED_COL+4))' alias io_feed_skip='IO_FEED_BUF="${IO_FEED_BUF#?}"; IO_FEED_COL=$((IO_FEED_COL+1))' alias io_feed_skip2='IO_FEED_BUF="${IO_FEED_BUF#??}"; IO_FEED_COL=$((IO_FEED_COL+2))' alias io_feed_skip3='IO_FEED_BUF="${IO_FEED_BUF#???}"; IO_FEED_COL=$((IO_FEED_COL+3))' alias _io_feed_advcol='IO_FEED_COL=$((IO_FEED_COL+${#IO_FEED_REST}))' alias io_feed_skip_ws='IO_FEED_REST="${IO_FEED_BUF%%[! $_TAB]*}"; _io_feed_advcol; IO_FEED_BUF="${IO_FEED_BUF#"$IO_FEED_REST"}"' alias io_feed_skip_wse='IO_FEED_REST="${IO_FEED_BUF%%[! $_TAB$_EOL]*}" IO_FEED_BUF="${IO_FEED_BUF#"$IO_FEED_REST"}"; io_feed_track_nl "$IO_FEED_REST"' # Newline-aware variants alias io_feed_skip_nl='IO_FEED_BUF="${IO_FEED_BUF#?}"; IO_FEED_LN=$((IO_FEED_LN+1)); IO_FEED_COL=1' alias io_feed_skip2_nl='IO_FEED_BUF="${IO_FEED_BUF#??}"; IO_FEED_LN=$((IO_FEED_LN+1)); IO_FEED_COL=1' alias io_feed_consume_nl='IO_FEED_REST="${IO_FEED_BUF#?}"; _io_feed_xfer; IO_FEED_LN=$((IO_FEED_LN+1)); IO_FEED_COL=1' # io_feed_bulk - append IO_FEED_REST to IO_FEED_CONSUMED, advance IO_FEED_BUF, loop alias io_feed_bulk='_io_feed_xbulk; _io_feed_advcol; continue' alias io_feed_bulk_nl='_io_feed_xbulk; io_feed_track_nl "$IO_FEED_REST"; continue' # Repeat a string N times via exponentiation-by-squaring. str_repeat () { case "${2:-}" in 0|"") return;; 1) REPLY="$1"; return;; 2) REPLY="$1$1"; return;; 3) REPLY="$1$1$1"; return;; 4) REPLY="$1$1$1$1"; return;; esac local VALUE="$1" COUNT="$2" POW=2; REPLY= while :; do if test $POW -gt $COUNT; then test $COUNT -gt 0 || break REPLY="$REPLY$VALUE" VALUE="$1" COUNT=$((COUNT - (POW / 2))) POW=2 continue; fi VALUE="$VALUE$VALUE" POW=$((POW * 2)) done } # Memoized '???...?' pattern generator (used to split long input lines). str_repeat_questn () { eval "case \${str_repeat_questn$1:-} in \"\") str_repeat \\? $1; str_repeat_questn$1=\$REPLY ;; esac; REPLY=\$str_repeat_questn$1" } err_display () { IFS=' ' eval "_a=\"\${IO_FEED_SRC$IO_FEED_LN:-}\"" _printr1 "$* at $IO_FEED_LN:$IO_FEED_COL" case "$_a" in ?*) _printr1 " $_a" str_repeat ' ' $IO_FEED_COL; _printr1 " ${REPLY}^" ;; esac exit 1 } # --- AST stack operations --- # ast_new - push state, save IO_FEED_IO_FEED_CONSUMED as V<n>, reset IO_FEED_IO_FEED_CONSUMED # ast_push - create tree node X<n>=<state>, push index onto NODES stack # ast_pop - pop state + node stacks # Primitives (used by composed aliases below): # ast_attach - attach node to parent # ast_collapse - collapse single-valueless-child wrapper into the child # lang_shell_common_attach_op - ast_attach + operator-precedence: if prev sibling is Ab/Ob/Pb/Bu, # restructure so the operator steals the prev sibling # Composed close operations (all start with ast_pop): # ast_discard - ast_pop + discard (for transient states like Cs) # ast_close - ast_pop + ast_attach # lang_shell_common_close_op - ast_pop + lang_shell_common_attach_op # ast_close_col - ast_pop + ast_collapse + ast_attach # lang_shell_common_close_op_col - ast_pop + ast_collapse + lang_shell_common_attach_op # lang_shell_common_close_redir - ast_close + error if redirect target is empty # ast_flush - flush IO_FEED_CONSUMED as Tx child (a"b"c) alias ast_new='STATES="$STATES $STATE"; V=$((V + 1)) case "$IO_FEED_CONSUMED" in "");; *) local V$V="$IO_FEED_CONSUMED";;esac IO_FEED_CONSUMED=' alias ast_push='local X$V="$STATE" PARN="${NODES##*" "}" eval "PARNT=\"\${X$PARN%% *}\"" NODE=$V; NODES="$NODES $NODE"' alias ast_pop='STATE="${STATES##*" "}"; STATES="${STATES%" ${STATE}"}" PARN="${NODES##*" "}" case "$IO_FEED_CONSUMED" in ?*) local V$PARN="$IO_FEED_CONSUMED";; esac IO_FEED_CONSUMED= NODE=$PARN; NODES="${NODES%" $NODE"}"; PARN="${NODES##*" "}" eval "PARNT=\"\${X$PARN%% *}\""' alias ast_attach='eval "X$PARN=\"\$X$PARN \$NODE\""' alias ast_collapse='eval "_D=\"\$X$NODE\""; _C="${_D#* }" case "$_C" in "$_D"|*" "*) ;; *) eval "case \"\${V$NODE:-}\" in \"\") unset X$NODE; NODE=$_C;; esac";; esac' alias ast_discard='ast_pop;unset X$NODE' alias ast_close='ast_pop;ast_attach' alias ast_close_col='ast_pop;ast_collapse;ast_attach' alias ast_flush='case "$IO_FEED_CONSUMED" in ?*) ast_Tx;ast_close;; esac' # Register AST node types. Each type gets an ast_XX alias # that creates a new node, sets STATE, and pushes onto the stack. # Register AST node types. Outputs alias definitions to be eval'd. # Usage: eval "$(ast_tokens "Ty Pe Na Me")" ast_tokens () { IFS=' ' for _s in $1; do _printr1 "alias ast_$_s=\"ast_new;STATE=$_s;ast_push\"" done IFS='' } # Walk the AST from root, printing V<n> (value) and X<n> (node) in tree order. # Format: X<n>='<type> [<child_indices>...]' V<n>='<value>' # Uses dynamic scoping to access caller's V<n>/X<n> locals. ast_out () { set -- 0 while test $# -gt 0; do NODE=$1; shift eval "_pq=\"\${V$NODE:-}\"; _D=\"\$X$NODE\"" case "$_pq" in ?*) # Escape single quotes in values: 'abc'd'ef' -> 'abc'\''ef' case "$_pq" in *"'"*) _printn1 "V$NODE=" while :; do case "$_pq" in *"'"*) ;; *) break;; esac _printn1 "'${_pq%%"'"*}'\\'"; _pq="${_pq#*"'"}" done _printn1 "'$_pq'$_EOL";; *) _printr1 "V$NODE='$_pq'";; esac;; esac _printr1 "X$NODE='$_D'" _C="${_D#* }" case "$_C" in "$_D") ;; ?*) IFS=' '; set -- $_C "$@"; IFS='' ;; esac done } # --- Progress check (loop detection) --- # Two-level check: # 1. Exact repeat (same CODE length + state + position) → immediate error # 2. No input consumed for 4096 iterations → stuck in state cycle alias pars_progress='_PLC=$((_PLC+1)); case $((_PLC & 63)) in 0) case "${#IO_FEED_BUF}.$STATE.$IO_FEED_LN.$IO_FEED_COL" in "$_PREV") err_display INTERNAL LOOP;; esac; _PREV="${#IO_FEED_BUF}.$STATE.$IO_FEED_LN.$IO_FEED_COL"; case ${#IO_FEED_BUF} in "$_PLEN") case $((_PLC>262144)) in 1) err_display INTERNAL LOOP;; esac;; *) _PLEN=${#IO_FEED_BUF}; _PLC=0;; esac;; esac' # --- Error reporting helpers --- alias _pars_err='eval "_a=\"\${_EXP_$STATE:-token}\""; err_display "expected: $_a"' alias _pars_err_eof='eval "_a=\"\${_EXP_$STATE:-token}\""; err_display "unexpected end of input, expected: $_a"' # --- Parser epilogue (emitter wrappers, footer, dispatch) --- # $1=grammar name, $2=script arg (optional command override). # Called after the emitter function is defined. _ast_engine_pars_epilogue () { eval "_emit_${1}_root () { _emit_$1 \"\$@\"; }" . "$_DIR/core/footer.sh" eval "unast_$1 () { io_readall; eval \"\$REPLY\"; _emit_${1}_root 0; _printr1 \"\$REPLY\"; }" eval "reast_$1 () { pars_$1 | unast_$1; }" case "${PARS_LIB:-}" in "") case "${2:-}" in "") "pars_$1";; pars_*|unast_*|reast_*) $2;; *) _printr1 "usage: $0 [pars_$1 | unast_$1 | reast_$1]" _printr1 " pars_$1 parse stdin, print AST (default)" _printr1 " unast_$1 read AST from stdin, print source" _printr1 " reast_$1 parse stdin, print source (round-trip)" exit 1;; esac ;; esac } # Fast-path negated patterns _ENP='[!0-9.eExXbBoOaAbBcCdDfF_]*' # numeric chars _EIP='[!a-zA-Z0-9_$]*' # identifier chars alias ast_Ed="ast_new;STATE=Ed;ast_push" alias ast_Es="ast_new;STATE=Es;ast_push" alias ast_En="ast_new;STATE=En;ast_push" alias ast_Eb="ast_new;STATE=Eb;ast_push" alias ast_El="ast_new;STATE=El;ast_push" alias ast_Eu="ast_new;STATE=Eu;ast_push" alias ast_Ei="ast_new;STATE=Ei;ast_push" alias ast_ET="ast_new;STATE=ET;ast_push" alias ast_Ef="ast_new;STATE=Ef;ast_push" alias ast_EX="ast_new;STATE=EX;ast_push" alias ast_Eo="ast_new;STATE=Eo;ast_push" alias ast_Ea="ast_new;STATE=Ea;ast_push" alias ast_Em="ast_new;STATE=Em;ast_push" alias ast_Ep="ast_new;STATE=Ep;ast_push" alias ast_EI="ast_new;STATE=EI;ast_push" alias ast_EC="ast_new;STATE=EC;ast_push" alias ast_EL="ast_new;STATE=EL;ast_push" alias ast_EW="ast_new;STATE=EW;ast_push" alias ast_EV="ast_new;STATE=EV;ast_push" alias ast_Eg="ast_new;STATE=Eg;ast_push" alias ast_Ey="ast_new;STATE=Ey;ast_push" alias ast_EU="ast_new;STATE=EU;ast_push" alias ast_EA="ast_new;STATE=EA;ast_push" alias ast_EB="ast_new;STATE=EB;ast_push" alias ast_EG="ast_new;STATE=EG;ast_push" alias ast_EK="ast_new;STATE=EK;ast_push" alias ast_EM="ast_new;STATE=EM;ast_push" alias ast_EQ="ast_new;STATE=EQ;ast_push" alias ast_ER="ast_new;STATE=ER;ast_push" alias ast_EH="ast_new;STATE=EH;ast_push" alias ast_Ek="ast_new;STATE=Ek;ast_push" alias ast_Ec="ast_new;STATE=Ec;ast_push" alias ast_EF="ast_new;STATE=EF;ast_push" alias ast_Ew="ast_new;STATE=Ew;ast_push" alias ast_ED="ast_new;STATE=ED;ast_push" alias ast_EO="ast_new;STATE=EO;ast_push" alias ast_Eh="ast_new;STATE=Eh;ast_push" alias ast_EZ="ast_new;STATE=EZ;ast_push" alias ast_EP="ast_new;STATE=EP;ast_push" alias ast_Eq="ast_new;STATE=Eq;ast_push" alias ast_EN="ast_new;STATE=EN;ast_push" alias ast_ES="ast_new;STATE=ES;ast_push" alias ast_Ej="ast_new;STATE=Ej;ast_push" alias ast_EJ="ast_new;STATE=EJ;ast_push" alias ast_EY="ast_new;STATE=EY;ast_push" alias ast_Ez="ast_new;STATE=Ez;ast_push" alias ast_Ex="ast_new;STATE=Ex;ast_push" alias ast_Er="ast_new;STATE=Er;ast_push" alias ast_Et="ast_new;STATE=Et;ast_push" alias ast_Ee="ast_new;STATE=Ee;ast_push" alias ast_Ev="ast_new;STATE=Ev;ast_push" alias ast_EE="ast_new;STATE=EE;ast_push" alias ast_E1="ast_new;STATE=E1;ast_push" # Steal last sibling from parent, make it first child of current NODE alias es6_steal='eval "_W=\"\${X$PARN##*\" \"}\" X$PARN=\"\${X$PARN% *}\" X$NODE=\"\$X$NODE \$_W\""' # Close current node, mark expression as complete, re-check for infix/postfix alias es6_xclose='ast_close; _XC=1; _PREV=; continue' # Operator precedence: sets REPLY to numeric level _lang_es6_parser_prec () { case "$1" in '||'|'??') REPLY=1;; '&&') REPLY=2;; '|') REPLY=3;; '^') REPLY=4;; '&') REPLY=5;; '=='|'!='|'==='|'!==') REPLY=6;; 'in'|'instanceof') REPLY=7;; '<'|'>'|'<='|'>=') REPLY=8;; '<<'|'>>'|'>>>') REPLY=9;; '+'|'-') REPLY=10;; '*'|'/'|'%') REPLY=11;; '**') REPLY=12;; *) REPLY=0;; esac } # Parse stdin as ES6, output AST as V<n>/X<n> assignments. lang_es6_parser () { local IO_FEED_BUF= STATE=Ed V=0 IO_FEED_CONSUMED= STATES= NODES=" 0" X0="Ed" \ NODE= PARN= PARNT= SIBL= IO_FEED_REST= MATCH= _a= _W= _ST= _D= _C= _pq= \ IO_FEED_EOF=0 IO_FEED_LINE= _qch= _PREV= _PLEN= _PLC=0 _XC=0 _np=0 _cp=0 \ IO_FEED_LN=1 IO_FEED_COL=1 IO_FEED_RD=0 _NL=0 while :; do pars_progress io_feed_fill # --- Expression completion: check for infix/postfix operators --- case $_XC in 1) _XC=0 case "$IO_FEED_BUF" in ' '*|"$_TAB"*) io_feed_skip_ws;; esac case "$IO_FEED_BUF" in "$_EOL"*) case $STATE in Ed|EB|ES|Ej|EJ|Er) io_feed_skip_wse; continue;; esac _NL=1; io_feed_skip_wse;; esac # Auto-close new expression on non-postfix tokens case $STATE in EN) _W="${NODES##*" "}"; eval "_W=\"\${V$_W:-}\"" case "$_W" in '(') ;; # in args mode, fall through *) case "$IO_FEED_BUF" in '('*) IO_FEED_BUF="${IO_FEED_BUF#?}"; IO_FEED_COL=$((IO_FEED_COL+1)) _W="${NODES##*" "}"; eval "V$_W='('" continue;; '.'*|'['*) ;; # member access on constructor '['*) ;; *) ast_close; _XC=1; _PREV=; continue;; esac;; esac ;; esac # Phase 1: Peek at operator — determine _OP and _np without consuming IO_FEED_BUF _OP= case "$IO_FEED_BUF" in '=''=''='*) _OP="==="; _np=6;; '=''='*) _OP="=="; _np=6;; '=''>'*) # arrow function => IO_FEED_BUF="${IO_FEED_BUF#??}"; IO_FEED_COL=$((IO_FEED_COL+2)) ast_EA; es6_steal; continue;; '='*) # assignment = IO_FEED_CONSUMED="="; IO_FEED_BUF="${IO_FEED_BUF#?}"; IO_FEED_COL=$((IO_FEED_COL+1)) ast_Eg; es6_steal; continue;; '!''=''='*) _OP="!=="; _np=6;; '!''='*) _OP="!="; _np=6;; '<''<'*) case ${IO_FEED_BUF#??} in '='*) IO_FEED_CONSUMED="<<="; IO_FEED_BUF="${IO_FEED_BUF#???}"; IO_FEED_COL=$((IO_FEED_COL+3)) ast_Eg; es6_steal; continue;; *) _OP="<<"; _np=9;; esac;; '<''='*) _OP="<="; _np=8;; '>''>''>'*) case ${IO_FEED_BUF#???} in '='*) IO_FEED_CONSUMED=">>>="; IO_FEED_BUF="${IO_FEED_BUF#????}"; IO_FEED_COL=$((IO_FEED_COL+4)) ast_Eg; es6_steal; continue;; *) _OP=">>>"; _np=9;; esac;; '>''>'*) case ${IO_FEED_BUF#??} in '='*) IO_FEED_CONSUMED=">>="; IO_FEED_BUF="${IO_FEED_BUF#???}"; IO_FEED_COL=$((IO_FEED_COL+3)) ast_Eg; es6_steal; continue;; *) _OP=">>"; _np=9;; esac;; '>''='*) _OP=">="; _np=8;; '&''&'*) case ${IO_FEED_BUF#??} in '='*) IO_FEED_CONSUMED="&&="; IO_FEED_BUF="${IO_FEED_BUF#???}"; IO_FEED_COL=$((IO_FEED_COL+3)) ast_Eg; es6_steal; continue;; *) _OP="&&"; _np=2;; esac;; '|''|'*) case ${IO_FEED_BUF#??} in '='*) IO_FEED_CONSUMED="||="; IO_FEED_BUF="${IO_FEED_BUF#???}"; IO_FEED_COL=$((IO_FEED_COL+3)) ast_Eg; es6_steal; continue;; *) _OP="||"; _np=1;; esac;; '*''*'*) case ${IO_FEED_BUF#??} in '='*) IO_FEED_CONSUMED="**="; IO_FEED_BUF="${IO_FEED_BUF#???}"; IO_FEED_COL=$((IO_FEED_COL+3)) ast_Eg; es6_steal; continue;; *) _OP="**"; _np=12;; esac;; '+'*) case ${IO_FEED_BUF#?} in '+'*) IO_FEED_CONSUMED="++"; IO_FEED_BUF="${IO_FEED_BUF#??}"; IO_FEED_COL=$((IO_FEED_COL+2)) ast_Eq; es6_steal; ast_close; _XC=1; _PREV=; continue;; '='*) IO_FEED_CONSUMED="+="; IO_FEED_BUF="${IO_FEED_BUF#??}"; IO_FEED_COL=$((IO_FEED_COL+2)) ast_Eg; es6_steal; continue;; *) _OP="+"; _np=10;; esac;; '-'*) case ${IO_FEED_BUF#?} in '-'*) IO_FEED_CONSUMED="--"; IO_FEED_BUF="${IO_FEED_BUF#??}"; IO_FEED_COL=$((IO_FEED_COL+2)) ast_Eq; es6_steal; ast_close; _XC=1; _PREV=; continue;; '='*) IO_FEED_CONSUMED="-="; IO_FEED_BUF="${IO_FEED_BUF#??}"; IO_FEED_COL=$((IO_FEED_COL+2)) ast_Eg; es6_steal; continue;; *) _OP="-"; _np=10;; esac;; '*'*) _OP="*"; _np=11;; '/'*) case ${IO_FEED_BUF#?} in '/'*) ast_EK; IO_FEED_BUF="${IO_FEED_BUF#??}"; IO_FEED_COL=$((IO_FEED_COL+2)); continue;; '*'*) ast_EM; IO_FEED_BUF="${IO_FEED_BUF#??}"; IO_FEED_COL=$((IO_FEED_COL+2)); continue;; '='*) IO_FEED_CONSUMED="/="; IO_FEED_BUF="${IO_FEED_BUF#??}"; IO_FEED_COL=$((IO_FEED_COL+2)) ast_Eg; es6_steal; continue;; *) _OP="/"; _np=11;; esac;; '%'*) case ${IO_FEED_BUF#?} in '='*) IO_FEED_CONSUMED="%="; IO_FEED_BUF="${IO_FEED_BUF#??}"; IO_FEED_COL=$((IO_FEED_COL+2)) ast_Eg; es6_steal; continue;; *) _OP="%"; _np=11;; esac;; '^'*) case ${IO_FEED_BUF#?} in '='*) IO_FEED_CONSUMED="^="; IO_FEED_BUF="${IO_FEED_BUF#??}"; IO_FEED_COL=$((IO_FEED_COL+2)) ast_Eg; es6_steal; continue;; *) _OP="^"; _np=4;; esac;; '<'*) _OP="<"; _np=8;; '>'*) _OP=">"; _np=8;; '&'*) case ${IO_FEED_BUF#?} in '='*) IO_FEED_CONSUMED="&="; IO_FEED_BUF="${IO_FEED_BUF#??}"; IO_FEED_COL=$((IO_FEED_COL+2)) ast_Eg; es6_steal; continue;; *) _OP="&"; _np=5;; esac;; '|'*) case ${IO_FEED_BUF#?} in '='*) IO_FEED_CONSUMED="|="; IO_FEED_BUF="${IO_FEED_BUF#??}"; IO_FEED_COL=$((IO_FEED_COL+2)) ast_Eg; es6_steal; continue;; *) _OP="|"; _np=3;; esac;; # --- keyword operators --- [a-zA-Z]*) io_feed_more; IO_FEED_REST="${IO_FEED_BUF%%$_EIP}" case "$IO_FEED_REST" in 'in') case $STATE in Eh) _W="${NODES##*" "}"; eval "V$_W=in" IO_FEED_BUF="${IO_FEED_BUF#in}"; IO_FEED_COL=$((IO_FEED_COL+2)); continue;; EL|EW|EV) ast_close _W="${NODES##*" "}"; eval "V$_W=in" IO_FEED_BUF="${IO_FEED_BUF#in}"; IO_FEED_COL=$((IO_FEED_COL+2)); continue;; esac; _OP="in"; _np=7;; 'of') case $STATE in Eh) _W="${NODES##*" "}"; eval "V$_W=of" IO_FEED_BUF="${IO_FEED_BUF#of}"; IO_FEED_COL=$((IO_FEED_COL+2)); continue;; EL|EW|EV) ast_close _W="${NODES##*" "}"; eval "V$_W=of" IO_FEED_BUF="${IO_FEED_BUF#of}"; IO_FEED_COL=$((IO_FEED_COL+2)); continue;; esac;; 'instanceof') _OP="instanceof"; _np=7;; *) case $_NL in 1) case $STATE in Ed|EB|ES|Ej|EJ|Er|Ee) _NL=0;; *) ast_close; _XC=1; _PREV=; continue;; esac ;; esac;; esac;; # --- ternary / nullish / optional chaining --- '?'*) case ${IO_FEED_BUF#?} in '?'*) case ${IO_FEED_BUF#??} in '='*) IO_FEED_CONSUMED="??="; IO_FEED_BUF="${IO_FEED_BUF#???}"; IO_FEED_COL=$((IO_FEED_COL+3)) ast_Eg; es6_steal; continue;; *) _OP="??"; _np=1;; esac;; '.'*) # optional chaining ?.prop ?.() ?.[ case ${IO_FEED_BUF#??} in '['*) # optional bracket access ?.[ IO_FEED_BUF="${IO_FEED_BUF#???}"; IO_FEED_COL=$((IO_FEED_COL+3)) IO_FEED_CONSUMED="?"; ast_EI; es6_steal; continue;; '('*) # optional call ?.() IO_FEED_BUF="${IO_FEED_BUF#???}"; IO_FEED_COL=$((IO_FEED_COL+3)) IO_FEED_CONSUMED="?"; ast_EC; es6_steal; continue;; *) # optional member access ?.prop IO_FEED_BUF="${IO_FEED_BUF#??}"; IO_FEED_COL=$((IO_FEED_COL+2)) IO_FEED_CONSUMED="?"; ast_Ep; es6_steal; continue;; esac;; *) # ternary ? case $STATE in Ey) ast_close; _XC=1; _PREV=; continue;; esac IO_FEED_BUF="${IO_FEED_BUF#?}"; IO_FEED_COL=$((IO_FEED_COL+1)) ast_EQ; es6_steal; continue;; esac;; # --- postfix operations (always highest binding) --- '.'*) IO_FEED_BUF="${IO_FEED_BUF#?}"; IO_FEED_COL=$((IO_FEED_COL+1)) ast_Ep; es6_steal; continue;; '('*) IO_FEED_BUF="${IO_FEED_BUF#?}"; IO_FEED_COL=$((IO_FEED_COL+1)) ast_EC; es6_steal; continue;; '['*) IO_FEED_BUF="${IO_FEED_BUF#?}"; IO_FEED_COL=$((IO_FEED_COL+1)) ast_EI; es6_steal; continue;; # --- tagged template literal --- '`'*) IO_FEED_CONSUMED='t'; ast_EC; es6_steal ast_ET; io_feed_skip; continue;; # --- terminators: fall through to state dispatch --- ';'*|','*|'{'*|'}'*|')'*|']'*|':'*|'') ;; *) case $_NL in 1) io_feed_more; _ST="${IO_FEED_BUF%%$_EIP}" case "$_ST" in 'let'|'const'|'var'|'if'|'else'|'while'|'do'|'for'|'switch'|'try'|'class'|'function'|'return'|'throw'|'break'|'continue'|'debugger'|'export'|'import'|'async') case $STATE in Ed|EB|ES|Ej|EJ|Er|Ee) _NL=0;; # at statement level — fall through to main dispatch *) ast_close; _XC=1; _PREV=; continue;; esac ;; # keyword matched at statement level — exit _XC *) _NL=0; err_display UNEXPECTED TOKEN AFTER EXPRESSION;; esac;; *) err_display UNEXPECTED TOKEN AFTER EXPRESSION;; esac;; esac # Phase 2: If we peeked a binary op, handle precedence case "$_OP" in ?*) # If inside Ey with higher or equal precedence, close first (don't consume) case $STATE in Ey) _W="${NODES##*" "}" eval "_W=\"\${V$_W:-}\"" _lang_es6_parser_prec "$_W"; _cp=$REPLY case $((_np <= _cp)) in 1) ast_close; _XC=1; continue;; esac ;; esac # Commit: consume the operator from IO_FEED_BUF, create Ey IO_FEED_CONSUMED="$_OP"; IO_FEED_BUF="${IO_FEED_BUF#"$_OP"}"; IO_FEED_COL=$((IO_FEED_COL+${#_OP})) ast_Ey; es6_steal; continue ;; esac ;; esac # --- Fast paths --- case $STATE in Es) case "$IO_FEED_BUF" in "$_qch"*|'\'*|'') ;; *) io_feed_more; IO_FEED_REST="${IO_FEED_BUF%%["$_qch"\\]*}"; io_feed_bulk_nl;; esac;; E1) case "$IO_FEED_BUF" in '/'*|'\'*|'') ;; *) io_feed_more; IO_FEED_REST="${IO_FEED_BUF%%[/\\]*}"; io_feed_bulk;; esac;; En) case "$IO_FEED_BUF" in [0-9.eExXbBoOaAbBcCdDfF_]*) io_feed_more; IO_FEED_REST="${IO_FEED_BUF%%$_ENP}"; io_feed_bulk;; *) ast_close; _XC=1; continue;; esac;; ET) case "$IO_FEED_BUF" in '`'*|'$'*|'\'*|'') ;; *) io_feed_more; IO_FEED_REST="${IO_FEED_BUF%%[\`\$\\]*}"; io_feed_bulk_nl;; esac;; EK) case "$IO_FEED_BUF" in "$_EOL"*|'') ast_close; continue;; *) io_feed_more; IO_FEED_REST="${IO_FEED_BUF%%"$_EOL"*}"; io_feed_bulk;; esac;; EM) case "$IO_FEED_BUF" in '*'*) ;; '') case $IO_FEED_EOF in 1) err_display UNTERMINATED COMMENT;; esac;; *) io_feed_more; IO_FEED_REST="${IO_FEED_BUF%%[*]*}"; io_feed_bulk_nl;; esac;; Ed|EB|Eo|Ea|EC|Em|Eg|EG|EI|Ey|EU|EX|EL|EW|EV|Ep|EA|EQ|ER|EH|EF|Ew|ED|EO|Eh|EZ|EP|EN|ES|Ej|EJ|EY|Ez|Ex|Er|Et|Ee|Ev|EE) case "$IO_FEED_BUF" in ' '*|"$_TAB"*|"$_EOL"*) io_feed_skip_wse; continue;; esac;; esac case "$IO_FEED_BUF" in # --- strings --- '"'*|"'"*) case $STATE in Es) # closing quote (must match opening) case "${IO_FEED_BUF%"${IO_FEED_BUF#?}"}" in "$_qch") ast_close; io_feed_skip; IO_FEED_CONSUMED=; _XC=1; continue;; *) io_feed_consume;; # mismatched quote is content esac;; *) _qch="${IO_FEED_BUF%"${IO_FEED_BUF#?}"}" ast_Es; io_feed_skip; continue;; esac;; # --- template literal --- '`'*) case $STATE in ET) # closing backtick case "$IO_FEED_CONSUMED" in ?*) ast_Ef; ast_close;; esac io_feed_skip; ast_close # auto-close tagged template EC _W="${NODES##*" "}" eval "_W=\"\${V$_W:-}\"" case "$_W" in 't') ast_close;; esac _XC=1; continue;; *) ast_ET; io_feed_skip; continue;; esac;; # --- numbers --- [0-9]*) case $STATE in Ed|EB|Eo|Ea|EC|EG|EI|Ey|EU|Em|Eg|EX|EL|EW|EV|EA|EQ|ER|EH|Eh|EN|Ej|EJ|Ez|Ee|Ev|EE) ast_En;; *) err_display UNEXPECTED NUMBER;; esac;; # --- template interpolation / $ identifier --- '$'*) case $STATE in ET) case ${IO_FEED_BUF#?} in '{'*) case "$IO_FEED_CONSUMED" in ?*) ast_Ef; ast_close;; esac ast_EX; IO_FEED_BUF="${IO_FEED_BUF#??}"; IO_FEED_COL=$((IO_FEED_COL+2)); continue;; *) io_feed_consume;; esac;; *) # $ as identifier start — fall through to identifier handler io_feed_more; IO_FEED_REST="${IO_FEED_BUF%%$_EIP}" _io_feed_xbulk; _io_feed_advcol ast_Ei; ast_close; _XC=1; continue;; esac;; # --- identifiers / keywords --- [a-zA-Z_$]*) case $STATE in Ep) # dot property name — store in Ep's V, close io_feed_more; IO_FEED_REST="${IO_FEED_BUF%%$_EIP}" _io_feed_xbulk; _io_feed_advcol _W="${NODES##*" "}" eval "_W=\"\${V$_W:-}\$IO_FEED_CONSUMED\"" eval "V${NODES##*" "}=\"\$_W\"" IO_FEED_CONSUMED= ast_close; _XC=1; continue;; EZ) # function name — append to EZ's V (preserves * prefix) io_feed_more; IO_FEED_REST="${IO_FEED_BUF%%$_EIP}" _io_feed_xbulk; _io_feed_advcol _W="${NODES##*" "}" eval "V$_W=\"\${V$_W:-}\$IO_FEED_CONSUMED\"" IO_FEED_CONSUMED= continue;; Ex) # class name, extends keyword, or superclass io_feed_more; IO_FEED_REST="${IO_FEED_BUF%%$_EIP}" _io_feed_xbulk; _io_feed_advcol case "$IO_FEED_CONSUMED" in 'extends') IO_FEED_CONSUMED=; continue;; *) _W="${NODES##*" "}" eval "_W=\"\${V$_W:-}\"" case "$_W" in ?*) # name already set → superclass expression ast_Ei; ast_close; _XC=1; continue;; *) # class name — store in Ex's V _W="${NODES##*" "}" eval "V$_W=\"\$IO_FEED_CONSUMED\"" IO_FEED_CONSUMED=; continue;; esac;; esac;; Er) # class method name, modifier, or field io_feed_more; IO_FEED_REST="${IO_FEED_BUF%%$_EIP}" _io_feed_xbulk; _io_feed_advcol case "$IO_FEED_CONSUMED" in 'static'|'get'|'set'|'async') # peek past next identifier: ( = method, else = field _ST="$IO_FEED_BUF" case "$_ST" in ' '*|"$_TAB"*) _ST="${_ST#"${_ST%%[! $_TAB]*}"}";; esac case "$_ST" in [a-zA-Z_$'#']*) _ST="${_ST%%$_EIP}" _ST="${IO_FEED_BUF#*"$_ST"}" case "$_ST" in ' '*|"$_TAB"*) _ST="${_ST#"${_ST%%[! $_TAB]*}"}";; esac case "$_ST" in '('*) ast_Et; continue;; esac ;; esac # not a method — modifier + field _ST="$IO_FEED_CONSUMED"; IO_FEED_CONSUMED= case "$IO_FEED_BUF" in ' '*|"$_TAB"*) io_feed_skip_ws;; esac io_feed_more; IO_FEED_REST="${IO_FEED_BUF%%$_EIP}" _io_feed_xbulk; _io_feed_advcol IO_FEED_CONSUMED="$_ST $IO_FEED_CONSUMED" case "$IO_FEED_BUF" in ' '*|"$_TAB"*) io_feed_skip_ws;; esac case "$IO_FEED_BUF" in '='[!'>']*) ast_Ei; ast_close IO_FEED_CONSUMED="="; io_feed_skip ast_Eg; es6_steal; continue;; *) ast_Ei; ast_close; continue;; esac;; *) # peek: ( = method, = = field, else = shorthand field case "$IO_FEED_BUF" in ' '*|"$_TAB"*) io_feed_skip_ws;; esac case "$IO_FEED_BUF" in '('*) ast_Et; continue;; '='*) # class field: name = value ast_Ei; ast_close IO_FEED_CONSUMED="="; io_feed_skip ast_Eg; es6_steal; continue;; *) ast_Ei; ast_close; continue;; esac;; esac;; Et) # method name after modifier (static/get/set) io_feed_more; IO_FEED_REST="${IO_FEED_BUF%%$_EIP}" _io_feed_xbulk; _io_feed_advcol _W="${NODES##*" "}" eval "_W=\"\${V$_W:-}\"" case "$_W" in '*') ;; ?*) _W="$_W ";; esac _W="$_W$IO_FEED_CONSUMED" eval "V${NODES##*" "}=\"\$_W\"" IO_FEED_CONSUMED=; continue;; EP) # parameter name (with optional default value) io_feed_more; IO_FEED_REST="${IO_FEED_BUF%%$_EIP}" _io_feed_xbulk; _io_feed_advcol ast_Ei; ast_close case "$IO_FEED_BUF" in ' '*|"$_TAB"*) io_feed_skip_ws;; esac case "$IO_FEED_BUF" in '='[!'>']*) IO_FEED_CONSUMED="="; io_feed_skip ast_Eg; es6_steal; continue;; esac continue;; Eo) # object: peek after identifier for member vs shorthand vs method io_feed_more; IO_FEED_REST="${IO_FEED_BUF%%$_EIP}" _io_feed_xbulk; _io_feed_advcol case "$IO_FEED_BUF" in ' '*|"$_TAB"*) io_feed_skip_ws;; esac case "$IO_FEED_BUF" in ':'*) # key: value — traditional member _ST="$IO_FEED_CONSUMED"; IO_FEED_CONSUMED= ast_Em; IO_FEED_CONSUMED="$_ST" ast_Ei; ast_close; continue;; '('*) # method shorthand: f() {} ast_Et; continue;; [a-zA-Z_$]*) # modifier, rename, or next shorthand case "$IO_FEED_CONSUMED" in 'get'|'set'|'async'|'static') ast_Et; continue;; esac # check for 'as' rename (export/import) _ST="${IO_FEED_BUF%%$_EIP}" case "$_ST" in 'as') _ST="$IO_FEED_CONSUMED"; IO_FEED_CONSUMED="as" ast_Em; IO_FEED_CONSUMED="$_ST" ast_Ei; ast_close IO_FEED_BUF="${IO_FEED_BUF#as}"; IO_FEED_COL=$((IO_FEED_COL+2)) continue;; esac ast_Ei; ast_close; continue;; '*'*) # generator method ast_Et; continue;; '='[!'>']*) # destructuring default: {a = 1} ast_Ei; ast_close IO_FEED_CONSUMED="="; io_feed_skip ast_Eg; es6_steal; continue;; *) # shorthand property: {x, y} ast_Ei; ast_close; continue;; esac;; Ed|EB|Ea|EC|EG|EI|Ey|EU|Em|Eg|EX|EL|EW|EV|EA|EQ|ER|EH|Eh|EF|EN|ES|Ej|EJ|Ez|Ee|Ev|EE) io_feed_more; IO_FEED_REST="${IO_FEED_BUF%%$_EIP}" _io_feed_xbulk; _io_feed_advcol case "$IO_FEED_CONSUMED" in 'true'|'false') ast_Eb; ast_close; _XC=1; continue;; 'null') IO_FEED_CONSUMED=; ast_El; ast_close; _XC=1; continue;; 'undefined') IO_FEED_CONSUMED=; ast_Eu; ast_close; _XC=1; continue;; 'let') IO_FEED_CONSUMED=; ast_EL; continue;; 'const') IO_FEED_CONSUMED=; ast_EW; continue;; 'var') IO_FEED_CONSUMED=; ast_EV; continue;; 'typeof'|'void'|'await'|'async') ast_EU; continue;; 'yield') ast_ER; continue;; 'return') IO_FEED_CONSUMED=; ast_ER; continue;; 'throw') IO_FEED_CONSUMED=; ast_EH; continue;; 'break') IO_FEED_CONSUMED=; ast_Ek; case "$IO_FEED_BUF" in ' '[a-zA-Z_$]*) io_feed_skip_ws; io_feed_more IO_FEED_REST="${IO_FEED_BUF%%$_EIP}"; _io_feed_xbulk; _io_feed_advcol _W="${NODES##*" "}" eval "V$_W=\"\$IO_FEED_CONSUMED\""; IO_FEED_CONSUMED= ;; esac; ast_close; continue;; 'continue') IO_FEED_CONSUMED=; ast_Ec; case "$IO_FEED_BUF" in ' '[a-zA-Z_$]*) io_feed_skip_ws; io_feed_more IO_FEED_REST="${IO_FEED_BUF%%$_EIP}"; _io_feed_xbulk; _io_feed_advcol _W="${NODES##*" "}" eval "V$_W=\"\$IO_FEED_CONSUMED\""; IO_FEED_CONSUMED= ;; esac; ast_close; continue;; 'debugger') IO_FEED_CONSUMED="debugger"; ast_Ek; ast_close; continue;; 'if') IO_FEED_CONSUMED=; ast_EF; continue;; 'while') IO_FEED_CONSUMED=; ast_Ew; continue;; 'do') IO_FEED_CONSUMED=; ast_ED; continue;; 'for') IO_FEED_CONSUMED=; ast_EO case "$IO_FEED_BUF" in ' '*|"$_TAB"*) io_feed_skip_ws;; esac case "$IO_FEED_BUF" in 'await'[!a-zA-Z0-9_$]*) IO_FEED_BUF="${IO_FEED_BUF#await}"; IO_FEED_COL=$((IO_FEED_COL+5)) _W="${NODES##*" "}" eval "V$_W=await" ;; esac; continue;; 'function') IO_FEED_CONSUMED=; ast_EZ; continue;; 'case') IO_FEED_CONSUMED= case $STATE in Ej|EJ) ast_close;; esac ast_Ej; continue;; 'default') case $STATE in Ev) _W="${NODES##*" "}" eval "V$_W=default" IO_FEED_CONSUMED=; continue;; esac; IO_FEED_CONSUMED= case $STATE in Ej|EJ) ast_close;; esac ast_EJ; continue;; 'new') IO_FEED_CONSUMED=; ast_EN; continue;; 'delete') ast_EU; continue;; 'switch') IO_FEED_CONSUMED=; ast_ES; continue;; 'try') IO_FEED_CONSUMED=; ast_EY; continue;; 'class') IO_FEED_CONSUMED=; ast_Ex; continue;; 'export') IO_FEED_CONSUMED=; ast_Ev; continue;; 'import') IO_FEED_CONSUMED=; ast_EE; continue;; 'from') case $STATE in EE|Ev) IO_FEED_CONSUMED=; continue;; esac ast_Ei; ast_close; _XC=1; continue;; *) ast_Ei; ast_close; _XC=1; continue;; esac;; *) err_display UNEXPECTED IDENTIFIER;; esac;; # --- string closing / escape --- '\'*) case $STATE in Es) case ${IO_FEED_BUF#?} in '"'*|"'"*|'\'*|'n'*|'r'*|'t'*|\ 'b'*|'f'*|'v'*|'0'*|'u'*|'x'*) io_feed_consume2;; *) err_display ESCAPE;; esac;; ET) case ${IO_FEED_BUF#?} in '`'*|'\'*|'$'*|'n'*|'r'*|'t'*) io_feed_consume2;; *) err_display ESCAPE;; esac;; E1) io_feed_consume2;; # regex: any escape is valid *) err_display UNEXPECTED \;; esac;; # --- closing string --- # (handled via fast path and _qch match) # --- unary operators --- '!'*) case $STATE in Ed|EB|Ea|EC|EG|EI|Ey|EU|Eg|EX|EL|EW|EV|EA|EQ|ER|EH|Eh|EN|Ej|EJ|Ez|Ee|Ev|EE) IO_FEED_CONSUMED="!"; io_feed_skip; ast_EU; continue;; *) err_display 'UNEXPECTED !';; esac;; '-'*) case $STATE in Ed|EB|Ea|EC|EG|EI|Ey|EU|Eg|EX|EL|EW|EV|EA|EQ|ER|EH|Eh|EN|Ej|EJ|Ez|Ee|Ev|EE) case ${IO_FEED_BUF#?} in '-'*) IO_FEED_CONSUMED="--"; IO_FEED_BUF="${IO_FEED_BUF#??}"; IO_FEED_COL=$((IO_FEED_COL+2)) ast_EU; continue;; *) IO_FEED_CONSUMED="-"; io_feed_skip; ast_EU; continue;; esac;; *) err_display 'UNEXPECTED -';; esac;; '+'*) case $STATE in Ed|EB|Ea|EC|EG|EI|Ey|EU|Eg|EX|EL|EW|EV|EA|EQ|ER|EH|Eh|EN|Ej|EJ|Ez|Ee|Ev|EE) case ${IO_FEED_BUF#?} in '+'*) IO_FEED_CONSUMED="++"; IO_FEED_BUF="${IO_FEED_BUF#??}"; IO_FEED_COL=$((IO_FEED_COL+2)) ast_EU; continue;; *) IO_FEED_CONSUMED="+"; io_feed_skip; ast_EU; continue;; esac;; *) err_display 'UNEXPECTED +';; esac;; '~'*) case $STATE in Ed|EB|Ea|EC|EG|EI|Ey|EU|Eg|EX|EL|EW|EV|EA|EQ|ER|EH|Eh|EN|Ej|EJ|Ez|Ee|Ev|EE) IO_FEED_CONSUMED="~"; io_feed_skip; ast_EU; continue;; *) err_display 'UNEXPECTED ~';; esac;; # --- open paren --- '('*) case $STATE in Ed|EB|Ea|EC|EG|EI|Ey|EU|Em|Eg|EX|EL|EW|EV|EA|EQ|ER|EH|Eh|EF|EN|ES|Ej|EJ|Ez|Ee|Ev|EE) ast_EG; io_feed_skip; continue;; EF|Ew|ED|ES|EY) # condition paren for if/while/do-while/switch/catch ast_EG; io_feed_skip; continue;; EO) # for-header paren ast_Eh; io_feed_skip; continue;; EZ|Et) # function/method parameter list ast_EP; io_feed_skip; continue;; *) err_display 'UNEXPECTED (';; esac;; # --- close paren --- ')'*) case $STATE in EG) io_feed_skip; ast_close_col; case $STATE in EF|Ew|ES|EY) continue;; ED) ast_close; continue;; Eh) continue;; esac _XC=1; continue;; EC) io_feed_skip; ast_close; _XC=1; continue;; EN) io_feed_skip; ast_close; _XC=1; continue;; Ey) es6_xclose;; EU) es6_xclose;; Eg) es6_xclose;; EA) es6_xclose;; EQ) es6_xclose;; ER|EH) es6_xclose;; Ez) es6_xclose;; EP) io_feed_skip; ast_close; continue;; Eh) io_feed_skip; ast_close; continue;; *) err_display 'UNEXPECTED ) UNMATCHED';; esac;; # --- open bracket --- '['*) case $STATE in Eo) # computed property key: {[expr]: val} ast_Em; ast_Ea; io_feed_skip; continue;; Ed|EB|Ea|EC|EG|EI|Ey|EU|Em|Eg|EX|EL|EW|EV|EA|EQ|ER|EH|Eh|EF|EN|ES|Ej|EJ|Ez|Ee|Ev|EE) ast_Ea; io_feed_skip case "$IO_FEED_BUF" in ' '*|"$_TAB"*) io_feed_skip_ws;; esac case "$IO_FEED_BUF" in ','*) IO_FEED_CONSUMED=","; ast_Eu; ast_close;; esac continue;; *) err_display 'UNEXPECTED [';; esac;; # --- close bracket --- ']'*) case $STATE in Ea) io_feed_skip; ast_close; _XC=1; continue;; EI) io_feed_skip; ast_close; _XC=1; continue;; Ey) es6_xclose;; EU) es6_xclose;; Eg) es6_xclose;; EA) es6_xclose;; EQ) es6_xclose;; ER|EH) es6_xclose;; Ez) es6_xclose;; *) err_display 'UNEXPECTED ] UNMATCHED';; esac;; # --- open brace --- '{'*) case $STATE in ES) io_feed_skip; continue;; Ex) ast_Er; io_feed_skip; continue;; EA|Ed|EB|Ey|EU|EX|EF|Ew|ED|EO|EZ|EY|Ej|EJ|Et|Ee) ast_EB; io_feed_skip; continue;; Ea|EC|EG|EI|Em|EL|EW|EV|Eg|ER|EH|EQ|Eh|Ez|Ev|EE) ast_Eo; io_feed_skip; continue;; *) err_display 'UNEXPECTED {';; esac;; # --- close brace --- '}'*) case $STATE in ES) io_feed_skip; ast_close; _XC=1; continue;; Er) io_feed_skip; ast_close; ast_close; _XC=1; continue;; Ej|EJ) ast_close; continue;; Eo) io_feed_skip; ast_close; _XC=1; continue;; EX) io_feed_skip; ast_close; continue;; EB) io_feed_skip; ast_close; case $STATE in EF) # peek for 'else' case "$IO_FEED_BUF" in ' '*|"$_TAB"*) io_feed_skip_ws;; esac case "$IO_FEED_BUF" in "$_EOL"*) io_feed_skip_wse;; esac io_feed_more case "$IO_FEED_BUF" in 'else'[!a-zA-Z0-9_$]*|'else') IO_FEED_BUF="${IO_FEED_BUF#else}"; IO_FEED_COL=$((IO_FEED_COL+4)) continue;; *) ast_close case $STATE in Ee) ast_close;; esac continue;; esac;; Ew|EO) ast_close case $STATE in Ee) ast_close;; esac continue;; ED) # peek for 'while' case "$IO_FEED_BUF" in ' '*|"$_TAB"*) io_feed_skip_ws;; esac case "$IO_FEED_BUF" in "$_EOL"*) io_feed_skip_wse;; esac io_feed_more case "$IO_FEED_BUF" in 'while'[!a-zA-Z0-9_$]*) IO_FEED_BUF="${IO_FEED_BUF#while}"; IO_FEED_COL=$((IO_FEED_COL+5)) continue;; *) err_display DO WHILE;; esac;; EZ) ast_close; continue;; Et) ast_close; continue;; Ee) ast_close; continue;; EY) # peek for catch/finally case "$IO_FEED_BUF" in ' '*|"$_TAB"*) io_feed_skip_ws;; esac case "$IO_FEED_BUF" in "$_EOL"*) io_feed_skip_wse;; esac io_feed_more case "$IO_FEED_BUF" in 'catch'[!a-zA-Z0-9_\$]*) IO_FEED_BUF="${IO_FEED_BUF#catch}"; IO_FEED_COL=$((IO_FEED_COL+5)) continue;; 'finally'[!a-zA-Z0-9_\$]*) IO_FEED_BUF="${IO_FEED_BUF#finally}"; IO_FEED_COL=$((IO_FEED_COL+7)) continue;; *) ast_close; continue;; esac;; *) _XC=1; continue;; esac;; Ey) es6_xclose;; EU) es6_xclose;; Eg) es6_xclose;; Em) es6_xclose;; EQ) es6_xclose;; ER) ast_close;; EH) es6_xclose;; Ez) es6_xclose;; *) err_display 'UNEXPECTED } UNMATCHED';; esac;; # --- colon (object member separator) --- ':'*) case $STATE in Em) io_feed_skip; continue;; Ej|EJ) _W="${NODES##*" "}"; eval "V$_W=:" io_feed_skip; continue;; EQ) _W="${NODES##*" "}" eval "_W=\"\${V$_W:-}\"" case "$_W" in ':') es6_xclose;; *) _W="${NODES##*" "}"; eval "V$_W=:" io_feed_skip; continue;; esac;; Ey|EU|Eg|EA) es6_xclose;; Ed|EB) # labeled statement: steal preceding Ei io_feed_skip; ast_Ee; es6_steal; continue;; *) err_display 'UNEXPECTED :';; esac;; # --- comma --- ','*) case $STATE in Eo) io_feed_skip; continue;; Ea) io_feed_skip case "$IO_FEED_BUF" in ' '*|"$_TAB"*) io_feed_skip_ws;; esac case "$IO_FEED_BUF" in ','*) IO_FEED_CONSUMED=","; ast_Eu; ast_close;; esac continue;; EC) io_feed_skip; continue;; EG) io_feed_skip; continue;; Em) ast_close; io_feed_skip; continue;; Ey) es6_xclose;; EU) es6_xclose;; Eg) es6_xclose;; EL|EW|EV) io_feed_skip; continue;; EA) es6_xclose;; EQ) es6_xclose;; ER|EH) es6_xclose;; Ez) es6_xclose;; EP) io_feed_skip; continue;; Eh) io_feed_skip; continue;; EN) io_feed_skip; continue;; Ed|EB|Ej|EJ|Ee) io_feed_skip; continue;; *) err_display 'UNEXPECTED ,';; esac;; # --- semicolon --- ';'*) case $STATE in Ed|EB|Ej|EJ|Ee|Er) io_feed_skip; continue;; EF) ast_close; io_feed_skip; continue;; Ey|EU|Eg|Ez|Ev|EE) es6_xclose;; EL|EW|EV) ast_close; io_feed_skip; continue;; EA) es6_xclose;; EQ) es6_xclose;; ER) ast_close; io_feed_skip; continue;; EH) es6_xclose;; Eh) io_feed_skip; continue;; *) err_display 'UNEXPECTED ;';; esac;; # --- block comment close / generator star --- '*'*) case $STATE in EM) case ${IO_FEED_BUF#?} in '/'*) ast_close; IO_FEED_BUF="${IO_FEED_BUF#??}"; IO_FEED_COL=$((IO_FEED_COL+2)); continue;; *) io_feed_consume;; esac;; EZ) # function* generator marker — prefix V with * _W="${NODES##*" "}" eval "V$_W=\"*\${V$_W:-}\"" io_feed_skip; continue;; Er) # class method *name() — generator method IO_FEED_CONSUMED="*"; io_feed_skip; ast_Et; continue;; EE) # import * as x — namespace import IO_FEED_CONSUMED="*"; io_feed_skip ast_Ei; ast_close; _XC=1; continue;; Ev) # export * from "mod" — re-export IO_FEED_CONSUMED="*"; io_feed_skip ast_Ei; ast_close; continue;; Eo) # object spread with * — generator method IO_FEED_CONSUMED="*"; io_feed_skip; ast_Et; continue;; *) io_feed_consume;; esac;; # --- string close --- # This catches the quote char when fast-path didn't # (e.g. empty string or quote immediately after escape) # --- slash (division in value context, or start comment/regex) --- '/'*) case $STATE in E1) # closing /flags — store pattern, collect flags IO_FEED_CONSUMED="$IO_FEED_CONSUMED/" io_feed_skip; IO_FEED_REST="${IO_FEED_BUF%%$_EIP}" case "$IO_FEED_REST" in ?*) _io_feed_xbulk; _io_feed_advcol;; esac _W="${NODES##*" "}" eval "V$_W=\"\$IO_FEED_CONSUMED\"" IO_FEED_CONSUMED= ast_close; _XC=1; continue;; Ed|EB|Ea|EC|EG|EI|Ey|EU|Em|Eg|EX|EL|EW|EV|EA|EQ|ER|EH|Eh|EF|EN|ES|Ej|EJ|Ez|Ee|Ev|EE) case ${IO_FEED_BUF#?} in '/'*) ast_EK; IO_FEED_BUF="${IO_FEED_BUF#??}"; IO_FEED_COL=$((IO_FEED_COL+2)); continue;; '*'*) ast_EM; IO_FEED_BUF="${IO_FEED_BUF#??}"; IO_FEED_COL=$((IO_FEED_COL+2)); continue;; *) ast_E1; io_feed_skip; continue;; esac;; *) err_display 'UNEXPECTED /';; esac;; # --- spread/rest / new.target --- '.'*) case ${IO_FEED_BUF#?} in '.''.'*) case $STATE in Ea|EC|EG|EP|Eo|Ej|EJ|EB|Ed) IO_FEED_CONSUMED="..."; IO_FEED_BUF="${IO_FEED_BUF#???}"; IO_FEED_COL=$((IO_FEED_COL+3)) ast_Ez; continue;; esac;; 'target'[!a-zA-Z0-9_$]*|'target') case $STATE in EN) IO_FEED_BUF="${IO_FEED_BUF#?target}"; IO_FEED_COL=$((IO_FEED_COL+7)) ast_discard # discard bare EN IO_FEED_CONSUMED="new.target" ast_Ei; ast_close; _XC=1; continue;; esac;; esac err_display UNEXPECTED CHARACTER;; # --- private identifier (#name) --- '#'*) case $STATE in Er) # class private field/method IO_FEED_CONSUMED="#"; io_feed_skip io_feed_more; IO_FEED_REST="${IO_FEED_BUF%%$_EIP}" _io_feed_xbulk; _io_feed_advcol case "$IO_FEED_BUF" in ' '*|"$_TAB"*) io_feed_skip_ws;; esac case "$IO_FEED_BUF" in '('*) ast_Et; continue;; '='*) ast_Ei; ast_close IO_FEED_CONSUMED="="; io_feed_skip ast_Eg; es6_steal; continue;; *) ast_Ei; ast_close; continue;; esac;; Ep) # private property access: this.#x IO_FEED_CONSUMED="#"; io_feed_skip io_feed_more; IO_FEED_REST="${IO_FEED_BUF%%$_EIP}" _io_feed_xbulk; _io_feed_advcol _W="${NODES##*" "}" eval "_W=\"\${V$_W:-}\$IO_FEED_CONSUMED\"" eval "V${NODES##*" "}=\"\$_W\"" IO_FEED_CONSUMED= ast_close; _XC=1; continue;; *) # #x at expression start (for #x in obj) IO_FEED_CONSUMED="#"; io_feed_skip io_feed_more; IO_FEED_REST="${IO_FEED_BUF%%$_EIP}" _io_feed_xbulk; _io_feed_advcol ast_Ei; ast_close; _XC=1; continue;; esac;; # --- end of input --- "") case $STATE in Ed) break;; En) ast_close; _XC=1; continue;; Ey) es6_xclose;; EU) es6_xclose;; Eg) es6_xclose;; EA) es6_xclose;; EQ) es6_xclose;; ER) ast_close; continue;; EH) es6_xclose;; EF) ast_close; continue;; EL|EW|EV) ast_close; continue;; Es) err_display UNTERMINATED STRING;; E1) err_display UNTERMINATED REGEX;; ET|Ef) err_display UNTERMINATED TEMPLATE;; EG) err_display 'UNMATCHED (';; Ea) err_display 'UNMATCHED [';; Eo|Em) err_display 'UNMATCHED {';; EB) err_display 'UNMATCHED {';; EC) err_display 'UNMATCHED (';; EI) err_display 'UNMATCHED [';; EP) err_display 'UNMATCHED (';; Eh) err_display 'UNMATCHED (';; Ez|Ev|EE) es6_xclose;; EN) ast_close; _XC=1; continue;; ES|Ej|EJ) err_display UNTERMINATED SWITCH;; Ex|Er|Et|Ee) err_display UNTERMINATED CLASS;; EY) err_display UNTERMINATED TRY;; EZ) err_display UNTERMINATED FUNCTION;; Ew) err_display UNTERMINATED WHILE;; ED) err_display UNTERMINATED DO;; EO) err_display UNTERMINATED FOR;; *) err_display UNEXPECTED END OF INPUT;; esac;; *) err_display UNEXPECTED CHARACTER;; esac done ast_out } # ES6 -> shell runtime library. # # Value model: every JS value is a tagged shell string. # n<num> number # s<chars> string # b0 / b1 boolean # l null # u undefined # o<id> object handle (plain object, array, function, class instance) # # Heap: flat globals keyed by object id. # _JSO_<id>_C [[Class]]: Object Array Function ConsoleLog # _JSO_<id>_P prototype link (object id, 0 = no proto) # _JSO_<id>_K space-separated own-key list (enumeration order) # _JSO_<id>_k_<key> own property value # Function-specific: # _JSO_<id>_F shell function name implementing the body # _JSO_<id>_S captured lexical scope id # _JSO_<id>_A parameter name list (space-separated) # _JSO_<id>_L 1 if arrow (lexical this), else unset # _JSO_<id>_T captured this (arrow only) # # Scope: flat globals keyed by scope id. Scope 0 = global. # _JSS_<sid>_P parent scope id (unset at root) # _JSS_<sid>_v_<name> binding value (tagged) _JS_NEXT_ID=0 _JS_NEXT_SID=0 _lang_es6_rt_err () { _printr1 "es6: $1" >&2; exit 1; } # --- object allocation --- # $1=class $2=proto-id; REPLY=o<id> _lang_es6_rt_obj_new () { _JS_NEXT_ID=$((_JS_NEXT_ID + 1)) eval "_JSO_${_JS_NEXT_ID}_C=\$1 _JSO_${_JS_NEXT_ID}_P=\$2 _JSO_${_JS_NEXT_ID}_K=" REPLY=o$_JS_NEXT_ID } # --- scope allocation --- # $1=parent sid; REPLY=<sid> _lang_es6_rt_scope_new () { _JS_NEXT_SID=$((_JS_NEXT_SID + 1)) eval "_JSS_${_JS_NEXT_SID}_P=\$1" REPLY=$_JS_NEXT_SID } # --- variable load: $1=name $2=sid --- _lang_es6_rt_load () { _rs=$2 while :; do eval "REPLY=\${_JSS_${_rs}_v_$1:-}" case $REPLY in ?*) return;; esac eval "_rs=\${_JSS_${_rs}_P:-X}" case $_rs in X) REPLY=u; return;; esac done } # --- variable store (walks to enclosing definition): $1=name $2=val $3=sid --- _lang_es6_rt_store () { _rs=$3 while :; do eval "_rv=\${_JSS_${_rs}_v_$1:-}" case $_rv in ?*) eval "_JSS_${_rs}_v_$1=\$2"; REPLY=$2; return;; esac eval "_rs=\${_JSS_${_rs}_P:-X}" case $_rs in X) break;; esac done # Not found: create at global (sloppy mode) eval "_JSS_0_v_$1=\$2" REPLY=$2 } # --- declaration: always writes to the given scope: $1=name $2=val $3=sid --- _lang_es6_rt_decl () { eval "_JSS_${3}_v_$1=\$2" } # --- property get: $1=value $2=key; REPLY=<tagged> --- _lang_es6_rt_get () { case $1 in o*) _rid=${1#o};; s*) case $2 in length) _rs=${1#s}; REPLY=n${#_rs}; return;; esac _rid=4;; n*) _rid=5;; b*) _rid=6;; l|u) _lang_es6_rt_err "TypeError: cannot read property '$2' of $1";; *) REPLY=u; return;; esac while :; do eval "REPLY=\${_JSO_${_rid}_k_$2:-}" case $REPLY in ?*) return;; esac eval "_rid=\${_JSO_${_rid}_P:-0}" case $_rid in 0) REPLY=u; return;; esac done } # --- property set: $1=obj $2=key $3=val --- _lang_es6_rt_set () { case $1 in o*) _rid=${1#o};; *) return;; esac eval "_rk=\$_JSO_${_rid}_K" case " $_rk " in *" $2 "*) ;; *) case $_rk in '') eval "_JSO_${_rid}_K=\$2";; *) eval "_JSO_${_rid}_K=\"\$_rk \$2\"";; esac;; esac eval "_JSO_${_rid}_k_$2=\$3" } # --- make function object: $1=shell-fn $2=defining-sid $3=params $4=lexthis (0/1) $5=captured-this --- _lang_es6_rt_mkfn () { _lang_es6_rt_obj_new Function 2 _rf=${REPLY#o} eval "_JSO_${_rf}_F=\$1 _JSO_${_rf}_S=\$2 _JSO_${_rf}_A=\$3" case $4 in 1) # arrow function: lexical this, no .prototype eval "_JSO_${_rf}_L=1 _JSO_${_rf}_T=\$5" REPLY=o$_rf return;; esac # Non-arrow: auto-create .prototype with .constructor back-ref. # The constructor back-ref is non-enumerable: set the slot directly # without touching _K so for...in does not iterate over it. _rfn=o$_rf _lang_es6_rt_obj_new Object 1 _lang_es6_rt_set "$_rfn" prototype "$REPLY" _rpid=${REPLY#o} eval "_JSO_${_rpid}_k_constructor=\$_rfn" REPLY=$_rfn } # --- call: $1=fn $2=this rest=args; REPLY=<return> --- _lang_es6_rt_call () { _rfn=$1; _rth=$2; shift 2 case $_rfn in o*) _rid=${_rfn#o};; *) _lang_es6_rt_err "TypeError: not a function: $_rfn";; esac eval "_rc=\${_JSO_${_rid}_C:-}" case $_rc in Function|ConsoleLog) ;; *) _lang_es6_rt_err "TypeError: not a function (class=$_rc)";; esac eval "_rshfn=\$_JSO_${_rid}_F" eval "_rpsid=\${_JSO_${_rid}_S:-0}" eval "_rlex=\${_JSO_${_rid}_L:-0}" case $_rlex in 1) eval "_rth=\$_JSO_${_rid}_T";; esac "$_rshfn" "$_rpsid" "$_rth" "$@" } # --- new: $1=ctor rest=args; REPLY=<new instance or ctor return> --- _lang_es6_rt_new () { _rctor=$1; shift case $_rctor in o*) ;; *) _lang_es6_rt_err "TypeError: not a constructor";; esac _lang_es6_rt_get "$_rctor" prototype case $REPLY in o*) _rpid=${REPLY#o};; *) _rpid=1;; esac _lang_es6_rt_obj_new Object "$_rpid" _rnew=$REPLY _lang_es6_rt_call "$_rctor" "$_rnew" "$@" case $REPLY in o*) ;; *) REPLY=$_rnew;; esac } # --- truthiness: sets shell exit status (0 = truthy) --- _lang_es6_rt_truthy () { case $1 in u|l|b0) return 1;; n0) return 1;; s) return 1;; *) return 0;; esac } # --- coercion: to string --- _lang_es6_rt_to_str () { case $1 in s*) REPLY=${1#s};; n*) REPLY=${1#n};; b0) REPLY=false;; b1) REPLY=true;; u) REPLY=undefined;; l) REPLY=null;; o*) _rid=${1#o}; eval "_rc=\$_JSO_${_rid}_C" case $_rc in Array) _lang_es6_rt_array_join "$1" ','; return;; Function) REPLY='function';; *) REPLY='[object Object]';; esac;; esac } # --- array join (comma by default) --- _lang_es6_rt_array_join () { _rid=${1#o}; _rsep=$2 eval "_rlen=\${_JSO_${_rid}_k_length:-n0}" _rn=${_rlen#n} _rr=; _ri=0 while test $_ri -lt $_rn; do case $_ri in 0) ;; *) _rr=$_rr$_rsep;; esac eval "_rv=\${_JSO_${_rid}_k_${_ri}:-u}" case $_rv in u|l) ;; *) _lang_es6_rt_to_str "$_rv"; _rr=$_rr$REPLY;; esac _ri=$((_ri + 1)) done REPLY=$_rr } # --- numeric coercion --- _lang_es6_rt_to_num () { case $1 in n*) REPLY=$1;; s*) _rv=${1#s}; case $_rv in '') REPLY=n0;; *) REPLY=n$_rv;; esac;; b0) REPLY=n0;; b1) REPLY=n1;; l) REPLY=n0;; *) REPLY=n0;; esac } # --- + (number or string based on operand tags) --- # If either operand is a string, result is string concat; otherwise numeric add. _lang_es6_rt_add () { case $1 in s*) _lang_es6_rt_to_str "$1"; _ra=$REPLY _lang_es6_rt_to_str "$2"; REPLY=s$_ra$REPLY; return;; esac case $2 in s*) _lang_es6_rt_to_str "$1"; _ra=$REPLY _lang_es6_rt_to_str "$2"; REPLY=s$_ra$REPLY; return;; esac REPLY=n$((${1#n} + ${2#n})) } _lang_es6_rt_sub () { REPLY=n$((${1#n} - ${2#n})); } _lang_es6_rt_mul () { REPLY=n$((${1#n} * ${2#n})); } _lang_es6_rt_div () { REPLY=n$((${1#n} / ${2#n})); } _lang_es6_rt_mod () { REPLY=n$((${1#n} % ${2#n})); } _lang_es6_rt_neg () { REPLY=n$((0 - ${1#n})); } _lang_es6_rt_lt () { case $((${1#n} < ${2#n})) in 1) REPLY=b1;; *) REPLY=b0;; esac; } _lang_es6_rt_le () { case $((${1#n} <= ${2#n})) in 1) REPLY=b1;; *) REPLY=b0;; esac; } _lang_es6_rt_gt () { case $((${1#n} > ${2#n})) in 1) REPLY=b1;; *) REPLY=b0;; esac; } _lang_es6_rt_ge () { case $((${1#n} >= ${2#n})) in 1) REPLY=b1;; *) REPLY=b0;; esac; } _lang_es6_rt_eeq () { case $1 in "$2") REPLY=b1;; *) REPLY=b0;; esac; } _lang_es6_rt_nee () { case $1 in "$2") REPLY=b0;; *) REPLY=b1;; esac; } _lang_es6_rt_eq () { _lang_es6_rt_eeq "$1" "$2"; } _lang_es6_rt_ne () { _lang_es6_rt_nee "$1" "$2"; } _lang_es6_rt_not () { if _lang_es6_rt_truthy "$1"; then REPLY=b0; else REPLY=b1; fi; } # --- set child's [[Proto]] to parent (for class extends): $1=child-obj $2=parent-obj --- _lang_es6_rt_setproto () { case $1 in o*) _rcid=${1#o};; *) return;; esac case $2 in o*) _rpid=${2#o};; *) _rpid=0;; esac eval "_JSO_${_rcid}_P=\$_rpid" } # ===== Bootstrap of built-in prototypes ===== # Fixed object ids: # 1 Object.prototype # 2 Function.prototype # 3 Array.prototype # 4 String.prototype # 5 Number.prototype # 6 Boolean.prototype _JS_NEXT_ID=0 _lang_es6_rt_obj_new Object 0 # id 1 _lang_es6_rt_obj_new Object 1 # id 2 Function.prototype _lang_es6_rt_obj_new Object 1 # id 3 Array.prototype _lang_es6_rt_obj_new Object 1 # id 4 String.prototype _lang_es6_rt_obj_new Object 1 # id 5 Number.prototype _lang_es6_rt_obj_new Object 1 # id 6 Boolean.prototype # --- native function: Array.prototype.push --- # shell fn signature: <parent-sid> <this> <arg1> ... _lang_es6_rt_n_array_push () { shift # parent-sid _nth=$1; shift case $_nth in o*) _nid=${_nth#o};; *) _lang_es6_rt_err "push on non-object";; esac eval "_nlen=\${_JSO_${_nid}_k_length:-n0}" _nn=${_nlen#n} for _narg; do eval "_JSO_${_nid}_k_${_nn}=\$_narg" _nn=$((_nn + 1)) done eval "_JSO_${_nid}_k_length=n\$_nn" REPLY=n$_nn } _lang_es6_rt_obj_new Function 2 ; _rfid=${REPLY#o} eval "_JSO_${_rfid}_F=_lang_es6_rt_n_array_push _JSO_${_rfid}_S=0 _JSO_${_rfid}_A=" _lang_es6_rt_set o3 push "o$_rfid" # --- native function: Number.prototype.toString --- _lang_es6_rt_n_num_tostr () { shift # parent-sid case $1 in n*) REPLY=s${1#n};; *) REPLY=sNaN;; esac } _lang_es6_rt_obj_new Function 2 ; _rfid=${REPLY#o} eval "_JSO_${_rfid}_F=_lang_es6_rt_n_num_tostr _JSO_${_rfid}_S=0 _JSO_${_rfid}_A=" _lang_es6_rt_set o5 toString "o$_rfid" # --- native function: console.log --- _lang_es6_rt_n_console_log () { shift 2 # parent-sid, this _nout= for _narg; do case $_nout in ?*) _nout=$_nout' ';; esac _lang_es6_rt_to_str "$_narg" _nout=$_nout$REPLY done _printr1 "$_nout" REPLY=u } _lang_es6_rt_obj_new ConsoleLog 2; _rfid=${REPLY#o} eval "_JSO_${_rfid}_F=_lang_es6_rt_n_console_log _JSO_${_rfid}_S=0 _JSO_${_rfid}_A=" # Global `console` object _lang_es6_rt_obj_new Object 1; _rconsole=$REPLY _lang_es6_rt_set "$_rconsole" log "o$_rfid" eval "_JSS_0_v_console=\$_rconsole" # ES6 AST -> shell codegen. # # Produces shell code that, when executed under runtime.sh, runs the JS program. # Emission buffers: # _CCOUT - accumulated body being built (either top-level or a function body) # _CCFNS - accumulated shell-function definitions (appended as encountered) # _CCT - monotonic temp counter; each saved value gets a unique _JT<n> # _CCF - monotonic function counter; each EZ/EA gets a unique _JS_fn_<n> # # Expression handlers append code that ends with the tagged value in $REPLY. # Callers that need to preserve the value across further emission save it into # a fresh _JT<n> temp and reference it as "$_JT<n>". # # Scope: one JS scope per shell function. Top-level scope id is 0. Inside a # function body, scope id lives in the shell variable $_SID. Block-level # let/const scoping is not modelled (Phase 1 limitation). # --- compile-time error abort --- _lang_es6_cc_err () { _printr1 "es6: compile error: $1" >&2; exit 1; } # --- shell single-quote escape --- _lang_es6_cc_shq () { local _r _s _r=; _s=$1 while :; do case $_s in *"'"*) _r=$_r${_s%%"'"*}"'\\''"; _s=${_s#*"'"};; *) _r=$_r$_s; break;; esac done REPLY=$_r } # --- identifier validation: ensures $1 is [A-Za-z_][A-Za-z0-9_]* --- # JS allows `$` in identifiers but POSIX shell variable names do not. # Phase 1: reject `$` and any other non-conforming character, rather than # silently splicing it into emitted shell (which breaks or injects). _lang_es6_cc_ident () { local _s _rest _s=$1 case $_s in '') _lang_es6_cc_err "empty identifier";; [0-9]*) _lang_es6_cc_err "identifier starts with digit: $_s";; esac _rest=$_s while :; do case $_rest in '') break;; [A-Za-z0-9_]*) _rest=${_rest#?};; *) _lang_es6_cc_err "identifier contains unsupported character: $_s";; esac done } # --- JS string escape processing --- # The parser stores string contents verbatim with escapes like \n left literal. # Convert the common ones: \n \t \r \\ \' \" \/ \b \f. # Others (\xHH, \uHHHH, \0, octal) are deferred — they pass through as literals. _lang_es6_cc_strproc () { local _s _r _s=$1; _r= while :; do case $_s in '') break;; '\'*) _s=${_s#?} case $_s in n*) _r=$_r$_EOL; _s=${_s#?};; t*) _r=$_r$_TAB; _s=${_s#?};; '\'*) _r=$_r'\'; _s=${_s#?};; "'"*) _r=$_r"'"; _s=${_s#?};; '"'*) _r=$_r'"'; _s=${_s#?};; '/'*) _r=$_r'/'; _s=${_s#?};; # Unknown escapes: keep backslash + char literal ?*) _r=$_r'\'${_s%"${_s#?}"}; _s=${_s#?};; '') _r=$_r'\';; esac;; *) _r=$_r${_s%"${_s#?}"} _s=${_s#?};; esac done REPLY=$_r } _lang_es6_cc_emit () { _CCOUT=$_CCOUT$1$_EOL; } # Allocate a fresh temp var name (scoped to the current function body). # Each function body has its own counter starting from 0. _lang_es6_cc_newtmp () { _CCT=$((_CCT + 1)) REPLY=_JT$_CCT } # Emit `local _JT1 _JT2 ...` for the temp range [1, $1]. _lang_es6_cc_locals () { local _i _r _i=1; _r= while test $_i -le $1; do _r="$_r _JT$_i" _i=$((_i + 1)) done REPLY=$_r } # Allocate a fresh function shell name. _lang_es6_cc_newfn () { _CCF=$((_CCF + 1)) REPLY=_JS_fn_$_CCF } # --- expression emit: after execution, REPLY holds the tagged value --- _lang_es6_cc_expr () { local _cn _ct _cv _cta _cto _ctf _ctk _ctr _tth _tfn _cn=$1 IFS=' '; eval "set -- \$X$_cn"; IFS='' _ct=$1; shift eval "_cv=\${V$_cn:-}" case $_ct in En) _lang_es6_cc_emit "REPLY=n$_cv";; Eb) case $_cv in true) _lang_es6_cc_emit "REPLY=b1";; *) _lang_es6_cc_emit "REPLY=b0";; esac;; El) _lang_es6_cc_emit "REPLY=l";; Eu) _lang_es6_cc_emit "REPLY=u";; Es) _lang_es6_cc_strproc "$_cv" _lang_es6_cc_shq "$REPLY" _lang_es6_cc_emit "REPLY='s$REPLY'";; Ei) case $_cv in undefined) _lang_es6_cc_emit "REPLY=u";; *) _lang_es6_cc_ident "$_cv" _lang_es6_cc_emit "_lang_es6_rt_load $_cv \"\$_SID\"";; esac;; Ey) _lang_es6_cc_binop "$_cv" "$1" "$2";; Eg) _lang_es6_cc_binop "$_cv" "$1" "$2";; EU) _lang_es6_cc_unary "$_cv" "$1";; Eo) _lang_es6_cc_object "$@";; Ea) _lang_es6_cc_array "$@";; Ep) _lang_es6_cc_ident "$_cv" _lang_es6_cc_expr "$1" _lang_es6_cc_emit "_lang_es6_rt_get \"\$REPLY\" $_cv";; EI) _lang_es6_cc_expr "$1" _lang_es6_cc_newtmp; _cta=$REPLY _lang_es6_cc_emit "$_cta=\$REPLY" _lang_es6_cc_expr "$2" _lang_es6_cc_emit "_lang_es6_rt_to_str \"\$REPLY\"" _lang_es6_cc_emit "_lang_es6_rt_get \"\$$_cta\" \"\$REPLY\"";; EC) _lang_es6_cc_call "$@";; EN) _lang_es6_cc_newexpr "$_cv" "$@";; EZ) _lang_es6_cc_function "$_cv" "$1" "$2" 0;; EA) _lang_es6_cc_function "" "$1" "$2" 1;; ET) _lang_es6_cc_template "$@";; EG) _lang_es6_cc_expr "$1";; EQ) _lang_es6_cc_ternary "$1" "$2" "$3";; Ex) _lang_es6_cc_class "$_cv" "$@";; *) _lang_es6_cc_emit "REPLY=u # unsupported expr: $_ct";; esac } # --- binop: supports arithmetic, comparison, logical, assignment --- _lang_es6_cc_binop () { local _bop _lhs _rhs _cta _bop=$1; _lhs=$2; _rhs=$3 case $_bop in '=') _lang_es6_cc_assign "$_lhs" "$_rhs"; return;; esac # short-circuit for && and || case $_bop in '&&') _lang_es6_cc_expr "$_lhs" _lang_es6_cc_newtmp; _cta=$REPLY _lang_es6_cc_emit "$_cta=\$REPLY" _lang_es6_cc_emit "if _lang_es6_rt_truthy \"\$$_cta\"; then" _lang_es6_cc_expr "$_rhs" _lang_es6_cc_emit "else REPLY=\$$_cta; fi" return;; '||') _lang_es6_cc_expr "$_lhs" _lang_es6_cc_newtmp; _cta=$REPLY _lang_es6_cc_emit "$_cta=\$REPLY" _lang_es6_cc_emit "if _lang_es6_rt_truthy \"\$$_cta\"; then REPLY=\$$_cta; else" _lang_es6_cc_expr "$_rhs" _lang_es6_cc_emit "fi" return;; esac _lang_es6_cc_expr "$_lhs" _lang_es6_cc_newtmp; _cta=$REPLY _lang_es6_cc_emit "$_cta=\$REPLY" _lang_es6_cc_expr "$_rhs" case $_bop in '+') _lang_es6_cc_emit "_lang_es6_rt_add \"\$$_cta\" \"\$REPLY\"";; '-') _lang_es6_cc_emit "_lang_es6_rt_sub \"\$$_cta\" \"\$REPLY\"";; '*') _lang_es6_cc_emit "_lang_es6_rt_mul \"\$$_cta\" \"\$REPLY\"";; '/') _lang_es6_cc_emit "_lang_es6_rt_div \"\$$_cta\" \"\$REPLY\"";; '%') _lang_es6_cc_emit "_lang_es6_rt_mod \"\$$_cta\" \"\$REPLY\"";; '<') _lang_es6_cc_emit "_lang_es6_rt_lt \"\$$_cta\" \"\$REPLY\"";; '<=') _lang_es6_cc_emit "_lang_es6_rt_le \"\$$_cta\" \"\$REPLY\"";; '>') _lang_es6_cc_emit "_lang_es6_rt_gt \"\$$_cta\" \"\$REPLY\"";; '>=') _lang_es6_cc_emit "_lang_es6_rt_ge \"\$$_cta\" \"\$REPLY\"";; '==') _lang_es6_cc_emit "_lang_es6_rt_eq \"\$$_cta\" \"\$REPLY\"";; '!=') _lang_es6_cc_emit "_lang_es6_rt_ne \"\$$_cta\" \"\$REPLY\"";; '===') _lang_es6_cc_emit "_lang_es6_rt_eeq \"\$$_cta\" \"\$REPLY\"";; '!==') _lang_es6_cc_emit "_lang_es6_rt_nee \"\$$_cta\" \"\$REPLY\"";; *) _lang_es6_cc_emit "REPLY=u # unsupported binop: $_bop";; esac } # --- assignment: lhs can be Ei (var), Ep (member), EI (indexed) --- _lang_es6_cc_assign () { local _alhs _arhs _alt _alv _cta _ctk _alhs=$1; _arhs=$2 IFS=' '; eval "set -- \$X$_alhs"; IFS='' _alt=$1; shift eval "_alv=\${V$_alhs:-}" case $_alt in Ei) _lang_es6_cc_ident "$_alv" _lang_es6_cc_expr "$_arhs" _lang_es6_cc_emit "_lang_es6_rt_store $_alv \"\$REPLY\" \"\$_SID\"";; Ep) _lang_es6_cc_ident "$_alv" _lang_es6_cc_expr "$1" _lang_es6_cc_newtmp; _cta=$REPLY _lang_es6_cc_emit "$_cta=\$REPLY" _lang_es6_cc_expr "$_arhs" _lang_es6_cc_emit "_lang_es6_rt_set \"\$$_cta\" $_alv \"\$REPLY\"";; EI) _lang_es6_cc_expr "$1" _lang_es6_cc_newtmp; _cta=$REPLY _lang_es6_cc_emit "$_cta=\$REPLY" _lang_es6_cc_expr "$2" _lang_es6_cc_newtmp; _ctk=$REPLY _lang_es6_cc_emit "_lang_es6_rt_to_str \"\$REPLY\"" _lang_es6_cc_emit "$_ctk=\$REPLY" _lang_es6_cc_expr "$_arhs" _lang_es6_cc_emit "_lang_es6_rt_set \"\$$_cta\" \"\$$_ctk\" \"\$REPLY\"";; *) _lang_es6_cc_emit "REPLY=u # unsupported assign lhs: $_alt";; esac } _lang_es6_cc_ternary () { local _tcond _tthen _telse _tcond=$1; _tthen=$2; _telse=$3 _lang_es6_cc_expr "$_tcond" _lang_es6_cc_emit "if _lang_es6_rt_truthy \"\$REPLY\"; then" _lang_es6_cc_expr "$_tthen" _lang_es6_cc_emit "else" _lang_es6_cc_expr "$_telse" _lang_es6_cc_emit "fi" } _lang_es6_cc_unary () { local _uop _ue _uop=$1; _ue=$2 _lang_es6_cc_expr "$_ue" case $_uop in '!') _lang_es6_cc_emit "_lang_es6_rt_not \"\$REPLY\"";; '-') _lang_es6_cc_emit "_lang_es6_rt_neg \"\$REPLY\"";; '+') _lang_es6_cc_emit "_lang_es6_rt_to_num \"\$REPLY\"";; *) _lang_es6_cc_emit "REPLY=u # unsupported unary: $_uop";; esac } # --- object literal: $@ = Em or Et children --- _lang_es6_cc_object () { local _cm _cmt _cmv _cto _ckv _lang_es6_cc_emit "_lang_es6_rt_obj_new Object 1" _lang_es6_cc_newtmp; _cto=$REPLY _lang_es6_cc_emit "$_cto=\$REPLY" for _cm; do IFS=' '; eval "set -- \$X$_cm"; IFS='' _cmt=$1; shift eval "_cmv=\${V$_cm:-}" case $_cmt in Em) # Em children: [key-node, value-node]; key-node is Ei whose V is the key name eval "_ckv=\${V$1:-}" _lang_es6_cc_ident "$_ckv" _lang_es6_cc_expr "$2" _lang_es6_cc_emit "_lang_es6_rt_set \"\$$_cto\" $_ckv \"\$REPLY\"";; Et) # method shorthand: value=method name, children=[EP params, EB body] _lang_es6_cc_ident "$_cmv" _lang_es6_cc_function "" "$1" "$2" 0 _lang_es6_cc_emit "_lang_es6_rt_set \"\$$_cto\" $_cmv \"\$REPLY\"";; Ei) # shorthand property { x } _lang_es6_cc_ident "$_cmv" _lang_es6_cc_emit "_lang_es6_rt_load $_cmv \"\$_SID\"" _lang_es6_cc_emit "_lang_es6_rt_set \"\$$_cto\" $_cmv \"\$REPLY\"";; *) _lang_es6_cc_emit "# unsupported object member: $_cmt";; esac done _lang_es6_cc_emit "REPLY=\$$_cto" } # --- array literal --- _lang_es6_cc_array () { local _cel _cta _cai _lang_es6_cc_emit "_lang_es6_rt_obj_new Array 3" _lang_es6_cc_newtmp; _cta=$REPLY _lang_es6_cc_emit "$_cta=\$REPLY" _cai=0 for _cel; do _lang_es6_cc_expr "$_cel" _lang_es6_cc_emit "_lang_es6_rt_set \"\$$_cta\" $_cai \"\$REPLY\"" _cai=$((_cai + 1)) done _lang_es6_cc_emit "_lang_es6_rt_set \"\$$_cta\" length n$_cai" _lang_es6_cc_emit "REPLY=\$$_cta" } # --- call: first arg is callee, rest are args --- _lang_es6_cc_call () { local _ccallee _ccargs _cct _ccv _crecv _ckey _cto _ctf _ccallee=$1; shift IFS=' '; _ccargs=$*; IFS='' eval "_cct=\${X$_ccallee%% *}" eval "_ccv=\${V$_ccallee:-}" case $_cct in Ep) # method call: a.b(args) _lang_es6_cc_ident "$_ccv" IFS=' '; eval "set -- \$X$_ccallee"; IFS='' shift _crecv=$1 _lang_es6_cc_expr "$_crecv" _lang_es6_cc_newtmp; _cto=$REPLY _lang_es6_cc_emit "$_cto=\$REPLY" _lang_es6_cc_emit "_lang_es6_rt_get \"\$$_cto\" $_ccv" _lang_es6_cc_newtmp; _ctf=$REPLY _lang_es6_cc_emit "$_ctf=\$REPLY" IFS=' '; set -- $_ccargs; IFS='' _lang_es6_cc_call_args "$_ctf" "\$$_cto" "$@";; EI) # indexed call: a[k](args) IFS=' '; eval "set -- \$X$_ccallee"; IFS='' shift _crecv=$1; _ckey=$2 _lang_es6_cc_expr "$_crecv" _lang_es6_cc_newtmp; _cto=$REPLY _lang_es6_cc_emit "$_cto=\$REPLY" _lang_es6_cc_expr "$_ckey" _lang_es6_cc_emit "_lang_es6_rt_to_str \"\$REPLY\"" _lang_es6_cc_emit "_lang_es6_rt_get \"\$$_cto\" \"\$REPLY\"" _lang_es6_cc_newtmp; _ctf=$REPLY _lang_es6_cc_emit "$_ctf=\$REPLY" IFS=' '; set -- $_ccargs; IFS='' _lang_es6_cc_call_args "$_ctf" "\$$_cto" "$@";; *) # plain call: f(args) _lang_es6_cc_expr "$_ccallee" _lang_es6_cc_newtmp; _ctf=$REPLY _lang_es6_cc_emit "$_ctf=\$REPLY" IFS=' '; set -- $_ccargs; IFS='' _lang_es6_cc_call_args "$_ctf" u "$@";; esac } # Build call with evaluated args. # $1=tmpname-of-fn $2=this-shell-expansion rest=arg nodes _lang_es6_cc_call_args () { local _cfn _cth _aa _ct _cavars _cfn=$1; _cth=$2; shift 2 _cavars= for _aa; do _lang_es6_cc_expr "$_aa" _lang_es6_cc_newtmp; _ct=$REPLY _lang_es6_cc_emit "$_ct=\$REPLY" _cavars="$_cavars \"\$$_ct\"" done _lang_es6_cc_emit "_lang_es6_rt_call \"\$$_cfn\" \"$_cth\"$_cavars" } # --- new expression --- _lang_es6_cc_newexpr () { local _nv _nctor _ctc _aa _ct _cavars _nv=$1; shift _nctor=$1; shift # constructor expression node _lang_es6_cc_expr "$_nctor" _lang_es6_cc_newtmp; _ctc=$REPLY _lang_es6_cc_emit "$_ctc=\$REPLY" _cavars= for _aa; do _lang_es6_cc_expr "$_aa" _lang_es6_cc_newtmp; _ct=$REPLY _lang_es6_cc_emit "$_ct=\$REPLY" _cavars="$_cavars \"\$$_ct\"" done _lang_es6_cc_emit "_lang_es6_rt_new \"\$$_ctc\"$_cavars" } # --- template literal --- _lang_es6_cc_template () { local _cc _ctt _ctv _ctr _lang_es6_cc_newtmp; _ctr=$REPLY _lang_es6_cc_emit "$_ctr=s" for _cc; do IFS=' '; eval "set -- \$X$_cc"; IFS='' _ctt=$1 eval "_ctv=\${V$_cc:-}" case $_ctt in Ef) # text chunk — process JS escapes, then shell-quote _lang_es6_cc_strproc "$_ctv" _lang_es6_cc_shq "$REPLY" _lang_es6_cc_emit "$_ctr=\$$_ctr'$REPLY'";; EX) # interpolation — evaluate and coerce to string IFS=' '; eval "set -- \$X$_cc"; IFS='' shift _lang_es6_cc_expr "$1" _lang_es6_cc_emit "_lang_es6_rt_to_str \"\$REPLY\"" _lang_es6_cc_emit "$_ctr=\$$_ctr\$REPLY";; esac done _lang_es6_cc_emit "REPLY=\$$_ctr" } # --- function declaration / expression --- # $1=name (empty for anonymous/arrow) # $2=params node (EP) # $3=body node (EB) # $4=1 if arrow (lexical this) _lang_es6_cc_function () { local _fname _fparams _fbody _flex _fshell _pnames _pp _pn _pi _sv_out _sv_cct _body _locs _tth _tfn _fname=$1; _fparams=$2; _fbody=$3; _flex=$4 _lang_es6_cc_newfn; _fshell=$REPLY # Extract param names from EP node IFS=' '; eval "set -- \$X$_fparams"; IFS='' shift # drop EP _pnames= for _pp; do eval "_pn=\${V$_pp:-}" _lang_es6_cc_ident "$_pn" _pnames="$_pnames $_pn" done # Emit the function body into a side buffer with its own temp namespace. _sv_out=$_CCOUT _sv_cct=$_CCT _CCOUT= _CCT=0 _lang_es6_cc_emit "_lang_es6_rt_scope_new \"\$1\"; _SID=\$REPLY" _lang_es6_cc_emit "eval \"_JSS_\${_SID}_v_this=\\\$2\"" _pi=3 IFS=' ' for _pn in $_pnames; do _lang_es6_cc_emit "eval \"_JSS_\${_SID}_v_$_pn=\\\$$_pi\"" _pi=$((_pi + 1)) done IFS='' _lang_es6_cc_emit "REPLY=u" _lang_es6_cc_stmt "$_fbody" _body=$_CCOUT _lang_es6_cc_locals "$_CCT"; _locs=$REPLY _CCOUT="$_fshell () {$_EOL local _SID$_locs$_EOL$_body}$_EOL" _CCFNS=$_CCFNS$_CCOUT _CCOUT=$_sv_out _CCT=$_sv_cct # Emit the mkfn call in the outer context case $_flex in 1) _lang_es6_cc_newtmp; _tth=$REPLY _lang_es6_cc_emit "_lang_es6_rt_load this \"\$_SID\"" _lang_es6_cc_emit "$_tth=\$REPLY" _lang_es6_cc_emit "_lang_es6_rt_mkfn $_fshell \"\$_SID\" '' 1 \"\$$_tth\"";; *) _lang_es6_cc_emit "_lang_es6_rt_mkfn $_fshell \"\$_SID\" '' 0 u";; esac case $_fname in ?*) _lang_es6_cc_ident "$_fname" _lang_es6_cc_newtmp; _tfn=$REPLY _lang_es6_cc_emit "$_tfn=\$REPLY" _lang_es6_cc_emit "_lang_es6_rt_decl $_fname \"\$$_tfn\" \"\$_SID\"" _lang_es6_cc_emit "REPLY=\$$_tfn";; esac } # --- class declaration --- # $1=name (V of Ex) $@=children (optional superclass Ei, then Er body) _lang_es6_cc_class () { local _clname _clsuper _clbody _clctor _clmethods _mm _mv _tcon _tsup _tpr _tpro _fshell _sv_out _clname=$1; shift # Detect superclass _clsuper= case $# in 2) _clsuper=$1; shift;; esac _clbody=$1 # Scan body for constructor method vs others IFS=' '; eval "set -- \$X$_clbody"; IFS='' shift # drop Er _clctor= _clmethods= for _mm; do eval "_mv=\${V$_mm:-}" case $_mv in constructor) _clctor=$_mm;; *) _clmethods="$_clmethods $_mm";; esac done # Build constructor function (default empty if missing) case $_clctor in '') # synthesize empty constructor _lang_es6_cc_newfn; _fshell=$REPLY _sv_out=$_CCOUT _CCOUT= _lang_es6_cc_emit "$_fshell () {" _lang_es6_cc_emit "local _SID" _lang_es6_cc_emit "_lang_es6_rt_scope_new \"\$1\"; _SID=\$REPLY" _lang_es6_cc_emit "eval \"_JSS_\${_SID}_v_this=\\\$2\"" _lang_es6_cc_emit "REPLY=u" _lang_es6_cc_emit "}" _CCFNS=$_CCFNS$_CCOUT _CCOUT=$_sv_out _lang_es6_cc_emit "_lang_es6_rt_mkfn $_fshell \"\$_SID\" '' 0 u";; *) # Et node: children [EP, EB] IFS=' '; eval "set -- \$X$_clctor"; IFS='' shift # drop Et _lang_es6_cc_function "" "$1" "$2" 0;; esac _lang_es6_cc_newtmp; _tcon=$REPLY _lang_es6_cc_emit "$_tcon=\$REPLY" # Set up prototype chain if extends case $_clsuper in ?*) _lang_es6_cc_expr "$_clsuper" _lang_es6_cc_newtmp; _tsup=$REPLY _lang_es6_cc_emit "$_tsup=\$REPLY" _lang_es6_cc_emit "_lang_es6_rt_get \"\$$_tcon\" prototype" _lang_es6_cc_newtmp; _tpr=$REPLY _lang_es6_cc_emit "$_tpr=\$REPLY" _lang_es6_cc_emit "_lang_es6_rt_get \"\$$_tsup\" prototype" _lang_es6_cc_emit "_lang_es6_rt_setproto \"\$$_tpr\" \"\$REPLY\"";; esac # Install instance methods into ctor.prototype _lang_es6_cc_emit "_lang_es6_rt_get \"\$$_tcon\" prototype" _lang_es6_cc_newtmp; _tpro=$REPLY _lang_es6_cc_emit "$_tpro=\$REPLY" IFS=' ' for _mm in $_clmethods; do IFS='' eval "_mv=\${V$_mm:-}" _lang_es6_cc_ident "$_mv" IFS=' '; eval "set -- \$X$_mm"; IFS='' shift # drop Et _lang_es6_cc_function "" "$1" "$2" 0 _lang_es6_cc_emit "_lang_es6_rt_set \"\$$_tpro\" $_mv \"\$REPLY\"" IFS=' ' done IFS='' _lang_es6_cc_emit "REPLY=\$$_tcon" # Bind the class name in current scope case $_clname in ?*) _lang_es6_cc_ident "$_clname" _lang_es6_cc_emit "_lang_es6_rt_decl $_clname \"\$$_tcon\" \"\$_SID\"";; esac } # --- statement emit --- _lang_es6_cc_stmt () { local _cn _ct _cv _ch _finit _fcond _fupd _cn=$1 IFS=' '; eval "set -- \$X$_cn"; IFS='' _ct=$1; shift eval "_cv=\${V$_cn:-}" case $_ct in Ed) for _ch; do _lang_es6_cc_stmt "$_ch"; done;; EB) for _ch; do _lang_es6_cc_stmt "$_ch"; done;; EL|EW|EV) _lang_es6_cc_decls "$@";; EF) _lang_es6_cc_expr "$1" _lang_es6_cc_emit "if _lang_es6_rt_truthy \"\$REPLY\"; then :" _lang_es6_cc_stmt "$2" case $# in 3) _lang_es6_cc_emit "else :" _lang_es6_cc_stmt "$3";; esac _lang_es6_cc_emit "fi";; Ew) _lang_es6_cc_emit "while :; do" _lang_es6_cc_expr "$1" _lang_es6_cc_emit "_lang_es6_rt_truthy \"\$REPLY\" || break" _lang_es6_cc_stmt "$2" _lang_es6_cc_emit "done";; ED) _lang_es6_cc_emit "while :; do" _lang_es6_cc_stmt "$1" _lang_es6_cc_expr "$2" _lang_es6_cc_emit "_lang_es6_rt_truthy \"\$REPLY\" || break" _lang_es6_cc_emit "done";; EO) # for loop: $1 is Eh header (init, cond, update), $2 is body IFS=' '; eval "set -- \$X$1"; IFS='' shift # drop Eh _finit=$1; _fcond=$2; _fupd=$3 _lang_es6_cc_stmt "$_finit" _lang_es6_cc_emit "while :; do" _lang_es6_cc_expr "$_fcond" _lang_es6_cc_emit "_lang_es6_rt_truthy \"\$REPLY\" || break" # Re-fetch body node IFS=' '; eval "set -- \$X$_cn"; IFS='' shift # drop EO shift # drop header _lang_es6_cc_stmt "$1" _lang_es6_cc_expr "$_fupd" _lang_es6_cc_emit "done";; ER) case $# in 0) _lang_es6_cc_emit "REPLY=u; return 0";; *) _lang_es6_cc_expr "$1" _lang_es6_cc_emit "return 0";; esac;; EZ) # function declaration as statement _lang_es6_cc_function "$_cv" "$1" "$2" 0;; Ex) _lang_es6_cc_class "$_cv" "$@";; Eh) for _ch; do _lang_es6_cc_stmt "$_ch"; done;; *) # expression statement _lang_es6_cc_expr "$_cn";; esac } # --- declaration emit: for EL/EW/EV; children are Ei or Eg(=) nodes --- _lang_es6_cc_decls () { local _dd _dt _dv _dname for _dd; do IFS=' '; eval "set -- \$X$_dd"; IFS='' _dt=$1; shift eval "_dv=\${V$_dd:-}" case $_dt in Ei) # let x; — no initializer _lang_es6_cc_ident "$_dv" _lang_es6_cc_emit "_lang_es6_rt_decl $_dv u \"\$_SID\"";; Eg) # let x = expr eval "_dname=\${V$1:-}" _lang_es6_cc_ident "$_dname" _lang_es6_cc_expr "$2" _lang_es6_cc_emit "_lang_es6_rt_decl $_dname \"\$REPLY\" \"\$_SID\"";; esac done } # --- top-level entry --- lang_es6_compile () { _CCOUT= _CCFNS= _CCT=0 _CCF=0 io_readall eval "$REPLY" _lang_es6_cc_emit "_SID=0" _lang_es6_cc_stmt 0 _printn1 "$_CCFNS" _printn1 "$_CCOUT" } # ksh93 fix: re-declare functions via eval. # In ksh93, functions defined via `. file` don't get alias expansion. # Re-declaring via eval fixes this. Two modes: # - Dynamic-scoped (POSIX name(){}): functions that read/write caller-scope # variables (IO_FEED_*, V*, X*, etc.) or entry points # whose locals must be visible to callees (parsers, generators, tests). # - Static-scoped (AT&T function name {}): everything else, especially # recursive emitters (_*_emit) that need isolated locals per call frame. # Convention: new utility functions (str_*, io_*, ds_*, bnf_charmap_*) # use only their own locals + REPLY, so static scoping (the default) is correct. _Ldefn_fix= eval "_Ldefn_fix(){ typeset _Ldefn_fix=local;} 2>/dev/null" _Ldefn_fix 2>/dev/null || : case $_Ldefn_fix in "local") _Ldefn_fix () { case "$1" in _Ldefn_fix) return;; esac _Ldefn_fix="$(typeset -f "$1" 2>/dev/null)" || return 0 _Ldefn_fix="${_Ldefn_fix#*\{}" case "$1" in *_parser|ast_out|err_display|io_feed_track_nl|_ast_engine_pars_epilogue) eval "$1 () {${_Ldefn_fix}" 2>/dev/null || :;; gen_*|test_*|unit_*|integration_*|full_*) eval "$1 () {${_Ldefn_fix}" 2>/dev/null || :;; _bnf_gen_*|lang_shell_common_stripq|lang_shell_common_shdelim) eval "$1 () {${_Ldefn_fix}" 2>/dev/null || :;; *) eval "function $1 {${_Ldefn_fix}" 2>/dev/null || :;; esac } IFS=' ' for _Ldefn_fix in $(typeset +f); do _Ldefn_fix "${_Ldefn_fix%%"()"*}" done IFS='' ;; esac unset _Ldefn_fix unset -f _Ldefn_fix 2>/dev/null || : # --- main driver --- josh_main () { case ${1:-} in ''|-) _SRC_=$(io_readall; _printn1 "$REPLY") || return 1;; *) _SRC_=$(io_readall < "$1"; _printn1 "$REPLY") || return 1;; esac _AST_=$(_printn1 "$_SRC_" | lang_es6_parser) || return 1 _CODE_=$(_printn1 "$_AST_" | lang_es6_compile) || return 1 eval "$_CODE_" } josh_main "$@"