sweet-moon 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/.rspec +1 -0
- data/.rubocop.yml +40 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +61 -0
- data/README.md +1149 -0
- data/components/api.rb +83 -0
- data/components/injections/injections_503.rb +21 -0
- data/components/injections/injections_514.rb +29 -0
- data/components/injections/injections_542.rb +49 -0
- data/components/injections.rb +11 -0
- data/components/interpreters/50/function.rb +52 -0
- data/components/interpreters/50/interpreter.rb +105 -0
- data/components/interpreters/50/reader.rb +65 -0
- data/components/interpreters/50/table.rb +99 -0
- data/components/interpreters/50/writer.rb +45 -0
- data/components/interpreters/51/function.rb +52 -0
- data/components/interpreters/51/interpreter.rb +104 -0
- data/components/interpreters/51/reader.rb +65 -0
- data/components/interpreters/51/table.rb +60 -0
- data/components/interpreters/51/writer.rb +45 -0
- data/components/interpreters/54/function.rb +52 -0
- data/components/interpreters/54/interpreter.rb +100 -0
- data/components/interpreters/54/reader.rb +65 -0
- data/components/interpreters/54/table.rb +60 -0
- data/components/interpreters/54/writer.rb +45 -0
- data/components/interpreters.rb +11 -0
- data/components/io.rb +11 -0
- data/config/tests.sample.yml +15 -0
- data/controllers/api.rb +143 -0
- data/controllers/cli/cli.rb +32 -0
- data/controllers/cli/help.rb +24 -0
- data/controllers/cli/signatures.rb +179 -0
- data/controllers/cli/version.rb +14 -0
- data/controllers/interpreter.rb +68 -0
- data/controllers/state.rb +74 -0
- data/dsl/api.rb +31 -0
- data/dsl/cache.rb +118 -0
- data/dsl/concerns/fennel.rb +13 -0
- data/dsl/concerns/packages.rb +37 -0
- data/dsl/errors.rb +28 -0
- data/dsl/fennel.rb +47 -0
- data/dsl/global.rb +42 -0
- data/dsl/state.rb +104 -0
- data/dsl/sweet_moon.rb +53 -0
- data/logic/api.rb +17 -0
- data/logic/interpreter.rb +84 -0
- data/logic/interpreters/interpreter_50.rb +34 -0
- data/logic/interpreters/interpreter_51.rb +37 -0
- data/logic/interpreters/interpreter_54.rb +41 -0
- data/logic/io.rb +6 -0
- data/logic/options.rb +14 -0
- data/logic/shared_object.rb +52 -0
- data/logic/signature.rb +258 -0
- data/logic/signatures/ffi_types.rb +27 -0
- data/logic/signatures/signatures_322.rb +418 -0
- data/logic/signatures/signatures_401.rb +243 -0
- data/logic/signatures/signatures_503.rb +575 -0
- data/logic/signatures/signatures_514.rb +460 -0
- data/logic/signatures/signatures_542.rb +591 -0
- data/logic/spec.rb +13 -0
- data/logic/tables.rb +32 -0
- data/ports/in/dsl/sweet-moon/errors.rb +3 -0
- data/ports/in/dsl/sweet-moon.rb +1 -0
- data/ports/in/shell/sweet-moon +5 -0
- data/ports/in/shell.rb +21 -0
- data/ports/out/shell.rb +9 -0
- data/sweet-moon.gemspec +35 -0
- metadata +137 -0
data/README.md
ADDED
@@ -0,0 +1,1149 @@
|
|
1
|
+
# Sweet Moon
|
2
|
+
|
3
|
+
_Sweet Moon_ is a resilient solution that makes working with [Lua](https://www.lua.org) / [Fennel](https://fennel-lang.org) from [Ruby](https://www.ruby-lang.org) and vice versa a delightful experience.
|
4
|
+
|
5
|
+
![Image with Lua, Fennel, and Ruby source code examples.](https://raw.githubusercontent.com/gbaptista/assets/main/sweet-moon/sweet-moon.png)
|
6
|
+
|
7
|
+
- [Supported Versions](#supported-versions)
|
8
|
+
- [Setup and TLDR](#setup-and-tldr)
|
9
|
+
- [Loading Configuration Files](#loading-configuration-files)
|
10
|
+
- [Lua Configuration Files](#lua-configuration-files)
|
11
|
+
- [Fennel Configuration Files](#fennel-configuration-files)
|
12
|
+
- [Performance and Benchmarks](#performance-and-benchmarks)
|
13
|
+
- [Fennel and Lua Versions](#fennel-and-lua-versions)
|
14
|
+
- [Comparison with other Gems](comparison-with-other-gems)
|
15
|
+
- [Interacting with a Lua State](#interacting-with-a-lua-state)
|
16
|
+
- [Setup](#setup)
|
17
|
+
- [Exchanging Data](#exchanging-data)
|
18
|
+
- [_eval_ and _load_](#eval-and-load)
|
19
|
+
- [Primitives](#primitives)
|
20
|
+
- [Tables, Arrays, and Hashes](#tables-arrays-and-hashes)
|
21
|
+
- [Functions](#functions)
|
22
|
+
- [Other Types](#other-types)
|
23
|
+
- [_destroy_ and _clear_](#destroy-and-clear)
|
24
|
+
- [Modules, Packages and LuaRocks](#modules-packages-and-luarocks)
|
25
|
+
- [Integration with LuaRocks](#integration-with-luarocks)
|
26
|
+
- [Fennel](#fennel)
|
27
|
+
- [Fennel Usage](#fennel-usage)
|
28
|
+
- [Fennel Setup](#fennel-setup)
|
29
|
+
- [Global vs Isolated](#global-vs-isolated)
|
30
|
+
- [Error Handling](#error-handling)
|
31
|
+
- [Where can I find .so files?](#where-can-i-find-so-files)
|
32
|
+
- [Low-Level C API](#low-level-c-api)
|
33
|
+
- [The API](#the-api)
|
34
|
+
- [Custom Shared Objects](#custom-shared-objects)
|
35
|
+
- [Custom API References](#custom-api-references)
|
36
|
+
- [Functions, Macros and Signatures](#functions-macros-and-signatures)
|
37
|
+
- [Low-Level C API Example](#low-level-c-api-example)
|
38
|
+
- [Lua 5.4](#lua-54)
|
39
|
+
- [Lua 4.0](#lua-40)
|
40
|
+
- [Development](#development)
|
41
|
+
|
42
|
+
## Supported Versions
|
43
|
+
|
44
|
+
_Sweet Moon_ was created to be resilient and adaptable. So it doesn't have a dependency on specific versions, and it will always try to create a working environment with whatever you have available.
|
45
|
+
|
46
|
+
That said, these are the officially tested versions:
|
47
|
+
|
48
|
+
C API:
|
49
|
+
- Lua: `3.2.2`, `4.0.1`, `5.0.3`, `5.1.4`, and `5.4.2`
|
50
|
+
|
51
|
+
Interpreter:
|
52
|
+
- Lua: `5.0`, `5.1`, and `5.4`
|
53
|
+
|
54
|
+
Interpreters' Compatibility:
|
55
|
+
- Lua: `5.0.3`, `5.1.4`, `5.1.5`, `5.2.4`, `5.3.3`, `5.4.2`, and `5.4.4`
|
56
|
+
- LuaJIT: `2.0.5`
|
57
|
+
|
58
|
+
## Setup and TLDR
|
59
|
+
|
60
|
+
```sh
|
61
|
+
gem install sweet-moon
|
62
|
+
```
|
63
|
+
|
64
|
+
> **Disclaimer:** It's an early-stage project, and you should expect breaking changes.
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
gem 'sweet-moon', '~> 0.0.1'
|
68
|
+
```
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
require 'sweet-moon'
|
72
|
+
|
73
|
+
# State
|
74
|
+
|
75
|
+
SweetMoon.global.state.eval('return 1 + 2') # => 3
|
76
|
+
SweetMoon.global.state.fennel.eval('(+ 2 3)') # => 5
|
77
|
+
|
78
|
+
state = SweetMoon::State.new
|
79
|
+
|
80
|
+
state.eval('return 3 + 4') # => 7
|
81
|
+
state.load('file.lua') # => {...}
|
82
|
+
|
83
|
+
state.fennel.eval('(+ 3 7)') # => 10
|
84
|
+
state.fennel.load('file.fnl') # => {...}
|
85
|
+
|
86
|
+
# API
|
87
|
+
|
88
|
+
SweetMoon.global.api.luaL_newstate
|
89
|
+
|
90
|
+
api = SweetMoon::API.new
|
91
|
+
|
92
|
+
state = api.luaL_newstate
|
93
|
+
api.luaL_openlibs(state)
|
94
|
+
```
|
95
|
+
|
96
|
+
## Loading Configuration Files
|
97
|
+
|
98
|
+
Lua as a _Configuration Language_ is a robust approach widely used in the industry for [decades](https://www.lua.org/about.html). It's a powerful alternative to _[YAML](https://yaml.org)_ or _[TOML](https://toml.io)_ and way more spread and battle-tested than _[edn](https://github.com/edn-format/edn)_.
|
99
|
+
|
100
|
+
### Lua Configuration Files
|
101
|
+
|
102
|
+
Create a `.lua` file:
|
103
|
+
|
104
|
+
```lua
|
105
|
+
return {
|
106
|
+
color = "red",
|
107
|
+
dimensions = { width = 200, height = 2 * 80 },
|
108
|
+
values = {4, 6} }
|
109
|
+
```
|
110
|
+
|
111
|
+
Load it:
|
112
|
+
```ruby
|
113
|
+
require 'sweet-moon'
|
114
|
+
|
115
|
+
SweetMoon.global.state.load('config.lua')
|
116
|
+
|
117
|
+
# => { 'color' => 'red',
|
118
|
+
# 'dimensions' => { 'width' => 200, 'height' => 160 },
|
119
|
+
# 'values' => [4, 6] }
|
120
|
+
```
|
121
|
+
|
122
|
+
Alternatively:
|
123
|
+
```ruby
|
124
|
+
require 'sweet-moon'
|
125
|
+
|
126
|
+
state = SweetMoon::State.new
|
127
|
+
|
128
|
+
state.load('config.lua')
|
129
|
+
```
|
130
|
+
|
131
|
+
### Fennel Configuration Files
|
132
|
+
|
133
|
+
Create a `.fnl` file:
|
134
|
+
|
135
|
+
```fnl
|
136
|
+
{:color "red"
|
137
|
+
:dimensions {:width 200 :height (* 2 80)}
|
138
|
+
:values [4 6]}
|
139
|
+
```
|
140
|
+
|
141
|
+
Load it:
|
142
|
+
```ruby
|
143
|
+
require 'sweet-moon'
|
144
|
+
|
145
|
+
SweetMoon.global.state.fennel.load('config.fnl')
|
146
|
+
|
147
|
+
# => { 'color' => 'red',
|
148
|
+
# 'dimensions' => { 'width' => 200, 'height' => 160 },
|
149
|
+
# 'values' => [4, 6] }
|
150
|
+
```
|
151
|
+
|
152
|
+
Alternatively:
|
153
|
+
```ruby
|
154
|
+
require 'sweet-moon'
|
155
|
+
|
156
|
+
state = SweetMoon::State.new
|
157
|
+
|
158
|
+
state.fennel.load('config.fnl')
|
159
|
+
```
|
160
|
+
|
161
|
+
## Performance and Benchmarks
|
162
|
+
|
163
|
+
Benchmarks created through [benchmark-ips](https://github.com/evanphx/benchmark-ips).
|
164
|
+
|
165
|
+
The task is to get a file with a source code equivalent to:
|
166
|
+
```lua
|
167
|
+
return {
|
168
|
+
color = "red",
|
169
|
+
dimensions = { width = 200, height = 2 * 80 },
|
170
|
+
values = {4, 6} }
|
171
|
+
```
|
172
|
+
|
173
|
+
And then bring the final Ruby representation:
|
174
|
+
```ruby
|
175
|
+
{ 'color' => 'red',
|
176
|
+
'dimensions' => { 'width' => 200, 'height' => 160 },
|
177
|
+
'values' => [4, 6] }
|
178
|
+
```
|
179
|
+
|
180
|
+
It is important to note that only Lua and Fennel natively support expressions like `2 * 80`, and the other solutions have only a static number in their source.
|
181
|
+
|
182
|
+
### Fennel and Lua Versions
|
183
|
+
|
184
|
+
Higher is better:
|
185
|
+
|
186
|
+
![Image of a chart with Benchmarks between different Lua and Fennel versions.](https://raw.githubusercontent.com/gbaptista/assets/main/sweet-moon/lua-versions.png)
|
187
|
+
|
188
|
+
### Comparison with other Gems
|
189
|
+
|
190
|
+
Higher is better:
|
191
|
+
|
192
|
+
![Image of a chart with Benchmarks between Sweet Moon and other Gems.](https://raw.githubusercontent.com/gbaptista/assets/main/sweet-moon/other-gems.png)
|
193
|
+
|
194
|
+
Compared to: [rufus-lua](https://github.com/jmettraux/rufus-lua), [YAML](https://ruby-doc.org/stdlib-3.0.1/libdoc/yaml/rdoc/YAML.html), [edn-ruby](https://github.com/relevance/edn-ruby), [toml-rb](https://github.com/emancu/toml-rb), and [toml](https://github.com/jm/toml).
|
195
|
+
|
196
|
+
## Interacting with a Lua State
|
197
|
+
|
198
|
+
> Lua is a fast language engine with small footprint that you can [embed](https://www.lua.org/about.html) easily into your application.
|
199
|
+
|
200
|
+
- [Setup](#setup)
|
201
|
+
- [Exchanging Data](#exchanging-data)
|
202
|
+
- [_eval_ and _load_](#eval-and-load)
|
203
|
+
- [Primitives](#primitives)
|
204
|
+
- [Tables, Arrays, and Hashes](#tables-arrays-and-hashes)
|
205
|
+
- [Functions](#functions)
|
206
|
+
- [Other Types](#other-types)
|
207
|
+
- [_destroy_ and _clear_](#destroy-and-clear)
|
208
|
+
|
209
|
+
### Setup
|
210
|
+
|
211
|
+
A state is composed of three key elements: `shared_object`, `api_reference`, and `interpreter`.
|
212
|
+
|
213
|
+
For the global state:
|
214
|
+
```ruby
|
215
|
+
require 'sweet-moon'
|
216
|
+
|
217
|
+
SweetMoon.global.config(
|
218
|
+
shared_object: '/usr/lib/liblua.so.5.4.4',
|
219
|
+
api_reference: '5.4.2',
|
220
|
+
interpreter: '5.4'
|
221
|
+
)
|
222
|
+
|
223
|
+
SweetMoon.global.state.eval('return 1 + 1') # => 2
|
224
|
+
```
|
225
|
+
|
226
|
+
For a new isolated state:
|
227
|
+
```ruby
|
228
|
+
require 'sweet-moon'
|
229
|
+
|
230
|
+
state = SweetMoon::State.new(
|
231
|
+
shared_object: '/usr/lib/liblua.so.5.4.4',
|
232
|
+
api_reference: '5.4.2',
|
233
|
+
interpreter: '5.4'
|
234
|
+
)
|
235
|
+
|
236
|
+
state.eval('return 1 + 1') # => 2
|
237
|
+
```
|
238
|
+
By default, _Sweet Moon_ will automatically identify all these elements and find the best possible combination for them. Usually, the only parameter you might want to set manually is the `shared_object`. To understand `shared_object` and `api_reference`, check [_Custom Shared Objects_](#custom-shared-objects).
|
239
|
+
|
240
|
+
The `interpreter` describes which version of _Sweet Moon's_ internal Interpreter will handle the interactions with the Lua state. The internal interpreter abstracts the Lua C API to provide methods like `state.eval`, `state.get`, etc.
|
241
|
+
|
242
|
+
_Sweet Moon_ may not have an interpreter for all Lua versions, especially the too old or very specific ones. For this scenario, an error will be raised:
|
243
|
+
|
244
|
+
```ruby
|
245
|
+
require 'sweet-moon'
|
246
|
+
|
247
|
+
SweetMoon::State.new(shared_object: '/usr/lib/liblua3.so')
|
248
|
+
|
249
|
+
# => SweetMoon::Errors::SweetMoonError
|
250
|
+
# No compatible interpreter found for Lua C API 3.2.2
|
251
|
+
```
|
252
|
+
|
253
|
+
To check all available Interpreters, you can:
|
254
|
+
|
255
|
+
```ruby
|
256
|
+
require 'sweet-moon'
|
257
|
+
|
258
|
+
SweetMoon.meta.interpreters
|
259
|
+
# => ['5.0', '5.1', '5.4']
|
260
|
+
```
|
261
|
+
|
262
|
+
You can also check information about a state with:
|
263
|
+
|
264
|
+
```ruby
|
265
|
+
require 'sweet-moon'
|
266
|
+
|
267
|
+
state = SweetMoon::State.new
|
268
|
+
|
269
|
+
state.meta.shared_objects # => ['/usr/lib/liblua.so.5.4.4']
|
270
|
+
state.meta.api_reference # => 5.4.2
|
271
|
+
state.meta.interpreter # => 5.4
|
272
|
+
state.meta.runtime # => Lua 5.4
|
273
|
+
|
274
|
+
state.meta.to_h
|
275
|
+
# => { shared_objects: ['/usr/lib/liblua.so.5.4.4'],
|
276
|
+
# api_reference: '5.4.2',
|
277
|
+
# interpreter: '5.4',
|
278
|
+
# runtime: 'Lua 5.4' }
|
279
|
+
```
|
280
|
+
|
281
|
+
The same is true for the global state with `SweetMoon.global.state.meta`.
|
282
|
+
|
283
|
+
### Exchanging Data
|
284
|
+
|
285
|
+
#### _eval_ and _load_
|
286
|
+
|
287
|
+
The `eval` method evaluates a Lua source code, and the `load` method loads a file and evaluates its content. Both return the output of the evaluation if it exists.
|
288
|
+
|
289
|
+
Caveat: The data exchange works through Lua [_global_](https://www.lua.org/pil/1.2.html) variables only.
|
290
|
+
|
291
|
+
```ruby
|
292
|
+
require 'sweet-moon'
|
293
|
+
|
294
|
+
state = SweetMoon::State.new
|
295
|
+
|
296
|
+
state.eval('return 2 + 2') # => 4
|
297
|
+
```
|
298
|
+
|
299
|
+
```lua
|
300
|
+
-- source.lua
|
301
|
+
|
302
|
+
from_lua = "Lua Text"
|
303
|
+
|
304
|
+
return { data = from_ruby }
|
305
|
+
```
|
306
|
+
|
307
|
+
```ruby
|
308
|
+
require 'sweet-moon'
|
309
|
+
|
310
|
+
state = SweetMoon::State.new
|
311
|
+
|
312
|
+
state.set('from_ruby', 'Ruby Text')
|
313
|
+
|
314
|
+
state.load('source.lua') # => { 'data' => 'Ruby Text' }
|
315
|
+
|
316
|
+
state.get('from_lua') # => 'Lua Text'
|
317
|
+
```
|
318
|
+
|
319
|
+
#### Primitives
|
320
|
+
|
321
|
+
With `get` and `set`, you can exchange between Lua and Ruby the following primitive types:
|
322
|
+
- Lua: `string`, `integer`, `number`, `boolean`, and `nil`.
|
323
|
+
- Ruby: `String`, `Symbol`, `Integer`, `Float`, `TrueClass` (`true`), `FalseClass` (`false`), and `NilClass` (`nil`).
|
324
|
+
|
325
|
+
```ruby
|
326
|
+
require 'sweet-moon'
|
327
|
+
|
328
|
+
state = SweetMoon::State.new
|
329
|
+
|
330
|
+
state.eval('lua_value = "Lua Text"') # => nil
|
331
|
+
|
332
|
+
state.get('lua_value') # => 'Lua Text'
|
333
|
+
|
334
|
+
state.set(:ruby_value, 'Ruby Text') # => nil
|
335
|
+
|
336
|
+
state.eval('return ruby_value') # => 'Ruby Text'
|
337
|
+
```
|
338
|
+
|
339
|
+
Caveats:
|
340
|
+
|
341
|
+
- Ruby `Symbol` (e.g. `:value`) is converted to Lua `string`.
|
342
|
+
- [_Floating-point arithmetic_](https://en.wikipedia.org/wiki/Floating-point_arithmetic) may be tricky when exchanging numbers between two different environments.
|
343
|
+
|
344
|
+
#### Tables, Arrays, and Hashes
|
345
|
+
|
346
|
+
You can exchange `Array`, `Hash` and `table` with `get` and `set`.
|
347
|
+
|
348
|
+
```ruby
|
349
|
+
require 'sweet-moon'
|
350
|
+
|
351
|
+
state = SweetMoon::State.new
|
352
|
+
|
353
|
+
state.eval('lua_value = {a = "text", b = 1.5, c = true}') # => nil
|
354
|
+
|
355
|
+
state.get(:lua_value) # => { 'a' => 'text', 'b' => 1.5, 'c' => true }
|
356
|
+
|
357
|
+
state.eval('list = {"a", "b", "c"}') # => nil
|
358
|
+
|
359
|
+
state.get('list') # => ['a', 'b', 'c']
|
360
|
+
|
361
|
+
state.eval('empty = {}') # => nil
|
362
|
+
|
363
|
+
state.get(:empty) # => { }
|
364
|
+
|
365
|
+
state.set('ruby_array', [3, 'a', true]) # => nil
|
366
|
+
|
367
|
+
state.eval('return ruby_array[1]') # => 3
|
368
|
+
state.eval('return ruby_array[2]') # => 'a'
|
369
|
+
|
370
|
+
state.set('ruby_hash', { a: 'b', values: ['c', 'd'] }) # => nil
|
371
|
+
|
372
|
+
state.eval('return ruby_hash["values"][2]') # => 'd'
|
373
|
+
```
|
374
|
+
|
375
|
+
With `get`, you can use a second parameter to read a field:
|
376
|
+
|
377
|
+
```ruby
|
378
|
+
require 'sweet-moon'
|
379
|
+
|
380
|
+
state = SweetMoon::State.new
|
381
|
+
|
382
|
+
state.eval('lua_value = {a = "text", b = 1.5, c = true}') # => nil
|
383
|
+
|
384
|
+
state.get(:lua_value, :b) # => 1.5
|
385
|
+
```
|
386
|
+
|
387
|
+
Caveats:
|
388
|
+
|
389
|
+
- Ruby `Symbol` (e.g. `:value`) is converted to Lua `string`.
|
390
|
+
- Ruby `Hash` is converted to Lua `table`.
|
391
|
+
- Ruby `Array` is converted to a _sequential_ Lua `table`.
|
392
|
+
- Lua _sequential_ `table` is converted to Ruby `Array`.
|
393
|
+
- Lua _non-sequential_ `table` is converted to Ruby `Hash`.
|
394
|
+
- Lua **empty** `table` is converted to `Hash` (`{}`).
|
395
|
+
- Lua _sequential_ `table` (_array_) [starts](https://www.lua.org/pil/2.5.html) at index `1`.
|
396
|
+
|
397
|
+
#### Functions
|
398
|
+
|
399
|
+
Lua [_Functions_](https://www.lua.org/pil/5.html) are converted to Ruby [_Lambdas_](https://docs.ruby-lang.org/en/3.1/Proc.html#class-Proc-label-Lambda+and+non-lambda+semantics), where the first parameter is an array of parameters, and the second is an optional expected number of results that default to 1 (Lua _Functions_ can return [multiple results](https://www.lua.org/pil/5.1.html)).
|
400
|
+
|
401
|
+
```ruby
|
402
|
+
require 'sweet-moon'
|
403
|
+
|
404
|
+
state = SweetMoon::State.new
|
405
|
+
|
406
|
+
state.eval('lua_fn = function(a, b) return "ok", a + b; end') # => nil
|
407
|
+
|
408
|
+
lua_fn = state.get(:lua_fn)
|
409
|
+
|
410
|
+
lua_fn.call([1, 2]) # => 'ok'
|
411
|
+
lua_fn.call([1, 2], 2) # => ['ok', 3]
|
412
|
+
|
413
|
+
lua_fn.([1, 2]) # => 'ok'
|
414
|
+
lua_fn.([1, 2], 2) # => ['ok', 3]
|
415
|
+
|
416
|
+
state.eval('second = function(list) return list[2]; end') # => nil
|
417
|
+
|
418
|
+
second = state.get(:second)
|
419
|
+
|
420
|
+
second.([%w[a b c]]) # => 'b'
|
421
|
+
```
|
422
|
+
|
423
|
+
You can call Ruby _Lambdas_ from _Lua_ as well:
|
424
|
+
|
425
|
+
```ruby
|
426
|
+
require 'sweet-moon'
|
427
|
+
|
428
|
+
state = SweetMoon::State.new
|
429
|
+
|
430
|
+
ruby_fn = lambda do |a, b|
|
431
|
+
return a + b
|
432
|
+
end
|
433
|
+
|
434
|
+
state.set(:rubyFn, ruby_fn) # => nil
|
435
|
+
|
436
|
+
state.eval('return rubyFn(2, 2)') # => 4
|
437
|
+
|
438
|
+
sum_list = -> (list) { list.sum }
|
439
|
+
|
440
|
+
state.set('sumList', sum_list) # => nil
|
441
|
+
|
442
|
+
state.eval('return sumList({2, 3, 5})') # => 10
|
443
|
+
```
|
444
|
+
|
445
|
+
#### Other Types
|
446
|
+
|
447
|
+
We encourage you to keep a clean and simple exchange between Lua and Ruby, avoiding complex data types and bloated data structures.
|
448
|
+
|
449
|
+
Anytime you try to exchange an unsupported data type, you won't get an error, but it will be converted to a string representation:
|
450
|
+
|
451
|
+
```ruby
|
452
|
+
require 'sweet-moon'
|
453
|
+
|
454
|
+
state = SweetMoon::State.new
|
455
|
+
|
456
|
+
state.eval('return coroutine.create(function() end)')
|
457
|
+
# => 'thread: 0x93924850822056'
|
458
|
+
|
459
|
+
state.set('ruby_thread', Thread.new { 1 + 1 })
|
460
|
+
|
461
|
+
state.eval('return ruby_thread') # => '#<Thread:0x0000000000000d0c>'
|
462
|
+
```
|
463
|
+
|
464
|
+
Also, avoid exchanging complex things unnecessarily, e.g., modules:
|
465
|
+
|
466
|
+
```ruby
|
467
|
+
require 'sweet-moon'
|
468
|
+
|
469
|
+
state = SweetMoon::State.new
|
470
|
+
|
471
|
+
state.require_module('fennel')
|
472
|
+
|
473
|
+
state.get(:fennel) # => {...}
|
474
|
+
# => It returns a huge chunk of data with
|
475
|
+
# a complex structure and mixed data types.
|
476
|
+
# It will work, but we encourage you to
|
477
|
+
# avoid that.
|
478
|
+
```
|
479
|
+
|
480
|
+
Prefer instead to extract what you need only:
|
481
|
+
|
482
|
+
```ruby
|
483
|
+
require 'sweet-moon'
|
484
|
+
|
485
|
+
state = SweetMoon::State.new
|
486
|
+
|
487
|
+
state.require_module('fennel')
|
488
|
+
|
489
|
+
fennel_eval = state.get(:fennel, :eval)
|
490
|
+
|
491
|
+
fennel_eval.(['(+ 1 1)']) # => 2
|
492
|
+
```
|
493
|
+
|
494
|
+
You can also abstract what you need into global variables:
|
495
|
+
|
496
|
+
```ruby
|
497
|
+
require 'sweet-moon'
|
498
|
+
|
499
|
+
state = SweetMoon::State.new
|
500
|
+
|
501
|
+
state.require_module('fennel')
|
502
|
+
|
503
|
+
state.eval('fennel_eval = fennel.eval')
|
504
|
+
|
505
|
+
fennel_eval = state.get(:fennel_eval)
|
506
|
+
|
507
|
+
fennel_eval.(['(+ 1 1)']) # => 2
|
508
|
+
```
|
509
|
+
|
510
|
+
## _destroy_ and _clear_
|
511
|
+
|
512
|
+
You can destroy a state:
|
513
|
+
|
514
|
+
```ruby
|
515
|
+
require 'sweet-moon'
|
516
|
+
|
517
|
+
state = SweetMoon::State.new
|
518
|
+
|
519
|
+
state.set(:a, 1)
|
520
|
+
state.get(:a) # => 1
|
521
|
+
|
522
|
+
state.destroy
|
523
|
+
|
524
|
+
state.get(:a)
|
525
|
+
# => SweetMoon::Errors::SweetMoonError
|
526
|
+
# The state no longer exists.
|
527
|
+
```
|
528
|
+
|
529
|
+
You can also clear a state:
|
530
|
+
|
531
|
+
```ruby
|
532
|
+
require 'sweet-moon'
|
533
|
+
|
534
|
+
state = SweetMoon::State.new
|
535
|
+
|
536
|
+
state.set(:a, 1)
|
537
|
+
state.get(:a) # => 1
|
538
|
+
|
539
|
+
state.clear
|
540
|
+
|
541
|
+
state.get(:a) # => nil
|
542
|
+
```
|
543
|
+
|
544
|
+
## Modules, Packages and LuaRocks
|
545
|
+
|
546
|
+
> Check the [Modules](https://www.lua.org/manual/5.4/manual.html#6.3) documentation at the _Lua Manual_ to understand the essentials.
|
547
|
+
|
548
|
+
You can achieve everything through eval:
|
549
|
+
|
550
|
+
```ruby
|
551
|
+
require 'sweet-moon'
|
552
|
+
|
553
|
+
state = SweetMoon::State.new
|
554
|
+
|
555
|
+
state.eval('package.path = package.path .. ";/my-modules/?.lua"')
|
556
|
+
state.eval('package.cpath = package.cpath .. ";/my-modules/?.so"')
|
557
|
+
|
558
|
+
state.eval('some_package = require("my_module")')
|
559
|
+
```
|
560
|
+
|
561
|
+
Regardless, we offer some helpers that you can use.
|
562
|
+
|
563
|
+
Adding a path to the Lua [`package.path`](https://www.lua.org/manual/5.4/manual.html#pdf-package.path):
|
564
|
+
|
565
|
+
```ruby
|
566
|
+
require 'sweet-moon'
|
567
|
+
|
568
|
+
state = SweetMoon::State.new
|
569
|
+
|
570
|
+
state.add_package_path('/home/me/my-lua-modules/?.lua')
|
571
|
+
state.add_package_path('/home/me/my-lua-modules/?/init.lua')
|
572
|
+
|
573
|
+
state.add_package_cpath('/home/me/my-lua-modules/?.so')
|
574
|
+
|
575
|
+
state.add_package_path('/home/me/fennel.lua')
|
576
|
+
|
577
|
+
state.add_package_cpath('/home/me/lib.so')
|
578
|
+
|
579
|
+
state.package_path
|
580
|
+
# => ['./?.lua',
|
581
|
+
# './?/init.lua',
|
582
|
+
# '/home/me/my-lua-modules/?.lua',
|
583
|
+
# '/home/me/my-lua-modules/?/init.lua',
|
584
|
+
# '/home/me/fennel.lua']
|
585
|
+
|
586
|
+
state.package_cpath
|
587
|
+
# => ['./?.so',
|
588
|
+
# '/home/me/my-lua-modules/?.so',
|
589
|
+
# '/home/me/lib.so']
|
590
|
+
```
|
591
|
+
|
592
|
+
Requiring a module:
|
593
|
+
```ruby
|
594
|
+
require 'sweet-moon'
|
595
|
+
|
596
|
+
state = SweetMoon::State.new
|
597
|
+
|
598
|
+
state.require_module('supernova')
|
599
|
+
|
600
|
+
state.require_module_as('fennel', 'f')
|
601
|
+
```
|
602
|
+
|
603
|
+
You can set packages in State constructors:
|
604
|
+
|
605
|
+
```ruby
|
606
|
+
require 'sweet-moon'
|
607
|
+
|
608
|
+
SweetMoon::State.new(
|
609
|
+
package_path: '/folder/lib.lua',
|
610
|
+
package_cpath: '/lib/lib.so',
|
611
|
+
)
|
612
|
+
```
|
613
|
+
|
614
|
+
Also, you can add packages through the global config:
|
615
|
+
|
616
|
+
```ruby
|
617
|
+
require 'sweet-moon'
|
618
|
+
|
619
|
+
SweetMoon.global.config(
|
620
|
+
package_path: '/folder/lib.lua',
|
621
|
+
package_cpath: '/lib/lib.so',
|
622
|
+
)
|
623
|
+
```
|
624
|
+
|
625
|
+
### Integration with LuaRocks:
|
626
|
+
|
627
|
+
> Read more about how to use LuaRocks in the official documentation: [_Using LuaRocks_](https://github.com/luarocks/luarocks/wiki/Using-LuaRocks)
|
628
|
+
|
629
|
+
[LuaRocks](https://luarocks.org) is a popular package manager for the Lua language.
|
630
|
+
|
631
|
+
You can install modules like [_supernova_](https://github.com/gbaptista/supernova#lua) with:
|
632
|
+
|
633
|
+
```sh
|
634
|
+
luarocks install supernova --local
|
635
|
+
```
|
636
|
+
|
637
|
+
You can figure out the path for LuaRocks modules with:
|
638
|
+
```sh
|
639
|
+
luarocks path
|
640
|
+
# => export LUA_PATH='.../home/me/.luarocks/share...
|
641
|
+
```
|
642
|
+
|
643
|
+
If you set the `LUA_PATH` and `LUA_CPATH` environment variable on your system, modules installed through LuaRocks will just work.
|
644
|
+
|
645
|
+
Alternatively, you can add it manually to the `package`:
|
646
|
+
|
647
|
+
```ruby
|
648
|
+
require 'sweet-moon'
|
649
|
+
|
650
|
+
state = SweetMoon::State.new
|
651
|
+
|
652
|
+
state.add_package_path('/home/me/.luarocks/share/lua/5.4/?.lua')
|
653
|
+
state.add_package_path('/home/me/.luarocks/share/lua/5.4/?/init.lua')
|
654
|
+
state.add_package_cpath('/home/me/.luarocks/lib/lua/5.4/?.so')
|
655
|
+
|
656
|
+
state.require_module('supernova')
|
657
|
+
|
658
|
+
state.eval('return supernova.enabled') # => true
|
659
|
+
|
660
|
+
state.require_module_as('supernova', 'sn')
|
661
|
+
|
662
|
+
state.eval('return sn.active_theme') # => 'default'
|
663
|
+
|
664
|
+
puts state.eval('return sn.red("hello")') # => "\e[31mhello\e[0m"
|
665
|
+
puts state.eval('return sn.blue("hello")') # => "\e[34mhello\e[0m"
|
666
|
+
```
|
667
|
+
|
668
|
+
You can also use the constructor:
|
669
|
+
```ruby
|
670
|
+
require 'sweet-moon'
|
671
|
+
|
672
|
+
state = SweetMoon::State.new(
|
673
|
+
package_path: [
|
674
|
+
'/home/me/.luarocks/share/lua/5.4/?.lua',
|
675
|
+
'/home/me/.luarocks/share/lua/5.4/?/init.lua'
|
676
|
+
],
|
677
|
+
package_cpath: '/home/me/.luarocks/lib/lua/5.4/?.so'
|
678
|
+
)
|
679
|
+
```
|
680
|
+
|
681
|
+
For global:
|
682
|
+
```ruby
|
683
|
+
require 'sweet-moon'
|
684
|
+
|
685
|
+
SweetMoon.global.config.new(
|
686
|
+
package_path: [
|
687
|
+
'/home/me/.luarocks/share/lua/5.4/?.lua',
|
688
|
+
'/home/me/.luarocks/share/lua/5.4/?/init.lua'
|
689
|
+
],
|
690
|
+
package_cpath: '/home/me/.luarocks/lib/lua/5.4/?.so'
|
691
|
+
)
|
692
|
+
```
|
693
|
+
|
694
|
+
|
695
|
+
## Fennel
|
696
|
+
|
697
|
+
> _[Fennel](https://fennel-lang.org) is a programming language that brings together the speed, simplicity, and reach of [Lua](https://www.lua.org) with the flexibility of a [lisp syntax and macro system.](https://en.wikipedia.org/wiki/Lisp_(programming_language))_
|
698
|
+
|
699
|
+
### Fennel Usage
|
700
|
+
|
701
|
+
Everything described for Lua is equivalent to Fennel, and you have the same capabilities, methods, and data exchanging.
|
702
|
+
|
703
|
+
The only thing needed is to prefix your calls with `.fennel` and ensure that the Fennel module is available:
|
704
|
+
|
705
|
+
```ruby
|
706
|
+
require 'sweet-moon'
|
707
|
+
|
708
|
+
state = SweetMoon::State.new
|
709
|
+
|
710
|
+
state.fennel.eval('(+ 1 2)') # => 3
|
711
|
+
|
712
|
+
state.fennel.eval('(global mySum (fn [a b] (+ a b)))')
|
713
|
+
state.fennel.eval('(mySum 2 3)') # => 5
|
714
|
+
|
715
|
+
mySum = state.fennel.get(:mySum)
|
716
|
+
|
717
|
+
mySum.([4, 5]) # => 9
|
718
|
+
|
719
|
+
sum_list = -> (list) { list.sum }
|
720
|
+
|
721
|
+
state.set('sumList', sum_list) # => nil
|
722
|
+
|
723
|
+
state.fennel.eval('(sumList [2 3 5])') # => 10
|
724
|
+
|
725
|
+
state.fennel.load('file.fnl')
|
726
|
+
```
|
727
|
+
|
728
|
+
Alternatively:
|
729
|
+
|
730
|
+
```ruby
|
731
|
+
require 'sweet-moon'
|
732
|
+
|
733
|
+
state = SweetMoon::State.new.fennel
|
734
|
+
|
735
|
+
state.eval('(+ 1 2)') # => 3
|
736
|
+
```
|
737
|
+
|
738
|
+
### Fennel Setup
|
739
|
+
|
740
|
+
To ensure that the Fennel module is available, you can set up the [_LuaRocks_](#integration-withluarocks) integration or manually add the `package_path` for the module.
|
741
|
+
|
742
|
+
You can download the `fennel.lua` file on the [Fennel's website](https://fennel-lang.org/setup#embedding-the-fennel-compiler-in-a-lua-application).
|
743
|
+
|
744
|
+
Manually:
|
745
|
+
|
746
|
+
```ruby
|
747
|
+
require 'sweet-moon'
|
748
|
+
|
749
|
+
state = SweetMoon::State.new
|
750
|
+
|
751
|
+
state.add_package_path('/folder/fennel.lua')
|
752
|
+
|
753
|
+
state.fennel.eval('(+ 1 1)') # => 2
|
754
|
+
```
|
755
|
+
|
756
|
+
With the constructor:
|
757
|
+
|
758
|
+
```ruby
|
759
|
+
require 'sweet-moon'
|
760
|
+
|
761
|
+
fennel = SweetMoon::State.new(package_path: '/folder/fennel.lua').fennel
|
762
|
+
|
763
|
+
fennel.eval('(+ 1 1)') # => 2
|
764
|
+
```
|
765
|
+
|
766
|
+
With global:
|
767
|
+
|
768
|
+
```ruby
|
769
|
+
require 'sweet-moon'
|
770
|
+
|
771
|
+
SweetMoon.global.state.add_package_path('/folder/fennel.lua')
|
772
|
+
|
773
|
+
SweetMoon.global.state.fennel.eval('(+ 1 1)') # => 2
|
774
|
+
```
|
775
|
+
|
776
|
+
Alternatively:
|
777
|
+
|
778
|
+
```ruby
|
779
|
+
require 'sweet-moon'
|
780
|
+
|
781
|
+
SweetMoon.global.config(package_path: '/folder/fennel.lua')
|
782
|
+
|
783
|
+
SweetMoon.global.state.fennel.eval('(+ 1 1)') # => 2
|
784
|
+
```
|
785
|
+
|
786
|
+
## Global vs Isolated
|
787
|
+
|
788
|
+
You can use the **global** helper that provides an _API_ and a _State_ for quick-and-dirty coding. It uses internally a Ruby [_Singleton_](https://docs.ruby-lang.org/en/3.1/Singleton.html):
|
789
|
+
|
790
|
+
```ruby
|
791
|
+
require 'sweet-moon'
|
792
|
+
|
793
|
+
SweetMoon.global.state.eval('return 1 + 1')
|
794
|
+
|
795
|
+
SweetMoon.global.api.luaL_newstate
|
796
|
+
```
|
797
|
+
|
798
|
+
You can configure **global** with:
|
799
|
+
```ruby
|
800
|
+
require 'sweet-moon'
|
801
|
+
|
802
|
+
SweetMoon.global.config(
|
803
|
+
shared_object: '/usr/lib/liblua.so.5.4.4',
|
804
|
+
api_reference: '5.4.2',
|
805
|
+
interpreter: '5.4'
|
806
|
+
)
|
807
|
+
```
|
808
|
+
|
809
|
+
To clean up, you can:
|
810
|
+
|
811
|
+
```ruby
|
812
|
+
require 'sweet-moon'
|
813
|
+
|
814
|
+
SweetMoon.global.clear
|
815
|
+
```
|
816
|
+
|
817
|
+
As the API is just a stateless binding to the Lua C API, you can use it without worries.
|
818
|
+
|
819
|
+
You may want to use an isolated API for scenarios like interacting with two Lua versions simultaneously:
|
820
|
+
|
821
|
+
```ruby
|
822
|
+
require 'sweet-moon'
|
823
|
+
|
824
|
+
api_5 = SweetMoon.global.config(shared_object: '/usr/lib/liblua5.s')
|
825
|
+
api_3 = SweetMoon.global.config(shared_object: '/usr/lib/liblua3.so')
|
826
|
+
|
827
|
+
api_5.luaL_newstate
|
828
|
+
|
829
|
+
api_3.luaH_new
|
830
|
+
```
|
831
|
+
|
832
|
+
On the other hand, using the **global** _State_ may lead to a lot of issues. You need to consider from simple things – _"If I load two different files, the first file may impact the state of the second one?"_ – to more complex ones like multithreading, concurrency, etc.
|
833
|
+
|
834
|
+
So, you can at any time create a new isolated _State_ and destroy it when you don't need it anymore:
|
835
|
+
|
836
|
+
```ruby
|
837
|
+
require 'sweet-moon'
|
838
|
+
|
839
|
+
state = SweetMoon::State.new
|
840
|
+
|
841
|
+
state.eval('return 3 + 4') # => 7
|
842
|
+
state.load('file.lua') # => {...}
|
843
|
+
|
844
|
+
state.destroy
|
845
|
+
```
|
846
|
+
|
847
|
+
It's possible to empty a state with [_clear_](#destroy-and-clear).
|
848
|
+
|
849
|
+
Like the _API_, you may want to use an isolated _State_ to run Lua code in different Lua Versions simultaneously:
|
850
|
+
|
851
|
+
```ruby
|
852
|
+
require 'sweet-moon'
|
853
|
+
|
854
|
+
state_5 = SweetMoon::State.new(shared_object: '/usr/lib/liblua5.s')
|
855
|
+
state_3 = SweetMoon::State.new(shared_object: '/usr/lib/liblua3.so')
|
856
|
+
|
857
|
+
state_5.eval('return _VERSION') # => Lua 5.4
|
858
|
+
state_3.eval('return _VERSION') # => Lua 3.2
|
859
|
+
```
|
860
|
+
|
861
|
+
|
862
|
+
## Error Handling
|
863
|
+
|
864
|
+
These are – hopefully – all the possible errors:
|
865
|
+
|
866
|
+
```ruby
|
867
|
+
SweetMoonError # inherits from StandardError
|
868
|
+
LuaError # inherits from SweetMoonError
|
869
|
+
|
870
|
+
# inherits from LuaError:
|
871
|
+
LuaRuntimeError
|
872
|
+
LuaMemoryAllocationError
|
873
|
+
LuaMessageHandlerError
|
874
|
+
LuaSyntaxError
|
875
|
+
LuaFileError
|
876
|
+
```
|
877
|
+
|
878
|
+
You can handle the errors from the `SweetMoon::Errors` namespace:
|
879
|
+
|
880
|
+
```ruby
|
881
|
+
require 'sweet-moon'
|
882
|
+
|
883
|
+
begin
|
884
|
+
SweetMoon.global.state.eval('return 1 + true')
|
885
|
+
rescue SweetMoon::Errors::LuaSyntaxError => error
|
886
|
+
puts error.message
|
887
|
+
# => [string "return 1 + true"]:1: attempt to perform arithmetic on a boolean value
|
888
|
+
end
|
889
|
+
```
|
890
|
+
|
891
|
+
Or you can _include_ the errors for a cleaner version with `sweet-moon/errors`:
|
892
|
+
|
893
|
+
```ruby
|
894
|
+
require 'sweet-moon'
|
895
|
+
require 'sweet-moon/errors'
|
896
|
+
|
897
|
+
begin
|
898
|
+
SweetMoon.global.state.eval('return 1 + true')
|
899
|
+
rescue LuaSyntaxError => error
|
900
|
+
puts error.message
|
901
|
+
# => [string "return 1 + true"]:1: attempt to perform arithmetic on a boolean value
|
902
|
+
end
|
903
|
+
```
|
904
|
+
|
905
|
+
## Where can I find .so files?
|
906
|
+
|
907
|
+
Due to the Lua's popularity, you likely have it already on your system, and _Sweet Moon_ will be able to find the files on its own.
|
908
|
+
|
909
|
+
Either way, you can download it from this page:
|
910
|
+
- [Lua Binaries](http://luabinaries.sourceforge.net)
|
911
|
+
- [LuaJIT releases](http://luajit.org/download.html)
|
912
|
+
|
913
|
+
## Low-Level C API
|
914
|
+
|
915
|
+
- [The API](#the-api)
|
916
|
+
- [Custom Shared Objects](#custom-shared-objects)
|
917
|
+
- [Custom API References](#custom-api-references)
|
918
|
+
- [Functions, Macros and Signatures](#functions-macros-and-signatures)
|
919
|
+
- [Low-Level C API Example](#low-level-c-api-example)
|
920
|
+
- [Lua 5.4](#lua-54)
|
921
|
+
- [Lua 4.0](#lua-40)
|
922
|
+
|
923
|
+
### The API
|
924
|
+
|
925
|
+
You can access a global instance of the low-level C API with:
|
926
|
+
|
927
|
+
```ruby
|
928
|
+
require 'sweet-moon'
|
929
|
+
|
930
|
+
SweetMoon.global.api
|
931
|
+
```
|
932
|
+
|
933
|
+
For a fresh new non-global instance:
|
934
|
+
|
935
|
+
```ruby
|
936
|
+
require 'sweet-moon'
|
937
|
+
|
938
|
+
api = SweetMoon::API.new
|
939
|
+
```
|
940
|
+
|
941
|
+
Informations about the API:
|
942
|
+
|
943
|
+
```ruby
|
944
|
+
api.meta.shared_objects # => ['/usr/lib/liblua.so.5.4.4']
|
945
|
+
api.meta.api_reference # => '5.4.2'
|
946
|
+
|
947
|
+
api.meta.to_h
|
948
|
+
|
949
|
+
# => { shared_objects: ['/usr/lib/liblua.so.5.4.4'],
|
950
|
+
# api_reference: '5.4.2' }
|
951
|
+
```
|
952
|
+
|
953
|
+
### Custom Shared Objects
|
954
|
+
|
955
|
+
> To learn more about _Shared Objects_ and `.so` files: [_Dynamic linking_](https://en.wikipedia.org/wiki/Library_(computing)#Dynamic_linking), [_Dynamic linker_](https://en.wikipedia.org/wiki/Dynamic_linker) and [_Executable and Linkable Format_](https://en.wikipedia.org/wiki/Executable_and_Linkable_Format).
|
956
|
+
|
957
|
+
By default, _Sweet Moon_ will try to find and identify the Shared Object with the highest version available. You can customize it through:
|
958
|
+
|
959
|
+
```ruby
|
960
|
+
require 'sweet-moon'
|
961
|
+
|
962
|
+
api = SweetMoon::API.new(shared_object: '/usr/lib/liblua.so.5.4.4')
|
963
|
+
```
|
964
|
+
|
965
|
+
For the global instance:
|
966
|
+
|
967
|
+
```ruby
|
968
|
+
require 'sweet-moon'
|
969
|
+
|
970
|
+
SweetMoon.global.config(shared_object: '/usr/lib/liblua.so.5.4.4')
|
971
|
+
|
972
|
+
SweetMoon.global.api
|
973
|
+
```
|
974
|
+
|
975
|
+
Important to notice that the API Reference will not always be the same version of the Shared Object:
|
976
|
+
|
977
|
+
```ruby
|
978
|
+
require 'sweet-moon'
|
979
|
+
|
980
|
+
api = SweetMoon::API.new(shared_object: '/usr/lib/liblua.so.5.4.4')
|
981
|
+
|
982
|
+
api.meta.api_reference # => "5.4.2"
|
983
|
+
```
|
984
|
+
|
985
|
+
The Shared Object is from Lua **5.4.4**, and the API Reference is from Lua **5.4.2**.
|
986
|
+
|
987
|
+
This happens because it is impossible to extract function signatures from Shared Objects. So, _Sweet Moon_ will use an API Reference with the highest proportion of expected functions detected in the Shared Object as a reference.
|
988
|
+
|
989
|
+
A difference in versions, for practical purposes, is not a problem, given that _Sweet Moon_ has several relevant versions to choose from.
|
990
|
+
|
991
|
+
### Custom API References
|
992
|
+
|
993
|
+
You can force an specific API Reference for your Shared Object:
|
994
|
+
|
995
|
+
```ruby
|
996
|
+
require 'sweet-moon'
|
997
|
+
|
998
|
+
api = SweetMoon::API.new(
|
999
|
+
shared_object: '/usr/lib/liblua.so.5.4.4',
|
1000
|
+
api_refence: '3.2.2'
|
1001
|
+
)
|
1002
|
+
api.meta.shared_objects # => ['/usr/lib/liblua.so.5.4.4']
|
1003
|
+
api.meta.api_reference # => '3.2.2'
|
1004
|
+
```
|
1005
|
+
|
1006
|
+
To check all available API References you can:
|
1007
|
+
|
1008
|
+
```ruby
|
1009
|
+
require 'sweet-moon'
|
1010
|
+
|
1011
|
+
SweetMoon.meta.api_references
|
1012
|
+
# => ['3.2.2', '4.0.1', '5.0.3', '5.1.4', '5.4.2']
|
1013
|
+
```
|
1014
|
+
|
1015
|
+
_Sweet Moon_ won't raise errors by you trying to use an API Reference different from the Shared Object, but it will only attach valid functions, so you need to know what you are doing:
|
1016
|
+
|
1017
|
+
```ruby
|
1018
|
+
require 'sweet-moon'
|
1019
|
+
|
1020
|
+
api_5 = SweetMoon::API.new(shared_object: '/usr/lib/liblua.so.5.4.4')
|
1021
|
+
|
1022
|
+
api_3 = SweetMoon::API.new(shared_object: '/usr/lib/liblua.so.3.2.2')
|
1023
|
+
|
1024
|
+
api_5_with_3 = SweetMoon::API.new(
|
1025
|
+
shared_object: '/usr/lib/liblua.so.5.4.4',
|
1026
|
+
api_reference: '3.2.2'
|
1027
|
+
)
|
1028
|
+
|
1029
|
+
api_5.functions.size # => 159
|
1030
|
+
api_3.functions.size # => 162
|
1031
|
+
api_5_with_3.functions.size # => 20
|
1032
|
+
```
|
1033
|
+
|
1034
|
+
### Functions, Macros, and Signatures
|
1035
|
+
|
1036
|
+
_Sweet Moon_ will provide the available Lua-related functions for a Shared Object:
|
1037
|
+
|
1038
|
+
```ruby
|
1039
|
+
require 'sweet-moon'
|
1040
|
+
|
1041
|
+
api = SweetMoon::API.new(shared_object: '/usr/lib/liblua.so.5.4.4')
|
1042
|
+
|
1043
|
+
api.functions.size # 159
|
1044
|
+
|
1045
|
+
api.functions[0] # => :luaL_buffinitsize
|
1046
|
+
api.functions[1] # => :luaL_prepbuffsize
|
1047
|
+
api.functions[2] # => :luaL_checklstring
|
1048
|
+
```
|
1049
|
+
|
1050
|
+
To check the signature of a function you can:
|
1051
|
+
|
1052
|
+
```ruby
|
1053
|
+
api.signature_for(:luaL_checklstring)
|
1054
|
+
# => { source: 'LUALIB_API const char *(luaL_checklstring) (lua_State *L, int arg, size_t *l);',
|
1055
|
+
# input: %i[pointer int pointer],
|
1056
|
+
# output: :pointer }
|
1057
|
+
|
1058
|
+
api.signature_for(:luaL_newstate)
|
1059
|
+
# => { source: 'LUALIB_API lua_State *(luaL_newstate) (void);',
|
1060
|
+
# input: [],
|
1061
|
+
# output: :pointer }
|
1062
|
+
|
1063
|
+
api.signature_for(:lua_pop)
|
1064
|
+
# => { source: '#define lua_pop(L,n) lua_settop(L, -(n)-1)',
|
1065
|
+
# macro: true,
|
1066
|
+
# requires: [
|
1067
|
+
# { source: 'LUA_API void (lua_settop) (lua_State *L, int idx);',
|
1068
|
+
# input: %i[pointer int],
|
1069
|
+
# output: :void }
|
1070
|
+
# ] }
|
1071
|
+
```
|
1072
|
+
|
1073
|
+
Notice that `lua_pop` is a [macro](https://en.wikipedia.org/wiki/C_preprocessor), so the information about its signature is described differently.
|
1074
|
+
|
1075
|
+
### Low-Level C API Example
|
1076
|
+
|
1077
|
+
Working at a low-level with Lua will differ from version to version, and I recommend the book [_Programming in Lua_](https://www.lua.org/pil/) according to your target version. Chapters related to _"C API"_ are what you will probably search for, and the [_Lua Reference Manual_](https://www.lua.org/manual/) is [also](https://www.lua.org/manual/5.4/manual.html#4) a great [source](https://www.lua.org/manual/5.4/contents.html#index) of information.
|
1078
|
+
|
1079
|
+
#### Lua 5.4
|
1080
|
+
|
1081
|
+
As an example, following-ish [this reference](https://www.lua.org/pil/24.1.html), to get the result of the expression `math.pow(2, 3)`, you would do something like:
|
1082
|
+
|
1083
|
+
```ruby
|
1084
|
+
require 'sweet-moon'
|
1085
|
+
|
1086
|
+
api = SweetMoon::API.new(shared_object: '/usr/lib/liblua.so.5.4.4')
|
1087
|
+
|
1088
|
+
state = api.luaL_newstate
|
1089
|
+
api.luaL_openlibs(state)
|
1090
|
+
|
1091
|
+
api.luaL_loadstring(state, 'return math.pow(2, 3);')
|
1092
|
+
api.lua_pcall(state, 0, 1, 0)
|
1093
|
+
|
1094
|
+
result = api.lua_tonumber(state, -1)
|
1095
|
+
|
1096
|
+
api.lua_pop(state)
|
1097
|
+
api.lua_close(state)
|
1098
|
+
|
1099
|
+
puts result # => 8.0
|
1100
|
+
```
|
1101
|
+
|
1102
|
+
This is a minimal example and does not consider things you probably should for production-ready purposes, like error handling, available stack space, type checking, etc.
|
1103
|
+
|
1104
|
+
#### Lua 4.0
|
1105
|
+
|
1106
|
+
As an example, following the [manual](https://www.lua.org/manual/4.0/manual.html#5.), to get the result of the expression `2 ^ 3`, you would do something like:
|
1107
|
+
|
1108
|
+
```ruby
|
1109
|
+
require 'sweet-moon'
|
1110
|
+
|
1111
|
+
api = SweetMoon::API.new(
|
1112
|
+
shared_objects: ['/usr/lib/liblua4.so', '/usr/lib/liblualib4.so']
|
1113
|
+
)
|
1114
|
+
|
1115
|
+
state = api.lua_open(0)
|
1116
|
+
|
1117
|
+
api.lua_mathlibopen(state)
|
1118
|
+
|
1119
|
+
api.lua_dostring(state, 'return 2 ^ 3')
|
1120
|
+
|
1121
|
+
result = api.lua_tonumber(state, -1)
|
1122
|
+
|
1123
|
+
api.lua_settop(state, -2)
|
1124
|
+
api.lua_close(state)
|
1125
|
+
|
1126
|
+
puts result # => 8.0
|
1127
|
+
```
|
1128
|
+
|
1129
|
+
Notice that two _Shared Objects_ were necessary for this Lua version, one for the _Standard API_ and another for the _Standard Libraries_.
|
1130
|
+
|
1131
|
+
This is a minimal example and does not consider things you probably should for production-ready purposes, like error handling, available stack space, type checking, etc.
|
1132
|
+
|
1133
|
+
## Development
|
1134
|
+
|
1135
|
+
```sh
|
1136
|
+
bundle
|
1137
|
+
rubocop -a
|
1138
|
+
rspec
|
1139
|
+
```
|
1140
|
+
|
1141
|
+
```sh
|
1142
|
+
./ports/in/shell/sweet-moon version
|
1143
|
+
|
1144
|
+
bundle exec sweet-moon version
|
1145
|
+
|
1146
|
+
bundle exec sweet-moon signatures /lua/lib/542 542.rb
|
1147
|
+
|
1148
|
+
bundle exec ruby some/file.rb
|
1149
|
+
```
|