Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,22 @@ state and closes it when the block exits:
```

The `Lua` module wraps the Lua C API directly: stack operations
(`push-int`, `push-float`, `push-bool`, `push-string`, `get-int`, etc.),
(`push-int`, `push-double`, `push-bool`, `push-string`, `get-int`, etc.),
globals (`get-global`, `set-global`), tables (`create-table`, `set-field`,
`get-field`, `next`), code execution (`do-string`, `do-file`, `call`), and
type checking (`type-of`, `TYPE_NIL`, `TYPE_NUMBER`, etc.). It also provides a
few conveniences: `Lua.fun` for defining Lua functions inline, `Lua.val` for
evaluating Lua expressions into globals, and `Lua.eval-file` for loading Lua
files with error handling.

A Lua number is a C double, so `push-double` and `get-double` (and the matching
`Luax.set-double-global`, `Luax.get-double-field`, …) carry Lua floats at full
precision. The `float` versions still work — a float widens into a double
exactly — but `get-float` narrows on the way back, so reading through it drops
everything past single precision. Lua integers are a separate 64-bit subtype;
`get-double` converts them to doubles, so integers above 2^53 are no longer all
exactly representable and may come back rounded.

The `Luax` module provides safe wrappers that return `Maybe` and `Result`
types instead of requiring manual type checks:

Expand All @@ -58,8 +66,8 @@ types instead of requiring manual type checks:
(Luax.make-table lua player
(name (Lua.push-carp-str "Ada"))
(hp (Lua.push-int 100))
(x (Lua.push-float 3.5f))
(y (Lua.push-float 7.0f)))
(x (Lua.push-double 3.5))
(y (Lua.push-double 7.0)))

; read table fields — returns Maybe, keeps the stack clean
(Lua.get-global lua (cstr "player"))
Expand Down
50 changes: 39 additions & 11 deletions lua.carp
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,10 @@ automatically.")
(register push-light-user-data (Fn [&Lua (Ptr ())] ()) "lua_pushlightuserdata")
(doc push-nil "Push nil onto the stack.")
(register push-nil (Fn [&Lua] ()) "lua_pushnil")
(doc push-float "Push a float (Lua number) onto the stack.")
(register push-float (Fn [&Lua Float] ()) "lua_pushnumber")
(doc push-double "Push a double (Lua number) onto the stack.")
(register push-double (Fn [&Lua Double] ()) "lua_pushnumber")
(doc push-float "Push a float onto the stack as a Lua number.")
(defn push-float [lua f] (push-double lua (Double.from-float f)))
(doc push-string "Push a C string onto the stack. For Carp strings, use
[`push-carp-str`](#push-carp-str) instead.")
(deftemplate push-string
Expand All @@ -189,9 +191,13 @@ elements on the stack.")
(doc get-int "Read the value at `index` as an integer. Does not check the
type—use [`Luax.maybe-get-int`](#maybe-get-int) for a safe version.")
(register get-int (Fn [&Lua Int] Int) "lua_tointeger")
(doc get-float "Read the value at `index` as a float. Does not check the
(doc get-double "Read the value at `index` as a double. Does not check the
type—use [`Luax.maybe-get-double`](#maybe-get-double) for a safe version.")
(register get-double (Fn [&Lua Int] Double) "lua_tonumber")
(doc get-float "Read the value at `index` as a float. Lua numbers are doubles,
so this narrows; use [`get-double`](#get-double) instead. Does not check the
type—use [`Luax.maybe-get-float`](#maybe-get-float) for a safe version.")
(register get-float (Fn [&Lua Int] Float) "lua_tonumber")
(defn get-float [lua index] (Double.to-float (get-double lua index)))
(doc get-string "Read the value at `index` as a C string pointer. Returns a
raw pointer; prefer [`Luax.get-carp-str`](#get-carp-str) or
[`Luax.maybe-get-string`](#maybe-get-string) for safe access.")
Expand Down Expand Up @@ -595,30 +601,35 @@ the Lua standard library available.
```

