Files
rose-ash/hosts/javascript/transpiler.sx
giles f828fb023b Fix 73 JS test failures: match transpiler, sxEq, deref frame, signals, stepper lib
Evaluator fixes (from broken match refactor in 8bba02f):
- Deref frame: use CEK state `value`, not `(get frame "value")`
- Deref frame: restore `(context "sx-reactive" nil)` (was undefined `get-tracking-context`)
- Scope-acc frame: restore missing `(get frame "value")` arg to make-scope-acc-frame
- Add missing `thread-insert-arg` helper for thread-first non-HO branch

Transpiler (hosts/javascript/transpiler.sx):
- Add `match` special form handler (IIFE with chained if/return, `_` wildcard)
- Replace `=`/`!=` infix `==` with `sxEq()` function call for proper symbol equality

JS platform (hosts/javascript/platform.py):
- Add `sxEq` for structural symbol/keyword comparison
- Add `componentFile`, `sort`, `defStore`/`useStore`/`clearStores` primitives
- Add `length`/`map`/`for-each`/`reduce` as VM-compatible HOF primitives
- Fix `SYM` → `makeSymbol` references

New files:
- sx/sx/stepper-lib.sx: extracted split-tag, build-code-tokens, steps-to-preview

JS tests: 0 → 1582/1585 passing (3 remaining are VM closure interop)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 08:33:27 +00:00

1246 lines
50 KiB
Plaintext

