From 63f5bd0d2aa2138a11db09b7a18424e9daac07d6 Mon Sep 17 00:00:00 2001 From: "carpentry-heartbeat[bot]" Date: Thu, 21 May 2026 05:30:00 +0200 Subject: [PATCH 1/3] Macro-generate repetitive Luax getter/setter functions Replace 19 hand-written getter/setter functions across 5 groups (maybe-get-*, set-*-global, get-*-global, set-*-field, get-*-field) with defndynamic generators that produce identical code from type specs. Reduces boilerplate by ~50 lines while preserving the same public API, behavior, and documentation. --- lua.carp | 256 ++++++++++++++++++++++--------------------------------- 1 file changed, 102 insertions(+), 154 deletions(-) diff --git a/lua.carp b/lua.carp index 779024d..d930b1f 100644 --- a/lua.carp +++ b/lua.carp @@ -23,6 +23,75 @@ (Lua.set-field %lua -2 (cstr %(Symbol.str field-name)))) (luax--make-field-stmts lua (cdr field-specs)))))) +(defndynamic luax--def-maybe-get [suffix type-const getter article type-article] + (let [fn-name (Symbol.concat ['maybe-get- suffix]) + doc-str (Dynamic.String.concat + ["Read the value at `index` as " article + ", returning `Nothing` if it is not " type-article + ". Leaves the stack unchanged."])] + (eval + `(do + (doc %fn-name %doc-str) + (defn %fn-name [lua index] + (if (= (Lua.type-of lua index) %type-const) + (Maybe.Just (%getter lua index)) + (Maybe.Nothing))))))) + +(defndynamic luax--def-set-global [suffix pusher] + (let [fn-name (Symbol.concat ['set- suffix '-global])] + (eval + `(do + (doc %fn-name "Push `value` and assign it to the global `name`.") + (defn %fn-name [lua name value] + (do (%pusher lua value) (Lua.set-global lua (cstr name)))))))) + +(defndynamic luax--def-get-global [suffix type-const getter article type-article] + (let [fn-name (Symbol.concat ['get- suffix '-global]) + doc-str (Dynamic.String.concat + ["Fetch the global `name` as " article + ". Returns `Nothing` if the global is nil or not " type-article + ". Pops the global from the stack internally."])] + (eval + `(do + (doc %fn-name %doc-str) + (defn %fn-name [lua name] + (do + (Lua.get-global lua (cstr name)) + (let-do [result (if (= (Lua.type-of lua -1) %type-const) + (Maybe.Just (%getter lua -1)) + (Maybe.Nothing))] + (Lua.pop lua 1) + result))))))) + +(defndynamic luax--def-set-field [suffix pusher] + (let [fn-name (Symbol.concat ['set- suffix '-field])] + (eval + `(do + (doc %fn-name + "Set field `name` to `value` on the table at `index`. Handles the stack index shift from pushing the value internally.") + (defn %fn-name [lua index name value] + (do + (%pusher lua value) + (Lua.set-field lua (- index 1) (cstr name)))))))) + +(defndynamic luax--def-get-field [suffix type-const getter article type-article] + (let [fn-name (Symbol.concat ['get- suffix '-field]) + doc-str (Dynamic.String.concat + ["Read field `name` from the table at `index` as " article + ". Returns `Nothing` if the field is nil or not " type-article + ". Pops the field value from the stack internally, leaving only the table."])] + (eval + `(do + (doc %fn-name %doc-str) + (defn %fn-name [lua index name] + (do + (ignore (Lua.get-field lua index (cstr name))) + (let-do [result (if (= (Lua.type-of lua -1) %type-const) + (Maybe.Just (%getter lua -1)) + (Maybe.Nothing))] + (Lua.pop lua 1) + result))))))) + (defmodule Lua (doc OK "Status code returned on success.") (register OK Int "LUA_OK") @@ -427,165 +496,44 @@ and assigns it to a global in one expression: ```") (defmodule Luax - (doc maybe-get-int "Read the value at `index` as an integer, returning -`Nothing` if it is not a number. Leaves the stack unchanged.") - (defn maybe-get-int [lua index] - (if (= (Lua.type-of lua index) Lua.TYPE_NUMBER) - (Maybe.Just (Lua.get-int lua index)) - (Maybe.Nothing))) - - (doc maybe-get-float "Read the value at `index` as a float, returning -`Nothing` if it is not a number. Leaves the stack unchanged.") - (defn maybe-get-float [lua index] - (if (= (Lua.type-of lua index) Lua.TYPE_NUMBER) - (Maybe.Just (Lua.get-float lua index)) - (Maybe.Nothing))) - - (doc maybe-get-bool "Read the value at `index` as a boolean, returning -`Nothing` if it is not a boolean. Leaves the stack unchanged.") - (defn maybe-get-bool [lua index] - (if (= (Lua.type-of lua index) Lua.TYPE_BOOLEAN) - (Maybe.Just (Lua.get-bool lua index)) - (Maybe.Nothing))) - - (doc maybe-get-string "Read the value at `index` as a Carp `String`, returning -`Nothing` if it is not a string. Leaves the stack unchanged.") - (defn maybe-get-string [lua index] - (if (= (Lua.type-of lua index) Lua.TYPE_STRING) - (Maybe.Just (String.from-cstr-or (Lua.to-string lua index) @"")) - (Maybe.Nothing))) - (doc get-carp-str "Read the value at `index` as a Carp `String`, using Lua's `tostring` coercion. Returns an empty string if the conversion fails.") (defn get-carp-str [lua index] (String.from-cstr-or (Lua.to-string lua index) @"")) - (doc set-int-global "Push `value` and assign it to the global `name`.") - (defn set-int-global [lua name value] - (do (Lua.push-int lua value) (Lua.set-global lua (cstr name)))) - - (doc set-float-global "Push `value` and assign it to the global `name`.") - (defn set-float-global [lua name value] - (do (Lua.push-float lua value) (Lua.set-global lua (cstr name)))) - - (doc set-bool-global "Push `value` and assign it to the global `name`.") - (defn set-bool-global [lua name value] - (do (Lua.push-bool lua value) (Lua.set-global lua (cstr name)))) - - (doc get-int-global "Fetch the global `name` as an integer. Returns `Nothing` -if the global is nil or not a number. Pops the global from the stack internally.") - (defn get-int-global [lua name] - (do - (Lua.get-global lua (cstr name)) - (let-do [result (if (= (Lua.type-of lua -1) Lua.TYPE_NUMBER) - (Maybe.Just (Lua.get-int lua -1)) - (Maybe.Nothing))] - (Lua.pop lua 1) - result))) - - (doc get-float-global "Fetch the global `name` as a float. Returns `Nothing` -if the global is nil or not a number. Pops the global from the stack internally.") - (defn get-float-global [lua name] - (do - (Lua.get-global lua (cstr name)) - (let-do [result (if (= (Lua.type-of lua -1) Lua.TYPE_NUMBER) - (Maybe.Just (Lua.get-float lua -1)) - (Maybe.Nothing))] - (Lua.pop lua 1) - result))) - - (doc get-bool-global "Fetch the global `name` as a boolean. Returns `Nothing` -if the global is nil or not a boolean. Pops the global from the stack internally.") - (defn get-bool-global [lua name] - (do - (Lua.get-global lua (cstr name)) - (let-do [result (if (= (Lua.type-of lua -1) Lua.TYPE_BOOLEAN) - (Maybe.Just (Lua.get-bool lua -1)) - (Maybe.Nothing))] - (Lua.pop lua 1) - result))) - - (doc get-string-global "Fetch the global `name` as a Carp `String`. Returns -`Nothing` if the global is nil or not a string. Pops the global from the stack -internally.") - (defn get-string-global [lua name] - (do - (Lua.get-global lua (cstr name)) - (let-do [result (if (= (Lua.type-of lua -1) Lua.TYPE_STRING) - (Maybe.Just (String.from-cstr-or (Lua.to-string lua -1) @"")) - (Maybe.Nothing))] - (Lua.pop lua 1) - result))) - - (doc set-int-field "Set field `name` to `value` on the table at `index`. -Handles the stack index shift from pushing the value internally.") - (defn set-int-field [lua index name value] - (do (Lua.push-int lua value) (Lua.set-field lua (- index 1) (cstr name)))) - - (doc set-float-field "Set field `name` to `value` on the table at `index`. -Handles the stack index shift from pushing the value internally.") - (defn set-float-field [lua index name value] - (do (Lua.push-float lua value) (Lua.set-field lua (- index 1) (cstr name)))) - - (doc set-bool-field "Set field `name` to `value` on the table at `index`. -Handles the stack index shift from pushing the value internally.") - (defn set-bool-field [lua index name value] - (do (Lua.push-bool lua value) (Lua.set-field lua (- index 1) (cstr name)))) - - (doc set-string-field "Set field `name` to the string `value` on the table at -`index`. Handles the stack index shift from pushing the value internally.") - (defn set-string-field [lua index name value] - (do - (Lua.push-carp-str lua value) - (Lua.set-field lua (- index 1) (cstr name)))) - - (doc get-int-field "Read field `name` from the table at `index` as an integer. -Returns `Nothing` if the field is nil or not a number. Pops the field value -from the stack internally, leaving only the table.") - (defn get-int-field [lua index name] - (do - (ignore (Lua.get-field lua index (cstr name))) - (let-do [result (if (= (Lua.type-of lua -1) Lua.TYPE_NUMBER) - (Maybe.Just (Lua.get-int lua -1)) - (Maybe.Nothing))] - (Lua.pop lua 1) - result))) - - (doc get-float-field "Read field `name` from the table at `index` as a float. -Returns `Nothing` if the field is nil or not a number. Pops the field value -from the stack internally, leaving only the table.") - (defn get-float-field [lua index name] - (do - (ignore (Lua.get-field lua index (cstr name))) - (let-do [result (if (= (Lua.type-of lua -1) Lua.TYPE_NUMBER) - (Maybe.Just (Lua.get-float lua -1)) - (Maybe.Nothing))] - (Lua.pop lua 1) - result))) - - (doc get-bool-field "Read field `name` from the table at `index` as a boolean. -Returns `Nothing` if the field is nil or not a boolean. Pops the field value -from the stack internally, leaving only the table.") - (defn get-bool-field [lua index name] - (do - (ignore (Lua.get-field lua index (cstr name))) - (let-do [result (if (= (Lua.type-of lua -1) Lua.TYPE_BOOLEAN) - (Maybe.Just (Lua.get-bool lua -1)) - (Maybe.Nothing))] - (Lua.pop lua 1) - result))) - - (doc get-string-field "Read field `name` from the table at `index` as a Carp -`String`. Returns `Nothing` if the field is nil or not a string. Pops the field -value from the stack internally, leaving only the table.") - (defn get-string-field [lua index name] - (do - (ignore (Lua.get-field lua index (cstr name))) - (let-do [result (if (= (Lua.type-of lua -1) Lua.TYPE_STRING) - (Maybe.Just (String.from-cstr-or (Lua.to-string lua -1) @"")) - (Maybe.Nothing))] - (Lua.pop lua 1) - result))) + ; === maybe-get-* family === + (luax--def-maybe-get int Lua.TYPE_NUMBER Lua.get-int "an integer" "a number") + (luax--def-maybe-get float Lua.TYPE_NUMBER Lua.get-float "a float" "a number") + (luax--def-maybe-get bool Lua.TYPE_BOOLEAN Lua.get-bool "a boolean" "a boolean") + (luax--def-maybe-get string Lua.TYPE_STRING get-carp-str + "a Carp `String`" "a string") + + ; === set-*-global family === + (luax--def-set-global int Lua.push-int) + (luax--def-set-global float Lua.push-float) + (luax--def-set-global bool Lua.push-bool) + + ; === get-*-global family === + (luax--def-get-global int Lua.TYPE_NUMBER Lua.get-int "an integer" "a number") + (luax--def-get-global float Lua.TYPE_NUMBER Lua.get-float "a float" "a number") + (luax--def-get-global bool Lua.TYPE_BOOLEAN Lua.get-bool + "a boolean" "a boolean") + (luax--def-get-global string Lua.TYPE_STRING get-carp-str + "a Carp `String`" "a string") + + ; === set-*-field family === + (luax--def-set-field int Lua.push-int) + (luax--def-set-field float Lua.push-float) + (luax--def-set-field bool Lua.push-bool) + (luax--def-set-field string Lua.push-carp-str) + + ; === get-*-field family === + (luax--def-get-field int Lua.TYPE_NUMBER Lua.get-int "an integer" "a number") + (luax--def-get-field float Lua.TYPE_NUMBER Lua.get-float "a float" "a number") + (luax--def-get-field bool Lua.TYPE_BOOLEAN Lua.get-bool + "a boolean" "a boolean") + (luax--def-get-field string Lua.TYPE_STRING get-carp-str + "a Carp `String`" "a string") (doc do-in "Compile and execute the Lua string `code`. Returns `(Success \"\")` From 9dd3f7f72c2c0111fa04f2c3aee89b4b1c086d0b Mon Sep 17 00:00:00 2001 From: Veit Date: Thu, 21 May 2026 12:41:33 +0200 Subject: [PATCH 2/3] fix: change luax generators from defndynamic to defmacro defndynamic evaluates arguments, so bare symbols like `int` and `Lua.TYPE_NUMBER` fail with "Can't find symbol". defmacro receives arguments unevaluated, which is what these code generators need. --- lua.carp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lua.carp b/lua.carp index d930b1f..435d365 100644 --- a/lua.carp +++ b/lua.carp @@ -23,7 +23,7 @@ (Lua.set-field %lua -2 (cstr %(Symbol.str field-name)))) (luax--make-field-stmts lua (cdr field-specs)))))) -(defndynamic luax--def-maybe-get [suffix type-const getter article type-article] +(defmacro luax--def-maybe-get [suffix type-const getter article type-article] (let [fn-name (Symbol.concat ['maybe-get- suffix]) doc-str (Dynamic.String.concat ["Read the value at `index` as " article @@ -37,7 +37,7 @@ (Maybe.Just (%getter lua index)) (Maybe.Nothing))))))) -(defndynamic luax--def-set-global [suffix pusher] +(defmacro luax--def-set-global [suffix pusher] (let [fn-name (Symbol.concat ['set- suffix '-global])] (eval `(do @@ -45,7 +45,7 @@ (defn %fn-name [lua name value] (do (%pusher lua value) (Lua.set-global lua (cstr name)))))))) -(defndynamic luax--def-get-global [suffix type-const getter article type-article] +(defmacro luax--def-get-global [suffix type-const getter article type-article] (let [fn-name (Symbol.concat ['get- suffix '-global]) doc-str (Dynamic.String.concat ["Fetch the global `name` as " article @@ -63,7 +63,7 @@ (Lua.pop lua 1) result))))))) -(defndynamic luax--def-set-field [suffix pusher] +(defmacro luax--def-set-field [suffix pusher] (let [fn-name (Symbol.concat ['set- suffix '-field])] (eval `(do @@ -74,7 +74,7 @@ (%pusher lua value) (Lua.set-field lua (- index 1) (cstr name)))))))) -(defndynamic luax--def-get-field [suffix type-const getter article type-article] +(defmacro luax--def-get-field [suffix type-const getter article type-article] (let [fn-name (Symbol.concat ['get- suffix '-field]) doc-str (Dynamic.String.concat ["Read field `name` from the table at `index` as " article From 4874339f6a208ccd950bc12ec47803a1097f3932 Mon Sep 17 00:00:00 2001 From: Veit Date: Thu, 21 May 2026 12:57:23 +0200 Subject: [PATCH 3/3] style: format with carp-fmt --- lua.carp | 69 +++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 46 insertions(+), 23 deletions(-) diff --git a/lua.carp b/lua.carp index 435d365..08ea5dd 100644 --- a/lua.carp +++ b/lua.carp @@ -26,9 +26,11 @@ (defmacro luax--def-maybe-get [suffix type-const getter article type-article] (let [fn-name (Symbol.concat ['maybe-get- suffix]) doc-str (Dynamic.String.concat - ["Read the value at `index` as " article - ", returning `Nothing` if it is not " type-article - ". Leaves the stack unchanged."])] + ["Read the value at `index` as " + article + ", returning `Nothing` if it is not " + type-article + ". Leaves the stack unchanged."])] (eval `(do (doc %fn-name %doc-str) @@ -48,9 +50,11 @@ (defmacro luax--def-get-global [suffix type-const getter article type-article] (let [fn-name (Symbol.concat ['get- suffix '-global]) doc-str (Dynamic.String.concat - ["Fetch the global `name` as " article - ". Returns `Nothing` if the global is nil or not " type-article - ". Pops the global from the stack internally."])] + ["Fetch the global `name` as " + article + ". Returns `Nothing` if the global is nil or not " + type-article + ". Pops the global from the stack internally."])] (eval `(do (doc %fn-name %doc-str) @@ -70,16 +74,16 @@ (doc %fn-name "Set field `name` to `value` on the table at `index`. Handles the stack index shift from pushing the value internally.") (defn %fn-name [lua index name value] - (do - (%pusher lua value) - (Lua.set-field lua (- index 1) (cstr name)))))))) + (do (%pusher lua value) (Lua.set-field lua (- index 1) (cstr name)))))))) (defmacro luax--def-get-field [suffix type-const getter article type-article] (let [fn-name (Symbol.concat ['get- suffix '-field]) doc-str (Dynamic.String.concat - ["Read field `name` from the table at `index` as " article - ". Returns `Nothing` if the field is nil or not " type-article - ". Pops the field value from the stack internally, leaving only the table."])] + ["Read field `name` from the table at `index` as " + article + ". Returns `Nothing` if the field is nil or not " + type-article + ". Pops the field value from the stack internally, leaving only the table."])] (eval `(do (doc %fn-name %doc-str) @@ -504,9 +508,16 @@ and assigns it to a global in one expression: ; === maybe-get-* family === (luax--def-maybe-get int Lua.TYPE_NUMBER Lua.get-int "an integer" "a number") (luax--def-maybe-get float Lua.TYPE_NUMBER Lua.get-float "a float" "a number") - (luax--def-maybe-get bool Lua.TYPE_BOOLEAN Lua.get-bool "a boolean" "a boolean") - (luax--def-maybe-get string Lua.TYPE_STRING get-carp-str - "a Carp `String`" "a string") + (luax--def-maybe-get bool + Lua.TYPE_BOOLEAN + Lua.get-bool + "a boolean" + "a boolean") + (luax--def-maybe-get string + Lua.TYPE_STRING + get-carp-str + "a Carp `String`" + "a string") ; === set-*-global family === (luax--def-set-global int Lua.push-int) @@ -516,10 +527,16 @@ and assigns it to a global in one expression: ; === get-*-global family === (luax--def-get-global int Lua.TYPE_NUMBER Lua.get-int "an integer" "a number") (luax--def-get-global float Lua.TYPE_NUMBER Lua.get-float "a float" "a number") - (luax--def-get-global bool Lua.TYPE_BOOLEAN Lua.get-bool - "a boolean" "a boolean") - (luax--def-get-global string Lua.TYPE_STRING get-carp-str - "a Carp `String`" "a string") + (luax--def-get-global bool + Lua.TYPE_BOOLEAN + Lua.get-bool + "a boolean" + "a boolean") + (luax--def-get-global string + Lua.TYPE_STRING + get-carp-str + "a Carp `String`" + "a string") ; === set-*-field family === (luax--def-set-field int Lua.push-int) @@ -530,10 +547,16 @@ and assigns it to a global in one expression: ; === get-*-field family === (luax--def-get-field int Lua.TYPE_NUMBER Lua.get-int "an integer" "a number") (luax--def-get-field float Lua.TYPE_NUMBER Lua.get-float "a float" "a number") - (luax--def-get-field bool Lua.TYPE_BOOLEAN Lua.get-bool - "a boolean" "a boolean") - (luax--def-get-field string Lua.TYPE_STRING get-carp-str - "a Carp `String`" "a string") + (luax--def-get-field bool + Lua.TYPE_BOOLEAN + Lua.get-bool + "a boolean" + "a boolean") + (luax--def-get-field string + Lua.TYPE_STRING + get-carp-str + "a Carp `String`" + "a string") (doc do-in "Compile and execute the Lua string `code`. Returns `(Success \"\")`