wcl 0.2.3.alpha1
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 +7 -0
- data/lib/wcl/callback.rb +59 -0
- data/lib/wcl/convert.rb +78 -0
- data/lib/wcl/document.rb +80 -0
- data/lib/wcl/types.rb +92 -0
- data/lib/wcl/version.rb +3 -0
- data/lib/wcl/wasm_runtime.rb +188 -0
- data/lib/wcl/wcl_wasm.wasm +0 -0
- data/lib/wcl.rb +53 -0
- metadata +65 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 3e5c8aad8cfd3f0c1dff7cf957a5dd20704f18eaad80e6b37286d2157e08bb6e
|
|
4
|
+
data.tar.gz: fa13a51a38fdbe98ec55d102eafdfbd33dc4cb8e3ed7823a9f19a1a77ac38ab7
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: de89bae27e663707e68fd03d8bbcfda250018c6c047f37d3b4c231b67564805d8a1fa48e86cc2173c6f6a53b025e27ce76a74aa89824cf4533f7430ddaae4396
|
|
7
|
+
data.tar.gz: b0ba5a0d6944c07e6c6760ba07a8ea78eac4d111d96eb67b585bab064b7f40b8522bae737d5333aea0659f7988c37b9ee32660a9b7c10d5699fc854cd0c04f0c
|
data/lib/wcl/callback.rb
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
module Wcl
|
|
4
|
+
# Thread-local callback bridge for custom functions invoked from WASM.
|
|
5
|
+
module Callback
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def set_functions(fn_hash)
|
|
9
|
+
Thread.current[:wcl_functions] = fn_hash
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def clear_functions
|
|
13
|
+
Thread.current[:wcl_functions] = nil
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def invoke(name, args_json)
|
|
17
|
+
functions = Thread.current[:wcl_functions]
|
|
18
|
+
return [false, "callback not found: #{name}"] if functions.nil? || !functions.key?(name)
|
|
19
|
+
|
|
20
|
+
begin
|
|
21
|
+
args = JSON.parse(args_json)
|
|
22
|
+
ruby_args = args.map { |a| json_to_ruby(a) }
|
|
23
|
+
result = functions[name].call(ruby_args)
|
|
24
|
+
result_json = JSON.generate(ruby_to_json(result))
|
|
25
|
+
[true, result_json]
|
|
26
|
+
rescue => e
|
|
27
|
+
[false, e.message]
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def json_to_ruby(val)
|
|
32
|
+
case val
|
|
33
|
+
when nil, true, false, Integer, Float, String
|
|
34
|
+
val
|
|
35
|
+
when Array
|
|
36
|
+
val.map { |v| json_to_ruby(v) }
|
|
37
|
+
when Hash
|
|
38
|
+
val.transform_values { |v| json_to_ruby(v) }
|
|
39
|
+
else
|
|
40
|
+
val
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def ruby_to_json(val)
|
|
45
|
+
case val
|
|
46
|
+
when nil, true, false, Integer, Float, String
|
|
47
|
+
val
|
|
48
|
+
when Array
|
|
49
|
+
val.map { |v| ruby_to_json(v) }
|
|
50
|
+
when Hash
|
|
51
|
+
val.transform_values { |v| ruby_to_json(v) }
|
|
52
|
+
when Set
|
|
53
|
+
{ "__type" => "set", "items" => val.map { |v| ruby_to_json(v) } }
|
|
54
|
+
else
|
|
55
|
+
val
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
data/lib/wcl/convert.rb
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
module Wcl
|
|
4
|
+
# JSON-to-Ruby value conversion for WCL WASM binding.
|
|
5
|
+
module Convert
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def json_to_ruby(val)
|
|
9
|
+
case val
|
|
10
|
+
when nil, true, false, String
|
|
11
|
+
val
|
|
12
|
+
when Integer
|
|
13
|
+
val
|
|
14
|
+
when Float
|
|
15
|
+
val == val.to_i && !val.is_a?(Float) ? val.to_i : val
|
|
16
|
+
when Array
|
|
17
|
+
val.map { |v| json_to_ruby(v) }
|
|
18
|
+
when Hash
|
|
19
|
+
# Check for set encoding
|
|
20
|
+
if val["__type"] == "set" && val.key?("items")
|
|
21
|
+
items = val["items"].map { |v| json_to_ruby(v) }
|
|
22
|
+
begin
|
|
23
|
+
Set.new(items)
|
|
24
|
+
rescue
|
|
25
|
+
items
|
|
26
|
+
end
|
|
27
|
+
# Check for block ref encoding
|
|
28
|
+
elsif val.key?("kind") && (val.key?("attributes") || val.key?("children") || val.key?("decorators"))
|
|
29
|
+
json_to_block_ref(val)
|
|
30
|
+
else
|
|
31
|
+
val.transform_values { |v| json_to_ruby(v) }
|
|
32
|
+
end
|
|
33
|
+
else
|
|
34
|
+
val
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def json_to_values(json_str)
|
|
39
|
+
data = JSON.parse(json_str)
|
|
40
|
+
data.transform_values { |v| json_to_ruby(v) }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def json_to_blocks(json_str)
|
|
44
|
+
data = JSON.parse(json_str)
|
|
45
|
+
data.map { |b| json_to_block_ref(b) }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def json_to_diagnostics(json_str)
|
|
49
|
+
data = JSON.parse(json_str)
|
|
50
|
+
data.map do |d|
|
|
51
|
+
Diagnostic.new(
|
|
52
|
+
severity: d["severity"],
|
|
53
|
+
message: d["message"],
|
|
54
|
+
code: d["code"]
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def json_to_block_ref(obj)
|
|
60
|
+
attrs = (obj["attributes"] || {}).transform_values { |v| json_to_ruby(v) }
|
|
61
|
+
children = (obj["children"] || []).map { |c| json_to_block_ref(c) }
|
|
62
|
+
decorators = (obj["decorators"] || []).map do |d|
|
|
63
|
+
Decorator.new(
|
|
64
|
+
name: d["name"],
|
|
65
|
+
args: (d["args"] || {}).transform_values { |v| json_to_ruby(v) }
|
|
66
|
+
)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
BlockRef.new(
|
|
70
|
+
kind: obj["kind"],
|
|
71
|
+
id: obj["id"],
|
|
72
|
+
attributes: attrs,
|
|
73
|
+
children: children,
|
|
74
|
+
decorators: decorators
|
|
75
|
+
)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
data/lib/wcl/document.rb
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
module Wcl
|
|
4
|
+
# A parsed and evaluated WCL document.
|
|
5
|
+
class Document
|
|
6
|
+
def initialize(handle)
|
|
7
|
+
@handle = handle
|
|
8
|
+
@closed = false
|
|
9
|
+
@values = nil
|
|
10
|
+
@diagnostics = nil
|
|
11
|
+
|
|
12
|
+
ObjectSpace.define_finalizer(self, self.class._invoke_release(handle))
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self._invoke_release(handle)
|
|
16
|
+
proc { WasmRuntime.get.document_free(handle) rescue nil }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def values
|
|
20
|
+
raise "Document is closed" if @closed
|
|
21
|
+
|
|
22
|
+
@values ||= Convert.json_to_values(WasmRuntime.get.document_values(@handle))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def has_errors?
|
|
26
|
+
raise "Document is closed" if @closed
|
|
27
|
+
|
|
28
|
+
WasmRuntime.get.document_has_errors(@handle)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def errors
|
|
32
|
+
diagnostics.select(&:error?)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def diagnostics
|
|
36
|
+
raise "Document is closed" if @closed
|
|
37
|
+
|
|
38
|
+
@diagnostics ||= Convert.json_to_diagnostics(WasmRuntime.get.document_diagnostics(@handle))
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def query(query_str)
|
|
42
|
+
raise "Document is closed" if @closed
|
|
43
|
+
|
|
44
|
+
json_str = WasmRuntime.get.document_query(@handle, query_str)
|
|
45
|
+
result = JSON.parse(json_str)
|
|
46
|
+
raise ValueError, result["error"] if result.key?("error")
|
|
47
|
+
|
|
48
|
+
Convert.json_to_ruby(result["ok"])
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def blocks
|
|
52
|
+
raise "Document is closed" if @closed
|
|
53
|
+
|
|
54
|
+
json_str = WasmRuntime.get.document_blocks(@handle)
|
|
55
|
+
Convert.json_to_blocks(json_str)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def blocks_of_type(kind)
|
|
59
|
+
raise "Document is closed" if @closed
|
|
60
|
+
|
|
61
|
+
json_str = WasmRuntime.get.document_blocks_of_type(@handle, kind)
|
|
62
|
+
Convert.json_to_blocks(json_str)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def close
|
|
66
|
+
return if @closed
|
|
67
|
+
|
|
68
|
+
@closed = true
|
|
69
|
+
WasmRuntime.get.document_free(@handle)
|
|
70
|
+
ObjectSpace.undefine_finalizer(self)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def to_h
|
|
74
|
+
{ values: values, has_errors: has_errors?, diagnostics: diagnostics.map(&:inspect) }
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Error raised for invalid queries.
|
|
79
|
+
class ValueError < StandardError; end
|
|
80
|
+
end
|
data/lib/wcl/types.rb
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
module Wcl
|
|
2
|
+
# A reference to a WCL block with its attributes.
|
|
3
|
+
class BlockRef
|
|
4
|
+
attr_reader :kind, :id, :attributes, :children, :decorators
|
|
5
|
+
|
|
6
|
+
def initialize(kind:, id: nil, attributes: {}, children: [], decorators: [])
|
|
7
|
+
@kind = kind
|
|
8
|
+
@id = id
|
|
9
|
+
@attributes = attributes
|
|
10
|
+
@children = children
|
|
11
|
+
@decorators = decorators
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def get(key)
|
|
15
|
+
@attributes[key]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def [](key)
|
|
19
|
+
@attributes[key]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def has_decorator?(name)
|
|
23
|
+
@decorators.any? { |d| d.name == name }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def to_h
|
|
27
|
+
{
|
|
28
|
+
kind: @kind,
|
|
29
|
+
id: @id,
|
|
30
|
+
attributes: @attributes,
|
|
31
|
+
children: @children.map(&:to_h),
|
|
32
|
+
decorators: @decorators.map(&:to_h)
|
|
33
|
+
}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def inspect
|
|
37
|
+
if @id
|
|
38
|
+
"#<Wcl::BlockRef(#{@kind} #{@id})>"
|
|
39
|
+
else
|
|
40
|
+
"#<Wcl::BlockRef(#{@kind})>"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
alias_method :to_s, :inspect
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# A WCL decorator with name and arguments.
|
|
47
|
+
class Decorator
|
|
48
|
+
attr_reader :name, :args
|
|
49
|
+
|
|
50
|
+
def initialize(name:, args: {})
|
|
51
|
+
@name = name
|
|
52
|
+
@args = args
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def to_h
|
|
56
|
+
{ name: @name, args: @args }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def inspect
|
|
60
|
+
"#<Wcl::Decorator(@#{@name})>"
|
|
61
|
+
end
|
|
62
|
+
alias_method :to_s, :inspect
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# A WCL diagnostic (error, warning, etc.).
|
|
66
|
+
class Diagnostic
|
|
67
|
+
attr_reader :severity, :message, :code
|
|
68
|
+
|
|
69
|
+
def initialize(severity:, message:, code: nil)
|
|
70
|
+
@severity = severity
|
|
71
|
+
@message = message
|
|
72
|
+
@code = code
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def error?
|
|
76
|
+
@severity == "error"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def warning?
|
|
80
|
+
@severity == "warning"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def inspect
|
|
84
|
+
if @code
|
|
85
|
+
"#<Wcl::Diagnostic(#{@severity}: [#{@code}] #{@message})>"
|
|
86
|
+
else
|
|
87
|
+
"#<Wcl::Diagnostic(#{@severity}: #{@message})>"
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
alias_method :to_s, :inspect
|
|
91
|
+
end
|
|
92
|
+
end
|
data/lib/wcl/version.rb
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
require "wasmtime"
|
|
2
|
+
|
|
3
|
+
module Wcl
|
|
4
|
+
# Singleton WASM runtime that loads and manages the wcl_wasm module.
|
|
5
|
+
class WasmRuntime
|
|
6
|
+
@instance = nil
|
|
7
|
+
@init_mutex = Mutex.new
|
|
8
|
+
|
|
9
|
+
def self.get
|
|
10
|
+
return @instance if @instance
|
|
11
|
+
|
|
12
|
+
@init_mutex.synchronize do
|
|
13
|
+
@instance ||= new
|
|
14
|
+
end
|
|
15
|
+
@instance
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def initialize
|
|
19
|
+
@mutex = Mutex.new
|
|
20
|
+
@engine = Wasmtime::Engine.new
|
|
21
|
+
|
|
22
|
+
wasm_path = File.join(__dir__, "wcl_wasm.wasm")
|
|
23
|
+
@module = Wasmtime::Module.from_file(@engine, wasm_path)
|
|
24
|
+
|
|
25
|
+
@linker = Wasmtime::Linker.new(@engine)
|
|
26
|
+
Wasmtime::WASI::P1.add_to_linker_sync(@linker)
|
|
27
|
+
define_host_functions
|
|
28
|
+
|
|
29
|
+
@store = Wasmtime::Store.new(@engine, wasi_p1_config: Wasmtime::WasiConfig.new)
|
|
30
|
+
@wasm_instance = @linker.instantiate(@store, @module)
|
|
31
|
+
|
|
32
|
+
# Cache exported functions
|
|
33
|
+
@alloc = @wasm_instance.export("wcl_wasm_alloc").to_func
|
|
34
|
+
@dealloc = @wasm_instance.export("wcl_wasm_dealloc").to_func
|
|
35
|
+
@string_free = @wasm_instance.export("wcl_wasm_string_free").to_func
|
|
36
|
+
@parse_fn = @wasm_instance.export("wcl_wasm_parse").to_func
|
|
37
|
+
@parse_with_functions_fn = @wasm_instance.export("wcl_wasm_parse_with_functions").to_func
|
|
38
|
+
@doc_free = @wasm_instance.export("wcl_wasm_document_free").to_func
|
|
39
|
+
@doc_values = @wasm_instance.export("wcl_wasm_document_values").to_func
|
|
40
|
+
@doc_has_errors = @wasm_instance.export("wcl_wasm_document_has_errors").to_func
|
|
41
|
+
@doc_diagnostics = @wasm_instance.export("wcl_wasm_document_diagnostics").to_func
|
|
42
|
+
@doc_query = @wasm_instance.export("wcl_wasm_document_query").to_func
|
|
43
|
+
@doc_blocks = @wasm_instance.export("wcl_wasm_document_blocks").to_func
|
|
44
|
+
@doc_blocks_of_type = @wasm_instance.export("wcl_wasm_document_blocks_of_type").to_func
|
|
45
|
+
@memory = @wasm_instance.export("memory").to_memory
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# -- Public API (all synchronized) ------------------------------------
|
|
49
|
+
|
|
50
|
+
def parse(source, options_json)
|
|
51
|
+
@mutex.synchronize do
|
|
52
|
+
src_ptr = write_string(source)
|
|
53
|
+
opts_ptr = write_string(options_json)
|
|
54
|
+
begin
|
|
55
|
+
@parse_fn.call(src_ptr, opts_ptr)
|
|
56
|
+
ensure
|
|
57
|
+
dealloc_string(src_ptr, source) if src_ptr != 0
|
|
58
|
+
dealloc_string(opts_ptr, options_json) if opts_ptr != 0 && options_json
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def parse_with_functions(source, options_json, func_names_json)
|
|
64
|
+
@mutex.synchronize do
|
|
65
|
+
src_ptr = write_string(source)
|
|
66
|
+
opts_ptr = write_string(options_json)
|
|
67
|
+
names_ptr = write_string(func_names_json)
|
|
68
|
+
begin
|
|
69
|
+
@parse_with_functions_fn.call(src_ptr, opts_ptr, names_ptr)
|
|
70
|
+
ensure
|
|
71
|
+
dealloc_string(src_ptr, source) if src_ptr != 0
|
|
72
|
+
dealloc_string(opts_ptr, options_json) if opts_ptr != 0 && options_json
|
|
73
|
+
dealloc_string(names_ptr, func_names_json) if names_ptr != 0
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def document_free(handle)
|
|
79
|
+
@mutex.synchronize { @doc_free.call(handle) }
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def document_values(handle)
|
|
83
|
+
@mutex.synchronize do
|
|
84
|
+
ptr = @doc_values.call(handle)
|
|
85
|
+
consume_string(ptr)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def document_has_errors(handle)
|
|
90
|
+
@mutex.synchronize { @doc_has_errors.call(handle) != 0 }
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def document_diagnostics(handle)
|
|
94
|
+
@mutex.synchronize do
|
|
95
|
+
ptr = @doc_diagnostics.call(handle)
|
|
96
|
+
consume_string(ptr)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def document_query(handle, query)
|
|
101
|
+
@mutex.synchronize do
|
|
102
|
+
q_ptr = write_string(query)
|
|
103
|
+
begin
|
|
104
|
+
ptr = @doc_query.call(handle, q_ptr)
|
|
105
|
+
consume_string(ptr)
|
|
106
|
+
ensure
|
|
107
|
+
dealloc_string(q_ptr, query) if q_ptr != 0
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def document_blocks(handle)
|
|
113
|
+
@mutex.synchronize do
|
|
114
|
+
ptr = @doc_blocks.call(handle)
|
|
115
|
+
consume_string(ptr)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def document_blocks_of_type(handle, kind)
|
|
120
|
+
@mutex.synchronize do
|
|
121
|
+
k_ptr = write_string(kind)
|
|
122
|
+
begin
|
|
123
|
+
ptr = @doc_blocks_of_type.call(handle, k_ptr)
|
|
124
|
+
consume_string(ptr)
|
|
125
|
+
ensure
|
|
126
|
+
dealloc_string(k_ptr, kind) if k_ptr != 0
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
private
|
|
132
|
+
|
|
133
|
+
def define_host_functions
|
|
134
|
+
@linker.func_new(
|
|
135
|
+
"env", "host_call_function",
|
|
136
|
+
[:i32, :i32, :i32, :i32, :i32, :i32], [:i32]
|
|
137
|
+
) do |caller, name_ptr, name_len, args_ptr, args_len, result_ptr_out, result_len_out|
|
|
138
|
+
memory = caller.export("memory").to_memory
|
|
139
|
+
|
|
140
|
+
name = memory.read(name_ptr, name_len).force_encoding("UTF-8")
|
|
141
|
+
args_json = memory.read(args_ptr, args_len).force_encoding("UTF-8")
|
|
142
|
+
|
|
143
|
+
success, result_json = Callback.invoke(name, args_json)
|
|
144
|
+
|
|
145
|
+
if result_json
|
|
146
|
+
result_bytes = result_json.encode("UTF-8")
|
|
147
|
+
alloc_fn = caller.export("wcl_wasm_alloc").to_func
|
|
148
|
+
ptr = alloc_fn.call(result_bytes.bytesize)
|
|
149
|
+
|
|
150
|
+
memory.write(ptr, result_bytes)
|
|
151
|
+
memory.write(result_ptr_out, [ptr].pack("V"))
|
|
152
|
+
memory.write(result_len_out, [result_bytes.bytesize].pack("V"))
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
success ? 0 : -1
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def write_string(str)
|
|
160
|
+
return 0 if str.nil?
|
|
161
|
+
|
|
162
|
+
encoded = str.encode("UTF-8")
|
|
163
|
+
ptr = @alloc.call(encoded.bytesize + 1)
|
|
164
|
+
@memory.write(ptr, encoded + "\0")
|
|
165
|
+
ptr
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def read_c_string(ptr)
|
|
169
|
+
return "" if ptr == 0
|
|
170
|
+
|
|
171
|
+
data = @memory.read(ptr, @memory.data_size - ptr)
|
|
172
|
+
null_idx = data.index("\0") || data.size
|
|
173
|
+
data[0, null_idx].force_encoding("UTF-8")
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def consume_string(ptr)
|
|
177
|
+
return "" if ptr == 0
|
|
178
|
+
|
|
179
|
+
s = read_c_string(ptr)
|
|
180
|
+
@string_free.call(ptr)
|
|
181
|
+
s
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def dealloc_string(ptr, original)
|
|
185
|
+
@dealloc.call(ptr, original.encode("UTF-8").bytesize + 1)
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
Binary file
|
data/lib/wcl.rb
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
require_relative "wcl/version"
|
|
3
|
+
require_relative "wcl/types"
|
|
4
|
+
require_relative "wcl/callback"
|
|
5
|
+
require_relative "wcl/convert"
|
|
6
|
+
require_relative "wcl/wasm_runtime"
|
|
7
|
+
require_relative "wcl/document"
|
|
8
|
+
|
|
9
|
+
module Wcl
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
# Parse a WCL source string and return a Document.
|
|
13
|
+
def parse(source, root_dir: nil, allow_imports: nil, max_import_depth: nil,
|
|
14
|
+
max_macro_depth: nil, max_loop_depth: nil, max_iterations: nil,
|
|
15
|
+
functions: nil, variables: nil)
|
|
16
|
+
options = {}
|
|
17
|
+
options["rootDir"] = root_dir.to_s if root_dir
|
|
18
|
+
options["allowImports"] = allow_imports unless allow_imports.nil?
|
|
19
|
+
options["maxImportDepth"] = max_import_depth if max_import_depth
|
|
20
|
+
options["maxMacroDepth"] = max_macro_depth if max_macro_depth
|
|
21
|
+
options["maxLoopDepth"] = max_loop_depth if max_loop_depth
|
|
22
|
+
options["maxIterations"] = max_iterations if max_iterations
|
|
23
|
+
options["variables"] = variables if variables
|
|
24
|
+
|
|
25
|
+
options_json = options.empty? ? nil : JSON.generate(options)
|
|
26
|
+
runtime = WasmRuntime.get
|
|
27
|
+
|
|
28
|
+
if functions && !functions.empty?
|
|
29
|
+
Callback.set_functions(functions)
|
|
30
|
+
begin
|
|
31
|
+
func_names_json = JSON.generate(functions.keys)
|
|
32
|
+
handle = runtime.parse_with_functions(source, options_json, func_names_json)
|
|
33
|
+
ensure
|
|
34
|
+
Callback.clear_functions
|
|
35
|
+
end
|
|
36
|
+
else
|
|
37
|
+
handle = runtime.parse(source, options_json)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
Document.new(handle)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Parse a WCL file and return a Document.
|
|
44
|
+
def parse_file(path, **kwargs)
|
|
45
|
+
path = path.to_s
|
|
46
|
+
source = File.read(path)
|
|
47
|
+
rescue Errno::ENOENT, Errno::EACCES => e
|
|
48
|
+
raise IOError, "#{path}: #{e.message}"
|
|
49
|
+
else
|
|
50
|
+
kwargs[:root_dir] ||= File.dirname(File.expand_path(path))
|
|
51
|
+
parse(source, **kwargs)
|
|
52
|
+
end
|
|
53
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: wcl
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.2.3.alpha1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Wil Taylor
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-03-22 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: wasmtime
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '42'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '42'
|
|
27
|
+
description: Ruby bindings for WCL, powered by a WASM module and the wasmtime runtime.
|
|
28
|
+
Provides the full 11-phase parsing pipeline with native Ruby types.
|
|
29
|
+
email:
|
|
30
|
+
executables: []
|
|
31
|
+
extensions: []
|
|
32
|
+
extra_rdoc_files: []
|
|
33
|
+
files:
|
|
34
|
+
- lib/wcl.rb
|
|
35
|
+
- lib/wcl/callback.rb
|
|
36
|
+
- lib/wcl/convert.rb
|
|
37
|
+
- lib/wcl/document.rb
|
|
38
|
+
- lib/wcl/types.rb
|
|
39
|
+
- lib/wcl/version.rb
|
|
40
|
+
- lib/wcl/wasm_runtime.rb
|
|
41
|
+
- lib/wcl/wcl_wasm.wasm
|
|
42
|
+
homepage: https://github.com/wiltaylor/wcl
|
|
43
|
+
licenses:
|
|
44
|
+
- MIT
|
|
45
|
+
metadata: {}
|
|
46
|
+
post_install_message:
|
|
47
|
+
rdoc_options: []
|
|
48
|
+
require_paths:
|
|
49
|
+
- lib
|
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '3.1'
|
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
56
|
+
requirements:
|
|
57
|
+
- - ">="
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: '0'
|
|
60
|
+
requirements: []
|
|
61
|
+
rubygems_version: 3.5.22
|
|
62
|
+
signing_key:
|
|
63
|
+
specification_version: 4
|
|
64
|
+
summary: WCL (Wil's Configuration Language) Ruby bindings
|
|
65
|
+
test_files: []
|