(define
js-reserved
(list
"abstract"
"arguments"
"boolean"
"break"
"byte"
"case"
"catch"
"char"
"class"
"const"
"continue"
"debugger"
"default"
"delete"
"do"
"double"
"else"
"enum"
"eval"
"export"
"extends"
"false"
"final"
"finally"
"float"
"for"
"function"
"goto"
"if"
"implements"
"import"
"in"
"instanceof"
"int"
"interface"
"let"
"long"
"native"
"new"
"null"
"package"
"private"
"protected"
"public"
"return"
"short"
"static"
"super"
"switch"
"synchronized"
"this"
"throw"
"throws"
"transient"
"true"
"try"
"typeof"
"undefined"
"var"
"void"
"volatile"
"while"
"with"
"yield"))
(define js-renames {:ho-filter "hoFilter" :thunk-env "thunkEnv" :cek-run "cekRun" :*custom-special-forms* "_customSpecialForms" :with-island-scope "withIslandScope" :step-sf-if "stepSfIf" :dom-is-fragment? "domIsFragment" :process-bindings "processBindings" :call-thunk "callThunk" :fetch-streaming "fetchStreaming" :bind-inline-handlers "bindInlineHandlers" :set-interval "setInterval_" :number? "isNumber" :reactive-list "reactiveList" :expand-macro "expandMacro" :handle-history "handleHistory" :page-render-plan "pageRenderPlan" :make-let-frame "makeLetFrame" :parse-comp-params "parseCompParams" :next-retry-ms "nextRetryMs" :fetch-request "fetchRequest" :kont-push "kontPush" :macro-body "macroBody" :for-each-indexed "forEachIndexed" :step-ho-for-each "stepHoForEach" :set-render-active! "setRenderActiveB" :local-storage-set "localStorageSet" :dom-get-attr "domGetAttr" :parse-element-args "parseElementArgs" :process-emit-elements "processEmitElements" :build-request-body "buildRequestBody" :kont-top "kontTop" :event-detail "eventDetail" :match-route "matchRoute" :handle-popstate "handlePopstate" :event-source-listen "eventSourceListen" :select-from-container "selectFromContainer" :try-eval-content "tryEvalContent" :query-page-scripts "queryPageScripts" :scope-emit! "scopeEmit" :promise-delayed "promiseDelayed" :make-call-frame "makeCallFrame" :HTML_TAGS "HTML_TAGS" :macro-rest-param "macroRestParam" :env-has? "envHas" :make-raw-html "makeRawHtml" :dom-set-style "domSetStyle" :try-parse-json "tryParseJson" :host-call "hostCall" :VERB_SELECTOR "VERB_SELECTOR" :render-dom-element "renderDomElement" :escape-html "escapeHtml" :parse-sse-swap "parseSseSwap" :disable-elements "disableElements" :starts-with? "startsWith" :parse-env-attr "parseEnvAttr" :ho-some "hoSome" :eval-cond-scheme "evalCondScheme" :ends-with? "endsWith" :>= "gte_" :dom-dispatch "domDispatch" :preload-cache-set "preloadCacheSet" :signal-subscribers "signalSubscribers" :step-sf-provide "stepSfProvide" :signal-add-sub! "signalAddSub" :render-lambda-html "renderLambdaHtml" :dom-set-data "domSetData" :make-thread-frame "makeThreadFrame" :make-sx-expr "makeSxExpr" :pop-wind! "popWind" :dom-append-to-head "domAppendToHead" :hoist-head-elements "hoistHeadElements" :make-reset-frame "makeResetFrame" :flush-subscribers "flushSubscribers" :controller-signal "controllerSignal" :clear-interval "clearInterval_" :children-to-fragment "childrenToFragment" :sx-render-component "sxRenderComponent" :with-transition "withTransition" :scan-io-refs-walk "scanIoRefsWalk" :step-sf-scope "stepSfScope" :get-primitive "getPrimitive" :_preload-cache "_preloadCache" :select-html-from-doc "selectHtmlFromDoc" :browser-location-href "browserLocationHref" :sf-case-loop "sfCaseLoop" :sf-dynamic-wind "sfDynamicWind" :symbol-name "symbolName" :set-lambda-name! "setLambdaName" :host-get "hostGet" :aser-fragment "aserFragment" :render-dom-unknown-component "renderDomUnknownComponent" :!= "notEqual_" :SX_VERSION "SX_VERSION" :render-html-element "renderHtmlElement" :dom-first-child "domFirstChild" :bind-client-route-click "bindClientRouteClick" :sf-cond-clojure "sfCondClojure" :MATH_NS "MATH_NS" :default-trigger "defaultTrigger" :signal-remove-sub! "signalRemoveSub" :make-cek-state "makeCekState" :emit! "sxEmit" :sf-quote "sfQuote" :bind-boost-form "bindBoostForm" :component-params "componentParams" :do-preload "doPreload" :component-affinity "componentAffinity" :eval-case-aser "evalCaseAser" :sf-begin "sfBegin" :revert-optimistic "revertOptimistic" :whitespace? "isWhitespace" :host-typeof "hostTypeof" :dom-insert-adjacent-html "domInsertAdjacentHtml" :step-sf-set! "stepSfSet" :error-message "errorMessage" :schedule-idle "scheduleIdle" :find-matching-route "findMatchingRoute" :component-body "componentBody" :qq-expand "qqExpand" :provide-push! "providePush" :make-keyword "makeKeyword" :do-fetch "doFetch" :component-deps "componentDeps" :component-set-io-refs! "componentSetIoRefs" :escape-string "escapeString" :make-island "makeIsland" :nil "NIL" :log-parse-error "logParseError" :enable-cek-reactive! "enableCekReactive" :signal-set-value! "signalSetValue" :env-set! "envSet" :clear-timeout "clearTimeout_" :sf-defcomp "sfDefcomp" :step-ho-map "stepHoMap" :dom-parse-html "domParseHtml" :make-lambda "makeLambda" :sf-if "sfIf" :make-route-segment "makeRouteSegment" :lambda-closure "lambdaClosure" :render-target "renderTarget" :dom-attr-list "domAttrList" :log-warn "logWarn" :eval-call "evalCall" :sync-attrs "syncAttrs" :make-case-frame "makeCaseFrame" :render-dom-component "renderDomComponent" :dom-child-nodes "domChildNodes" :collect! "sxCollect" :use-store "useStore" :classify-trigger "classifyTrigger" :engine-init "engineInit" :list? "isList" :index-of "indexOf_" :component-io-refs "componentIoRefs" :dom-remove "domRemove" :set-document-title "setDocumentTitle" :primitive? "isPrimitive" :parse-trigger-spec "parseTriggerSpec" :local-storage-get "localStorageGet" :dom-get-data "domGetData" :scan-refs-walk "scanRefsWalk" :abort-previous-target "abortPreviousTarget" :thunk-expr "thunkExpr" :create-comment "createComment" :component-closure "componentClosure" :render-dom-form? "isRenderDomForm" :sx-render-with-env "sxRenderWithEnv" :cek-phase "cekPhase" :prevent-default "preventDefault_" :true "true" :definition-form? "isDefinitionForm" :make-map-frame "makeMapFrame" :scope-pop! "scopePop" :contains? "contains" :bind-preload-for "bindPreloadFor" :dom-focus "domFocus" :sf-thread-first "sfThreadFirst" :find-oob-swaps "findOobSwaps" :dom-query-by-id "domQueryById" :handle-sx-response "handleSxResponse" :page-css-classes "pageCssClasses" :odd? "isOdd" :compute-all-deps "computeAllDeps" :has-reactive-reset-frame? "hasReactiveResetFrame_p" :sx-expr-source "sxExprSource" :render-html-form? "isRenderHtmlForm" :lambda-name "lambdaName" :parse-number "parseNumber" :regex-find-all "regexFindAll" :step-sf-define "stepSfDefine" :resolve-mount-target "resolveMountTarget" :emitted "sxEmitted" :browser-push-state "browserPushState" :signal-value "signalValue" :sf-defmacro "sfDefmacro" :swap-dom-nodes "swapDomNodes" :scan-components-from-source "scanComponentsFromSource" :lambda-body "lambdaBody" :scope-peek "scopePeek" :signal-deps "signalDeps" :aser-call "aserCall" :bind-sse-swap "bindSseSwap" :make-for-each-frame "makeForEachFrame" :make-and-frame "makeAndFrame" :parse-macro-params "parseMacroParams" :dispatch-trigger-events "dispatchTriggerEvents" :event-source-connect "eventSourceConnect" :type-of "typeOf" :map-indexed "mapIndexed" :render-lambda-dom "renderLambdaDom" :boot-init "bootInit" :clear-collected! "sxClearCollected" :render-value-to-html "renderValueToHtml" :dispatch-html-form "dispatchHtmlForm" :should-boost-link? "shouldBoostLink" :step-eval "stepEval" :morph-node "morphNode" :track-controller "trackController" :cek-kont "cekKont" :dom-query-all "domQueryAll" :env-merge "envMerge" :raw-html-content "rawHtmlContent" :reactive-fragment "reactiveFragment" :ho-map "hoMap" :browser-scroll-to "browserScrollTo" :render-attrs "renderAttrs" :RENDER_HTML_FORMS "RENDER_HTML_FORMS" :make-reduce-frame "makeReduceFrame" :*batch-depth* "_batchDepth" :kf-name "kfName" :parse-retry-spec "parseRetrySpec" :dom-document "domDocument" :render-to-sx "renderToSx" :host-global "hostGlobal" :scan-refs "scanRefs" :dom-replace-child "domReplaceChild" :signal-set-deps! "signalSetDeps" :empty-dict? "isEmptyDict" :execute-request "executeRequest" :step-eval-list "stepEvalList" :zero? "isZero" :dom-remove-child "domRemoveChild" :compute-all-io-refs "computeAllIoRefs" :sx-render "sxRender" :components-needed "componentsNeeded" :host-set! "hostSet" :sf-case "sfCase" :make-cek-continuation "makeCekContinuation" :sf-let "sfLet" :cek-env "cekEnv" :step-sf-lambda "stepSfLambda" :notify-subscribers "notifySubscribers" :*render-check* "_renderCheck" :step-sf-deref "stepSfDeref" :browser-media-matches? "browserMediaMatches" :parse-time "parseTime" :process-elements "processElements" :try-catch "tryCatch" :filter-params "filterParams" :ident-start? "isIdentStart" :format-date "formatDate" :def-store "defStore" :post-swap "postSwap" :fetch-preload "fetchPreload" :is-processed? "isProcessed" :call-lambda "callLambda" :_page-routes "_pageRoutes" :continuation-data "continuationData" :try-client-route "tryClientRoute" :merge-spread-attrs "mergeSpreadAttrs" :*use-cek-reactive* "_useCekReactive" :cek-step "cekStep" :promise-resolve "promiseResolve" :clear-processed! "clearProcessed" :step-sf-and "stepSfAnd" :strip-component-scripts "stripComponentScripts" :split-path-segments "splitPathSegments" :<= "lte_" :dom-has-class? "domHasClass" :bind-event "bindEvent" :render-to-html "renderToHtml" :dom-add-class "domAddClass" :process-one "processOne" :sx-hydrate "sxHydrate" :render-active? "renderActiveP" :collected "sxCollected" :clear-stores "clearStores" :dom-get-prop "domGetProp" :empty? "isEmpty" :step-sf-when "stepSfWhen" :strip-tags "stripTags" :component-has-children? "componentHasChildren" :VOID_ELEMENTS "VOID_ELEMENTS" :promise-then "promiseThen" :parse-swap-spec "parseSwapSpec" :json-parse "jsonParse" :dom-parent "domParent" :process-oob-swaps "processOobSwaps" :signal? "isSignal" :local-storage-remove "localStorageRemove" :register-io-deps "registerIoDeps" :parse-route-pattern "parseRoutePattern" :process-sx-scripts "processSxScripts" :*store-registry* "_storeRegistry" :dom-ensure-element "domEnsureElement" :eval-expr "evalExpr" :transitive-deps "transitiveDeps" :make-set-frame "makeSetFrame" :get-render-env "getRenderEnv" :sf-named-let "sfNamedLet" :reactive-shift-deref "reactiveShiftDeref" :escape-attr "escapeAttr" :process-component-script "processComponentScript" :transitive-io-refs "transitiveIoRefs" :component-pure? "componentPure_p" :sf-and "sfAnd" :apply-optimistic "applyOptimistic" :ho-every "hoEvery" :dom-parse-html-document "domParseHtmlDocument" :island? "isIsland" :emit-event "emitEvent" :step-ho-reduce "stepHoReduce" :render-dom-raw "renderDomRaw" :clear-loading-state "clearLoadingState" :dom-clone "domClone" :fetch-and-restore "fetchAndRestore" :render-dom-island "renderDomIsland" :step-sf-begin "stepSfBegin" :to-kebab "toKebab" :replace "replace_" :mark-processed! "markProcessed" :insert-remaining-siblings "insertRemainingSiblings" :sx-update-element "sxUpdateElement" :env-extend "envExtend" :handle-html-response "handleHtmlResponse" :dict-delete! "dictDelete" :make-component "makeComponent" :make-cond-frame "makeCondFrame" :sx-load-components "sxLoadComponents" :sf-lambda "sfLambda" :abort-previous "abortPrevious" :step-eval-call "stepEvalCall" :store-env-attr "storeEnvAttr" :chunk-every "chunkEvery" :dom-append "domAppend" :eval-cond-clojure "evalCondClojure" :morph-children "morphChildren" :make-when-frame "makeWhenFrame" :frame-type "frameType" :dom-set-inner-html "domSetInnerHtml" :process-response-headers "processResponseHeaders" :dom-query "domQuery" :dom-remove-class "domRemoveClass" :thunk? "isThunk" :kont-pop "kontPop" :eval-list "evalList" :resolve-target "resolveTarget" :dom-is-child-of? "domIsChildOf" :lambda? "isLambda" :dom-insert-after "domInsertAfter" :make-dynamic-wind-frame "makeDynamicWindFrame" :promise-catch "promiseCatch" :host-new "hostNew" :kont-capture-to-reactive-reset "kontCaptureToReactiveReset" :serialize-island-state "serializeIslandState" :handle-retry "handleRetry" :step-sf-thread-first "stepSfThreadFirst" :make-reactive-reset-frame "makeReactiveResetFrame" :dom-listen "domListen" :even? "isEven" :get-verb-info "getVerbInfo" :dispose-island "disposeIsland" :dom-child-list "domChildList" :log-info "logInfo" :macro-closure "macroClosure" :dict-has? "dictHas" :browser-reload "browserReload" :cond-scheme? "condScheme_p" :make-scope-frame "makeScopeFrame" :sf-define "sfDefine" :ident-char? "isIdentChar" :sx-serialize "sxSerialize" :render-dom-fragment "renderDomFragment" :dom-has-attr? "domHasAttr" :dom-is-active-element? "domIsActiveElement" :dom-create-element "domCreateElement" :create-text-node "createTextNode" :lambda-params "lambdaParams" :host-await "hostAwait" :macro? "isMacro" :dom-text-content "domTextContent" :step-sf-case "stepSfCase" :request-animation-frame "requestAnimationFrame_" :sf-case-step-loop "sfCaseStepLoop" :process-boosted "processBoosted" :sf-cond "sfCond" :dom-head "domHead" :component-io-refs-cached "componentIoRefsCached" :bind-triggers "bindTriggers" :every? "isEvery" :dom-closest "domClosest" :component? "isComponent" :make-handler-def "makeHandlerDef" :should-boost-form? "shouldBoostForm" :parse-header-value "parseHeaderValue" :render-to-dom "renderToDom" :make-or-frame "makeOrFrame" :has-key? "dictHas" :dom-body-inner-html "domBodyInnerHtml" :process-css-response "processCssResponse" :url-pathname "urlPathname" :aser-special "aserSpecial" :create-script-clone "createScriptClone" :match-route-segments "matchRouteSegments" :cek-reactive-text "cekReactiveText" :PRELOAD_TTL "PRELOAD_TTL" :cek-control "cekControl" :bridge-event "bridgeEvent" :resolve-suspense "resolveSuspense" :dom-remove-children-after "domRemoveChildrenAfter" :track-controller-target "trackControllerTarget" :clear-sx-comp-cookie "clearSxCompCookie" :cross-origin? "isCrossOrigin" :extract-response-css "extractResponseCss" :bind-sse "bindSse" :show-indicator "showIndicator" :bind-client-route-link "bindClientRouteLink" :scope-push! "scopePush" :component-set-deps! "componentSetDeps" :element-value "elementValue" :cek-try "cekTry" :make-page-def "makePageDef" :render-html-component "renderHtmlComponent" :ENGINE_VERBS "ENGINE_VERBS" :process-sse "processSse" :loaded-component-names "loadedComponentNames" :browser-replace-state "browserReplaceState" :dom-next-sibling "domNextSibling" :sf-when "sfWhen" :sx-mount "sxMount" :make-query-def "makeQueryDef" :activate-scripts "activateScripts" :now-ms "nowMs" :bind-preload "bindPreload" :preload-cache-get "preloadCacheGet" :validate-for-request "validateForRequest" :BOOLEAN_ATTRS "BOOLEAN_ATTRS" :digit? "isDigit" :zip-pairs "zipPairs" :dom-set-text-content "domSetTextContent" :parse-keyword-args "parseKeywordArgs" :ho-map-indexed "hoMapIndexed" :cek-value "cekValue" :env-components "envComponents" :dict? "isDict" :is-else-clause? "isElseClause" :reactive-attr "reactiveAttr" :sf-quasiquote "sfQuasiquote" :create-fragment "createFragment" :is-render-expr? "isRenderExpr" :spread-attrs "spreadAttrs" :render-html-island "renderHtmlIsland" :aser-list "aserList" :provide-pop! "providePop" :swap-html-string "swapHtmlString" :render-expr "renderExpr" :dom-set-attr "domSetAttr" :boost-descendants "boostDescendants" :browser-prompt "browserPrompt" :HEAD_HOIST_SELECTOR "HEAD_HOIST_SELECTOR" :make-deref-frame "makeDerefFrame" :dom-tag-name "domTagName" :scope-emitted "sxEmitted" :query-sx-scripts "querySxScripts" :strip-prefix "stripPrefix" :scan-io-refs "scanIoRefs" :step-sf-cond "stepSfCond" :dom-id "domId" :dom-body "domBody" :make-macro "makeMacro" :identical? "isIdentical" :cek-reactive-attr "cekReactiveAttr" :step-sf-or "stepSfOr" :render-dom-list "renderDomList" :init-css-tracking "initCssTracking" :sx-serialize-dict "sxSerializeDict" :try-async-eval-content "tryAsyncEvalContent" :register-in-scope "registerInScope" :cek-terminal? "cekTerminal_p" :step-ho-filter "stepHoFilter" :sf-set! "sfSetBang" :false "false" :browser-navigate "browserNavigate" :dom-node-type "domNodeType" :bind-boost-link "bindBoostLink" :scan-css-classes "scanCssClasses" :dom-matches? "domMatches" :set-sx-comp-cookie "setSxCompCookie" :ho-reduce "hoReduce" :ho-form? "isHoForm" :macro-params "macroParams" :on-event "onEvent" :parse-int "parseInt_" :step-sf-reset "stepSfReset" :*render-fn* "_renderFn" :dom-outer-html "domOuterHtml" :special-form? "isSpecialForm" :observe-intersection "observeIntersection" :make-env "makeEnv" :make-signal "makeSignal" :push-wind! "pushWind" :dom-set-prop "domSetProp" :eval-expr-cek "evalExprCek" :callable? "isCallable" :sf-defisland "sfDefisland" :kont-capture-to-reset "kontCaptureToReset" :handle-fetch-success "handleFetchSuccess" :dom-get-style "domGetStyle" :sf-cond-scheme "sfCondScheme" :keyword-name "keywordName" :env-bind! "envBind" :map-dict "mapDict" :host-callback "hostCallback" :remove-head-element "removeHeadElement" :context "sxContext" :dom-is-input-element? "domIsInputElement" :spread? "isSpread" :make-cek-value "makeCekValue" :step-continue "stepContinue" :dom-window "domWindow" :hydrate-island "hydrateIsland" :make-action-def "makeActionDef" :kont-empty? "kontEmpty_p" :make-filter-frame "makeFilterFrame" :make-thunk "makeThunk" :make-symbol "makeSymbol" :dict-get "dictGet" :dispatch-render-form "dispatchRenderForm" :dom-prepend "domPrepend" :make-begin-frame "makeBeginFrame" :merge-envs "mergeEnvs" :continue-with-call "continueWithCall" :browser-confirm "browserConfirm" :make-spread "makeSpread" :register-special-form! "registerSpecialForm" :csrf-token "csrfToken" :for-each "forEach" :make-dict-frame "makeDictFrame" :trampoline-cek "trampolineCek" :sf-letrec "sfLetrec" :DEFAULT_SWAP "DEFAULT_SWAP" :component-name "componentName" :*batch-queue* "_batchQueue" :component-css-classes "componentCssClasses" :make-arg-frame "makeArgFrame" :dict-set! "dictSet" :step-sf-let "stepSfLet" :browser-same-origin? "browserSameOrigin" :sx-hydrate-islands "sxHydrateIslands" :make-define-frame "makeDefineFrame" :process-page-scripts "processPageScripts" :ho-for-each "hoForEach" :stop-propagation "stopPropagation_" :sx-process-scripts "sxProcessScripts" :make-if-frame "makeIfFrame" :sf-or "sfOr" :dom-insert-before "domInsertBefore" :step-sf-shift "stepSfShift" :format-decimal "formatDecimal" :json-serialize "jsonSerialize" :defcomp-kwarg "defcompKwarg" :reactive-text "reactiveText" :dom-remove-attr "domRemoveAttr" :eval-cond "evalCond" :_css-hash "_cssHash" :fetch-location "fetchLocation" :sx-hydrate-elements "sxHydrateElements" :dispose-computed "disposeComputed" :abort-error? "isAbortError" :set-timeout "setTimeout_" :new-abort-controller "newAbortController" :nil? "isNil" :env-get "envGet" :call-component "callComponent" :SVG_NS "SVG_NS" :RENDER_DOM_FORMS "RENDER_DOM_FORMS" :build-request-headers "buildRequestHeaders" :page-component-bundle "pageComponentBundle" :render-list-to-html "renderListToHtml" :string? "isString" :dom-node-name "domNodeName" :hoist-head-elements-full "hoistHeadElementsFull"})
(define
js-mangle
(fn
((name :as string))
(let
((renamed (get js-renames name)))
(if
(not (nil? renamed))
renamed
(let
((result (replace name "*" "_")))
(let
((result (cond (ends-with? result "?") (str (slice result 0 (- (string-length result) 1)) "_p") (ends-with? result "!") (str (slice result 0 (- (string-length result) 1)) "_b") :else result)))
(let
((result (js-kebab-to-camel result)))
(if
(some (fn (r) (= r result)) js-reserved)
(str result "_")
result))))))))
(define
js-kebab-to-camel
(fn
((s :as string))
(let
((parts (split s "-")))
(if
(<= (len parts) 1)
s
(str
(first parts)
(join "" (map (fn (p) (js-capitalize p)) (rest parts))))))))
(define
js-capitalize
(fn
((s :as string))
(if (empty? s) s (str (upper (slice s 0 1)) (slice s 1)))))
(define
js-quote-string
(fn
((s :as string))
(str
"\""
(replace
(replace
(replace
(replace (replace (replace s "\\" "\\\\") "\"" "\\\"") "\n" "\\n")
"\r"
"\\r")
"\t"
"\\t")
(char-from-code 0)
"\\u0000")
"\"")))
(define js-infix-ops (list "+" "-" "*" "/" "<" ">" "<=" ">=" "mod"))
(define
js-infix?
(fn ((op :as string)) (some (fn (x) (= x op)) js-infix-ops)))
(define
js-op-symbol
(fn ((op :as string)) (case op "=" "==" "!=" "!=" "mod" "%" :else op)))
(define
js-is-self-tail-recursive?
(fn
((name :as string) (body :as list))
(if (empty? body) false (js-has-tail-call? name (last body)))))
(define
js-has-tail-call?
(fn
((name :as string) expr)
(if
(not (and (list? expr) (not (empty? expr))))
false
(let
((head (first expr)))
(if
(not (= (type-of head) "symbol"))
false
(let
((h (symbol-name head)))
(cond
(= h name)
true
(= h "if")
(or
(js-has-tail-call? name (nth expr 2))
(and
(>= (len expr) 4)
(js-has-tail-call? name (nth expr 3))))
(= h "when")
(some
(fn (e) (js-has-tail-call? name e))
(rest (rest expr)))
(= h "cond")
(some
(fn
(clause)
(if
(and (list? clause) (= (len clause) 2))
(js-has-tail-call? name (nth clause 1))
(if
(= (type-of clause) "keyword")
false
(js-has-tail-call? name clause))))
(rest expr))
(or (= h "do") (= h "begin"))
(if
(> (len expr) 1)
(js-has-tail-call? name (last expr))
false)
(or (= h "let") (= h "let*"))
(if
(> (len expr) 2)
(js-has-tail-call? name (last expr))
false)
:else false)))))))
(define
js-emit-tail-as-stmt
(fn
((name :as string) expr)
(if
(not (and (list? expr) (not (empty? expr))))
(str "return " (js-expr expr) ";")
(let
((head (first expr)))
(if
(not (= (type-of head) "symbol"))
(str "return " (js-expr expr) ";")
(let
((h (symbol-name head)))
(cond
(= h name)
"continue;"
(or (= h "do") (= h "begin"))
(str
(join
"\n"
(map
(fn (e) (js-statement e))
(slice expr 1 (- (len expr) 1))))
"\n"
(js-emit-tail-as-stmt name (last expr)))
(= h "if")
(str
"if (isSxTruthy("
(js-expr (nth expr 1))
")) { "
(js-emit-tail-as-stmt name (nth expr 2))
" } else { "
(if
(>= (len expr) 4)
(js-emit-tail-as-stmt name (nth expr 3))
"return NIL;")
" }")
(= h "when")
(let
((body-parts (rest (rest expr))))
(str
"if (isSxTruthy("
(js-expr (nth expr 1))
")) { "
(if
(empty? body-parts)
""
(str
(join
"\n"
(map
(fn (e) (js-statement e))
(slice body-parts 0 (- (len body-parts) 1))))
(if (> (len body-parts) 1) "\n" "")
(js-emit-tail-as-stmt name (last body-parts))))
" } else { return NIL; }"))
(= h "cond")
(js-emit-cond-as-loop-stmt name (rest expr))
(or (= h "let") (= h "let*"))
(let
((bindings (nth expr 1))
(body (rest (rest expr)))
(parts (list)))
(begin
(js-append-let-binding-parts bindings parts)
(for-each
(fn (e) (append! parts (js-statement e)))
(slice body 0 (- (len body) 1)))
(append! parts (js-emit-tail-as-stmt name (last body)))
(str "{ " (join "\n" parts) " }")))
:else (str "return " (js-expr expr) ";"))))))))
(define
js-emit-cond-as-loop-stmt
(fn
((name :as string) (clauses :as list))
(if
(empty? clauses)
"return NIL;"
(let
((is-scheme (and (every? (fn (c) (and (list? c) (= (len c) 2))) clauses) (not (some (fn (c) (= (type-of c) "keyword")) clauses)))))
(if
is-scheme
(js-cond-scheme-loop name clauses 0)
(js-cond-clojure-loop name clauses 0 0 false))))))
(define
js-cond-scheme-loop
(fn
((name :as string) (clauses :as list) (i :as number))
(if
(>= i (len clauses))
"else { return NIL; }"
(let
((clause (nth clauses i))
(test (first clause))
(body (nth clause 1)))
(if
(js-is-else? test)
(str "{ " (js-emit-tail-as-stmt name body) " }")
(str
(if (= i 0) "if" " else if")
" (isSxTruthy("
(js-expr test)
")) { "
(js-emit-tail-as-stmt name body)
" }"
(js-cond-scheme-loop name clauses (+ i 1))))))))
(define
js-cond-clojure-loop
(fn
((name :as string)
(clauses :as list)
(i :as number)
(clause-idx :as number)
(has-else :as boolean))
(if
(>= i (len clauses))
(if has-else "" " else { return NIL; }")
(let
((c (nth clauses i)))
(if
(and (= (type-of c) "keyword") (= (keyword-name c) "else"))
(if
(< (+ i 1) (len clauses))
(str
" else { "
(js-emit-tail-as-stmt name (nth clauses (+ i 1)))
" }")
"")
(if
(< (+ i 1) (len clauses))
(str
(if (= clause-idx 0) "if" " else if")
" (isSxTruthy("
(js-expr c)
")) { "
(js-emit-tail-as-stmt name (nth clauses (+ i 1)))
" }"
(js-cond-clojure-loop
name
clauses
(+ i 2)
(+ clause-idx 1)
has-else))
(str " else { " (js-emit-tail-as-stmt name c) " }")))))))
(define
js-emit-loop-body
(fn
((name :as string) (body :as list))
(if
(empty? body)
"return NIL;"
(str
(join
"\n"
(map (fn (e) (js-statement e)) (slice body 0 (- (len body) 1))))
(if (> (len body) 1) "\n" "")
(js-emit-tail-as-stmt name (last body))))))
(define
js-expr
(fn
(expr)
(cond
(= (type-of expr) "boolean")
(if expr "true" "false")
(nil? expr)
"NIL"
(number? expr)
(str expr)
(string? expr)
(js-quote-string expr)
(= (type-of expr) "symbol")
(js-mangle (symbol-name expr))
(= (type-of expr) "keyword")
(js-quote-string (keyword-name expr))
(= (type-of expr) "dict")
(js-emit-native-dict expr)
(list? expr)
(if (empty? expr) "[]" (js-emit-list expr))
:else (str expr))))
(define
js-emit-native-dict
(fn
((d :as dict))
(let
((items (keys d)))
(str
"{"
(join
", "
(map
(fn (k) (str (js-quote-string k) ": " (js-expr (get d k))))
items))
"}"))))
(define
js-emit-list
(fn
(expr)
(let
((head (first expr)) (args (rest expr)))
(if
(not (= (type-of head) "symbol"))
(if
(= (type-of head) "list")
(str "(" (js-expr head) ")(" (join ", " (map js-expr args)) ")")
(str "[" (join ", " (map js-expr expr)) "]"))
(let
((op (symbol-name head)))
(cond
(or (= op "fn") (= op "lambda"))
(js-emit-fn expr)
(or (= op "let") (= op "let*"))
(js-emit-let expr)
(= op "if")
(let
((cond-e (js-expr (nth args 0)))
(then-e (js-expr (nth args 1)))
(else-e
(if (>= (len args) 3) (js-expr (nth args 2)) "NIL")))
(str "(isSxTruthy(" cond-e ") ? " then-e " : " else-e ")"))
(= op "when")
(js-emit-when expr)
(= op "cond")
(js-emit-cond args)
(= op "case")
(js-emit-case args)
(= op "and")
(js-emit-and args)
(= op "or")
(js-emit-or args)
(= op "not")
(str "!isSxTruthy(" (js-expr (first args)) ")")
(or (= op "do") (= op "begin"))
(js-emit-do args)
(= op "list")
(str "[" (join ", " (map js-expr args)) "]")
(= op "dict")
(js-emit-dict-literal args)
(= op "quote")
(js-emit-quote (first args))
(= op "set!")
(str
"("
(js-mangle (symbol-name (first args)))
" = "
(js-expr (nth args 1))
")")
(= op "str")
(if
(empty? args)
"\"\""
(str
"("
(join
" + "
(map (fn (x) (str "String(" (js-expr x) ")")) args))
")"))
(= op "append!")
(str
"append_b("
(js-expr (nth args 0))
", "
(js-expr (nth args 1))
")")
(= op "dict-set!")
(str
"dictSet("
(js-expr (nth args 0))
", "
(js-expr (nth args 1))
", "
(js-expr (nth args 2))
")")
(= op "env-bind!")
(str
"envBind("
(js-expr (nth args 0))
", "
(js-expr (nth args 1))
", "
(js-expr (nth args 2))
")")
(= op "env-set!")
(str
"envSet("
(js-expr (nth args 0))
", "
(js-expr (nth args 1))
", "
(js-expr (nth args 2))
")")
(= op "set-lambda-name!")
(str
"setLambdaName("
(js-expr (nth args 0))
", "
(js-expr (nth args 1))
")")
(js-infix? op)
(js-emit-infix op args)
(= op "inc")
(str "(" (js-expr (first args)) " + 1)")
(= op "=")
(str
"sxEq("
(js-expr (first args))
", "
(js-expr (nth args 1))
")")
(= op "!=")
(str
"!sxEq("
(js-expr (first args))
", "
(js-expr (nth args 1))
")")
(= op "dec")
(str "(" (js-expr (first args)) " - 1)")
(= op "match")
(js-emit-match args)
:else (str (js-mangle op) "(" (join ", " (map js-expr args)) ")")))))))
(define
js-emit-fn
(fn
(expr)
(let
((params (nth expr 1))
(body (rest (rest expr)))
(param-info (js-collect-params params)))
(let
((param-strs (first param-info))
(rest-name (nth param-info 1))
(params-str (join ", " param-strs)))
(let
((rest-preamble (if (nil? rest-name) "" (str "var " rest-name " = Array.prototype.slice.call(arguments, " (str (len param-strs)) "); "))))
(if
(= (len body) 1)
(let
((body-js (js-expr (first body))))
(if
(not (= rest-preamble ""))
(str
"function("
params-str
") { "
rest-preamble
"return "
body-js
"; }")
(str "function(" params-str ") { return " body-js "; }")))
(let
((parts (list)))
(begin
(when
(not (= rest-preamble ""))
(append!
parts
(slice
rest-preamble
0
(- (string-length rest-preamble) 1))))
(for-each
(fn (b) (append! parts (js-statement b)))
(slice body 0 (- (len body) 1)))
(append! parts (str "return " (js-expr (last body)) ";"))
(str "function(" params-str ") { " (join "\n" parts) " }")))))))))
(define
js-collect-params
(fn ((params :as list)) (js-collect-params-loop params 0 (list) nil)))
(define
js-collect-params-loop
(fn
((params :as list) (i :as number) (result :as list) rest-name)
(if
(>= i (len params))
(list result rest-name)
(let
((p (nth params i)))
(cond
(and (= (type-of p) "symbol") (= (symbol-name p) "&rest"))
(if
(< (+ i 1) (len params))
(let
((rp (nth params (+ i 1))))
(js-collect-params-loop
params
(+ i 2)
result
(js-mangle
(if
(and
(= (type-of rp) "list")
(= (len rp) 3)
(= (type-of (nth rp 1)) "keyword")
(= (keyword-name (nth rp 1)) "as"))
(symbol-name (first rp))
(if
(= (type-of rp) "symbol")
(symbol-name rp)
(str rp))))))
(js-collect-params-loop params (+ i 1) result rest-name))
(= (type-of p) "symbol")
(js-collect-params-loop
params
(+ i 1)
(append result (js-mangle (symbol-name p)))
rest-name)
(and
(= (type-of p) "list")
(= (len p) 3)
(= (type-of (nth p 1)) "keyword")
(= (keyword-name (nth p 1)) "as"))
(js-collect-params-loop
params
(+ i 1)
(append result (js-mangle (symbol-name (first p))))
rest-name)
:else (js-collect-params-loop
params
(+ i 1)
(append result (str p))
rest-name))))))
(define
js-emit-let
(fn
(expr)
(if
(= (type-of (nth expr 1)) "symbol")
(js-emit-named-let expr)
(let
((bindings (nth expr 1)) (body (rest (rest expr))))
(let
((binding-lines (js-parse-let-bindings bindings))
(body-strs (list)))
(begin
(for-each
(fn (b) (append! body-strs (str " " (js-statement b))))
(slice body 0 (- (len body) 1)))
(append! body-strs (str " return " (js-expr (last body)) ";"))
(str
"(function() {\n"
(join "\n" binding-lines)
(if (empty? binding-lines) "" "\n")
(join "\n" body-strs)
"\n})()")))))))
(define
js-emit-named-let
(fn
(expr)
(let
((loop-name (symbol-name (nth expr 1)))
(bindings (nth expr 2))
(body (slice expr 3))
(params (list))
(inits (list)))
(for-each
(fn
(b)
(let
((pname (if (= (type-of (first b)) "symbol") (symbol-name (first b)) (str (first b)))))
(append! params (js-mangle pname))
(append! inits (js-expr (nth b 1)))))
bindings)
(let
((body-strs (list)) (mangled-name (js-mangle loop-name)))
(for-each
(fn (b) (append! body-strs (str " " (js-statement b))))
(slice body 0 (- (len body) 1)))
(append! body-strs (str " return " (js-expr (last body)) ";"))
(str
"(function "
mangled-name
"("
(join ", " params)
") {\n"
(join "\n" body-strs)
"\n})("
(join ", " inits)
")")))))
(define
js-parse-let-bindings
(fn
(bindings)
(if
(not (and (list? bindings) (not (empty? bindings))))
(list)
(if
(list? (first bindings))
(map
(fn
(b)
(let
((vname (if (= (type-of (first b)) "symbol") (symbol-name (first b)) (str (first b)))))
(str " var " (js-mangle vname) " = " (js-expr (nth b 1)) ";")))
bindings)
(js-parse-clojure-let-bindings bindings 0 (list))))))
(define
js-parse-clojure-let-bindings
(fn
(bindings (i :as number) (result :as list))
(if
(>= i (- (len bindings) 1))
result
(let
((vname (if (= (type-of (nth bindings i)) "symbol") (symbol-name (nth bindings i)) (str (nth bindings i)))))
(js-parse-clojure-let-bindings
bindings
(+ i 2)
(append
result
(str
" var "
(js-mangle vname)
" = "
(js-expr (nth bindings (+ i 1)))
";")))))))
(define
js-emit-let-bindings-as-vars
(fn
(bindings)
(if
(not (and (list? bindings) (not (empty? bindings))))
""
(if
(list? (first bindings))
(str
(join
" "
(map
(fn
(b)
(let
((vname (if (= (type-of (first b)) "symbol") (symbol-name (first b)) (str (first b)))))
(str
"var "
(js-mangle vname)
" = "
(js-expr (nth b 1))
";")))
bindings))
" ")
(str (js-emit-clojure-let-vars bindings 0 (list)) " ")))))
(define
js-emit-clojure-let-vars
(fn
(bindings (i :as number) (result :as list))
(if
(>= i (- (len bindings) 1))
(join " " result)
(let
((vname (if (= (type-of (nth bindings i)) "symbol") (symbol-name (nth bindings i)) (str (nth bindings i)))))
(js-emit-clojure-let-vars
bindings
(+ i 2)
(append
result
(str
"var "
(js-mangle vname)
" = "
(js-expr (nth bindings (+ i 1)))
";")))))))
(define
js-append-let-binding-parts
(fn
(bindings (parts :as list))
(when
(and (list? bindings) (not (empty? bindings)))
(if
(list? (first bindings))
(for-each
(fn
(b)
(let
((vname (if (= (type-of (first b)) "symbol") (symbol-name (first b)) (str (first b)))))
(append!
parts
(str "var " (js-mangle vname) " = " (js-expr (nth b 1)) ";"))))
bindings)
(js-append-clojure-bindings bindings parts 0)))))
(define
js-append-clojure-bindings
(fn
(bindings (parts :as list) (i :as number))
(when
(< i (- (len bindings) 1))
(let
((vname (if (= (type-of (nth bindings i)) "symbol") (symbol-name (nth bindings i)) (str (nth bindings i)))))
(begin
(append!
parts
(str
"var "
(js-mangle vname)
" = "
(js-expr (nth bindings (+ i 1)))
";"))
(js-append-clojure-bindings bindings parts (+ i 2)))))))
(define
js-emit-when
(fn
(expr)
(let
((cond-e (js-expr (nth expr 1))) (body-parts (rest (rest expr))))
(if
(= (len body-parts) 1)
(str
"(isSxTruthy("
cond-e
") ? "
(js-expr (first body-parts))
" : NIL)")
(str "(isSxTruthy(" cond-e ") ? " (js-emit-do body-parts) " : NIL)")))))
(define
js-emit-cond
(fn
((clauses :as list))
(if
(empty? clauses)
"NIL"
(let
((is-scheme (and (every? (fn (c) (and (list? c) (= (len c) 2))) clauses) (not (some (fn (c) (= (type-of c) "keyword")) clauses)))))
(if is-scheme (js-cond-scheme clauses) (js-cond-clojure clauses))))))
(define
js-is-else?
(fn
(test)
(or
(and
(= (type-of test) "symbol")
(or (= (symbol-name test) "else") (= (symbol-name test) ":else")))
(and (= (type-of test) "keyword") (= (keyword-name test) "else")))))
(define
js-cond-scheme
(fn
((clauses :as list))
(if
(empty? clauses)
"NIL"
(let
((clause (first clauses))
(test (first clause))
(body (nth clause 1)))
(if
(js-is-else? test)
(js-expr body)
(str
"(isSxTruthy("
(js-expr test)
") ? "
(js-expr body)
" : "
(js-cond-scheme (rest clauses))
")"))))))
(define
js-cond-clojure
(fn
((clauses :as list))
(if
(< (len clauses) 2)
"NIL"
(let
((test (first clauses)) (body (nth clauses 1)))
(if
(js-is-else? test)
(js-expr body)
(str
"(isSxTruthy("
(js-expr test)
") ? "
(js-expr body)
" : "
(js-cond-clojure (rest (rest clauses)))
")"))))))
(define
js-emit-case
(fn
((args :as list))
(let
((match-expr (js-expr (first args))) (clauses (rest args)))
(str
"(function() { var _m = "
match-expr
"; "
(js-case-chain clauses)
" })()"))))
(define
js-emit-match
(fn
((args :as list))
(let
((match-expr (js-expr (first args))) (clauses (rest args)))
(str
"(function() { var _m = "
match-expr
"; "
(js-match-chain clauses)
" })()"))))
(define
js-match-chain
(fn
((clauses :as list))
(if
(empty? clauses)
"return NIL;"
(let
((clause (first clauses))
(pattern (first clause))
(body (nth clause 1)))
(if
(and
(= (type-of pattern) "symbol")
(= (symbol-name pattern) "_"))
(str "return " (js-expr body) ";")
(str
"if (_m == "
(js-expr pattern)
") return "
(js-expr body)
"; "
(js-match-chain (rest clauses))))))))
(define
js-case-chain
(fn
((clauses :as list))
(if
(< (len clauses) 2)
"return NIL;"
(let
((test (nth clauses 0)) (body (nth clauses 1)))
(if
(js-is-else? test)
(str "return " (js-expr body) ";")
(str
"if (_m == "
(js-expr test)
") return "
(js-expr body)
"; "
(js-case-chain (rest (rest clauses)))))))))
(define
js-emit-and
(fn
((args :as list))
(let
((parts (map js-expr args)))
(if
(= (len parts) 1)
(first parts)
(str
"("
(join
" && "
(map
(fn (p) (str "isSxTruthy(" p ")"))
(slice parts 0 (- (len parts) 1))))
" && "
(last parts)
")")))))
(define
js-emit-or
(fn
((args :as list))
(if
(= (len args) 1)
(js-expr (first args))
(str "sxOr(" (join ", " (map js-expr args)) ")"))))
(define
js-emit-do
(fn
((args :as list))
(if
(= (len args) 1)
(js-expr (first args))
(str "(" (join ", " (map js-expr args)) ")"))))
(define
js-emit-dict-literal
(fn ((pairs :as list)) (str "{" (js-dict-pairs-str pairs 0 (list)) "}")))
(define
js-dict-pairs-str
(fn
((pairs :as list) (i :as number) (result :as list))
(if
(>= i (- (len pairs) 1))
(join ", " result)
(let
((key (nth pairs i)) (val (nth pairs (+ i 1))))
(let
((key-str (if (= (type-of key) "keyword") (js-quote-string (keyword-name key)) (str "[" (js-expr key) "]")))
(val-str (js-expr val)))
(js-dict-pairs-str
pairs
(+ i 2)
(append result (str key-str ": " val-str))))))))
(define
js-emit-infix
(fn
((op :as string) (args :as list))
(let
((js-op (js-op-symbol op)) (n (len args)))
(cond
(and (= n 1) (= op "-"))
(str "(-" (js-expr (first args)) ")")
(= n 2)
(str
"("
(js-expr (first args))
" "
js-op
" "
(js-expr (nth args 1))
")")
:else (let
((result (js-expr (first args))))
(for-each
(fn
(arg)
(set! result (str "(" result " " js-op " " (js-expr arg) ")")))
(rest args))
result)))))
(define
js-emit-quote
(fn
(expr)
(cond
(= (type-of expr) "boolean")
(if expr "true" "false")
(number? expr)
(str expr)
(string? expr)
(js-quote-string expr)
(nil? expr)
"NIL"
(= (type-of expr) "symbol")
(str "new Symbol(" (js-quote-string (symbol-name expr)) ")")
(= (type-of expr) "keyword")
(str "new Keyword(" (js-quote-string (keyword-name expr)) ")")
(list? expr)
(str "[" (join ", " (map js-emit-quote expr)) "]")
:else (str expr))))
(define
js-statement
(fn
(expr)
(if
(and
(list? expr)
(not (empty? expr))
(= (type-of (first expr)) "symbol"))
(let
((name (symbol-name (first expr))))
(cond
(= name "define")
(js-emit-define expr)
(= name "set!")
(str
(js-mangle (symbol-name (nth expr 1)))
" = "
(js-expr (nth expr 2))
";")
(= name "when")
(js-emit-when-stmt expr)
(or (= name "do") (= name "begin"))
(join "\n" (map js-statement (rest expr)))
(= name "for-each")
(js-emit-for-each-stmt expr)
(= name "dict-set!")
(str
(js-expr (nth expr 1))
"["
(js-expr (nth expr 2))
"] = "
(js-expr (nth expr 3))
";")
(= name "append!")
(str (js-expr (nth expr 1)) ".push(" (js-expr (nth expr 2)) ");")
(= name "env-bind!")
(str
"envBind("
(js-expr (nth expr 1))
", "
(js-expr (nth expr 2))
", "
(js-expr (nth expr 3))
");")
(= name "env-set!")
(str
"envSet("
(js-expr (nth expr 1))
", "
(js-expr (nth expr 2))
", "
(js-expr (nth expr 3))
");")
(= name "set-lambda-name!")
(str
(js-expr (nth expr 1))
".name = "
(js-expr (nth expr 2))
";")
:else (str (js-expr expr) ";")))
(str (js-expr expr) ";"))))
(define
js-emit-define
(fn
(expr)
(let
((name (if (= (type-of (nth expr 1)) "symbol") (symbol-name (nth expr 1)) (str (nth expr 1))))
(val-expr
(if
(and
(>= (len expr) 5)
(= (type-of (nth expr 2)) "keyword")
(= (keyword-name (nth expr 2)) "effects"))
(nth expr 4)
(nth expr 2))))
(let
((mangled (js-mangle name))
(var-decl
(if
(nil? val-expr)
(str "var " (js-mangle name) " = NIL;")
(if
(and
(list? val-expr)
(not (empty? val-expr))
(= (type-of (first val-expr)) "symbol")
(or
(= (symbol-name (first val-expr)) "fn")
(= (symbol-name (first val-expr)) "lambda"))
(list? (nth val-expr 1))
(= (len (nth val-expr 1)) 0)
(js-is-self-tail-recursive? name (rest (rest val-expr))))
(let
((body (rest (rest val-expr)))
(loop-body (js-emit-loop-body name body)))
(str
"var "
mangled
" = function() { while(true) { "
loop-body
" } };"))
(str "var " mangled " = " (js-expr val-expr) ";")))))
(str var-decl "\nPRIMITIVES[\"" name "\"] = " mangled ";")))))
(define
js-emit-when-stmt
(fn
(expr)
(let
((cond-e (js-expr (nth expr 1))) (body-parts (rest (rest expr))))
(str
"if (isSxTruthy("
cond-e
")) {\n"
(join "\n" (map (fn (e) (str " " (js-statement e))) body-parts))
"\n}"))))
(define
js-emit-for-each-stmt
(fn
(expr)
(let
((fn-expr (nth expr 1))
(coll-expr (nth expr 2))
(coll (js-expr coll-expr)))
(if
(and
(list? fn-expr)
(= (type-of (first fn-expr)) "symbol")
(= (symbol-name (first fn-expr)) "fn"))
(let
((params (nth fn-expr 1))
(body (rest (rest fn-expr)))
(raw-p (first params))
(p
(cond
(= (type-of raw-p) "symbol")
(symbol-name raw-p)
(and
(= (type-of raw-p) "list")
(= (len raw-p) 3)
(= (type-of (nth raw-p 1)) "keyword")
(= (keyword-name (nth raw-p 1)) "as"))
(symbol-name (first raw-p))
:else (str raw-p)))
(p-js (js-mangle p)))
(str
"{ var _c = "
coll
"; for (var _i = 0; _i < _c.length; _i++) { var "
p-js
" = _c[_i]; "
(join "\n" (map js-statement body))
" } }"))
(str
"{ var _c = "
coll
"; for (var _i = 0; _i < _c.length; _i++) { "
(js-expr fn-expr)
"(_c[_i]); } }")))))
(define
js-translate-file
(fn
((defines :as list))
(join
"\n"
(map
(fn
(pair)
(let
((name (first pair)) (expr (nth pair 1)))
(str " // " name "\n " (js-statement expr) "\n")))
defines))))