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
@@ -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