sweet-moon 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
@@ -0,0 +1,143 @@
1
+ require_relative '../components/injections'
2
+ require_relative '../components/api'
3
+ require_relative '../components/io'
4
+ require_relative '../dsl/errors'
5
+
6
+ require_relative '../logic/api'
7
+ require_relative '../logic/shared_object'
8
+
9
+ module Controller
10
+ API = {
11
+ handle!: ->(options) {
12
+ shared_objects = API[:elect_shared_objects!].(options[:shared_objects])
13
+
14
+ api = Component::API[:open!].(shared_objects)
15
+
16
+ api_reference = API[:elect_api_reference!].(
17
+ api.ffi_libraries, options[:api_reference]
18
+ )
19
+
20
+ injections = Component::Injections[api_reference[:version]]
21
+ injections = if injections
22
+ injections[:injections]
23
+ else
24
+ { macros: {},
25
+ callbacks: [] }
26
+ end
27
+
28
+ component = Component::API[:attach!].(api, api_reference, injections)
29
+
30
+ component[:meta] = {
31
+ options: options,
32
+ elected: {
33
+ shared_objects: shared_objects.map { |o| o[:path] },
34
+ api_reference: api_reference[:version]
35
+ }
36
+ }
37
+
38
+ component
39
+ },
40
+
41
+ elect_shared_objects!: ->(paths) {
42
+ candidates = []
43
+ shared_objects = []
44
+
45
+ if paths&.size&.positive?
46
+ candidates = paths
47
+
48
+ shared_objects = Component::IO[:reject_non_existent!].(paths).map do |path|
49
+ { path: path }
50
+ end
51
+ else
52
+ bases = %w[/usr/lib /usr/lib/* /usr/local/lib /opt/local/lib]
53
+
54
+ # XDG
55
+ if ENV['HOME']
56
+ bases << "#{ENV['HOME']}/.local/lib"
57
+ bases << "#{ENV['HOME']}/.local/lib/*"
58
+ end
59
+
60
+ bases.each do |base|
61
+ candidates.concat Component::IO[:find_by_pattern!].("#{base}/liblua*so*")
62
+ candidates.concat Component::IO[:find_by_pattern!].("#{base}/liblua*dylib*")
63
+ end
64
+
65
+ candidates = Component::IO[:reject_non_existent!].(candidates)
66
+
67
+ shared_objects = Logic::SharedObject[:choose].(candidates)
68
+ end
69
+
70
+ if shared_objects.size.zero?
71
+ raise SweetMoon::Errors::SweetMoonError,
72
+ "Lua shared object (liblua.so) not found: #{candidates}"
73
+ end
74
+
75
+ shared_objects
76
+ },
77
+
78
+ elect_api_reference!: ->(ffi_libraries, api_reference) {
79
+ availabe_candidates = Logic::API[:candidates].values
80
+
81
+ if api_reference
82
+ availabe_candidates = availabe_candidates.select do |candidate|
83
+ candidate[:version] == api_reference
84
+ end
85
+
86
+ if availabe_candidates.size.zero?
87
+ raise SweetMoon::Errors::SweetMoonError,
88
+ "API Reference #{api_reference} not available."
89
+ end
90
+ end
91
+
92
+ candidates = API[:calculate_compatibility!].(
93
+ availabe_candidates, ffi_libraries
94
+ )
95
+
96
+ candidates.sort_by do |_, functions|
97
+ functions[:found]
98
+ end.reverse
99
+
100
+ # TODO: This is the best strategy?
101
+ # version = candidates.sort_by do |_, functions|
102
+ # functions[:proportion]
103
+ # end.reverse.first.first
104
+
105
+ version = candidates.sort_by do |_, functions|
106
+ functions[:found] * functions[:proportion]
107
+ end.reverse.first.first
108
+
109
+ Logic::API[:candidates][version]
110
+ },
111
+
112
+ calculate_compatibility!: ->(availabe_candidates, ffi_libraries) {
113
+ candidates = {}
114
+
115
+ availabe_candidates.each do |candidate|
116
+ candidates[candidate[:version]] = {
117
+ found: 0,
118
+ expected: (
119
+ candidate[:signatures][:functions].size +
120
+ candidate[:signatures][:macros].size
121
+ )
122
+ }
123
+ candidate[:signatures][:functions].each do |signature|
124
+ function = signature[:ffi].first
125
+
126
+ ffi_libraries.each do |ffi_library|
127
+ if ffi_library.find_function(function.to_s)
128
+ candidates[candidate[:version]][:found] += 1
129
+ break
130
+ end
131
+ end
132
+ end
133
+
134
+ candidates[candidate[:version]][:proportion] = (
135
+ candidates[candidate[:version]][:found].to_f /
136
+ candidates[candidate[:version]][:expected]
137
+ )
138
+ end
139
+
140
+ candidates
141
+ }
142
+ }
143
+ end
@@ -0,0 +1,32 @@
1
+ require 'yaml'
2
+
3
+ require_relative '../../ports/out/shell'
4
+
5
+ module Controller
6
+ module CLI
7
+ CLI = {
8
+ handle!: ->(arguments, _fennel = false) {
9
+ options = {}
10
+
11
+ arguments = arguments.select do |argument|
12
+ if argument[/^-/]
13
+ options[argument] = true
14
+ false
15
+ else
16
+ true
17
+ end
18
+ end
19
+
20
+ if options['-i']
21
+ return Port::Out::Shell[:dispatch!].(YAML.dump({ TODO: true }))
22
+ end
23
+
24
+ input = arguments.first
25
+
26
+ output = options['-e'] ? "TODO eval #{input}" : "TODO file #{input}"
27
+
28
+ Port::Out::Shell[:dispatch!].(output) if options['-o']
29
+ }
30
+ }
31
+ end
32
+ end
@@ -0,0 +1,24 @@
1
+ require_relative '../../ports/out/shell'
2
+ require_relative '../../logic/spec'
3
+
4
+ module Controller
5
+ module CLI
6
+ Help = {
7
+ handle!: -> {
8
+ Port::Out::Shell[:dispatch!].(
9
+ "\n#{Logic::Spec[:command]} #{Logic::Spec[:version]}\n\n" \
10
+ "usage:\n" \
11
+ " #{Logic::Spec[:command]} version\n"\
12
+ " #{Logic::Spec[:command]} signatures /lua/source [output.rb]\n"\
13
+ " #{Logic::Spec[:command]} lua -i\n"\
14
+ " #{Logic::Spec[:command]} lua file.lua [-o]\n"\
15
+ " #{Logic::Spec[:command]} lua -e \"print(1 + 2);\" [-o]\n"\
16
+ " #{Logic::Spec[:command]} fennel -i\n"\
17
+ " #{Logic::Spec[:command]} fennel file.fnl [-o]\n"\
18
+ " #{Logic::Spec[:command]} fennel -e \"(+ 1 2)\" [-o]"\
19
+ "\n\n"
20
+ )
21
+ }
22
+ }
23
+ end
24
+ end
@@ -0,0 +1,179 @@
1
+ require 'pp'
2
+ require 'yaml'
3
+
4
+ require_relative '../../components/io'
5
+ require_relative '../../logic/io'
6
+ require_relative '../../logic/signature'
7
+ require_relative '../../ports/out/shell'
8
+
9
+ module Controller
10
+ module CLI
11
+ Signatures = {
12
+ handle!: ->(source_path, output_path = nil) {
13
+ functions = Signatures[:functions_from_shared_objects!].(source_path)
14
+ signatures, types = Signatures[:signatures_from_headers!].(source_path)
15
+
16
+ result = Signatures[:match_functions_and_signatures!].(
17
+ functions, signatures, types
18
+ )
19
+
20
+ primitives = Signatures[:collect_primitives!].(result[:attachables])
21
+
22
+ Signatures[:print_samples!].(result, functions, signatures, primitives)
23
+
24
+ return if output_path.nil?
25
+
26
+ output = {
27
+ functions: result[:attachables].values.map do |a|
28
+ { source: a[:source], ffi: a[:ffi] }
29
+ end,
30
+ macros: result[:macros].values.map do |a|
31
+ { source: a[:source], name: a[:name],
32
+ input: a[:input].map { |i| i[:name] } }
33
+ end
34
+ }
35
+
36
+ output[:functions] = output[:functions].sort_by { |a| a[:source] }
37
+ output[:macros] = output[:macros].sort_by { |a| a[:source] }
38
+
39
+ Component::IO[:write!].(output_path, output.pretty_inspect)
40
+
41
+ Port::Out::Shell[:dispatch!].(
42
+ "\n > attachables dumped to: \"#{output_path}\""
43
+ )
44
+ },
45
+
46
+ print_samples!: ->(result, functions, signatures, primitives) {
47
+ Port::Out::Shell[:dispatch!].("#{functions.size} functions:\n")
48
+ 2.times { Port::Out::Shell[:dispatch!].(" #{functions.sample}") }
49
+
50
+ Port::Out::Shell[:dispatch!].("\n#{signatures.size} signatures:\n")
51
+ 2.times { Port::Out::Shell[:dispatch!].(" #{signatures.sample}") }
52
+
53
+ Port::Out::Shell[:dispatch!].("\n#{result[:attachables].size} attachables:\n")
54
+ 2.times do
55
+ Port::Out::Shell[:dispatch!].(
56
+ " #{result[:attachables].values.sample[:name]}"
57
+ )
58
+ end
59
+
60
+ Port::Out::Shell[:dispatch!].("\n#{result[:macros].size} macros:\n")
61
+ 2.times do
62
+ Port::Out::Shell[:dispatch!].(" #{result[:macros].values.sample[:name]}")
63
+ end
64
+
65
+ Port::Out::Shell[:dispatch!].("\n#{result[:missing].size} missing:\n")
66
+ (result[:missing].size < 3 ? 1 : 3).times do
67
+ Port::Out::Shell[:dispatch!].(" #{result[:missing].sample}")
68
+ end
69
+
70
+ Port::Out::Shell[:dispatch!].("\n#{primitives.size} primitives:\n")
71
+ Port::Out::Shell[:dispatch!].(
72
+ YAML.dump(primitives).lines[1..-1].map { |line| " #{line}" }
73
+ )
74
+ },
75
+
76
+ collect_primitives!: ->(attachables) {
77
+ types = {}
78
+
79
+ attachables.each_value do |attachable|
80
+ unless attachable[:output][:pointer]
81
+ if types[attachable[:output][:primitive]].nil?
82
+ types[attachable[:output][:primitive]] = 0
83
+ end
84
+ types[attachable[:output][:primitive]] += 1
85
+ end
86
+
87
+ attachable[:input].each do |input|
88
+ unless input[:pointer]
89
+ types[input[:primitive]] = 0 if types[input[:primitive]].nil?
90
+ types[input[:primitive]] += 1
91
+ end
92
+ end
93
+ end
94
+
95
+ types
96
+ },
97
+
98
+ match_functions_and_signatures!: ->(functions, signatures, types) {
99
+ exists = {}
100
+
101
+ functions.each { |function| exists[function] = true }
102
+
103
+ attachables = {}
104
+ macros = {}
105
+ missing = []
106
+
107
+ signatures = signatures.map do |signature|
108
+ Logic::Signature[:extract_from_source].(signature, types)
109
+ end.compact
110
+
111
+ signatures.each do |signature|
112
+ next unless signature[:name][/^lua/i] # TODO: Is it true for Lua < 5?
113
+
114
+ if signature[:macro]
115
+ macros[signature[:name].to_sym] = signature
116
+ elsif exists[signature[:name]]
117
+ attachables[signature[:name].to_sym] = signature
118
+ end
119
+ end
120
+
121
+ attachables.values.map do |attachable|
122
+ attachable[:ffi] = Logic::Signature[:to_ffi].(attachable)
123
+ end
124
+
125
+ functions.each do |function|
126
+ missing << function unless attachables[function.to_sym]
127
+ end
128
+
129
+ { attachables: attachables, macros: macros, missing: missing }
130
+ },
131
+
132
+ functions_from_shared_objects!: ->(path) {
133
+ shared_objects = Component::IO[:find_recursively!].(
134
+ path
135
+ ).select do |candidate|
136
+ Logic::IO[:extension].(candidate) == '.so'
137
+ end
138
+
139
+ puts shared_objects
140
+
141
+ functions = []
142
+
143
+ command = 'nm --demangle --dynamic --defined-only --extern-only'
144
+
145
+ shared_objects.each do |shared_object_path|
146
+ functions.concat(
147
+ Logic::Signature[:extract_from_nm].(`#{command} #{shared_object_path}`)
148
+ )
149
+ end
150
+
151
+ functions.uniq.sort
152
+ },
153
+
154
+ signatures_from_headers!: ->(path) {
155
+ headers = Component::IO[:find_recursively!].(path).select do |candidate|
156
+ Logic::IO[:extension].(candidate) == '.h'
157
+ end
158
+
159
+ sources = headers.map { |header_path| Component::IO[:read!].(header_path) }
160
+
161
+ types = {}
162
+
163
+ sources.each do |source|
164
+ types = Logic::Signature[:extract_types_from_header].(source, types)
165
+ end
166
+
167
+ signatures = []
168
+
169
+ sources.each do |source|
170
+ signatures.concat(
171
+ Logic::Signature[:extract_from_header].(source)
172
+ )
173
+ end
174
+
175
+ [signatures.uniq.sort, types]
176
+ }
177
+ }
178
+ end
179
+ end
@@ -0,0 +1,14 @@
1
+ require_relative '../../ports/out/shell'
2
+ require_relative '../../logic/spec'
3
+
4
+ module Controller
5
+ module CLI
6
+ Version = {
7
+ handle!: -> {
8
+ Port::Out::Shell[:dispatch!].(
9
+ "\n#{Logic::Spec[:command]} #{Logic::Spec[:version]}\n\n"
10
+ )
11
+ }
12
+ }
13
+ end
14
+ end
@@ -0,0 +1,68 @@
1
+ require_relative '../components/interpreters'
2
+ require_relative '../logic/interpreter'
3
+ require_relative 'state'
4
+ require_relative '../dsl/errors'
5
+
6
+ module Controller
7
+ Interpreter = {
8
+ handle!: ->(api, options = {}) {
9
+ component = {}
10
+
11
+ component[:interpreter] = Interpreter[:elect_interpreter!].(
12
+ api, options
13
+ )
14
+
15
+ component = Interpreter[:build_meta!].(component, options)
16
+ component = Interpreter[:build_runtime!].(api, component, options)
17
+
18
+ component
19
+ },
20
+
21
+ build_meta!: ->(component, options) {
22
+ component[:meta] = {
23
+ options: options,
24
+ elected: {
25
+ interpreter: component[:interpreter][:version]
26
+ },
27
+ runtime: {}
28
+ }
29
+
30
+ component
31
+ },
32
+
33
+ build_runtime!: ->(api, component, _options) {
34
+ state = State[:create!].(api[:api], component[:interpreter])[:state]
35
+
36
+ result = State[:eval!].(
37
+ api[:api], component[:interpreter], state, 'return _VERSION;'
38
+ )
39
+
40
+ is_jit = State[:get!].(
41
+ api[:api], component[:interpreter], state, 'jit', 'version'
42
+ )[:output]
43
+
44
+ State[:destroy!].(api[:api], component[:interpreter], state)
45
+
46
+ component[:meta][:runtime][:lua] = if is_jit
47
+ "#{is_jit} (#{result[:output]})"
48
+ else
49
+ result[:output]
50
+ end
51
+
52
+ component
53
+ },
54
+
55
+ elect_interpreter!: ->(api, options) {
56
+ result = Logic::Interpreter[:elect].(
57
+ api[:signatures], api[:meta][:elected][:api_reference], options
58
+ )
59
+
60
+ unless result[:compatible]
61
+ raise SweetMoon::Errors::SweetMoonError,
62
+ result[:error]
63
+ end
64
+
65
+ return Component::Interpreters[result[:version]][:interpreter]
66
+ }
67
+ }
68
+ end
@@ -0,0 +1,74 @@
1
+ require_relative '../dsl/errors'
2
+
3
+ module Controller
4
+ State = {
5
+ create!: ->(api, interpreter) {
6
+ result = State[:_check!].(interpreter[:create_state!].(api))
7
+ result = State[:_check!].(
8
+ interpreter[:open_standard_libraries!].(api, result[:state])
9
+ )
10
+
11
+ { state: result[:state] }
12
+ },
13
+
14
+ eval!: ->(api, interpreter, state, input, outputs = 1) {
15
+ result = State[:_check!].(interpreter[:push_chunk!].(api, state, input))
16
+
17
+ State[:_call_and_read!].(api, interpreter, result[:state], outputs)
18
+ },
19
+
20
+ load!: ->(api, interpreter, state, path, outputs = 1) {
21
+ result = State[:_check!].(
22
+ interpreter[:load_file_and_push_chunck!].(api, state, path)
23
+ )
24
+
25
+ State[:_call_and_read!].(api, interpreter, result[:state], outputs)
26
+ },
27
+
28
+ get!: ->(api, interpreter, state, variable, key = nil) {
29
+ result = State[:_check!].(
30
+ interpreter[:get_variable_and_push!].(api, state, variable, key)
31
+ )
32
+
33
+ result = State[:_check!].(interpreter[:read_and_pop!].(
34
+ api, result[:state], -1, extra_pop: !key.nil?
35
+ ))
36
+
37
+ { state: result[:state], output: result[:output] }
38
+ },
39
+
40
+ set!: ->(api, interpreter, state, variable, value) {
41
+ result = State[:_check!].(interpreter[:push_value!].(api, state, value))
42
+
43
+ result = State[:_check!].(
44
+ interpreter[:pop_and_set_as!].(api, result[:state], variable.to_s)
45
+ )
46
+
47
+ { state: result[:state], output: result[:output] }
48
+ },
49
+
50
+ destroy!: ->(api, interpreter, state) {
51
+ State[:_check!].(interpreter[:destroy_state!].(api, state))
52
+
53
+ { state: nil }
54
+ },
55
+
56
+ _call_and_read!: ->(api, interpreter, state, outputs = 1) {
57
+ result = State[:_check!].(interpreter[:call!].(api, state, 0, outputs))
58
+ result = State[:_check!].(interpreter[:read_all!].(api, result[:state]))
59
+
60
+ { state: result[:state],
61
+ output: outputs <= 1 ? result[:output].first : result[:output] }
62
+ },
63
+
64
+ _check!: ->(result) {
65
+ if result[:error]
66
+ raise SweetMoon::Errors::SweetMoonErrorHelper.for(
67
+ result[:error][:status]
68
+ ), result[:error][:value]
69
+ end
70
+
71
+ result
72
+ }
73
+ }
74
+ end
data/dsl/api.rb ADDED
@@ -0,0 +1,31 @@
1
+ module DSL
2
+ class Api
3
+ attr_reader :functions, :meta
4
+
5
+ def initialize(component)
6
+ @component = component
7
+
8
+ @functions = @component[:signatures].keys
9
+
10
+ @meta = Struct.new(
11
+ *@component[:meta][:elected].keys
12
+ ).new(*@component[:meta][:elected].values)
13
+
14
+ extend @component[:api]
15
+ end
16
+
17
+ def signature_for(function)
18
+ @component[:signatures][function.to_sym]
19
+ end
20
+
21
+ def inspect
22
+ output = "#<#{self.class}:0x#{format('%016x', object_id)}"
23
+
24
+ variables = ['@meta'].map do |struct_name|
25
+ "#{struct_name}=#{instance_variable_get(struct_name).inspect}"
26
+ end
27
+
28
+ "#{output} #{variables.join(' ')}>"
29
+ end
30
+ end
31
+ end
data/dsl/cache.rb ADDED
@@ -0,0 +1,118 @@
1
+ require 'singleton'
2
+
3
+ require_relative '../controllers/api'
4
+ require_relative '../controllers/interpreter'
5
+ require_relative '../controllers/state'
6
+
7
+ require_relative 'api'
8
+ require_relative 'sweet_moon'
9
+
10
+ class Cache
11
+ include Singleton
12
+
13
+ def clear_global!
14
+ @cache[:global_state]&._unsafely_destroy
15
+
16
+ @cache.each_key { |key| @cache.delete(key) if key[/^global/] }
17
+ end
18
+
19
+ def keys
20
+ @cache.keys
21
+ end
22
+
23
+ def initialize
24
+ @cache = {}
25
+ end
26
+
27
+ def global_state(options = {}, recreate: false)
28
+ key = :global_state
29
+
30
+ clear_global_state_cache!(options) if recreate
31
+
32
+ return @cache[key] if @cache[key]
33
+
34
+ api = Cache.instance.api_module(options, :global_api_module)
35
+ interpreter = Cache.instance.interpreter_module(
36
+ api, options, :global_interpreter_module
37
+ )
38
+
39
+ @cache[key] = DSL::State.new(api, interpreter, Controller::State, options)
40
+ @cache[key].instance_eval('undef :destroy', __FILE__, __LINE__)
41
+
42
+ @cache[key]
43
+ end
44
+
45
+ def global_api(options = {}, recreate: false)
46
+ key = :global_api
47
+
48
+ clear_global_api_cache! if recreate
49
+
50
+ @cache[key] ||= api(options, :global_api, :global_api_module)
51
+ end
52
+
53
+ def api(options = {}, key = nil, api_module_key = nil)
54
+ key ||= cache_key_for(:api, options, %i[shared_objects api_reference])
55
+ @cache[key] ||= DSL::Api.new(api_module(options, api_module_key))
56
+ end
57
+
58
+ def api_module(options = {}, key = nil)
59
+ key ||= cache_key_for(:api_module, options, %i[shared_objects api_reference])
60
+ @cache[key] ||= Controller::API[:handle!].(options)
61
+ end
62
+
63
+ def interpreter_module(api, options = {}, key = nil)
64
+ key ||= cache_key_for(
65
+ :interpreter_module,
66
+ { shared_objects: api[:meta][:elected][:shared_objects],
67
+ api_reference: api[:meta][:elected][:api_reference],
68
+ interpreter: options[:interpreter], package_path: options[:package_path],
69
+ package_cpath: options[:package_cpath] },
70
+ %i[shared_objects api_reference interpreter package_path package_cpath]
71
+ )
72
+
73
+ @cache[key] ||= Controller::Interpreter[:handle!].(api, options)
74
+ end
75
+
76
+ private
77
+
78
+ def clear_global_api_cache!
79
+ @cache[:global_state]&._unsafely_destroy
80
+
81
+ %i[global_api global_api_module
82
+ global_interpreter_module
83
+ global_state].each do |key|
84
+ @cache.delete(key)
85
+ end
86
+ end
87
+
88
+ def clear_global_state_cache!(options)
89
+ @cache[:global_state]&._unsafely_destroy
90
+
91
+ %i[global_interpreter_module global_state].each do |key|
92
+ @cache.delete(key)
93
+ end
94
+
95
+ return unless options.key?(:shared_objects) || options.key?(:api_reference)
96
+
97
+ %i[global_api global_api_module].each do |key|
98
+ @cache.delete(key)
99
+ end
100
+
101
+ global_api(options, recreate: true)
102
+ end
103
+
104
+ def cache_key_for(prefix, options = {}, relevant_keys = [])
105
+ values = [prefix]
106
+
107
+ relevant_keys.each do |key|
108
+ value = options[key] || options[key.to_s]
109
+ if value.is_a?(Array)
110
+ values << value.sort.join(':')
111
+ elsif value
112
+ values << value
113
+ end
114
+ end
115
+
116
+ values.join('|')
117
+ end
118
+ end
@@ -0,0 +1,13 @@
1
+ require_relative '../fennel'
2
+
3
+ module DSL
4
+ module Concerns
5
+ module Fennel
6
+ def fennel
7
+ _ensure_min_version!('Fennel', '5.1', '2')
8
+
9
+ @fennel ||= DSL::Fennel.new(self)
10
+ end
11
+ end
12
+ end
13
+ end