Values are passed between Carp and Lua through the stack. Push values with
[`push-int`](#push-int), [`push-float`](#push-float),
[`push-int`](#push-int), [`push-double`](#push-double),
[`push-bool`](#push-bool), [`push-carp-str`](#push-carp-str), etc. Read them
back with [`get-int`](#get-int), [`get-float`](#get-float), and so on, using
back with [`get-int`](#get-int), [`get-double`](#get-double), and so on, using
negative indices to address from the top of the stack (-1 is the top element).

```
; push two values, read the top one
(Lua.push-int lua 42)
(Lua.push-float lua 3.14f)
(let [f (Lua.get-float lua -1) ; 3.14
i (Lua.get-int lua -2)] ; 42
(Lua.push-double lua 3.14)
(let [d (Lua.get-double lua -1) ; 3.14
i (Lua.get-int lua -2)] ; 42
(Lua.pop lua 2))
```

A Lua number is a C double. [`push-float`](#push-float) widens exactly, but
[`get-float`](#get-float) narrows on the way back, so a number read through it
loses everything past single precision and anything above the float range comes
back as an infinity.

To call a Lua function at the low level, push the function, then its arguments,
then use [`call`](#call) with the argument and result counts. The result
replaces the function and arguments on the stack.

```
(Lua.get-global lua (cstr \"math\"))
(ignore (Lua.get-field lua -1 (cstr \"sqrt\")))
(Lua.push-float lua 9.0f)
(Lua.push-double lua 9.0)
(ignore (Lua.call lua 1 1 0))
(IO.println &(str (Lua.get-float lua -1))) ; 3.0
(IO.println &(str (Lua.get-double lua -1))) ; 3.0
(Lua.pop lua 2) ; pop result and math table
```

Expand Down Expand Up @@ -782,6 +793,11 @@ and assigns it to a global in one expression:
(String.from-cstr-or (Lua.to-string lua index) @""))

(luax--def-maybe-get int Lua.TYPE_NUMBER Lua.get-int "an integer" "a number")
(luax--def-maybe-get double
Lua.TYPE_NUMBER
Lua.get-double
"a double"
"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
Expand All @@ -804,6 +820,7 @@ unchanged.")
(Maybe.Nothing))))

(luax--def-set-global int Lua.push-int)
(luax--def-set-global double Lua.push-double)
(luax--def-set-global float Lua.push-float)
(luax--def-set-global bool Lua.push-bool)
; `string` is defined explicitly rather than via luax--def-set-global: the
Expand All @@ -814,6 +831,11 @@ unchanged.")
(do (Lua.push-carp-str lua value) (Lua.set-global lua (cstr name))))

(luax--def-get-global int Lua.TYPE_NUMBER Lua.get-int "an integer" "a number")
(luax--def-get-global double
Lua.TYPE_NUMBER
Lua.get-double
"a double"
"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
Expand All @@ -833,11 +855,17 @@ unchanged.")
result)))

(luax--def-set-field int Lua.push-int)
(luax--def-set-field double Lua.push-double)
(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)

(luax--def-get-field int Lua.TYPE_NUMBER Lua.get-int "an integer" "a number")
(luax--def-get-field double
Lua.TYPE_NUMBER
Lua.get-double
"a double"
"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
Expand Down
53 changes: 53 additions & 0 deletions test/lua.carp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,59 @@
2.5f
(Lua.with-lua-do (Lua.push-float lua 2.5f) (Lua.get-float lua -1))
"push-float / get-float round-trip")
(assert-equal test
3.1415926535897931
(Lua.with-lua-do (Lua.push-double lua 3.1415926535897931)
(Lua.get-double lua -1))
"push-double / get-double round-trip keeps all 17 significant digits")
(assert-equal test
16777217.0
(Lua.with-lua-do (Lua.libs lua)
(ignore (Luax.do-in lua "n = 16777217"))
(Lua.get-global lua (cstr "n"))
(Lua.get-double lua -1))
"get-double reads a Lua integer above 2^24 intact")
(assert-true test
(let [n (Lua.with-lua-do (Lua.libs lua)
(ignore (Luax.do-in lua "n = 1e300"))
(Lua.get-global lua (cstr "n"))
(Lua.get-double lua -1))]
(and (Double.> n 0.0) (Double.< n Double.MAX)))
"get-double reads 1e300 as a finite value")
(assert-true test
(let [n (Lua.with-lua-do (Lua.libs lua)
(ignore (Luax.do-in lua "n = -1e300"))
(Lua.get-global lua (cstr "n"))
(Lua.get-double lua -1))]
(and (Double.< n 0.0) (Double.> n (Double.neg Double.MAX))))
"get-double reads -1e300 as a finite value")
(assert-equal test
3.1415926535897931
(Lua.with-lua-do (Lua.libs lua)
(Lua.push-double lua 3.1415926535897931)
(Lua.set-global lua (cstr "n"))
(ignore (Luax.do-in lua "n = n * 2 / 2"))
(Lua.get-global lua (cstr "n"))
(Lua.get-double lua -1))
"push-double hands Lua the full value")

(assert-equal test
(Double.to-float 3.1415926535897931)
(Lua.with-lua-do (Lua.push-double lua 3.1415926535897931)
(Lua.get-float lua -1))
"get-float still narrows to single precision")
(assert-true test
(Float.>
(Lua.with-lua-do (Lua.libs lua)
(ignore (Luax.do-in lua "n = 1e300"))
(Lua.get-global lua (cstr "n"))
(Lua.get-float lua -1))
Float.MAX)
"get-float still overflows past the float range")
(assert-equal test
(Double.from-float 2.5f)
(Lua.with-lua-do (Lua.push-float lua 2.5f) (Lua.get-double lua -1))
"push-float still accepts a Float")
(assert-true test
(Lua.with-lua-do (Lua.push-bool lua true) (Lua.get-bool lua -1))
"push-bool true round-trip")
Expand Down
78 changes: 78 additions & 0 deletions test/midlevel.carp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,22 @@
&(Lua.with-lua-do (Lua.push-bool lua true) (Luax.maybe-get-float lua -1)))
"maybe-get-float returns Nothing for boolean value")

