Skip to content

feat: expose Crystal procs as Lua global functions#44

Merged
veelenga merged 1 commit into
masterfrom
feat/crystal-callback
May 18, 2026
Merged

feat: expose Crystal procs as Lua global functions#44
veelenga merged 1 commit into
masterfrom
feat/crystal-callback

Conversation

@veelenga

@veelenga veelenga commented May 18, 2026

Copy link
Copy Markdown
Owner

Summary

Closes #38 and #15

Adds Stack#function(name, proc) so Crystal callbacks can be invoked from Lua. Argument and return types come from the proc's signature, so values flow naturally between the two languages.

lua = Lua.load

# inline proc literal
lua.function "add", ->(x : Float64, y : Float64) { x + y }
lua.run "return add(3, 4)" # => 7.0

# closure over local Crystal state
counter = 0
lua.function "tick", -> { counter += 1; nil }
lua.run "tick(); tick()"
counter # => 2

# method pointer
def greet(name : String); "Hi, #{name}" end
lua.function "greet", ->greet(String)
lua.run "return greet('Lua')" # => "Hi, Lua"

Closes #15.

Design

  • Uses Proc(*Args, R) forall Args, R so types are picked up at compile time without macros at the call site.
  • Reuses the existing LuaCallable::LuaConvert(T).convert for Lua→Crystal coercion and pushclosure (via set_global) for closure-data upvalues — so closures over locals just work.
  • Nil return type → no Lua return value; otherwise the result is pushed and 1 is returned to Lua.
  • Validates argument count up front and raises ArgumentError if Lua passes too few args (silent nil substitution was the easiest footgun).

Test plan

  • crystal spec — 144 examples, 0 failures
  • crystal run examples/crystal_callback.cr — outputs 7.0, counter = 3, Hi, Lua, requests:5:true (30.0)
  • crystal tool format clean

Follow-ups (out of scope)

  • Sharing the wrapper-build dance with LuaCallable._lua_call in src/lua/callable.cr — both do the same arg-extract / call / push pattern. Worth extracting into a shared macro once the surface settles.
  • Avoiding open_libs + set_error_handler on every Lua→Crystal callback: same pre-existing pattern is in callable.cr at four call sites; a dedicated lightweight Stack view would fix all of them at once.
  • Promoting LuaCallable::LuaConvert to Lua::Convert so it's not reached through LuaCallable's namespace.

Adds `Stack#function(name, proc)` so Crystal callbacks can be called
from Lua. Argument and return types are taken from the proc's
signature; closures, method pointers, and proc literals all work.

Closes #15.
@veelenga veelenga merged commit fcb3be5 into master May 18, 2026
1 check passed
@veelenga veelenga deleted the feat/crystal-callback branch May 18, 2026 19:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Crystal callback

1 participant