diff --git a/shared/sx/ref/py.sx b/shared/sx/ref/py.sx new file mode 100644 index 0000000..e172579 --- /dev/null +++ b/shared/sx/ref/py.sx @@ -0,0 +1,1182 @@ +;; ========================================================================== +;; py.sx — SX-to-Python translator, written in SX +;; +;; Translates (define ...) forms from .sx spec files into Python source. +;; This is the self-hosting bootstrapper: the Python evaluator executes +;; this file against the spec to produce sx_ref.py — identical output to +;; the hand-written bootstrap_py.py. +;; +;; Usage (from SX): +;; (py-expr expr) — translate one expression to Python +;; (py-statement expr indent) — translate to Python statement +;; (py-translate-file defines) — translate a list of (name . define-expr) pairs +;; +;; Usage (as reader macro): +;; #py(define foo (fn (x) (+ x 1))) +;; → "foo = lambda x: (x + 1)" +;; ========================================================================== + + +;; -------------------------------------------------------------------------- +;; Python reserved words — names that get _ suffix +;; -------------------------------------------------------------------------- + +(define py-reserved + (list "False" "None" "True" "and" "as" "assert" "async" "await" + "break" "class" "continue" "def" "del" "elif" "else" "except" + "finally" "for" "from" "global" "if" "import" "in" "is" + "lambda" "nonlocal" "not" "or" "pass" "raise" "return" "try" + "while" "with" "yield" + "default" "type" "id" "input" "open" "print" "set" "super")) + + +;; -------------------------------------------------------------------------- +;; RENAMES table — explicit SX name → Python name mappings +;; -------------------------------------------------------------------------- + +(define py-renames { + :nil "NIL" + :true "True" + :false "False" + "nil?" "is_nil" + "type-of" "type_of" + "symbol-name" "symbol_name" + "keyword-name" "keyword_name" + "make-lambda" "make_lambda" + "make-component" "make_component" + "make-macro" "make_macro" + "make-thunk" "make_thunk" + "make-handler-def" "make_handler_def" + "make-query-def" "make_query_def" + "make-action-def" "make_action_def" + "make-page-def" "make_page_def" + "make-symbol" "make_symbol" + "make-keyword" "make_keyword" + "lambda-params" "lambda_params" + "lambda-body" "lambda_body" + "lambda-closure" "lambda_closure" + "lambda-name" "lambda_name" + "set-lambda-name!" "set_lambda_name" + "component-params" "component_params" + "component-body" "component_body" + "component-closure" "component_closure" + "component-has-children?" "component_has_children" + "component-name" "component_name" + "component-affinity" "component_affinity" + "macro-params" "macro_params" + "macro-rest-param" "macro_rest_param" + "macro-body" "macro_body" + "macro-closure" "macro_closure" + "thunk?" "is_thunk" + "thunk-expr" "thunk_expr" + "thunk-env" "thunk_env" + "callable?" "is_callable" + "lambda?" "is_lambda" + "component?" "is_component" + "island?" "is_island" + "make-island" "make_island" + "make-signal" "make_signal" + "signal?" "is_signal" + "signal-value" "signal_value" + "signal-set-value!" "signal_set_value" + "signal-subscribers" "signal_subscribers" + "signal-add-sub!" "signal_add_sub" + "signal-remove-sub!" "signal_remove_sub" + "signal-deps" "signal_deps" + "signal-set-deps!" "signal_set_deps" + "set-tracking-context!" "set_tracking_context" + "get-tracking-context" "get_tracking_context" + "make-tracking-context" "make_tracking_context" + "tracking-context-deps" "tracking_context_deps" + "tracking-context-add-dep!" "tracking_context_add_dep" + "tracking-context-notify-fn" "tracking_context_notify_fn" + "identical?" "is_identical" + "notify-subscribers" "notify_subscribers" + "flush-subscribers" "flush_subscribers" + "dispose-computed" "dispose_computed" + "with-island-scope" "with_island_scope" + "register-in-scope" "register_in_scope" + "*batch-depth*" "_batch_depth" + "*batch-queue*" "_batch_queue" + "*island-scope*" "_island_scope" + "*store-registry*" "_store_registry" + "def-store" "def_store" + "use-store" "use_store" + "clear-stores" "clear_stores" + "emit-event" "emit_event" + "on-event" "on_event" + "bridge-event" "bridge_event" + "dom-listen" "dom_listen" + "dom-dispatch" "dom_dispatch" + "event-detail" "event_detail" + "macro?" "is_macro" + "primitive?" "is_primitive" + "get-primitive" "get_primitive" + "env-has?" "env_has" + "env-get" "env_get" + "env-set!" "env_set" + "env-extend" "env_extend" + "env-merge" "env_merge" + "dict-set!" "dict_set" + "dict-get" "dict_get" + "dict-has?" "dict_has" + "dict-delete!" "dict_delete" + "eval-expr" "eval_expr" + "eval-list" "eval_list" + "eval-call" "eval_call" + "is-render-expr?" "is_render_expr" + "render-expr" "render_expr" + "call-lambda" "call_lambda" + "call-component" "call_component" + "parse-keyword-args" "parse_keyword_args" + "parse-comp-params" "parse_comp_params" + "parse-macro-params" "parse_macro_params" + "expand-macro" "expand_macro" + "render-to-html" "render_to_html" + "render-to-sx" "render_to_sx" + "render-value-to-html" "render_value_to_html" + "render-list-to-html" "render_list_to_html" + "render-html-element" "render_html_element" + "render-html-component" "render_html_component" + "parse-element-args" "parse_element_args" + "render-attrs" "render_attrs" + "aser-list" "aser_list" + "aser-fragment" "aser_fragment" + "aser-call" "aser_call" + "aser-special" "aser_special" + "sf-if" "sf_if" + "sf-when" "sf_when" + "sf-cond" "sf_cond" + "sf-cond-scheme" "sf_cond_scheme" + "sf-cond-clojure" "sf_cond_clojure" + "sf-case" "sf_case" + "sf-case-loop" "sf_case_loop" + "sf-and" "sf_and" + "sf-or" "sf_or" + "sf-let" "sf_let" + "sf-lambda" "sf_lambda" + "sf-define" "sf_define" + "sf-defcomp" "sf_defcomp" + "defcomp-kwarg" "defcomp_kwarg" + "sf-defmacro" "sf_defmacro" + "sf-begin" "sf_begin" + "sf-quote" "sf_quote" + "sf-quasiquote" "sf_quasiquote" + "sf-thread-first" "sf_thread_first" + "sf-set!" "sf_set_bang" + "sf-reset" "sf_reset" + "sf-shift" "sf_shift" + "qq-expand" "qq_expand" + "ho-map" "ho_map" + "ho-map-indexed" "ho_map_indexed" + "ho-filter" "ho_filter" + "ho-reduce" "ho_reduce" + "ho-some" "ho_some" + "ho-every" "ho_every" + "ho-for-each" "ho_for_each" + "sf-defstyle" "sf_defstyle" + "special-form?" "is_special_form" + "ho-form?" "is_ho_form" + "strip-prefix" "strip_prefix" + "escape-html" "escape_html" + "escape-attr" "escape_attr" + "escape-string" "escape_string" + "raw-html-content" "raw_html_content" + "HTML_TAGS" "HTML_TAGS" + "VOID_ELEMENTS" "VOID_ELEMENTS" + "BOOLEAN_ATTRS" "BOOLEAN_ATTRS" + "definition-form?" "is_definition_form" + "RENDER_HTML_FORMS" "RENDER_HTML_FORMS" + "render-html-form?" "is_render_html_form" + "dispatch-html-form" "dispatch_html_form" + "render-lambda-html" "render_lambda_html" + "make-raw-html" "make_raw_html" + "render-html-island" "render_html_island" + "serialize-island-state" "serialize_island_state" + "json-serialize" "json_serialize" + "empty-dict?" "is_empty_dict" + "sf-defisland" "sf_defisland" + "render-to-sx" "render_to_sx" + "aser" "aser" + "eval-case-aser" "eval_case_aser" + "sx-serialize" "sx_serialize" + "sx-serialize-dict" "sx_serialize_dict" + "sx-expr-source" "sx_expr_source" + "contains?" "contains_p" + "starts-with?" "starts_with_p" + "ends-with?" "ends_with_p" + "empty?" "empty_p" + "every?" "every_p" + "for-each" "for_each" + "for-each-indexed" "for_each_indexed" + "map-indexed" "map_indexed" + "map-dict" "map_dict" + "eval-cond" "eval_cond" + "eval-cond-scheme" "eval_cond_scheme" + "eval-cond-clojure" "eval_cond_clojure" + "process-bindings" "process_bindings" + "scan-refs" "scan_refs" + "scan-refs-walk" "scan_refs_walk" + "transitive-deps" "transitive_deps" + "compute-all-deps" "compute_all_deps" + "scan-components-from-source" "scan_components_from_source" + "components-needed" "components_needed" + "page-component-bundle" "page_component_bundle" + "page-css-classes" "page_css_classes" + "component-deps" "component_deps" + "component-set-deps!" "component_set_deps" + "component-css-classes" "component_css_classes" + "component-io-refs" "component_io_refs" + "component-set-io-refs!" "component_set_io_refs" + "env-components" "env_components" + "regex-find-all" "regex_find_all" + "scan-css-classes" "scan_css_classes" + "scan-io-refs" "scan_io_refs" + "scan-io-refs-walk" "scan_io_refs_walk" + "transitive-io-refs" "transitive_io_refs" + "compute-all-io-refs" "compute_all_io_refs" + "component-pure?" "component_pure_p" + "render-target" "render_target" + "page-render-plan" "page_render_plan" + "split-path-segments" "split_path_segments" + "make-route-segment" "make_route_segment" + "parse-route-pattern" "parse_route_pattern" + "match-route-segments" "match_route_segments" + "match-route" "match_route" + "find-matching-route" "find_matching_route" +}) + + +;; -------------------------------------------------------------------------- +;; Name mangling: SX identifier → valid Python identifier +;; -------------------------------------------------------------------------- + +(define py-mangle + (fn (name) + (let ((renamed (get py-renames name))) + (if (not (nil? renamed)) + renamed + ;; General mangling rules + (let ((result name)) + ;; Handle trailing ? and ! + (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))) + ;; Kebab to snake_case + (let ((result (replace result "-" "_"))) + ;; Escape Python reserved words + (if (some (fn (r) (= r result)) py-reserved) + (str result "_") + result)))))))) + + +;; -------------------------------------------------------------------------- +;; String quoting for Python +;; -------------------------------------------------------------------------- + +(define py-quote-string + (fn (s) + ;; Produce a Python repr-style string literal + (str "'" (replace (replace (replace (replace s "\\" "\\\\") "'" "\\'") "\n" "\\n") "\t" "\\t") "'"))) + + +;; -------------------------------------------------------------------------- +;; Infix operators +;; -------------------------------------------------------------------------- + +(define py-infix-ops + (list "+" "-" "*" "/" "=" "!=" "<" ">" "<=" ">=" "mod")) + +(define py-infix? + (fn (op) + (some (fn (x) (= x op)) py-infix-ops))) + +(define py-op-symbol + (fn (op) + (case op + "=" "==" + "!=" "!=" + "mod" "%" + :else op))) + + +;; -------------------------------------------------------------------------- +;; Cell variable detection: find set! targets crossing lambda boundaries +;; -------------------------------------------------------------------------- + +(define py-find-nested-set-vars + (fn (body) + ;; Returns a list of mangled variable names that are set! from within + ;; nested fn/lambda bodies + (let ((result (list))) + (begin + (for-each (fn (b) (py-scan-set-vars b false result)) body) + result)))) + +(define py-scan-set-vars + (fn (node in-nested result) + (when (and (list? node) (not (empty? node))) + (let ((head (first node))) + (cond + ;; fn/lambda — mark as nested and scan body + (and (= (type-of head) "symbol") + (or (= (symbol-name head) "fn") + (= (symbol-name head) "lambda"))) + (for-each (fn (child) (py-scan-set-vars child true result)) + (rest (rest node))) + ;; set! inside nested fn — record the variable + (and (= (type-of head) "symbol") + (= (symbol-name head) "set!") + in-nested + (>= (len node) 2)) + (let ((var-name (if (= (type-of (nth node 1)) "symbol") + (symbol-name (nth node 1)) + (str (nth node 1))))) + (let ((mangled (py-mangle var-name))) + (when (not (some (fn (x) (= x mangled)) result)) + (append! result mangled)))) + ;; Otherwise scan children + :else + (for-each (fn (child) + (when (list? child) + (py-scan-set-vars child in-nested result))) + node)))))) + +;; Check if a fn expression's body uses set! anywhere +(define py-body-uses-set? + (fn (fn-expr) + (let ((body (rest (rest fn-expr)))) + (py-has-set? body)))) + +(define py-has-set? + (fn (nodes) + (some (fn (node) + (and (list? node) + (not (empty? node)) + (or (and (= (type-of (first node)) "symbol") + (= (symbol-name (first node)) "set!")) + (py-has-set? (filter (fn (x) (list? x)) node))))) + nodes))) + + +;; -------------------------------------------------------------------------- +;; Expression translator: SX AST → Python expression string +;; -------------------------------------------------------------------------- + +(define py-expr + (fn (expr) + (py-expr-with-cells expr (list)))) + +(define py-expr-with-cells + (fn (expr cell-vars) + (cond + ;; Bool MUST come before number check (Python: bool is subclass of int) + (= (type-of expr) "boolean") + (if expr "True" "False") + + ;; Nil + (nil? expr) "NIL" + + ;; Numbers + (number? expr) (str expr) + + ;; Strings + (string? expr) (py-quote-string expr) + + ;; Symbols + (= (type-of expr) "symbol") + (let ((mangled (py-mangle (symbol-name expr)))) + (if (some (fn (c) (= c mangled)) cell-vars) + (str "_cells[" (py-quote-string mangled) "]") + mangled)) + + ;; Keywords → string + (= (type-of expr) "keyword") + (py-quote-string (keyword-name expr)) + + ;; Dicts (native {:key val} syntax) + (= (type-of expr) "dict") + (py-emit-native-dict expr cell-vars) + + ;; Lists (function calls / special forms) + (list? expr) + (if (empty? expr) + "[]" + (py-emit-list expr cell-vars)) + + ;; Fallback + :else (str expr)))) + + +;; -------------------------------------------------------------------------- +;; Dict emission +;; -------------------------------------------------------------------------- + +(define py-emit-native-dict + (fn (d cell-vars) + (let ((items (keys d))) + (str "{" (join ", " (map (fn (k) + (str (py-quote-string k) ": " (py-expr-with-cells (get d k) cell-vars))) + items)) "}")))) + + +;; -------------------------------------------------------------------------- +;; List/call emission — the main dispatch +;; -------------------------------------------------------------------------- + +(define py-emit-list + (fn (expr cell-vars) + (let ((head (first expr)) + (args (rest expr))) + (if (not (= (type-of head) "symbol")) + ;; Data list — not a function call + (str "[" (join ", " (map (fn (x) (py-expr-with-cells x cell-vars)) expr)) "]") + (let ((op (symbol-name head))) + (cond + ;; fn/lambda + (or (= op "fn") (= op "lambda")) + (py-emit-fn expr cell-vars) + + ;; let/let* + (or (= op "let") (= op "let*")) + (py-emit-let expr cell-vars) + + ;; if + (= op "if") + (let ((cond-e (py-expr-with-cells (nth args 0) cell-vars)) + (then-e (py-expr-with-cells (nth args 1) cell-vars)) + (else-e (if (>= (len args) 3) + (py-expr-with-cells (nth args 2) cell-vars) + "NIL"))) + (str "(" then-e " if sx_truthy(" cond-e ") else " else-e ")")) + + ;; when + (= op "when") + (py-emit-when expr cell-vars) + + ;; cond + (= op "cond") + (py-emit-cond args cell-vars) + + ;; case + (= op "case") + (py-emit-case args cell-vars) + + ;; and + (= op "and") + (py-emit-and args cell-vars) + + ;; or + (= op "or") + (py-emit-or args cell-vars) + + ;; not + (= op "not") + (str "(not sx_truthy(" (py-expr-with-cells (first args) cell-vars) "))") + + ;; do/begin + (or (= op "do") (= op "begin")) + (py-emit-do args cell-vars) + + ;; list literal + (= op "list") + (str "[" (join ", " (map (fn (x) (py-expr-with-cells x cell-vars)) args)) "]") + + ;; dict literal + (= op "dict") + (py-emit-dict-literal args cell-vars) + + ;; quote + (= op "quote") + (py-emit-quote (first args)) + + ;; set! + (= op "set!") + (let ((var-name (if (= (type-of (first args)) "symbol") + (symbol-name (first args)) + (str (first args))))) + (let ((mangled (py-mangle var-name))) + (str "_sx_cell_set(_cells, " (py-quote-string mangled) + ", " (py-expr-with-cells (nth args 1) cell-vars) ")"))) + + ;; str + (= op "str") + (str "sx_str(" (join ", " (map (fn (x) (py-expr-with-cells x cell-vars)) args)) ")") + + ;; Mutation forms in expression context + (= op "append!") + (str "_sx_append(" (py-expr-with-cells (nth args 0) cell-vars) + ", " (py-expr-with-cells (nth args 1) cell-vars) ")") + + (= op "dict-set!") + (str "_sx_dict_set(" (py-expr-with-cells (nth args 0) cell-vars) + ", " (py-expr-with-cells (nth args 1) cell-vars) + ", " (py-expr-with-cells (nth args 2) cell-vars) ")") + + (= op "env-set!") + (str "_sx_dict_set(" (py-expr-with-cells (nth args 0) cell-vars) + ", " (py-expr-with-cells (nth args 1) cell-vars) + ", " (py-expr-with-cells (nth args 2) cell-vars) ")") + + (= op "set-lambda-name!") + (str "_sx_set_attr(" (py-expr-with-cells (nth args 0) cell-vars) + ", 'name', " (py-expr-with-cells (nth args 1) cell-vars) ")") + + ;; Infix operators + (py-infix? op) + (py-emit-infix op args cell-vars) + + ;; inc/dec + (= op "inc") + (str "(" (py-expr-with-cells (first args) cell-vars) " + 1)") + (= op "dec") + (str "(" (py-expr-with-cells (first args) cell-vars) " - 1)") + + ;; Regular function call + :else + (str (py-mangle op) "(" + (join ", " (map (fn (x) (py-expr-with-cells x cell-vars)) args)) + ")"))))))) + + +;; -------------------------------------------------------------------------- +;; fn/lambda → Python lambda +;; -------------------------------------------------------------------------- + +(define py-emit-fn + (fn (expr cell-vars) + (let ((params (nth expr 1)) + (body (rest (rest expr))) + (param-strs (py-collect-params params))) + (let ((params-str (join ", " param-strs))) + (if (= (len body) 1) + (str "lambda " params-str ": " (py-expr-with-cells (first body) cell-vars)) + ;; Multi-expression body + (str "_sx_fn(lambda " params-str ": (\n" + (join ",\n" (map (fn (b) (str " " (py-expr-with-cells b cell-vars))) (slice body 0 (- (len body) 1)))) + ",\n " (py-expr-with-cells (last body) cell-vars) + "\n)[-1])")))))) + +(define py-collect-params + (fn (params) + (py-collect-params-loop params 0 (list)))) + +(define py-collect-params-loop + (fn (params i result) + (if (>= i (len params)) + result + (let ((p (nth params i))) + (cond + ;; &rest marker + (and (= (type-of p) "symbol") (= (symbol-name p) "&rest")) + (if (< (+ i 1) (len params)) + (py-collect-params-loop params (+ i 2) + (append result (str "*" (py-mangle (symbol-name (nth params (+ i 1))))))) + (py-collect-params-loop params (+ i 1) result)) + ;; Normal param + (= (type-of p) "symbol") + (py-collect-params-loop params (+ i 1) + (append result (py-mangle (symbol-name p)))) + ;; Something else + :else + (py-collect-params-loop params (+ i 1) + (append result (str p)))))))) + + +;; -------------------------------------------------------------------------- +;; let → nested IIFE lambdas +;; -------------------------------------------------------------------------- + +(define py-emit-let + (fn (expr cell-vars) + (let ((bindings (nth expr 1)) + (body (rest (rest expr)))) + (let ((assignments (py-parse-bindings bindings cell-vars))) + (let ((body-str (if (= (len body) 1) + (py-expr-with-cells (first body) cell-vars) + (str "_sx_begin(" (join ", " (map (fn (b) (py-expr-with-cells b cell-vars)) body)) ")")))) + ;; Build from inside out + (py-wrap-let-bindings assignments body-str cell-vars)))))) + +(define py-parse-bindings + (fn (bindings cell-vars) + (if (and (list? bindings) (not (empty? bindings))) + (if (list? (first bindings)) + ;; Scheme-style: ((name val) ...) + (map (fn (b) + (let ((vname (if (= (type-of (first b)) "symbol") + (symbol-name (first b)) + (str (first b))))) + (list (py-mangle vname) (py-expr-with-cells (nth b 1) cell-vars)))) + bindings) + ;; Clojure-style: (name val name val ...) + (py-parse-clojure-bindings bindings 0 (list) cell-vars)) + (list)))) + +(define py-parse-clojure-bindings + (fn (bindings i result cell-vars) + (if (>= i (- (len bindings) 1)) + result + (let ((vname (if (= (type-of (nth bindings i)) "symbol") + (symbol-name (nth bindings i)) + (str (nth bindings i))))) + (py-parse-clojure-bindings bindings (+ i 2) + (append result (list (py-mangle vname) (py-expr-with-cells (nth bindings (+ i 1)) cell-vars))) + cell-vars))))) + +(define py-wrap-let-bindings + (fn (assignments body-str cell-vars) + (if (empty? assignments) + body-str + (let ((binding (last assignments)) + (rest-bindings (slice assignments 0 (- (len assignments) 1))) + (name (first binding)) + (val (nth binding 1))) + (let ((inner (if (some (fn (c) (= c name)) cell-vars) + ;; Cell var: initialize in _cells dict + (str "_sx_begin(_sx_cell_set(_cells, " (py-quote-string name) ", " val "), " body-str ")") + ;; Normal: lambda wrapper + (str "(lambda " name ": " body-str ")(" val ")")))) + (py-wrap-let-bindings rest-bindings inner cell-vars)))))) + + +;; -------------------------------------------------------------------------- +;; when → ternary +;; -------------------------------------------------------------------------- + +(define py-emit-when + (fn (expr cell-vars) + (let ((cond-e (py-expr-with-cells (nth expr 1) cell-vars)) + (body-parts (rest (rest expr)))) + (if (= (len body-parts) 1) + (str "(" (py-expr-with-cells (first body-parts) cell-vars) " if sx_truthy(" cond-e ") else NIL)") + (str "(_sx_begin(" (join ", " (map (fn (b) (py-expr-with-cells b cell-vars)) body-parts)) + ") if sx_truthy(" cond-e ") else NIL)"))))) + + +;; -------------------------------------------------------------------------- +;; cond → chained ternaries +;; -------------------------------------------------------------------------- + +(define py-emit-cond + (fn (clauses cell-vars) + (if (empty? clauses) + "NIL" + ;; Detect scheme vs clojure style + (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 + (py-cond-scheme clauses cell-vars) + (py-cond-clojure clauses cell-vars)))))) + +(define py-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 py-cond-scheme + (fn (clauses cell-vars) + (if (empty? clauses) + "NIL" + (let ((clause (first clauses)) + (test (first clause)) + (body (nth clause 1))) + (if (py-is-else? test) + (py-expr-with-cells body cell-vars) + (str "(" (py-expr-with-cells body cell-vars) + " if sx_truthy(" (py-expr-with-cells test cell-vars) + ") else " (py-cond-scheme (rest clauses) cell-vars) ")")))))) + +(define py-cond-clojure + (fn (clauses cell-vars) + (if (< (len clauses) 2) + "NIL" + (let ((test (first clauses)) + (body (nth clauses 1))) + (if (py-is-else? test) + (py-expr-with-cells body cell-vars) + (str "(" (py-expr-with-cells body cell-vars) + " if sx_truthy(" (py-expr-with-cells test cell-vars) + ") else " (py-cond-clojure (rest (rest clauses)) cell-vars) ")")))))) + + +;; -------------------------------------------------------------------------- +;; case → _sx_case +;; -------------------------------------------------------------------------- + +(define py-emit-case + (fn (args cell-vars) + (let ((match-expr (py-expr-with-cells (first args) cell-vars)) + (clauses (rest args))) + (str "_sx_case(" match-expr ", [" (py-case-pairs clauses cell-vars) "])")))) + +(define py-case-pairs + (fn (clauses cell-vars) + (py-case-pairs-loop clauses 0 (list) cell-vars))) + +(define py-case-pairs-loop + (fn (clauses i result cell-vars) + (if (>= i (- (len clauses) 1)) + (join ", " result) + (let ((test (nth clauses i)) + (body (nth clauses (+ i 1)))) + (let ((pair (if (py-is-else? test) + (str "(None, lambda: " (py-expr-with-cells body cell-vars) ")") + (str "(" (py-expr-with-cells test cell-vars) + ", lambda: " (py-expr-with-cells body cell-vars) ")")))) + (py-case-pairs-loop clauses (+ i 2) (append result pair) cell-vars)))))) + + +;; -------------------------------------------------------------------------- +;; and/or → short-circuit ternaries +;; -------------------------------------------------------------------------- + +(define py-emit-and + (fn (args cell-vars) + (let ((parts (map (fn (x) (py-expr-with-cells x cell-vars)) args))) + (if (= (len parts) 1) + (first parts) + (py-and-chain parts))))) + +(define py-and-chain + (fn (parts) + (if (= (len parts) 1) + (first parts) + (let ((p (first parts))) + (str "(" p " if not sx_truthy(" p ") else " (py-and-chain (rest parts)) ")"))))) + +(define py-emit-or + (fn (args cell-vars) + (if (= (len args) 1) + (py-expr-with-cells (first args) cell-vars) + (let ((parts (map (fn (x) (py-expr-with-cells x cell-vars)) args))) + (py-or-chain parts))))) + +(define py-or-chain + (fn (parts) + (if (= (len parts) 1) + (first parts) + (let ((p (first parts))) + (str "(" p " if sx_truthy(" p ") else " (py-or-chain (rest parts)) ")"))))) + + +;; -------------------------------------------------------------------------- +;; do/begin → _sx_begin +;; -------------------------------------------------------------------------- + +(define py-emit-do + (fn (args cell-vars) + (if (= (len args) 1) + (py-expr-with-cells (first args) cell-vars) + (str "_sx_begin(" (join ", " (map (fn (e) (py-expr-with-cells e cell-vars)) args)) ")")))) + + +;; -------------------------------------------------------------------------- +;; dict literal → Python dict +;; -------------------------------------------------------------------------- + +(define py-emit-dict-literal + (fn (pairs cell-vars) + (str "{" (py-dict-pairs-str pairs 0 (list) cell-vars) "}"))) + +(define py-dict-pairs-str + (fn (pairs i result cell-vars) + (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") + (py-quote-string (keyword-name key)) + (py-expr-with-cells key cell-vars))) + (val-str (py-expr-with-cells val cell-vars))) + (py-dict-pairs-str pairs (+ i 2) + (append result (str key-str ": " val-str)) + cell-vars)))))) + + +;; -------------------------------------------------------------------------- +;; Infix operators +;; -------------------------------------------------------------------------- + +(define py-emit-infix + (fn (op args cell-vars) + (let ((py-op (py-op-symbol op))) + (if (and (= (len args) 1) (= op "-")) + (str "(-" (py-expr-with-cells (first args) cell-vars) ")") + (str "(" (py-expr-with-cells (first args) cell-vars) + " " py-op " " (py-expr-with-cells (nth args 1) cell-vars) ")"))))) + + +;; -------------------------------------------------------------------------- +;; quote → Python AST literals +;; -------------------------------------------------------------------------- + +(define py-emit-quote + (fn (expr) + (cond + (= (type-of expr) "boolean") + (if expr "True" "False") + (number? expr) (str expr) + (string? expr) (py-quote-string expr) + (nil? expr) "NIL" + (= (type-of expr) "symbol") + (str "Symbol(" (py-quote-string (symbol-name expr)) ")") + (= (type-of expr) "keyword") + (str "Keyword(" (py-quote-string (keyword-name expr)) ")") + (list? expr) + (str "[" (join ", " (map py-emit-quote expr)) "]") + :else (str expr)))) + + +;; -------------------------------------------------------------------------- +;; Statement translator: SX AST → Python statement string +;; -------------------------------------------------------------------------- + +(define py-pad + (fn (indent) + (join "" (map (fn (i) " ") (range 0 indent))))) + +(define py-statement + (fn (expr indent) + (py-statement-with-cells expr indent (list)))) + +(define py-statement-with-cells + (fn (expr indent cell-vars) + (let ((pad (py-pad indent))) + (if (and (list? expr) (not (empty? expr)) + (= (type-of (first expr)) "symbol")) + (let ((name (symbol-name (first expr)))) + (cond + (= name "define") + (py-emit-define expr indent cell-vars) + (= name "set!") + (str pad (py-mangle (symbol-name (nth expr 1))) " = " + (py-expr-with-cells (nth expr 2) cell-vars)) + (= name "when") + (py-emit-when-stmt expr indent cell-vars) + (or (= name "do") (= name "begin")) + (join "\n" (map (fn (e) (py-statement-with-cells e indent cell-vars)) + (rest expr))) + (= name "for-each") + (py-emit-for-each-stmt expr indent cell-vars) + (= name "dict-set!") + (str pad (py-expr-with-cells (nth expr 1) cell-vars) + "[" (py-expr-with-cells (nth expr 2) cell-vars) + "] = " (py-expr-with-cells (nth expr 3) cell-vars)) + (= name "append!") + (str pad (py-expr-with-cells (nth expr 1) cell-vars) + ".append(" (py-expr-with-cells (nth expr 2) cell-vars) ")") + (= name "env-set!") + (str pad (py-expr-with-cells (nth expr 1) cell-vars) + "[" (py-expr-with-cells (nth expr 2) cell-vars) + "] = " (py-expr-with-cells (nth expr 3) cell-vars)) + (= name "set-lambda-name!") + (str pad (py-expr-with-cells (nth expr 1) cell-vars) + ".name = " (py-expr-with-cells (nth expr 2) cell-vars)) + :else + (str pad (py-expr-with-cells expr cell-vars)))) + (str pad (py-expr-with-cells expr cell-vars)))))) + + +;; -------------------------------------------------------------------------- +;; define → assignment or def statement +;; -------------------------------------------------------------------------- + +(define py-emit-define + (fn (expr indent cell-vars) + (let ((pad (py-pad indent)) + (name (if (= (type-of (nth expr 1)) "symbol") + (symbol-name (nth expr 1)) + (str (nth expr 1)))) + (val-expr (nth expr 2))) + ;; If value is fn/lambda with set! in body, emit as def + (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")) + (py-body-uses-set? val-expr)) + (py-emit-define-as-def name val-expr indent) + (str pad (py-mangle name) " = " (py-expr-with-cells val-expr cell-vars)))))) + + +;; -------------------------------------------------------------------------- +;; define-as-def: function with set! → proper def + _cells +;; -------------------------------------------------------------------------- + +(define py-emit-define-as-def + (fn (name fn-expr indent) + (let ((pad (py-pad indent)) + (params (nth fn-expr 1)) + (body (rest (rest fn-expr))) + (param-strs (py-collect-params params)) + (params-str (join ", " param-strs)) + (py-name (py-mangle name)) + (nested-set-vars (py-find-nested-set-vars body))) + (let ((lines (list (str pad "def " py-name "(" params-str "):")))) + (begin + (when (not (empty? nested-set-vars)) + (append! lines (str pad " _cells = {}"))) + (py-emit-body-stmts body lines (+ indent 1) nested-set-vars) + (join "\n" lines)))))) + + +;; -------------------------------------------------------------------------- +;; Body statement emission (for def bodies) +;; -------------------------------------------------------------------------- + +(define py-emit-body-stmts + (fn (body lines indent cell-vars) + (let ((pad (py-pad indent)) + (total (len body))) + (py-emit-body-stmts-loop body lines indent cell-vars 0 total pad)))) + +(define py-emit-body-stmts-loop + (fn (body lines indent cell-vars i total pad) + (when (< i total) + (let ((expr (nth body i)) + (is-last (= i (- total 1)))) + (begin + (if (and (list? expr) (not (empty? expr)) (= (type-of (first expr)) "symbol")) + (let ((name (symbol-name (first expr)))) + (cond + (or (= name "let") (= name "let*")) + (py-emit-let-as-stmts expr lines indent is-last cell-vars) + (or (= name "do") (= name "begin")) + (if is-last + (py-emit-body-stmts (rest expr) lines indent cell-vars) + (for-each (fn (sub) (py-emit-stmt-recursive sub lines indent cell-vars)) + (rest expr))) + :else + (if is-last + (append! lines (str pad "return " (py-expr-with-cells expr cell-vars))) + (append! lines (py-statement-with-cells expr indent cell-vars))))) + (if is-last + (append! lines (str pad "return " (py-expr-with-cells expr cell-vars))) + (append! lines (py-statement-with-cells expr indent cell-vars)))) + (py-emit-body-stmts-loop body lines indent cell-vars (+ i 1) total pad)))))) + + +;; -------------------------------------------------------------------------- +;; let as statements (inside def bodies) +;; -------------------------------------------------------------------------- + +(define py-emit-let-as-stmts + (fn (expr lines indent is-last cell-vars) + (let ((pad (py-pad indent)) + (bindings (nth expr 1)) + (body (rest (rest expr)))) + (begin + ;; Emit bindings as assignments + (py-emit-binding-assignments bindings lines indent cell-vars) + ;; Emit body + (if is-last + (py-emit-body-stmts body lines indent cell-vars) + (for-each (fn (b) (py-emit-stmt-recursive b lines indent cell-vars)) body)))))) + +(define py-emit-binding-assignments + (fn (bindings lines indent cell-vars) + (let ((pad (py-pad indent))) + (when (and (list? bindings) (not (empty? bindings))) + (if (list? (first bindings)) + ;; Scheme-style + (for-each (fn (b) + (let ((vname (if (= (type-of (first b)) "symbol") + (symbol-name (first b)) + (str (first b)))) + (mangled (py-mangle (if (= (type-of (first b)) "symbol") + (symbol-name (first b)) + (str (first b)))))) + (if (some (fn (c) (= c mangled)) cell-vars) + (append! lines (str pad "_cells[" (py-quote-string mangled) "] = " + (py-expr-with-cells (nth b 1) cell-vars))) + (append! lines (str pad mangled " = " (py-expr-with-cells (nth b 1) cell-vars)))))) + bindings) + ;; Clojure-style + (py-emit-clojure-binding-assignments bindings lines indent 0 cell-vars)))))) + +(define py-emit-clojure-binding-assignments + (fn (bindings lines indent i cell-vars) + (let ((pad (py-pad indent))) + (when (< i (- (len bindings) 1)) + (let ((vname (if (= (type-of (nth bindings i)) "symbol") + (symbol-name (nth bindings i)) + (str (nth bindings i)))) + (mangled (py-mangle (if (= (type-of (nth bindings i)) "symbol") + (symbol-name (nth bindings i)) + (str (nth bindings i)))))) + (begin + (if (some (fn (c) (= c mangled)) cell-vars) + (append! lines (str pad "_cells[" (py-quote-string mangled) "] = " + (py-expr-with-cells (nth bindings (+ i 1)) cell-vars))) + (append! lines (str pad mangled " = " (py-expr-with-cells (nth bindings (+ i 1)) cell-vars)))) + (py-emit-clojure-binding-assignments bindings lines indent (+ i 2) cell-vars))))))) + + +;; -------------------------------------------------------------------------- +;; Recursive statement emission (for loop/control flow bodies) +;; -------------------------------------------------------------------------- + +(define py-emit-stmt-recursive + (fn (expr lines indent cell-vars) + (let ((pad (py-pad indent))) + (if (not (and (list? expr) (not (empty? expr)))) + (append! lines (py-statement-with-cells expr indent cell-vars)) + (if (not (= (type-of (first expr)) "symbol")) + (append! lines (py-statement-with-cells expr indent cell-vars)) + (let ((name (symbol-name (first expr)))) + (cond + (= name "set!") + (let ((var-name (if (= (type-of (nth expr 1)) "symbol") + (symbol-name (nth expr 1)) + (str (nth expr 1)))) + (mangled (py-mangle (if (= (type-of (nth expr 1)) "symbol") + (symbol-name (nth expr 1)) + (str (nth expr 1)))))) + (if (some (fn (c) (= c mangled)) cell-vars) + (append! lines (str pad "_cells[" (py-quote-string mangled) "] = " + (py-expr-with-cells (nth expr 2) cell-vars))) + (append! lines (str pad mangled " = " (py-expr-with-cells (nth expr 2) cell-vars))))) + (or (= name "let") (= name "let*")) + (py-emit-let-as-stmts expr lines indent false cell-vars) + (= name "when") + (begin + (append! lines (str pad "if sx_truthy(" (py-expr-with-cells (nth expr 1) cell-vars) "):")) + (for-each (fn (b) (py-emit-stmt-recursive b lines (+ indent 1) cell-vars)) + (rest (rest expr)))) + (= name "cond") + (py-emit-cond-stmt expr lines indent cell-vars) + (or (= name "do") (= name "begin")) + (for-each (fn (b) (py-emit-stmt-recursive b lines indent cell-vars)) + (rest expr)) + (= name "if") + (begin + (append! lines (str pad "if sx_truthy(" (py-expr-with-cells (nth expr 1) cell-vars) "):")) + (py-emit-stmt-recursive (nth expr 2) lines (+ indent 1) cell-vars) + (when (>= (len expr) 4) + (append! lines (str pad "else:")) + (py-emit-stmt-recursive (nth expr 3) lines (+ indent 1) cell-vars))) + (= name "append!") + (append! lines (str pad (py-expr-with-cells (nth expr 1) cell-vars) + ".append(" (py-expr-with-cells (nth expr 2) cell-vars) ")")) + (= name "dict-set!") + (append! lines (str pad (py-expr-with-cells (nth expr 1) cell-vars) + "[" (py-expr-with-cells (nth expr 2) cell-vars) + "] = " (py-expr-with-cells (nth expr 3) cell-vars))) + (= name "env-set!") + (append! lines (str pad (py-expr-with-cells (nth expr 1) cell-vars) + "[" (py-expr-with-cells (nth expr 2) cell-vars) + "] = " (py-expr-with-cells (nth expr 3) cell-vars))) + :else + (append! lines (py-statement-with-cells expr indent cell-vars))))))))) + + +;; -------------------------------------------------------------------------- +;; cond as statement (if/elif/else chain) +;; -------------------------------------------------------------------------- + +(define py-emit-cond-stmt + (fn (expr lines indent cell-vars) + (let ((pad (py-pad indent)) + (clauses (rest expr))) + ;; Detect scheme vs clojure + (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 + (py-cond-stmt-scheme clauses lines indent true cell-vars) + (py-cond-stmt-clojure clauses lines indent 0 true cell-vars)))))) + +(define py-cond-stmt-scheme + (fn (clauses lines indent first-clause cell-vars) + (let ((pad (py-pad indent))) + (when (not (empty? clauses)) + (let ((clause (first clauses)) + (test (first clause)) + (body (nth clause 1))) + (begin + (if (py-is-else? test) + (append! lines (str pad "else:")) + (begin + (append! lines (str pad (if first-clause "if" "elif") + " sx_truthy(" (py-expr-with-cells test cell-vars) "):")) + nil)) + (py-emit-stmt-recursive body lines (+ indent 1) cell-vars) + (py-cond-stmt-scheme (rest clauses) lines indent false cell-vars))))))) + +(define py-cond-stmt-clojure + (fn (clauses lines indent i first-clause cell-vars) + (let ((pad (py-pad indent))) + (when (< i (- (len clauses) 1)) + (let ((test (nth clauses i)) + (body (nth clauses (+ i 1)))) + (begin + (if (py-is-else? test) + (append! lines (str pad "else:")) + (begin + (append! lines (str pad (if first-clause "if" "elif") + " sx_truthy(" (py-expr-with-cells test cell-vars) "):")) + nil)) + (py-emit-stmt-recursive body lines (+ indent 1) cell-vars) + (py-cond-stmt-clojure clauses lines indent (+ i 2) false cell-vars))))))) + + +;; -------------------------------------------------------------------------- +;; when as statement +;; -------------------------------------------------------------------------- + +(define py-emit-when-stmt + (fn (expr indent cell-vars) + (let ((pad (py-pad indent)) + (cond-e (py-expr-with-cells (nth expr 1) cell-vars)) + (body-parts (rest (rest expr)))) + (str pad "if sx_truthy(" cond-e "):\n" + (join "\n" (map (fn (b) (py-statement-with-cells b (+ indent 1) cell-vars)) + body-parts)))))) + + +;; -------------------------------------------------------------------------- +;; for-each as statement +;; -------------------------------------------------------------------------- + +(define py-emit-for-each-stmt + (fn (expr indent cell-vars) + (let ((pad (py-pad indent)) + (fn-expr (nth expr 1)) + (coll-expr (nth expr 2)) + (coll (py-expr-with-cells coll-expr cell-vars))) + (if (and (list? fn-expr) + (= (type-of (first fn-expr)) "symbol") + (= (symbol-name (first fn-expr)) "fn")) + ;; Inline lambda → for loop + (let ((params (nth fn-expr 1)) + (body (rest (rest fn-expr))) + (p (if (= (type-of (first params)) "symbol") + (symbol-name (first params)) + (str (first params)))) + (p-py (py-mangle p))) + (let ((lines (list (str pad "for " p-py " in " coll ":")))) + (begin + (for-each (fn (b) (py-emit-stmt-recursive b lines (+ indent 1) cell-vars)) body) + (join "\n" lines)))) + ;; Non-inline → for _item + (str pad "for _item in " coll ":\n" + pad " " (py-expr-with-cells fn-expr cell-vars) "(_item)"))))) + + +;; -------------------------------------------------------------------------- +;; File translation: process a list of (name, define-expr) pairs +;; -------------------------------------------------------------------------- + +(define py-translate-file + (fn (defines) + (join "\n" (map (fn (pair) + (let ((name (first pair)) + (expr (nth pair 1))) + (str "# " name "\n" (py-statement expr 0) "\n"))) + defines)))) diff --git a/shared/sx/ref/run_py_sx.py b/shared/sx/ref/run_py_sx.py new file mode 100644 index 0000000..97f5267 --- /dev/null +++ b/shared/sx/ref/run_py_sx.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +""" +Bootstrap runner: execute py.sx against spec files to produce sx_ref.py. + +This is the G1 bootstrapper — py.sx (SX-to-Python translator written in SX) +is loaded into the Python evaluator, which then uses it to translate the +spec .sx files into Python. + +The output should be identical to: python bootstrap_py.py + +Usage: + python run_py_sx.py > sx_ref_g1.py +""" +from __future__ import annotations + +import os +import sys + +_HERE = os.path.dirname(os.path.abspath(__file__)) +_PROJECT = os.path.abspath(os.path.join(_HERE, "..", "..", "..")) +sys.path.insert(0, _PROJECT) + +from shared.sx.parser import parse_all +from shared.sx.types import Symbol +from shared.sx.ref.bootstrap_py import ( + PREAMBLE, PLATFORM_PY, PRIMITIVES_PY_PRE, PRIMITIVES_PY_POST, + PLATFORM_DEPS_PY, FIXUPS_PY, CONTINUATIONS_PY, + ADAPTER_FILES, SPEC_MODULES, + _assemble_primitives_py, public_api_py, +) + + +def load_py_sx(evaluator_env: dict) -> dict: + """Load py.sx into an evaluator environment and return it.""" + py_sx_path = os.path.join(_HERE, "py.sx") + with open(py_sx_path) as f: + source = f.read() + + exprs = parse_all(source) + + # Import the evaluator + from shared.sx.evaluator import evaluate, make_env + + env = make_env() + for expr in exprs: + evaluate(expr, env) + + return env + + +def extract_defines(source: str) -> list[tuple[str, list]]: + """Parse .sx source, return list of (name, define-expr) for top-level defines.""" + exprs = parse_all(source) + defines = [] + for expr in exprs: + if isinstance(expr, list) and expr and isinstance(expr[0], Symbol): + if expr[0].name == "define": + name = expr[1].name if isinstance(expr[1], Symbol) else str(expr[1]) + defines.append((name, expr)) + return defines + + +def main(): + from shared.sx.evaluator import evaluate + + # Load py.sx into evaluator + env = load_py_sx({}) + + # Get the py-translate-file function + py_translate_file = env.get("py-translate-file") + if py_translate_file is None: + print("ERROR: py-translate-file not found in py.sx environment", file=sys.stderr) + sys.exit(1) + + # Same file list and order as bootstrap_py.py compile_ref_to_py() + sx_files = [ + ("eval.sx", "eval"), + ("forms.sx", "forms (server definition forms)"), + ("render.sx", "render (core)"), + ("adapter-html.sx", "adapter-html"), + ("adapter-sx.sx", "adapter-sx"), + ("deps.sx", "deps (component dependency analysis)"), + ("signals.sx", "signals (reactive signal runtime)"), + ] + + # Build output — static sections are identical + parts = [] + parts.append(PREAMBLE) + parts.append(PLATFORM_PY) + parts.append(PRIMITIVES_PY_PRE) + parts.append(_assemble_primitives_py(None)) + parts.append(PRIMITIVES_PY_POST) + parts.append(PLATFORM_DEPS_PY) + + # Translate each spec file using py.sx + for filename, label in sx_files: + filepath = os.path.join(_HERE, filename) + if not os.path.exists(filepath): + continue + with open(filepath) as f: + src = f.read() + defines = extract_defines(src) + + # Convert defines to SX-compatible format: list of [name, expr] pairs + sx_defines = [[name, expr] for name, expr in defines] + + parts.append(f"\n# === Transpiled from {label} ===\n") + # Bind defines as data in env to avoid evaluator trying to execute AST + env["_defines"] = sx_defines + result = evaluate( + [Symbol("py-translate-file"), Symbol("_defines")], + env, + ) + parts.append(result) + + parts.append(FIXUPS_PY) + parts.append(public_api_py(True, True, True)) + + print("\n".join(parts)) + + +if __name__ == "__main__": + main()