(assert-true test
(= &(Maybe.Just 3.1415926535897931)
&(Lua.with-lua-do (Lua.push-double lua 3.1415926535897931)
(Luax.maybe-get-double lua -1)))
"maybe-get-double returns Just for number value")
(assert-true test
(Maybe.nothing?
&(Lua.with-lua-do (Lua.push-bool lua true) (Luax.maybe-get-double lua -1)))
"maybe-get-double returns Nothing for boolean value")
(assert-true test
(Maybe.nothing?
&(Lua.with-lua-do
(ignore (Lua.push-string lua (cstr "hello")))
(Luax.maybe-get-double lua -1)))
"maybe-get-double returns Nothing for string value")

(assert-true test
(= &(Maybe.Just true)
&(Lua.with-lua-do (Lua.push-bool lua true) (Luax.maybe-get-bool lua -1)))
Expand Down Expand Up @@ -191,6 +207,22 @@
(Lua.get-float lua -1))
"set-float-global sets a float global")

(assert-equal test
3.1415926535897931
(Lua.with-lua-do (Lua.libs lua)
(Luax.set-double-global lua "d" 3.1415926535897931)
(Lua.get-global lua (cstr "d"))
(Lua.get-double lua -1))
"set-double-global sets a double global without losing precision")
(assert-true test
(let [n (Lua.with-lua-do (Lua.libs lua)
(ignore (Luax.do-in lua "d = 1e300"))
(ignore (Luax.do-in lua "d = d + 0"))
(Lua.get-global lua (cstr "d"))
(Lua.get-double lua -1))]
(and (Double.> n 0.0) (Double.< n Double.MAX)))
"double globals survive a round trip through Lua arithmetic")

(assert-true test
(Lua.with-lua-do (Lua.libs lua)
(Luax.set-bool-global lua "flag" true)
Expand Down Expand Up @@ -238,6 +270,23 @@
(Luax.get-float-global lua "f")))
"get-float-global returns Just for existing float")

(assert-true test
(= &(Maybe.Just 3.1415926535897931)
&(Lua.with-lua-do (Lua.libs lua)
(Luax.set-double-global lua "d" 3.1415926535897931)
(Luax.get-double-global lua "d")))
"get-double-global returns Just for existing number")
(assert-true test
(Maybe.nothing?
&(Lua.with-lua-do (Lua.libs lua) (Luax.get-double-global lua "nope")))
"get-double-global returns Nothing for missing global")
(assert-true test
(Maybe.nothing?
&(Lua.with-lua-do (Lua.libs lua)
(Luax.set-string-global lua "s" "hello")
(Luax.get-double-global lua "s")))
"get-double-global returns Nothing for wrong type")

(assert-true test
(= &(Maybe.Just true)
&(Lua.with-lua-do (Lua.libs lua)
Expand Down Expand Up @@ -302,6 +351,24 @@
(Luax.get-float-field lua -1 "val")))
"get-float-field returns Just for existing float field")

(assert-true test
(= &(Maybe.Just 3.1415926535897931)
&(Lua.with-lua-do (Lua.libs lua)
(ignore
(Lua.do-string lua
(cstr "t = {val = 3.141592653589793}")))
(Lua.get-global lua (cstr "t"))
(Luax.get-double-field lua -1 "val")))
"get-double-field returns Just for existing number field")
(assert-true test
(Maybe.nothing?
&(Lua.with-lua-do (Lua.libs lua)
(ignore
(Lua.do-string lua (cstr "t = {name = 'Alice'}")))
(Lua.get-global lua (cstr "t"))
(Luax.get-double-field lua -1 "name")))
"get-double-field returns Nothing on type mismatch")

(assert-true test
(= &(Maybe.Just true)
&(Lua.with-lua-do (Lua.libs lua)
Expand Down Expand Up @@ -344,6 +411,17 @@
(Lua.get-float lua -1))
"set-float-field sets a float field on a table")

(assert-equal test
3.1415926535897931
(Lua.with-lua-do (Lua.libs lua)
(Lua.create-table lua 0 1)
(Luax.set-double-field lua -1 "val" 3.1415926535897931)
(Lua.set-global lua (cstr "t"))
(Lua.get-global lua (cstr "t"))
(ignore (Lua.get-field lua -1 (cstr "val")))
(Lua.get-double lua -1))
"set-double-field sets a double field without losing precision")

(assert-true test
(Lua.with-lua-do (Lua.libs lua)
(Lua.create-table lua 0 1)
Expand Down
Loading