hegeltest 0.3.1-aarch64-linux → 0.4.0-aarch64-linux
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +262 -0
- data/README.ja.md +408 -0
- data/README.md +209 -103
- data/Rakefile +6 -3
- data/docs/README.md +11 -0
- data/docs/adr/0025-copy-each-string-input-into-a-hegel-string-t.md +61 -0
- data/docs/adr/0026-select-the-engine-release-repository-by-version.md +47 -0
- data/docs/adr/0027-report-each-runs-test-location-from-the-callers-frame.md +63 -0
- data/docs/adr/0028-expose-settings-profiles-as-a-keyword-and-module-functions.md +91 -0
- data/docs/adr/0029-keep-notes-local-and-defer-run-error-kinds.md +72 -0
- data/docs/adr/0030-represent-a-duration-as-integer-nanoseconds.md +48 -0
- data/docs/adr/0031-take-the-alphabet-options-from-hegel-rust.md +52 -0
- data/docs/adr/0032-suppress-span-closes-while-an-abandoned-recursion-attempt-unwinds.md +147 -0
- data/docs/adr/0033-cache-compiled-grammars-for-the-length-of-a-run.md +80 -0
- data/docs/adr/0034-repeat-a-test-body-with-a-ruby-side-indent.md +120 -0
- data/docs/adr/0035-defer-concurrent-state-machines-and-the-engine-printer.md +136 -0
- data/docs/architecture.md +33 -11
- data/lib/hegel/errors.rb +24 -0
- data/lib/hegel/generator.rb +52 -25
- data/lib/hegel/generators.rb +715 -45
- data/lib/hegel/grammar_cache.rb +36 -0
- data/lib/hegel/lib_hegel/real.rb +455 -120
- data/lib/hegel/lib_hegel.rb +33 -8
- data/lib/hegel/libhegel/libhegel-linux-arm64.so +0 -0
- data/lib/hegel/libhegel_version.rb +1 -1
- data/lib/hegel/locate.rb +10 -10
- data/lib/hegel/report.rb +13 -10
- data/lib/hegel/runner.rb +61 -27
- data/lib/hegel/settings.rb +92 -16
- data/lib/hegel/state_machine.rb +17 -4
- data/lib/hegel/stateful/pool.rb +52 -0
- data/lib/hegel/stateful.rb +2 -2
- data/lib/hegel/syntax/methods.rb +99 -15
- data/lib/hegel/test_case.rb +256 -35
- data/lib/hegel/test_location.rb +49 -0
- data/lib/hegel/version.rb +1 -1
- data/lib/hegel.rb +21 -5
- data/lib/tasks/libhegel.rake +17 -5
- data/lib/tasks/platform_gems.rake +3 -3
- data/sig/hegel.rbs +296 -43
- data/skills/hegel-ruby/references/ruby/reference.md +465 -43
- metadata +15 -1
data/lib/hegel/lib_hegel/real.rb
CHANGED
|
@@ -18,7 +18,7 @@ module Hegel
|
|
|
18
18
|
# symbol.
|
|
19
19
|
class Real
|
|
20
20
|
# hegel_date_t / hegel_time_t / hegel_datetime_t, transcribed field
|
|
21
|
-
# for field from
|
|
21
|
+
# for field from libhegel/include/hegel.h. FFI::Struct computes each
|
|
22
22
|
# field's offset and the struct's own alignment padding from this
|
|
23
23
|
# layout -- the same C-ABI rules the compiled library was built
|
|
24
24
|
# against -- so there is no hand-computed byte offset here to drift
|
|
@@ -40,17 +40,19 @@ module Hegel
|
|
|
40
40
|
layout :date, DateStruct, :time, TimeStruct
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
# and
|
|
49
|
-
|
|
43
|
+
# hegel_string_t (`{ const char *data; size_t len; }`), and
|
|
44
|
+
# hegel_generate_bytes_result_t (`{ uint8_t *data; size_t len; }`),
|
|
45
|
+
# from libhegel/include/hegel.h: the same layout under two names. Since
|
|
46
|
+
# libhegel 0.46 every string crosses the ABI as a hegel_string_t: by
|
|
47
|
+
# value for an ordinary input, through a nullable pointer for an
|
|
48
|
+
# optional input, and as a struct the engine writes for an output.
|
|
49
|
+
# hegel_owned_string_t, which #generate_string reads, is a typedef of
|
|
50
|
+
# hegel_string_t. One Ruby struct mirrors all of them.
|
|
51
|
+
class StringStruct < FFI::Struct
|
|
50
52
|
layout :data, :pointer, :len, :size_t
|
|
51
53
|
end
|
|
52
54
|
|
|
53
|
-
private_constant :DateStruct, :TimeStruct, :DatetimeStruct, :
|
|
55
|
+
private_constant :DateStruct, :TimeStruct, :DatetimeStruct, :StringStruct
|
|
54
56
|
|
|
55
57
|
# Opens +path+ (default: Hegel::Locate.resolve) and binds the
|
|
56
58
|
# functions this boundary calls. Immediately after, opens a context
|
|
@@ -75,11 +77,18 @@ module Hegel
|
|
|
75
77
|
|
|
76
78
|
@hegel_context_new_fn = bind("hegel_context_new", [], :pointer)
|
|
77
79
|
@hegel_context_free_fn = bind("hegel_context_free", [:pointer], :int32)
|
|
78
|
-
@hegel_context_last_error_fn = bind("hegel_context_last_error", [:pointer],
|
|
80
|
+
@hegel_context_last_error_fn = bind("hegel_context_last_error", [:pointer], StringStruct.by_value)
|
|
79
81
|
@hegel_version_fn = bind("hegel_version", [:pointer, :pointer], :int32)
|
|
80
82
|
|
|
81
83
|
@hegel_settings_new_fn = bind("hegel_settings_new", [:pointer, :pointer], :int32)
|
|
82
84
|
@hegel_settings_free_fn = bind("hegel_settings_free", [:pointer, :pointer], :int32)
|
|
85
|
+
@hegel_settings_new_for_profile_fn = bind(
|
|
86
|
+
"hegel_settings_new_for_profile", [:pointer, StringStruct.by_value, :pointer], :int32
|
|
87
|
+
)
|
|
88
|
+
@hegel_settings_register_profile_fn = bind(
|
|
89
|
+
"hegel_settings_register_profile", [:pointer, StringStruct.by_value, :pointer], :int32
|
|
90
|
+
)
|
|
91
|
+
@hegel_set_default_profile_fn = bind("hegel_set_default_profile", [:pointer, StringStruct.by_value], :int32)
|
|
83
92
|
@hegel_settings_set_test_cases_fn = bind(
|
|
84
93
|
"hegel_settings_set_test_cases", [:pointer, :pointer, :uint64], :int32
|
|
85
94
|
)
|
|
@@ -93,13 +102,13 @@ module Hegel
|
|
|
93
102
|
"hegel_settings_set_derandomize", [:pointer, :pointer, :bool], :int32
|
|
94
103
|
)
|
|
95
104
|
@hegel_settings_set_database_fn = bind(
|
|
96
|
-
"hegel_settings_set_database", [:pointer, :pointer, :
|
|
105
|
+
"hegel_settings_set_database", [:pointer, :pointer, :pointer], :int32
|
|
97
106
|
)
|
|
98
107
|
@hegel_settings_set_report_multiple_failures_fn = bind(
|
|
99
108
|
"hegel_settings_set_report_multiple_failures", [:pointer, :pointer, :bool], :int32
|
|
100
109
|
)
|
|
101
110
|
@hegel_settings_set_database_key_fn = bind(
|
|
102
|
-
"hegel_settings_set_database_key", [:pointer, :pointer, :
|
|
111
|
+
"hegel_settings_set_database_key", [:pointer, :pointer, :pointer], :int32
|
|
103
112
|
)
|
|
104
113
|
@hegel_settings_set_phases_fn = bind(
|
|
105
114
|
"hegel_settings_set_phases", [:pointer, :pointer, :uint32], :int32
|
|
@@ -107,12 +116,30 @@ module Hegel
|
|
|
107
116
|
@hegel_settings_set_suppress_health_check_fn = bind(
|
|
108
117
|
"hegel_settings_set_suppress_health_check", [:pointer, :pointer, :uint32], :int32
|
|
109
118
|
)
|
|
119
|
+
@hegel_settings_set_stop_on_first_failure_fn = bind(
|
|
120
|
+
"hegel_settings_set_stop_on_first_failure", [:pointer, :pointer, :bool], :int32
|
|
121
|
+
)
|
|
122
|
+
@hegel_settings_set_print_blob_fn = bind("hegel_settings_set_print_blob", [:pointer, :pointer, :bool], :int32)
|
|
123
|
+
@hegel_settings_get_print_blob_fn = bind(
|
|
124
|
+
"hegel_settings_get_print_blob", [:pointer, :pointer, :pointer], :int32
|
|
125
|
+
)
|
|
126
|
+
@hegel_settings_set_show_statistics_fn = bind(
|
|
127
|
+
"hegel_settings_set_show_statistics", [:pointer, :pointer, :bool], :int32
|
|
128
|
+
)
|
|
129
|
+
@hegel_settings_set_nondeterminism_strictness_fn = bind(
|
|
130
|
+
"hegel_settings_set_nondeterminism_strictness", [:pointer, :pointer, :uint32], :int32
|
|
131
|
+
)
|
|
132
|
+
@hegel_settings_set_backend_fn = bind("hegel_settings_set_backend", [:pointer, :pointer, :uint32], :int32)
|
|
133
|
+
@hegel_settings_set_test_location_fn = bind(
|
|
134
|
+
"hegel_settings_set_test_location",
|
|
135
|
+
[:pointer, :pointer, StringStruct.by_value, :uint32, StringStruct.by_value, StringStruct.by_value], :int32
|
|
136
|
+
)
|
|
110
137
|
|
|
111
138
|
@hegel_run_start_fn = bind(
|
|
112
139
|
"hegel_run_start", [:pointer, :pointer, :pointer, :pointer, :pointer], :int32
|
|
113
140
|
)
|
|
114
141
|
@hegel_run_start_blob_fn = bind(
|
|
115
|
-
"hegel_run_start_blob", [:pointer, :pointer,
|
|
142
|
+
"hegel_run_start_blob", [:pointer, :pointer, StringStruct.by_value, :pointer, :pointer, :pointer], :int32
|
|
116
143
|
)
|
|
117
144
|
@hegel_next_test_case_fn = bind("hegel_next_test_case", [:pointer, :pointer, :pointer], :int32)
|
|
118
145
|
@hegel_test_case_should_capture_fn = bind(
|
|
@@ -120,8 +147,12 @@ module Hegel
|
|
|
120
147
|
)
|
|
121
148
|
@hegel_run_free_fn = bind("hegel_run_free", [:pointer, :pointer], :int32)
|
|
122
149
|
@hegel_test_case_free_fn = bind("hegel_test_case_free", [:pointer, :pointer], :int32)
|
|
123
|
-
@hegel_mark_complete_fn = bind("hegel_mark_complete", [:pointer, :pointer, :uint32, :
|
|
124
|
-
@hegel_target_fn = bind("hegel_target", [:pointer, :pointer, :double,
|
|
150
|
+
@hegel_mark_complete_fn = bind("hegel_mark_complete", [:pointer, :pointer, :uint32, :pointer], :int32)
|
|
151
|
+
@hegel_target_fn = bind("hegel_target", [:pointer, :pointer, :double, StringStruct.by_value], :int32)
|
|
152
|
+
@hegel_event_fn = bind("hegel_event", [:pointer, :pointer, StringStruct.by_value], :int32)
|
|
153
|
+
@hegel_event_value_fn = bind(
|
|
154
|
+
"hegel_event_value", [:pointer, :pointer, :double, StringStruct.by_value], :int32
|
|
155
|
+
)
|
|
125
156
|
|
|
126
157
|
@hegel_run_result_fn = bind("hegel_run_result", [:pointer, :pointer, :pointer], :int32)
|
|
127
158
|
@hegel_run_result_free_fn = bind("hegel_run_result_free", [:pointer, :pointer], :int32)
|
|
@@ -159,7 +190,7 @@ module Hegel
|
|
|
159
190
|
)
|
|
160
191
|
@hegel_collection_more_fn = bind("hegel_collection_more", [:pointer, :pointer, :pointer, :pointer], :int32)
|
|
161
192
|
@hegel_collection_reject_fn = bind(
|
|
162
|
-
"hegel_collection_reject", [:pointer, :pointer, :pointer, :
|
|
193
|
+
"hegel_collection_reject", [:pointer, :pointer, :pointer, :pointer], :int32
|
|
163
194
|
)
|
|
164
195
|
@hegel_collection_free_fn = bind("hegel_collection_free", [:pointer, :pointer], :int32)
|
|
165
196
|
|
|
@@ -168,8 +199,20 @@ module Hegel
|
|
|
168
199
|
@hegel_pool_generate_fn = bind(
|
|
169
200
|
"hegel_pool_generate", [:pointer, :pointer, :pointer, :bool, :pointer], :int32
|
|
170
201
|
)
|
|
202
|
+
@hegel_pool_restore_fn = bind("hegel_pool_restore", [:pointer, :pointer, :pointer, :int64], :int32)
|
|
171
203
|
@hegel_pool_free_fn = bind("hegel_pool_free", [:pointer, :pointer], :int32)
|
|
172
204
|
|
|
205
|
+
@hegel_new_recursion_fn = bind(
|
|
206
|
+
"hegel_new_recursion", [:pointer, :pointer, :uint64, :uint64, :pointer], :int32
|
|
207
|
+
)
|
|
208
|
+
@hegel_recursion_branch_fn = bind(
|
|
209
|
+
"hegel_recursion_branch", [:pointer, :pointer, :pointer, :uint64, :pointer], :int32
|
|
210
|
+
)
|
|
211
|
+
@hegel_recursion_leaf_fn = bind("hegel_recursion_leaf", [:pointer, :pointer, :pointer], :int32)
|
|
212
|
+
@hegel_recursion_retry_fn = bind("hegel_recursion_retry", [:pointer, :pointer, :pointer], :int32)
|
|
213
|
+
@hegel_recursion_finish_fn = bind("hegel_recursion_finish", [:pointer, :pointer, :pointer], :int32)
|
|
214
|
+
@hegel_recursion_free_fn = bind("hegel_recursion_free", [:pointer, :pointer], :int32)
|
|
215
|
+
|
|
173
216
|
@hegel_new_state_machine_fn = bind(
|
|
174
217
|
"hegel_new_state_machine",
|
|
175
218
|
[
|
|
@@ -196,14 +239,10 @@ module Hegel
|
|
|
196
239
|
[:pointer, :pointer, :uint32, :double, :double, :bool, :bool, :bool, :bool, :double, :pointer], :int32
|
|
197
240
|
)
|
|
198
241
|
|
|
199
|
-
# hegel_string_generator_text takes 14 arguments past ctx; the
|
|
200
|
-
# last 8 (categories_len through exclude_characters_len) are the
|
|
201
|
-
# category and explicit-character filter parameters this task
|
|
202
|
-
# does not wire up (see #string_generator_text).
|
|
203
242
|
@hegel_string_generator_text_fn = bind(
|
|
204
243
|
"hegel_string_generator_text",
|
|
205
|
-
[:pointer, :uint64, :uint64, :
|
|
206
|
-
:pointer, :size_t, :pointer, :size_t,
|
|
244
|
+
[:pointer, :uint64, :uint64, :pointer, :uint32, :uint32,
|
|
245
|
+
:pointer, :size_t, :pointer, :size_t, StringStruct.by_value, StringStruct.by_value,
|
|
207
246
|
:pointer],
|
|
208
247
|
:int32
|
|
209
248
|
)
|
|
@@ -211,9 +250,7 @@ module Hegel
|
|
|
211
250
|
@hegel_generate_string_fn = bind(
|
|
212
251
|
"hegel_generate_string", [:pointer, :pointer, :pointer, :pointer], :int32
|
|
213
252
|
)
|
|
214
|
-
@
|
|
215
|
-
"hegel_generate_string_result_free", [:pointer, :pointer], :int32
|
|
216
|
-
)
|
|
253
|
+
@hegel_owned_string_free_fn = bind("hegel_owned_string_free", [:pointer, :pointer], :int32)
|
|
217
254
|
|
|
218
255
|
@hegel_generate_bytes_fn = bind(
|
|
219
256
|
"hegel_generate_bytes", [:pointer, :pointer, :uint64, :uint64, :pointer], :int32
|
|
@@ -223,7 +260,7 @@ module Hegel
|
|
|
223
260
|
)
|
|
224
261
|
|
|
225
262
|
@hegel_string_generator_regex_fn = bind(
|
|
226
|
-
"hegel_string_generator_regex", [:pointer,
|
|
263
|
+
"hegel_string_generator_regex", [:pointer, StringStruct.by_value, :bool, :pointer, :pointer], :int32
|
|
227
264
|
)
|
|
228
265
|
@hegel_string_generator_email_fn = bind("hegel_string_generator_email", [:pointer, :pointer], :int32)
|
|
229
266
|
@hegel_string_generator_url_fn = bind("hegel_string_generator_url", [:pointer, :pointer], :int32)
|
|
@@ -248,6 +285,28 @@ module Hegel
|
|
|
248
285
|
[:pointer, :pointer, DatetimeStruct.by_value, DatetimeStruct.by_value, :pointer], :int32
|
|
249
286
|
)
|
|
250
287
|
|
|
288
|
+
@hegel_grammar_builder_new_fn = bind(
|
|
289
|
+
"hegel_grammar_builder_new", [:pointer, StringStruct.by_value, :pointer], :int32
|
|
290
|
+
)
|
|
291
|
+
@hegel_grammar_builder_file_fn = bind(
|
|
292
|
+
"hegel_grammar_builder_file", [:pointer, :pointer, :size_t, :pointer], :int32
|
|
293
|
+
)
|
|
294
|
+
@hegel_grammar_builder_provide_fn = bind(
|
|
295
|
+
"hegel_grammar_builder_provide", [:pointer, :pointer, :size_t, :pointer], :int32
|
|
296
|
+
)
|
|
297
|
+
@hegel_grammar_builder_free_fn = bind("hegel_grammar_builder_free", [:pointer, :pointer], :int32)
|
|
298
|
+
@hegel_grammar_new_fn = bind(
|
|
299
|
+
"hegel_grammar_new",
|
|
300
|
+
[:pointer, :pointer, :pointer, :uint64, :uint32, :pointer, :pointer, :size_t, :pointer], :int32
|
|
301
|
+
)
|
|
302
|
+
@hegel_grammar_placeholder_fn = bind(
|
|
303
|
+
"hegel_grammar_placeholder", [:pointer, :pointer, StringStruct.by_value, :pointer], :int32
|
|
304
|
+
)
|
|
305
|
+
@hegel_grammar_free_fn = bind("hegel_grammar_free", [:pointer, :pointer], :int32)
|
|
306
|
+
@hegel_generate_grammar_fn = bind(
|
|
307
|
+
"hegel_generate_grammar", [:pointer, :pointer, :pointer, :uint64, :pointer], :int32
|
|
308
|
+
)
|
|
309
|
+
|
|
251
310
|
LibHegel.with_context(self) { |ctx| LibHegel.warn_on_version_mismatch(self, ctx, io: io) }
|
|
252
311
|
end
|
|
253
312
|
|
|
@@ -273,18 +332,18 @@ module Hegel
|
|
|
273
332
|
# before returning, since the header documents that buffer as
|
|
274
333
|
# borrowed and invalidated by the next call taking the same context.
|
|
275
334
|
def context_last_error(ctx)
|
|
276
|
-
|
|
335
|
+
read_string(@hegel_context_last_error_fn.call(ctx))
|
|
277
336
|
end
|
|
278
337
|
|
|
279
338
|
# Returns the loaded engine's version string, or raises the
|
|
280
339
|
# exception LibHegel.check! translates this call's result code to.
|
|
281
340
|
def version(ctx)
|
|
282
|
-
out =
|
|
341
|
+
out = StringStruct.new
|
|
283
342
|
code = @hegel_version_fn.call(ctx, out)
|
|
284
343
|
# This call takes only ctx and this method's own out argument; neither
|
|
285
344
|
# makes the real engine return anything but HEGEL_OK.
|
|
286
345
|
LibHegel.check!(self, ctx, code) # mutineer:disable-line statement_removal
|
|
287
|
-
|
|
346
|
+
read_string(out)
|
|
288
347
|
end
|
|
289
348
|
|
|
290
349
|
# Returns a settings handle initialized from the engine's resolved
|
|
@@ -298,6 +357,31 @@ module Hegel
|
|
|
298
357
|
out.read_pointer
|
|
299
358
|
end
|
|
300
359
|
|
|
360
|
+
# Like #settings_new, but starts from the profile +name+, which still
|
|
361
|
+
# extends the default profile (except "base"). An unknown name returns
|
|
362
|
+
# HEGEL_E_INVALID_ARG.
|
|
363
|
+
def settings_new_for_profile(ctx, name)
|
|
364
|
+
out = FFI::MemoryPointer.new(:pointer)
|
|
365
|
+
code = @hegel_settings_new_for_profile_fn.call(ctx, string_view(name), out)
|
|
366
|
+
LibHegel.check!(self, ctx, code)
|
|
367
|
+
out.read_pointer
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
# Registers a snapshot of +s+ as the profile +name+ for the whole
|
|
371
|
+
# process. The caller keeps ownership of +s+.
|
|
372
|
+
def settings_register_profile(ctx, name, s)
|
|
373
|
+
code = @hegel_settings_register_profile_fn.call(ctx, string_view(name), s)
|
|
374
|
+
LibHegel.check!(self, ctx, code)
|
|
375
|
+
nil
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
# "" clears an earlier call.
|
|
379
|
+
def set_default_profile(ctx, name)
|
|
380
|
+
code = @hegel_set_default_profile_fn.call(ctx, string_view(name))
|
|
381
|
+
LibHegel.check!(self, ctx, code)
|
|
382
|
+
nil
|
|
383
|
+
end
|
|
384
|
+
|
|
301
385
|
# No-op when +s+ is nil, matching hegel_settings_free's documented
|
|
302
386
|
# no-op-on-NULL contract; not translated, for the same reason as
|
|
303
387
|
# #context_free: the header documents this call as always returning
|
|
@@ -334,11 +418,11 @@ module Hegel
|
|
|
334
418
|
end
|
|
335
419
|
|
|
336
420
|
# +database+ may be nil (libhegel's own default path) or a String,
|
|
337
|
-
# including "" to disable the database.
|
|
338
|
-
#
|
|
339
|
-
# to
|
|
421
|
+
# including "" to disable the database. The header takes an optional
|
|
422
|
+
# string here, so nil goes over as a NULL pointer and "" as a pointer
|
|
423
|
+
# to an empty string; see #optional_string.
|
|
340
424
|
def settings_set_database(ctx, s, database)
|
|
341
|
-
code = @hegel_settings_set_database_fn.call(ctx, s, database)
|
|
425
|
+
code = @hegel_settings_set_database_fn.call(ctx, s, optional_string(database))
|
|
342
426
|
LibHegel.check!(self, ctx, code)
|
|
343
427
|
nil
|
|
344
428
|
end
|
|
@@ -350,10 +434,18 @@ module Hegel
|
|
|
350
434
|
end
|
|
351
435
|
|
|
352
436
|
# +key+ may be nil, which the header documents as clearing the key
|
|
353
|
-
# (the default); nil
|
|
437
|
+
# (the default); nil goes over as NULL through #optional_string, the
|
|
354
438
|
# same as #settings_set_database's own nilable database argument.
|
|
355
439
|
def settings_set_database_key(ctx, s, key)
|
|
356
|
-
code = @hegel_settings_set_database_key_fn.call(ctx, s, key)
|
|
440
|
+
code = @hegel_settings_set_database_key_fn.call(ctx, s, optional_string(key))
|
|
441
|
+
LibHegel.check!(self, ctx, code)
|
|
442
|
+
nil
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
def settings_set_test_location(ctx, s, file, begin_line, class_name, function)
|
|
446
|
+
code = @hegel_settings_set_test_location_fn.call(
|
|
447
|
+
ctx, s, string_view(file), begin_line, string_view(class_name), string_view(function)
|
|
448
|
+
)
|
|
357
449
|
LibHegel.check!(self, ctx, code)
|
|
358
450
|
nil
|
|
359
451
|
end
|
|
@@ -373,6 +465,44 @@ module Hegel
|
|
|
373
465
|
nil
|
|
374
466
|
end
|
|
375
467
|
|
|
468
|
+
def settings_set_stop_on_first_failure(ctx, s, yes)
|
|
469
|
+
code = @hegel_settings_set_stop_on_first_failure_fn.call(ctx, s, yes)
|
|
470
|
+
LibHegel.check!(self, ctx, code)
|
|
471
|
+
nil
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
def settings_set_print_blob(ctx, s, yes)
|
|
475
|
+
code = @hegel_settings_set_print_blob_fn.call(ctx, s, yes)
|
|
476
|
+
LibHegel.check!(self, ctx, code)
|
|
477
|
+
nil
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
# libhegel never prints the blob; the caller decides from the resolved value.
|
|
481
|
+
def settings_get_print_blob(ctx, s)
|
|
482
|
+
out = FFI::MemoryPointer.new(:bool)
|
|
483
|
+
code = @hegel_settings_get_print_blob_fn.call(ctx, s, out)
|
|
484
|
+
LibHegel.check!(self, ctx, code)
|
|
485
|
+
out.read_uint8 != 0
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
def settings_set_show_statistics(ctx, s, yes)
|
|
489
|
+
code = @hegel_settings_set_show_statistics_fn.call(ctx, s, yes)
|
|
490
|
+
LibHegel.check!(self, ctx, code)
|
|
491
|
+
nil
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
def settings_set_nondeterminism_strictness(ctx, s, strictness)
|
|
495
|
+
code = @hegel_settings_set_nondeterminism_strictness_fn.call(ctx, s, strictness)
|
|
496
|
+
LibHegel.check!(self, ctx, code)
|
|
497
|
+
nil
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
def settings_set_backend(ctx, s, backend)
|
|
501
|
+
code = @hegel_settings_set_backend_fn.call(ctx, s, backend)
|
|
502
|
+
LibHegel.check!(self, ctx, code)
|
|
503
|
+
nil
|
|
504
|
+
end
|
|
505
|
+
|
|
376
506
|
# +settings+ can be freed by the caller as soon as this call returns:
|
|
377
507
|
# the header documents that hegel_run_start copies the settings it is
|
|
378
508
|
# given rather than borrowing them. callback and user_data are always
|
|
@@ -393,7 +523,7 @@ module Hegel
|
|
|
393
523
|
# version.
|
|
394
524
|
def run_start_blob(ctx, settings, blob)
|
|
395
525
|
out = FFI::MemoryPointer.new(:pointer)
|
|
396
|
-
code = @hegel_run_start_blob_fn.call(ctx, settings, blob, nil, nil, out)
|
|
526
|
+
code = @hegel_run_start_blob_fn.call(ctx, settings, string_view(blob), nil, nil, out)
|
|
397
527
|
LibHegel.check!(self, ctx, code)
|
|
398
528
|
out.read_pointer
|
|
399
529
|
end
|
|
@@ -442,9 +572,9 @@ module Hegel
|
|
|
442
572
|
# +origin+ must be non-nil only when +status+ is
|
|
443
573
|
# HEGEL_STATUS_INTERESTING, per the header; this layer neither builds
|
|
444
574
|
# nor validates that string, only passes through what the caller
|
|
445
|
-
# supplies. nil
|
|
575
|
+
# supplies. nil goes over as NULL through #optional_string.
|
|
446
576
|
def mark_complete(ctx, tc, status, origin)
|
|
447
|
-
code = @hegel_mark_complete_fn.call(ctx, tc, status, origin)
|
|
577
|
+
code = @hegel_mark_complete_fn.call(ctx, tc, status, optional_string(origin))
|
|
448
578
|
LibHegel.check!(self, ctx, code)
|
|
449
579
|
nil
|
|
450
580
|
end
|
|
@@ -456,7 +586,19 @@ module Hegel
|
|
|
456
586
|
# checked here, matching how this layer leaves every other
|
|
457
587
|
# argument-shape rule to the engine's own HEGEL_E_INVALID_ARG.
|
|
458
588
|
def target(ctx, tc, value, label)
|
|
459
|
-
code = @hegel_target_fn.call(ctx, tc, value, label)
|
|
589
|
+
code = @hegel_target_fn.call(ctx, tc, value, string_view(label))
|
|
590
|
+
LibHegel.check!(self, ctx, code)
|
|
591
|
+
nil
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
def event(ctx, tc, label)
|
|
595
|
+
code = @hegel_event_fn.call(ctx, tc, string_view(label))
|
|
596
|
+
LibHegel.check!(self, ctx, code)
|
|
597
|
+
nil
|
|
598
|
+
end
|
|
599
|
+
|
|
600
|
+
def event_value(ctx, tc, label, value)
|
|
601
|
+
code = @hegel_event_value_fn.call(ctx, tc, value, string_view(label))
|
|
460
602
|
LibHegel.check!(self, ctx, code)
|
|
461
603
|
nil
|
|
462
604
|
end
|
|
@@ -538,10 +680,10 @@ module Hegel
|
|
|
538
680
|
# returning, since it is owned by the failure and only valid until
|
|
539
681
|
# #failure_free.
|
|
540
682
|
def failure_origin(ctx, f)
|
|
541
|
-
out =
|
|
683
|
+
out = StringStruct.new
|
|
542
684
|
code = @hegel_failure_origin_fn.call(ctx, f, out)
|
|
543
685
|
LibHegel.check!(self, ctx, code)
|
|
544
|
-
|
|
686
|
+
read_string(out)
|
|
545
687
|
end
|
|
546
688
|
|
|
547
689
|
# Returns nil when libhegel produced no reproduction blob for this
|
|
@@ -659,12 +801,12 @@ module Hegel
|
|
|
659
801
|
end
|
|
660
802
|
|
|
661
803
|
# Tells libhegel the last element +collection+ produced is invalid.
|
|
662
|
-
# +why+ is an optional human-readable reason (nil
|
|
663
|
-
#
|
|
804
|
+
# +why+ is an optional human-readable reason (nil goes over as NULL
|
|
805
|
+
# through #optional_string, which the header allows); the header
|
|
664
806
|
# documents it as validated but reserved for future rejection
|
|
665
807
|
# diagnostics, unused today.
|
|
666
808
|
def collection_reject(ctx, tc, collection, why = nil)
|
|
667
|
-
code = @hegel_collection_reject_fn.call(ctx, tc, collection, why)
|
|
809
|
+
code = @hegel_collection_reject_fn.call(ctx, tc, collection, optional_string(why))
|
|
668
810
|
LibHegel.check!(self, ctx, code)
|
|
669
811
|
nil
|
|
670
812
|
end
|
|
@@ -721,6 +863,16 @@ module Hegel
|
|
|
721
863
|
out.read_int64
|
|
722
864
|
end
|
|
723
865
|
|
|
866
|
+
# Puts back +variable_id+, which a consuming #pool_generate removed, so
|
|
867
|
+
# it can be drawn again. libhegel never does this by itself: a consumed
|
|
868
|
+
# id stays consumed until it is restored. An id that was not consumed
|
|
869
|
+
# raises Hegel::Error (HEGEL_E_INVALID_ARG).
|
|
870
|
+
def pool_restore(ctx, tc, pool, variable_id)
|
|
871
|
+
code = @hegel_pool_restore_fn.call(ctx, tc, pool, variable_id)
|
|
872
|
+
LibHegel.check!(self, ctx, code)
|
|
873
|
+
nil
|
|
874
|
+
end
|
|
875
|
+
|
|
724
876
|
# No-op when +pool+ is nil, matching hegel_pool_free's documented
|
|
725
877
|
# no-op-on-NULL contract; not translated, for the same reason as
|
|
726
878
|
# #context_free.
|
|
@@ -731,23 +883,75 @@ module Hegel
|
|
|
731
883
|
nil
|
|
732
884
|
end
|
|
733
885
|
|
|
886
|
+
# Returns a caller-owned recursion handle, released separately with
|
|
887
|
+
# #recursion_free.
|
|
888
|
+
def new_recursion(ctx, tc, max_depth, max_leaves)
|
|
889
|
+
out = FFI::MemoryPointer.new(:pointer)
|
|
890
|
+
code = @hegel_new_recursion_fn.call(ctx, tc, max_depth, max_leaves, out)
|
|
891
|
+
LibHegel.check!(self, ctx, code)
|
|
892
|
+
out.read_pointer
|
|
893
|
+
end
|
|
894
|
+
|
|
895
|
+
# Returns true when the sub-value at +depth+ is a branch, false when
|
|
896
|
+
# it is a leaf.
|
|
897
|
+
def recursion_branch(ctx, tc, recursion, depth)
|
|
898
|
+
out = FFI::MemoryPointer.new(:bool)
|
|
899
|
+
code = @hegel_recursion_branch_fn.call(ctx, tc, recursion, depth, out)
|
|
900
|
+
LibHegel.check!(self, ctx, code)
|
|
901
|
+
out.read_uint8 != 0
|
|
902
|
+
end
|
|
903
|
+
|
|
904
|
+
# Returns false for HEGEL_E_RETRY: the attempt outgrew its leaf
|
|
905
|
+
# budget, and the caller unwinds it and calls #recursion_retry.
|
|
906
|
+
def recursion_leaf(ctx, tc, recursion)
|
|
907
|
+
code = @hegel_recursion_leaf_fn.call(ctx, tc, recursion)
|
|
908
|
+
retry_or_check!(ctx, code)
|
|
909
|
+
end
|
|
910
|
+
|
|
911
|
+
def recursion_retry(ctx, tc, recursion)
|
|
912
|
+
code = @hegel_recursion_retry_fn.call(ctx, tc, recursion)
|
|
913
|
+
LibHegel.check!(self, ctx, code)
|
|
914
|
+
nil
|
|
915
|
+
end
|
|
916
|
+
|
|
917
|
+
# Returns false for HEGEL_E_RETRY: the engine discarded the finished
|
|
918
|
+
# attempt, and the caller starts again from the root without calling
|
|
919
|
+
# #recursion_retry.
|
|
920
|
+
def recursion_finish(ctx, tc, recursion)
|
|
921
|
+
code = @hegel_recursion_finish_fn.call(ctx, tc, recursion)
|
|
922
|
+
retry_or_check!(ctx, code)
|
|
923
|
+
end
|
|
924
|
+
|
|
925
|
+
# No-op when +recursion+ is nil, matching hegel_recursion_free's
|
|
926
|
+
# documented no-op-on-NULL contract; not translated, for the same
|
|
927
|
+
# reason as #context_free.
|
|
928
|
+
def recursion_free(ctx, recursion)
|
|
929
|
+
# Removing this call only leaks native memory; Ruby has no way to
|
|
930
|
+
# observe the leak.
|
|
931
|
+
@hegel_recursion_free_fn.call(ctx, recursion) # mutineer:disable-line statement_removal
|
|
932
|
+
nil
|
|
933
|
+
end
|
|
934
|
+
|
|
734
935
|
# Returns a caller-owned state-machine handle, released separately
|
|
735
936
|
# with #state_machine_free. +rule_names+ and +invariant_names+ are
|
|
736
|
-
# each an Array of Ruby Strings, packed into the
|
|
737
|
-
#
|
|
738
|
-
#
|
|
937
|
+
# each an Array of Ruby Strings, packed into the hegel_string_t arrays
|
|
938
|
+
# hegel_new_state_machine expects by #pack_name_array; see that
|
|
939
|
+
# method's own comment for how and why. +always_check+ holds one
|
|
739
940
|
# flag per invariant name. Validating +rule_names+ as non-empty (the
|
|
740
941
|
# header's own requirement) is left to the caller, the same division
|
|
741
942
|
# of labor #new_collection leaves to the caller for its own
|
|
742
943
|
# min_size/max_size ordering.
|
|
743
944
|
#
|
|
744
|
-
#
|
|
745
|
-
#
|
|
746
|
-
#
|
|
945
|
+
# +rule_weights+ holds one number per rule name; the engine rejects one
|
|
946
|
+
# that is not finite and positive.
|
|
947
|
+
#
|
|
948
|
+
# The machine is always sequential: one concurrency group and a
|
|
949
|
+
# concurrency of exactly 1, so the engine draws no concurrency level
|
|
950
|
+
# and the drawn level needs no reading. These
|
|
747
951
|
# bindings drive the engine from one thread, and a concurrent machine
|
|
748
952
|
# needs one worker thread per drawn level. For the same reason the
|
|
749
953
|
# rule calls below pass worker index 0, the only worker there is.
|
|
750
|
-
def new_state_machine(ctx, tc, rule_names, invariant_names, always_check, step_count)
|
|
954
|
+
def new_state_machine(ctx, tc, rule_names, rule_weights, invariant_names, always_check, step_count)
|
|
751
955
|
out = FFI::MemoryPointer.new(:pointer)
|
|
752
956
|
out_concurrency = FFI::MemoryPointer.new(:int64)
|
|
753
957
|
# _rule_pointers / _invariant_pointers are unread past the call
|
|
@@ -762,9 +966,11 @@ module Hegel
|
|
|
762
966
|
# A fresh MemoryPointer is zero-filled, which puts every rule in
|
|
763
967
|
# group 0.
|
|
764
968
|
rule_groups = FFI::MemoryPointer.new(:int64, rule_names.size)
|
|
969
|
+
weights = FFI::MemoryPointer.new(:double, rule_weights.size)
|
|
970
|
+
weights.write_array_of_double(rule_weights.map(&:to_f))
|
|
765
971
|
|
|
766
972
|
code = @hegel_new_state_machine_fn.call(
|
|
767
|
-
ctx, tc, rule_names_ptr, rule_groups,
|
|
973
|
+
ctx, tc, rule_names_ptr, rule_groups, weights, rule_names.size,
|
|
768
974
|
invariant_names_ptr, pack_flags(always_check), invariant_names.size,
|
|
769
975
|
1, 1, step_count, out, out_concurrency
|
|
770
976
|
)
|
|
@@ -840,17 +1046,20 @@ module Hegel
|
|
|
840
1046
|
|
|
841
1047
|
# Returns a caller-owned string generator handle, released
|
|
842
1048
|
# separately with #string_generator_free (or scoped with
|
|
843
|
-
# Hegel::TestCase#with_text_generator). +categories
|
|
844
|
-
# +exclude_categories
|
|
845
|
-
#
|
|
846
|
-
#
|
|
847
|
-
#
|
|
848
|
-
|
|
849
|
-
|
|
1049
|
+
# Hegel::TestCase#with_text_generator). +categories+ and
|
|
1050
|
+
# +exclude_categories+ are Arrays of Unicode general category names,
|
|
1051
|
+
# or nil for none: nil goes over as NULL, which is no restriction,
|
|
1052
|
+
# while an empty Array goes over as a non-NULL pointer with length
|
|
1053
|
+
# zero, which the header reads as an empty alphabet.
|
|
1054
|
+
def string_generator_text(ctx, min_size:, max_size:, codec: nil, min_codepoint: 0, max_codepoint: 0xFFFFFFFF,
|
|
1055
|
+
categories: nil, exclude_categories: nil, include_characters: "", exclude_characters: "")
|
|
850
1056
|
out = FFI::MemoryPointer.new(:pointer)
|
|
1057
|
+
category_array, category_slots = optional_name_array(categories)
|
|
1058
|
+
exclude_array, exclude_slots = optional_name_array(exclude_categories)
|
|
851
1059
|
code = @hegel_string_generator_text_fn.call(
|
|
852
|
-
ctx, min_size, max_size, codec, min_codepoint, max_codepoint,
|
|
853
|
-
|
|
1060
|
+
ctx, min_size, max_size, optional_string(codec), min_codepoint, max_codepoint,
|
|
1061
|
+
category_array, category_slots.size, exclude_array, exclude_slots.size,
|
|
1062
|
+
string_view(include_characters), string_view(exclude_characters),
|
|
854
1063
|
out
|
|
855
1064
|
)
|
|
856
1065
|
LibHegel.check!(self, ctx, code)
|
|
@@ -867,47 +1076,46 @@ module Hegel
|
|
|
867
1076
|
nil
|
|
868
1077
|
end
|
|
869
1078
|
|
|
870
|
-
# Returns a drawn String,
|
|
871
|
-
#
|
|
872
|
-
#
|
|
873
|
-
# NUL-terminated and possibly containing interior NUL bytes, since
|
|
874
|
-
# the drawn alphabet can include U+0000.
|
|
1079
|
+
# Returns a drawn String, labelled UTF-8 by #read_string. The draw can
|
|
1080
|
+
# hold interior NUL bytes, since the drawn alphabet can include
|
|
1081
|
+
# U+0000, so #read_string copies exactly the returned +len+ bytes.
|
|
875
1082
|
#
|
|
876
1083
|
# The native buffer is released in +ensure+ via
|
|
877
|
-
# #
|
|
1084
|
+
# #owned_string_free, called directly from here rather
|
|
878
1085
|
# than left to the caller: nothing outside this file may hold or
|
|
879
1086
|
# read the raw struct (ffi is confined to this file), so unlike
|
|
880
1087
|
# #new_collection or #string_generator_text, the freeable handle
|
|
881
1088
|
# here never leaves this method. Freeing is safe even when the draw
|
|
882
1089
|
# above raised: +out+ starts zero-filled (FFI::Struct.new allocates
|
|
883
1090
|
# cleared memory) and libhegel only writes into it on success, so
|
|
884
|
-
# the struct #
|
|
1091
|
+
# the struct #owned_string_free sees here is always
|
|
885
1092
|
# either a completed draw or the all-zero state the header
|
|
886
1093
|
# documents as already safe to free.
|
|
887
1094
|
def generate_string(ctx, tc, generator)
|
|
888
|
-
out =
|
|
1095
|
+
out = StringStruct.new
|
|
889
1096
|
code = @hegel_generate_string_fn.call(ctx, tc, generator, out)
|
|
890
1097
|
LibHegel.check!(self, ctx, code)
|
|
891
|
-
|
|
1098
|
+
read_string(out)
|
|
892
1099
|
ensure
|
|
893
|
-
|
|
1100
|
+
owned_string_free(ctx, out)
|
|
894
1101
|
end
|
|
895
1102
|
|
|
896
|
-
# No-op when +result+ is nil, matching
|
|
897
|
-
#
|
|
898
|
-
#
|
|
899
|
-
#
|
|
900
|
-
|
|
1103
|
+
# No-op when +result+ is nil, matching hegel_owned_string_free's
|
|
1104
|
+
# documented no-op-on-NULL contract (also safe on an already-freed,
|
|
1105
|
+
# zeroed struct); not translated, for the same reason as
|
|
1106
|
+
# #context_free. Before libhegel 0.46 this call was
|
|
1107
|
+
# hegel_generate_string_result_free.
|
|
1108
|
+
def owned_string_free(ctx, result)
|
|
901
1109
|
# Removing this call only leaks native memory; Ruby has no way to
|
|
902
1110
|
# observe the leak.
|
|
903
|
-
@
|
|
1111
|
+
@hegel_owned_string_free_fn.call(ctx, result) # mutineer:disable-line statement_removal
|
|
904
1112
|
nil
|
|
905
1113
|
end
|
|
906
1114
|
|
|
907
1115
|
# Returns drawn bytes as a String, read via +len+ the same way
|
|
908
1116
|
# #generate_string reads its own out-parameter: the header gives no
|
|
909
|
-
# NUL-termination guarantee for this buffer
|
|
910
|
-
#
|
|
1117
|
+
# NUL-termination guarantee for this buffer, so the length is what
|
|
1118
|
+
# makes the copy exact.
|
|
911
1119
|
#
|
|
912
1120
|
# Unlike #generate_string, the result is never force-encoded.
|
|
913
1121
|
# hegel_generate_bytes_result_t is documented as a byte buffer, not
|
|
@@ -923,7 +1131,7 @@ module Hegel
|
|
|
923
1131
|
# header documents hegel_generate_bytes_result_free as safe on an
|
|
924
1132
|
# already-freed (zeroed) struct too.
|
|
925
1133
|
def generate_bytes(ctx, tc, min_size, max_size)
|
|
926
|
-
out =
|
|
1134
|
+
out = StringStruct.new
|
|
927
1135
|
code = @hegel_generate_bytes_fn.call(ctx, tc, min_size, max_size, out)
|
|
928
1136
|
LibHegel.check!(self, ctx, code)
|
|
929
1137
|
out[:data].read_bytes(out[:len])
|
|
@@ -953,9 +1161,8 @@ module Hegel
|
|
|
953
1161
|
# a NUL character in it reaches the engine instead of ending it.
|
|
954
1162
|
def string_generator_regex(ctx, pattern, fullmatch, alphabet = nil)
|
|
955
1163
|
out = FFI::MemoryPointer.new(:pointer)
|
|
956
|
-
bytes = pattern.encode(Encoding::UTF_8)
|
|
957
1164
|
code = @hegel_string_generator_regex_fn.call(
|
|
958
|
-
ctx,
|
|
1165
|
+
ctx, string_view(pattern.encode(Encoding::UTF_8)), fullmatch, alphabet, out
|
|
959
1166
|
)
|
|
960
1167
|
LibHegel.check!(self, ctx, code)
|
|
961
1168
|
out.read_pointer
|
|
@@ -1088,8 +1295,93 @@ module Hegel
|
|
|
1088
1295
|
[read_date(out[:date]), read_time(out[:time])]
|
|
1089
1296
|
end
|
|
1090
1297
|
|
|
1298
|
+
# Returns a caller-owned grammar builder for +text+, a grammar in
|
|
1299
|
+
# Lark's syntax, released with #grammar_builder_free.
|
|
1300
|
+
def grammar_builder_new(ctx, text)
|
|
1301
|
+
out = FFI::MemoryPointer.new(:pointer)
|
|
1302
|
+
code = @hegel_grammar_builder_new_fn.call(ctx, string_view(text.encode(Encoding::UTF_8)), out)
|
|
1303
|
+
LibHegel.check!(self, ctx, code)
|
|
1304
|
+
out.read_pointer
|
|
1305
|
+
end
|
|
1306
|
+
|
|
1307
|
+
# The path of the +index+th file the grammar imports, or nil past the
|
|
1308
|
+
# end of the list. HEGEL_E_NOT_FOUND marks that end rather than an
|
|
1309
|
+
# error, so it is answered before LibHegel.check! sees it.
|
|
1310
|
+
def grammar_builder_file(ctx, builder, index)
|
|
1311
|
+
out = StringStruct.new
|
|
1312
|
+
code = @hegel_grammar_builder_file_fn.call(ctx, builder, index, out)
|
|
1313
|
+
return nil if code == HEGEL_E_NOT_FOUND
|
|
1314
|
+
|
|
1315
|
+
LibHegel.check!(self, ctx, code)
|
|
1316
|
+
read_string(out)
|
|
1317
|
+
end
|
|
1318
|
+
|
|
1319
|
+
# Supplies the +index+th file's +text+, or nil for a file that does
|
|
1320
|
+
# not exist, which the header keeps apart from an empty file.
|
|
1321
|
+
def grammar_builder_provide(ctx, builder, index, text)
|
|
1322
|
+
code = @hegel_grammar_builder_provide_fn.call(ctx, builder, index, optional_string(text))
|
|
1323
|
+
LibHegel.check!(self, ctx, code)
|
|
1324
|
+
nil
|
|
1325
|
+
end
|
|
1326
|
+
|
|
1327
|
+
def grammar_builder_free(ctx, builder)
|
|
1328
|
+
# Removing this call only leaks native memory; Ruby has no way to
|
|
1329
|
+
# observe the leak.
|
|
1330
|
+
@hegel_grammar_builder_free_fn.call(ctx, builder) # mutineer:disable-line statement_removal
|
|
1331
|
+
nil
|
|
1332
|
+
end
|
|
1333
|
+
|
|
1334
|
+
# Returns a caller-owned grammar compiled from +builder+, released
|
|
1335
|
+
# with #grammar_free. +start+ nil is the rule `start`; +alphabet+ is
|
|
1336
|
+
# an optional text generator handle. +names+ always goes over as a
|
|
1337
|
+
# non-NULL array, as hegel-rust passes it, even when it is empty.
|
|
1338
|
+
def grammar_new(ctx, builder, start, max_size, max_depth, alphabet, names)
|
|
1339
|
+
out = FFI::MemoryPointer.new(:pointer)
|
|
1340
|
+
names_array, names_slots = optional_name_array(names)
|
|
1341
|
+
code = @hegel_grammar_new_fn.call(
|
|
1342
|
+
ctx, builder, optional_string(start), max_size, max_depth, alphabet, names_array, names_slots.size, out
|
|
1343
|
+
)
|
|
1344
|
+
LibHegel.check!(self, ctx, code)
|
|
1345
|
+
out.read_pointer
|
|
1346
|
+
end
|
|
1347
|
+
|
|
1348
|
+
# The marker standing for the explicit terminal +name+ in the strings
|
|
1349
|
+
# drawn from +grammar+, copied out of the grammar's own buffer.
|
|
1350
|
+
def grammar_placeholder(ctx, grammar, name)
|
|
1351
|
+
out = StringStruct.new
|
|
1352
|
+
code = @hegel_grammar_placeholder_fn.call(ctx, grammar, string_view(name), out)
|
|
1353
|
+
LibHegel.check!(self, ctx, code)
|
|
1354
|
+
read_string(out)
|
|
1355
|
+
end
|
|
1356
|
+
|
|
1357
|
+
def grammar_free(ctx, grammar)
|
|
1358
|
+
# Removing this call only leaks native memory; Ruby has no way to
|
|
1359
|
+
# observe the leak.
|
|
1360
|
+
@hegel_grammar_free_fn.call(ctx, grammar) # mutineer:disable-line statement_removal
|
|
1361
|
+
nil
|
|
1362
|
+
end
|
|
1363
|
+
|
|
1364
|
+
# A drawn string of +grammar+ of at most +max_size+ characters outside
|
|
1365
|
+
# its markers, 0 meaning the grammar's own cap. Freed like
|
|
1366
|
+
# #generate_string's result.
|
|
1367
|
+
def generate_grammar(ctx, tc, grammar, max_size)
|
|
1368
|
+
out = StringStruct.new
|
|
1369
|
+
code = @hegel_generate_grammar_fn.call(ctx, tc, grammar, max_size, out)
|
|
1370
|
+
LibHegel.check!(self, ctx, code)
|
|
1371
|
+
read_string(out)
|
|
1372
|
+
ensure
|
|
1373
|
+
owned_string_free(ctx, out)
|
|
1374
|
+
end
|
|
1375
|
+
|
|
1091
1376
|
private
|
|
1092
1377
|
|
|
1378
|
+
def retry_or_check!(ctx, code)
|
|
1379
|
+
return false if code == HEGEL_E_RETRY
|
|
1380
|
+
|
|
1381
|
+
LibHegel.check!(self, ctx, code)
|
|
1382
|
+
true
|
|
1383
|
+
end
|
|
1384
|
+
|
|
1093
1385
|
# Resolves +symbol+ against @handle and wraps it as a callable
|
|
1094
1386
|
# FFI::Function, the direct form #initialize's own comment explains
|
|
1095
1387
|
# the choice of.
|
|
@@ -1103,8 +1395,8 @@ module Hegel
|
|
|
1103
1395
|
end
|
|
1104
1396
|
|
|
1105
1397
|
# Copies +bytes+ into a freshly allocated buffer, for
|
|
1106
|
-
# #generate_integer_big's min_value/max_value
|
|
1107
|
-
#
|
|
1398
|
+
# #generate_integer_big's min_value/max_value and for #write_string:
|
|
1399
|
+
# input bytes libhegel reads from, the opposite direction of an
|
|
1108
1400
|
# out-parameter buffer (which libhegel writes into, and the caller
|
|
1109
1401
|
# then reads).
|
|
1110
1402
|
def bytes_to_pointer(bytes)
|
|
@@ -1113,21 +1405,50 @@ module Hegel
|
|
|
1113
1405
|
ptr
|
|
1114
1406
|
end
|
|
1115
1407
|
|
|
1116
|
-
#
|
|
1117
|
-
#
|
|
1118
|
-
#
|
|
1119
|
-
#
|
|
1408
|
+
# Writes +string+'s bytes into +struct+, a StringStruct, as a borrowed
|
|
1409
|
+
# hegel_string_t input. The bytes go over with their length and no
|
|
1410
|
+
# terminator, so an interior NUL reaches the engine. An empty string
|
|
1411
|
+
# gets a zero-size buffer: the header accepts any pointer for a
|
|
1412
|
+
# length of zero.
|
|
1120
1413
|
#
|
|
1121
|
-
#
|
|
1122
|
-
#
|
|
1123
|
-
#
|
|
1124
|
-
#
|
|
1125
|
-
#
|
|
1126
|
-
#
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1414
|
+
# FFI::Struct keeps a reference to a pointer it stores in a field, so
|
|
1415
|
+
# the copied buffer lives as long as +struct+ does. Measured on ffi
|
|
1416
|
+
# 1.17.4: after a full GC, a WeakRef to a buffer held only by a struct
|
|
1417
|
+
# field is still alive, and one to a buffer with no holder is not. A
|
|
1418
|
+
# caller keeps the struct live for the native call by passing it as an
|
|
1419
|
+
# argument.
|
|
1420
|
+
def write_string(struct, string)
|
|
1421
|
+
struct[:data] = bytes_to_pointer(string)
|
|
1422
|
+
struct[:len] = string.bytesize
|
|
1423
|
+
struct
|
|
1424
|
+
end
|
|
1425
|
+
|
|
1426
|
+
# A hegel_string_t for an ordinary input argument, passed by value.
|
|
1427
|
+
def string_view(string)
|
|
1428
|
+
write_string(StringStruct.new, string)
|
|
1429
|
+
end
|
|
1430
|
+
|
|
1431
|
+
# A pointer to a hegel_string_t for an optional input argument: nil
|
|
1432
|
+
# for an absent value, which marshals to NULL, and a struct
|
|
1433
|
+
# otherwise. The header separates the two cases: NULL means absent,
|
|
1434
|
+
# and a pointer to an empty string means present and empty. "" for
|
|
1435
|
+
# #settings_set_database depends on that difference.
|
|
1436
|
+
def optional_string(string)
|
|
1437
|
+
string && string_view(string)
|
|
1438
|
+
end
|
|
1439
|
+
|
|
1440
|
+
# Packs +names+ (an Array of Ruby Strings) into a contiguous array of
|
|
1441
|
+
# hegel_string_t for #new_state_machine's rule_names/invariant_names
|
|
1442
|
+
# arguments: one StringStruct per name, each written in place over
|
|
1443
|
+
# its own slot of a single buffer.
|
|
1444
|
+
#
|
|
1445
|
+
# Returns [pointer, kept_alive]. #write_string copies each name into
|
|
1446
|
+
# a fresh native buffer -- nothing here depends on the original
|
|
1447
|
+
# +names+ Strings staying live -- and each slot's struct holds a
|
|
1448
|
+
# reference to that buffer. The array buffer holds only addresses,
|
|
1449
|
+
# not references, so the caller keeping +kept_alive+ (the slot
|
|
1450
|
+
# structs) as a live local for the length of the call is what keeps
|
|
1451
|
+
# the buffers each one owns from being garbage-collected first.
|
|
1131
1452
|
#
|
|
1132
1453
|
# Empty +names+ maps to [nil, []] -- NULL and (by the caller passing
|
|
1133
1454
|
# names.size, 0) the header's own contract for invariant_names' "no
|
|
@@ -1142,10 +1463,21 @@ module Hegel
|
|
|
1142
1463
|
# hegel_new_state_machine reads zero names either way.
|
|
1143
1464
|
return [nil, []] if names.empty? # mutineer:disable-line statement_removal
|
|
1144
1465
|
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1466
|
+
array = FFI::MemoryPointer.new(StringStruct, names.size)
|
|
1467
|
+
slots = names.each_with_index.map do |name, i|
|
|
1468
|
+
write_string(StringStruct.new(array + (i * StringStruct.size)), name)
|
|
1469
|
+
end
|
|
1470
|
+
[array, slots]
|
|
1471
|
+
end
|
|
1472
|
+
|
|
1473
|
+
# #pack_name_array for an argument where nil and an empty Array mean
|
|
1474
|
+
# different things: nil stays NULL, and an empty Array becomes a
|
|
1475
|
+
# non-NULL pointer, which #pack_name_array alone would turn into NULL.
|
|
1476
|
+
def optional_name_array(names)
|
|
1477
|
+
return [nil, []] unless names
|
|
1478
|
+
|
|
1479
|
+
array, slots = pack_name_array(names)
|
|
1480
|
+
[array || FFI::MemoryPointer.new(StringStruct), slots]
|
|
1149
1481
|
end
|
|
1150
1482
|
|
|
1151
1483
|
# Packs +flags+ (an Array of true/false) into a const bool * buffer,
|
|
@@ -1222,26 +1554,29 @@ module Hegel
|
|
|
1222
1554
|
[struct[:hour], struct[:minute], struct[:second], struct[:nanosecond]]
|
|
1223
1555
|
end
|
|
1224
1556
|
|
|
1225
|
-
# Reads +out+'s const
|
|
1226
|
-
# returns nil if libhegel left it NULL. Shared by
|
|
1227
|
-
# #failure_reproduction_blob, and #failure_caveat,
|
|
1228
|
-
# the header documents as nullable, so both
|
|
1229
|
-
# exercised once between the call sites
|
|
1557
|
+
# Reads +out+'s const hegel_string_t* out-parameter into a Ruby
|
|
1558
|
+
# String, or returns nil if libhegel left it NULL. Shared by
|
|
1559
|
+
# #run_result_error, #failure_reproduction_blob, and #failure_caveat,
|
|
1560
|
+
# the out-parameters the header documents as nullable, so both
|
|
1561
|
+
# branches only need to be exercised once between the call sites
|
|
1562
|
+
# rather than at each one.
|
|
1230
1563
|
def nullable_out_string(out)
|
|
1231
1564
|
ptr = out.read_pointer
|
|
1232
|
-
ptr.null? ? nil :
|
|
1233
|
-
end
|
|
1234
|
-
|
|
1235
|
-
#
|
|
1236
|
-
# The header calls every string libhegel hands back valid
|
|
1237
|
-
# FFI::Pointer#
|
|
1238
|
-
# who matches an error message against a pattern of their
|
|
1239
|
-
# gets Encoding::CompatibilityError rather than an answer,
|
|
1240
|
-
# engine's own messages do carry characters outside ASCII:
|
|
1241
|
-
# FilterTooMuch health check writes an em dash into the sentence
|
|
1242
|
-
# caller is most likely to read.
|
|
1243
|
-
|
|
1244
|
-
|
|
1565
|
+
ptr.null? ? nil : read_string(StringStruct.new(ptr))
|
|
1566
|
+
end
|
|
1567
|
+
|
|
1568
|
+
# Copies the +len+ bytes at +struct+'s data pointer and labels them
|
|
1569
|
+
# UTF-8. The header calls every string libhegel hands back valid
|
|
1570
|
+
# UTF-8, but FFI::Pointer#read_bytes labels what it reads ASCII-8BIT.
|
|
1571
|
+
# A caller who matches an error message against a pattern of their
|
|
1572
|
+
# own then gets Encoding::CompatibilityError rather than an answer,
|
|
1573
|
+
# and the engine's own messages do carry characters outside ASCII:
|
|
1574
|
+
# the FilterTooMuch health check writes an em dash into the sentence
|
|
1575
|
+
# a caller is most likely to read. The length decides the copy, not
|
|
1576
|
+
# the trailing NUL the header also promises, since a string can hold
|
|
1577
|
+
# an interior NUL.
|
|
1578
|
+
def read_string(struct)
|
|
1579
|
+
struct[:data].read_bytes(struct[:len]).force_encoding(Encoding::UTF_8)
|
|
1245
1580
|
end
|
|
1246
1581
|
end
|
|
1247
1582
|
end
|