sweet-moon 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -0
  3. data/.rspec +1 -0
  4. data/.rubocop.yml +40 -0
  5. data/Gemfile +12 -0
  6. data/Gemfile.lock +61 -0
  7. data/README.md +1149 -0
  8. data/components/api.rb +83 -0
  9. data/components/injections/injections_503.rb +21 -0
  10. data/components/injections/injections_514.rb +29 -0
  11. data/components/injections/injections_542.rb +49 -0
  12. data/components/injections.rb +11 -0
  13. data/components/interpreters/50/function.rb +52 -0
  14. data/components/interpreters/50/interpreter.rb +105 -0
  15. data/components/interpreters/50/reader.rb +65 -0
  16. data/components/interpreters/50/table.rb +99 -0
  17. data/components/interpreters/50/writer.rb +45 -0
  18. data/components/interpreters/51/function.rb +52 -0
  19. data/components/interpreters/51/interpreter.rb +104 -0
  20. data/components/interpreters/51/reader.rb +65 -0
  21. data/components/interpreters/51/table.rb +60 -0
  22. data/components/interpreters/51/writer.rb +45 -0
  23. data/components/interpreters/54/function.rb +52 -0
  24. data/components/interpreters/54/interpreter.rb +100 -0
  25. data/components/interpreters/54/reader.rb +65 -0
  26. data/components/interpreters/54/table.rb +60 -0
  27. data/components/interpreters/54/writer.rb +45 -0
  28. data/components/interpreters.rb +11 -0
  29. data/components/io.rb +11 -0
  30. data/config/tests.sample.yml +15 -0
  31. data/controllers/api.rb +143 -0
  32. data/controllers/cli/cli.rb +32 -0
  33. data/controllers/cli/help.rb +24 -0
  34. data/controllers/cli/signatures.rb +179 -0
  35. data/controllers/cli/version.rb +14 -0
  36. data/controllers/interpreter.rb +68 -0
  37. data/controllers/state.rb +74 -0
  38. data/dsl/api.rb +31 -0
  39. data/dsl/cache.rb +118 -0
  40. data/dsl/concerns/fennel.rb +13 -0
  41. data/dsl/concerns/packages.rb +37 -0
  42. data/dsl/errors.rb +28 -0
  43. data/dsl/fennel.rb +47 -0
  44. data/dsl/global.rb +42 -0
  45. data/dsl/state.rb +104 -0
  46. data/dsl/sweet_moon.rb +53 -0
  47. data/logic/api.rb +17 -0
  48. data/logic/interpreter.rb +84 -0
  49. data/logic/interpreters/interpreter_50.rb +34 -0
  50. data/logic/interpreters/interpreter_51.rb +37 -0
  51. data/logic/interpreters/interpreter_54.rb +41 -0
  52. data/logic/io.rb +6 -0
  53. data/logic/options.rb +14 -0
  54. data/logic/shared_object.rb +52 -0
  55. data/logic/signature.rb +258 -0
  56. data/logic/signatures/ffi_types.rb +27 -0
  57. data/logic/signatures/signatures_322.rb +418 -0
  58. data/logic/signatures/signatures_401.rb +243 -0
  59. data/logic/signatures/signatures_503.rb +575 -0
  60. data/logic/signatures/signatures_514.rb +460 -0
  61. data/logic/signatures/signatures_542.rb +591 -0
  62. data/logic/spec.rb +13 -0
  63. data/logic/tables.rb +32 -0
  64. data/ports/in/dsl/sweet-moon/errors.rb +3 -0
  65. data/ports/in/dsl/sweet-moon.rb +1 -0
  66. data/ports/in/shell/sweet-moon +5 -0
  67. data/ports/in/shell.rb +21 -0
  68. data/ports/out/shell.rb +9 -0
  69. data/sweet-moon.gemspec +35 -0
  70. 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
+ ```