|
#!/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 "$@" |