yeptris 0.6.3.1-x86_64-linux → 0.6.3.3-x86_64-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/lib/yeptris/ffi.rb +16 -0
- data/lib/yeptris/json/descriptor.rb +220 -0
- data/lib/yeptris/json.rb +2 -0
- data/lib/yeptris/psych/visitors.rb +23 -0
- data/lib/yeptris.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 64863a76bee2d4031ae2b32b30adaa176d49b26a0ee96f84d11a0cbbc99f4d5c
|
|
4
|
+
data.tar.gz: f44e4eba9c5631d3e0fdba508b65b46ce2b5647e09dd3fdb41b4a0e33742b324
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a982a90f971918164a31fba17e4928665493457a519cd8c57f00f0d884c04440283536082f902bc99f56474565d71b3c4519c8fd358d022d8cbb107e3a0dbd34
|
|
7
|
+
data.tar.gz: 56003cd24cbcca3e0833329bc8bb2c944c2b867cdb8bce7621eda6a9d5ab91623282081bd3f0b6a1b8af454e9974190b241aaa2775872225c08df6d37ec323b6
|
data/lib/yeptris/ffi.rb
CHANGED
|
@@ -75,6 +75,22 @@ module Yeptris
|
|
|
75
75
|
attach_function :yeptris_tape_free, [:pointer], :void
|
|
76
76
|
attach_function :yeptris_tape_convert, %i[pointer size_t size_t pointer pointer], :size_t
|
|
77
77
|
|
|
78
|
+
# the compiled plan walk (#293 / TODO.restructure/87): compile a
|
|
79
|
+
# strict-JSON spec once, apply it to a parsed tape in one C pass,
|
|
80
|
+
# read the typed COLUMNAR result
|
|
81
|
+
attach_function :yeptris_plan_compile, %i[pointer size_t pointer], :pointer
|
|
82
|
+
attach_function :yeptris_plan_free, [:pointer], :void
|
|
83
|
+
attach_function :yeptris_plan_column_count, [:pointer], :size_t
|
|
84
|
+
attach_function :yeptris_tape_plan_walk, %i[pointer pointer pointer], :pointer
|
|
85
|
+
attach_function :yeptris_plan_result_free, [:pointer], :void
|
|
86
|
+
attach_function :yeptris_plan_result_rows, [:pointer], :size_t
|
|
87
|
+
attach_function :yeptris_plan_result_kind, %i[pointer size_t], :int
|
|
88
|
+
attach_function :yeptris_plan_result_ints, %i[pointer size_t], :pointer
|
|
89
|
+
attach_function :yeptris_plan_result_floats, %i[pointer size_t], :pointer
|
|
90
|
+
attach_function :yeptris_plan_result_str_offs, %i[pointer size_t], :pointer
|
|
91
|
+
attach_function :yeptris_plan_result_str_lens, %i[pointer size_t], :pointer
|
|
92
|
+
attach_function :yeptris_plan_result_nulls, %i[pointer size_t], :pointer
|
|
93
|
+
|
|
78
94
|
attach_function :yeptris_document_free, [:yeptris_document], :void
|
|
79
95
|
attach_function :yeptris_document_count, [:yeptris_document], :size_t
|
|
80
96
|
attach_function :yeptris_document_root, [:yeptris_document, :size_t], :yeptris_node
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# The Descriptor plan walk (yeptris#293 slice two, over the C plan
|
|
4
|
+
# ABI of libyeptris v0.6.3): compile a row shape once, then apply it
|
|
5
|
+
# to a JSON document in ONE native pass producing typed COLUMNS —
|
|
6
|
+
# the engine never materializes the intermediate Ruby Hash/Array
|
|
7
|
+
# tree (lutaml-model's hydration reads columns, not trees).
|
|
8
|
+
#
|
|
9
|
+
# descriptor = Yeptris::JSON::Descriptor.build(
|
|
10
|
+
# kind: :seq, # or :map with path: — the rows container
|
|
11
|
+
# children: [
|
|
12
|
+
# { name: "id", kind: :int },
|
|
13
|
+
# { name: "name", kind: :str },
|
|
14
|
+
# ])
|
|
15
|
+
# result = descriptor.walk(json)
|
|
16
|
+
# result.column("id") # => [1, 2, ...] (nil where null/missing)
|
|
17
|
+
# result.to_a # => [{ "id" => 1, "name" => "x" }, ...]
|
|
18
|
+
#
|
|
19
|
+
# Vocabulary alignment with Leptris::XML::Descriptor (the shape
|
|
20
|
+
# vocabulary lutaml-model shares across engines): :collection at the
|
|
21
|
+
# root is the rows container (the seq form), :scalar is the string
|
|
22
|
+
# leaf. Slice one covers rows of scalar leaves — nested plans, the
|
|
23
|
+
# YAML leg, and partial descriptors ride the board item
|
|
24
|
+
# (TODO.restructure/87).
|
|
25
|
+
class Yeptris::JSON::Descriptor
|
|
26
|
+
class Error < ::Yeptris::JSON::Error; end
|
|
27
|
+
|
|
28
|
+
# spec leaf kinds → the C column kind codes (plan.h)
|
|
29
|
+
LEAF_KINDS = {
|
|
30
|
+
int: 0,
|
|
31
|
+
float: 1,
|
|
32
|
+
str: 2,
|
|
33
|
+
bool: 3,
|
|
34
|
+
scalar: 2, # leptris's string-value row kind
|
|
35
|
+
}.freeze
|
|
36
|
+
private_constant :LEAF_KINDS
|
|
37
|
+
|
|
38
|
+
# root forms: the rows container's shape (:collection is leptris's
|
|
39
|
+
# name for a repeated-rows container)
|
|
40
|
+
ROOT_KINDS = %i[seq map collection].freeze
|
|
41
|
+
private_constant :ROOT_KINDS
|
|
42
|
+
|
|
43
|
+
LEAF_SPEC_NAMES = %w[int float str bool].freeze
|
|
44
|
+
private_constant :LEAF_SPEC_NAMES
|
|
45
|
+
|
|
46
|
+
class Handle < ::FFI::AutoPointer
|
|
47
|
+
def self.release(ptr)
|
|
48
|
+
::Yeptris::FFI.yeptris_plan_free(ptr)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# +kind+: :seq (rows are the root array) or :map (rows live under
|
|
53
|
+
# +path:+'s value). +children+: the leaf columns; each row is a
|
|
54
|
+
# mapping, every child names one typed column.
|
|
55
|
+
def self.build(kind:, path: nil, children:)
|
|
56
|
+
unless ROOT_KINDS.include?(kind)
|
|
57
|
+
raise ArgumentError, "kind must be one of #{ROOT_KINDS.inspect}, got #{kind.inspect}"
|
|
58
|
+
end
|
|
59
|
+
unless children.is_a?(::Array) && !children.empty?
|
|
60
|
+
raise ArgumentError, "children must be a non-empty Array"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
leaves = children.map do |child|
|
|
64
|
+
name = child[:name]
|
|
65
|
+
code = LEAF_KINDS[child[:kind]]
|
|
66
|
+
if code.nil?
|
|
67
|
+
raise ArgumentError,
|
|
68
|
+
"leaf kind must be one of #{LEAF_KINDS.keys.inspect}, got #{child[:kind].inspect}"
|
|
69
|
+
end
|
|
70
|
+
unless name.is_a?(::String) && !name.empty?
|
|
71
|
+
raise ArgumentError, "leaf name must be a non-empty String, got #{name.inspect}"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
{ "name" => name, "kind" => LEAF_SPEC_NAMES[code] }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
spec = { "kind" => kind == :map ? "map" : "seq", "children" => leaves }
|
|
78
|
+
spec["path"] = path if path
|
|
79
|
+
|
|
80
|
+
# the spec is strict JSON (the C compiler parses it through the
|
|
81
|
+
# strict JSON DOM); compile once, the engine owns the copy
|
|
82
|
+
spec_json = ::JSON.generate(spec)
|
|
83
|
+
st = ::FFI::MemoryPointer.new(:int)
|
|
84
|
+
raw = ::Yeptris::FFI.yeptris_plan_compile(spec_json, spec_json.bytesize, st)
|
|
85
|
+
if raw.null?
|
|
86
|
+
raise Error, "plan compile failed (status=#{st.read_int}): #{spec_json}"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
new(Handle.new(raw), leaves.map { |leaf| leaf["name"] })
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
attr_reader :names # the planned leaf names, in spec order
|
|
93
|
+
|
|
94
|
+
# @api private — handles come from .build
|
|
95
|
+
def initialize(handle, names)
|
|
96
|
+
@handle = handle
|
|
97
|
+
@names = names.dup.freeze
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Applies the plan to +json+ (String or IO): parse the tape, one C
|
|
101
|
+
# plan pass, bulk-read the typed columns. The returned PlanResult
|
|
102
|
+
# is standalone Ruby (nothing borrowed from the document).
|
|
103
|
+
def walk(json)
|
|
104
|
+
src = ::Yeptris.read_input(json).to_s
|
|
105
|
+
tape = ::Yeptris::FFI::JsonTape.new
|
|
106
|
+
rc = ::Yeptris::FFI.yeptris_parse_json_tape(src, src.bytesize, tape)
|
|
107
|
+
raise ::Yeptris::JSON::ParseError, ::Yeptris::FFI.last_error_message if rc != ::Yeptris::FFI::OK
|
|
108
|
+
|
|
109
|
+
begin
|
|
110
|
+
st = ::FFI::MemoryPointer.new(:int)
|
|
111
|
+
raw = ::Yeptris::FFI.yeptris_tape_plan_walk(tape, @handle, st)
|
|
112
|
+
if raw.null?
|
|
113
|
+
raise Error,
|
|
114
|
+
"plan walk failed (status=#{st.read_int}) — the document shape " \
|
|
115
|
+
"disagrees with the plan"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
begin
|
|
119
|
+
PlanResult.read(raw, src, @names)
|
|
120
|
+
ensure
|
|
121
|
+
::Yeptris::FFI.yeptris_plan_result_free(raw)
|
|
122
|
+
end
|
|
123
|
+
ensure
|
|
124
|
+
::Yeptris::FFI.yeptris_tape_free(tape)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def column_count
|
|
129
|
+
::Yeptris::FFI.yeptris_plan_column_count(@handle)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# The eager columnar result of Descriptor#walk: typed Ruby columns
|
|
133
|
+
# keyed by leaf name, plus the row-hash conveniences. Nothing here
|
|
134
|
+
# borrows C memory.
|
|
135
|
+
class PlanResult
|
|
136
|
+
# @api private — one bulk read per column; the C result is freed
|
|
137
|
+
# on return (PlanResult owns plain Ruby data)
|
|
138
|
+
def self.read(raw, src, names)
|
|
139
|
+
rows = ::Yeptris::FFI.yeptris_plan_result_rows(raw)
|
|
140
|
+
columns = {}
|
|
141
|
+
names.each_with_index do |name, c|
|
|
142
|
+
kind = ::Yeptris::FFI.yeptris_plan_result_kind(raw, c)
|
|
143
|
+
columns[name] = read_column(raw, c, kind, rows, src)
|
|
144
|
+
end
|
|
145
|
+
new(rows, names, columns)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def self.read_column(raw, c, kind, rows, src)
|
|
149
|
+
return [] if rows.zero?
|
|
150
|
+
|
|
151
|
+
nulls = ::Yeptris::FFI.yeptris_plan_result_nulls(raw, c).read_bytes(rows).unpack("C*")
|
|
152
|
+
has_nulls = nulls.include?(1)
|
|
153
|
+
case kind
|
|
154
|
+
when 0 # int: the unpacked lane IS the column when no nulls
|
|
155
|
+
ints = ::Yeptris::FFI.yeptris_plan_result_ints(raw, c).read_bytes(rows * 8).unpack("q<*")
|
|
156
|
+
return ints unless has_nulls
|
|
157
|
+
|
|
158
|
+
Array.new(rows) { |i| nulls[i] == 1 ? nil : ints[i] }
|
|
159
|
+
when 1
|
|
160
|
+
floats = ::Yeptris::FFI.yeptris_plan_result_floats(raw, c).read_bytes(rows * 8).unpack("E*")
|
|
161
|
+
return floats unless has_nulls
|
|
162
|
+
|
|
163
|
+
Array.new(rows) { |i| nulls[i] == 1 ? nil : floats[i] }
|
|
164
|
+
when 3 # bool: 0/1 → false/true (always maps)
|
|
165
|
+
ints = ::Yeptris::FFI.yeptris_plan_result_ints(raw, c).read_bytes(rows * 8).unpack("q<*")
|
|
166
|
+
Array.new(rows) { |i| nulls[i] == 1 ? nil : ints[i] == 1 }
|
|
167
|
+
else # str: spans into the source, escapes decoded on the Ruby side
|
|
168
|
+
offs = ::Yeptris::FFI.yeptris_plan_result_str_offs(raw, c).read_bytes(rows * 4).unpack("V*")
|
|
169
|
+
lens = ::Yeptris::FFI.yeptris_plan_result_str_lens(raw, c).read_bytes(rows * 4).unpack("V*")
|
|
170
|
+
decode = ::Yeptris::JSON.method(:decode_span)
|
|
171
|
+
Array.new(rows) { |i| nulls[i] == 1 ? nil : decode.call(src, offs[i], lens[i]) }
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
private_class_method :read_column
|
|
175
|
+
|
|
176
|
+
def initialize(rows, names, columns)
|
|
177
|
+
@rows = rows
|
|
178
|
+
@names = names
|
|
179
|
+
@columns = columns
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def rows
|
|
183
|
+
@rows
|
|
184
|
+
end
|
|
185
|
+
alias count rows
|
|
186
|
+
|
|
187
|
+
# The planned leaf names, in spec order.
|
|
188
|
+
def names
|
|
189
|
+
@names.dup
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# One typed column: an Array of rows values (nil where the leaf
|
|
193
|
+
# was null, missing, or shape-mismatched).
|
|
194
|
+
def column(name)
|
|
195
|
+
@columns.fetch(name) { raise ArgumentError, "no planned column #{name.inspect}" }
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Row +i+ as a Hash of the planned leaves (nil out of range).
|
|
199
|
+
def at(i)
|
|
200
|
+
return nil if i >= @rows || i.negative?
|
|
201
|
+
|
|
202
|
+
row = {}
|
|
203
|
+
@names.each { |n| row[n] = @columns[n][i] }
|
|
204
|
+
row
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Rows as Hashes — the tree-shaped convenience; column readers
|
|
208
|
+
# are the allocation-lean path.
|
|
209
|
+
def to_a
|
|
210
|
+
Array.new(@rows) { |i| at(i) }
|
|
211
|
+
end
|
|
212
|
+
alias to_ruby to_a # leptris's eager-tree vocabulary
|
|
213
|
+
|
|
214
|
+
def each_row(&block)
|
|
215
|
+
return enum_for(:each_row) unless block
|
|
216
|
+
|
|
217
|
+
@rows.times { |i| block.call(at(i)) }
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
end
|
data/lib/yeptris/json.rb
CHANGED
|
@@ -17,6 +17,8 @@ module Yeptris
|
|
|
17
17
|
class Error < ::Yeptris::Error; end
|
|
18
18
|
class ParseError < Error; end
|
|
19
19
|
|
|
20
|
+
autoload :Descriptor, "yeptris/json/descriptor"
|
|
21
|
+
|
|
20
22
|
# json gem 3.0 made duplicate keys an error by DEFAULT; 2.x is
|
|
21
23
|
# last-wins. The parity target is the RESOLVED json gem's own
|
|
22
24
|
# behavior — strictness follows it (issue #37, found by canon's
|
|
@@ -32,11 +32,34 @@ module Yeptris
|
|
|
32
32
|
@tree.set_root(root) if root
|
|
33
33
|
self
|
|
34
34
|
end
|
|
35
|
+
alias_method :<<, :push # stdlib's dump body pushes via <<
|
|
36
|
+
|
|
37
|
+
# stdlib Psych.dump's tail: `visitor.tree.yaml io, options` —
|
|
38
|
+
# the tree wraps the built document; yaml serializes it
|
|
39
|
+
# (string return, or writes + returns the io)
|
|
40
|
+
def tree
|
|
41
|
+
BuiltDocument.new(@tree)
|
|
42
|
+
end
|
|
35
43
|
|
|
36
44
|
def finish
|
|
37
45
|
@tree.serialize(explicit_doc_start: true)
|
|
38
46
|
end
|
|
39
47
|
|
|
48
|
+
# The built-document handle stdlib's dump tail serializes
|
|
49
|
+
class BuiltDocument
|
|
50
|
+
def initialize(doc)
|
|
51
|
+
@doc = doc
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def yaml(io = nil, _options = {})
|
|
55
|
+
out = @doc.serialize(explicit_doc_start: true)
|
|
56
|
+
return out unless io
|
|
57
|
+
|
|
58
|
+
io.write(out)
|
|
59
|
+
io
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
40
63
|
def visit(obj)
|
|
41
64
|
case obj
|
|
42
65
|
when nil, true, false, ::Integer, ::Float, ::String then scalar(obj)
|
data/lib/yeptris.rb
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module Yeptris
|
|
4
4
|
# The gem's version lives in the parent namespace's file — the last
|
|
5
5
|
# internal require (yeptris/version) retired with it.
|
|
6
|
-
VERSION = "0.6.3.
|
|
6
|
+
VERSION = "0.6.3.3".freeze
|
|
7
7
|
# The error hierarchy lives in THIS file (the parent namespace's
|
|
8
8
|
# own file): nested constants do not trigger a parent-constant
|
|
9
9
|
# autoload, and the law forbids internal requires — defining the
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: yeptris
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.6.3.
|
|
4
|
+
version: 0.6.3.3
|
|
5
5
|
platform: x86_64-linux
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -42,6 +42,7 @@ files:
|
|
|
42
42
|
- lib/yeptris/document.rb
|
|
43
43
|
- lib/yeptris/ffi.rb
|
|
44
44
|
- lib/yeptris/json.rb
|
|
45
|
+
- lib/yeptris/json/descriptor.rb
|
|
45
46
|
- lib/yeptris/materializer.rb
|
|
46
47
|
- lib/yeptris/native.so
|
|
47
48
|
- lib/yeptris/node.rb
|