kumi 0.0.14 → 0.0.16

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.
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+ module Kumi
3
+ module Core
4
+ module Compiler
5
+ module AccessEmit
6
+ module Base
7
+ module_function
8
+
9
+ # ---------- IR segmentation ----------
10
+ def segment_ops(ops)
11
+ segs, cur = [], []
12
+ i = 0
13
+ while i < ops.length
14
+ case ops[i][:type]
15
+ when :enter_hash
16
+ preview = (i + 1 < ops.length) && ops[i + 1][:type] == :enter_array
17
+ cur << [:enter_hash, ops[i][:key].to_s, preview]
18
+ when :enter_array
19
+ segs << cur unless cur.empty?
20
+ segs << :array
21
+ cur = []
22
+ else
23
+ raise "Unknown operation: #{ops[i].inspect}"
24
+ end
25
+ i += 1
26
+ end
27
+ segs << cur unless cur.empty?
28
+ segs
29
+ end
30
+
31
+ # ---------- codegen helpers ----------
32
+ def fetch_hash_code(node_var:, key:, key_policy:, preview_array:, mode:, policy:, path_key:, map_depth:)
33
+ effective_policy = preview_array ? :indifferent : (key_policy || :indifferent)
34
+ str = key.to_s.inspect
35
+ sym = key.to_sym.inspect
36
+
37
+ fetch =
38
+ case effective_policy
39
+ when :string
40
+ %(next_node = #{node_var}.key?(#{str}) ? #{node_var}[#{str}] : :__missing__)
41
+ when :symbol
42
+ %(next_node = #{node_var}.key?(#{sym}) ? #{node_var}[#{sym}] : :__missing__)
43
+ else # :indifferent
44
+ <<~RB.chomp
45
+ next_node =
46
+ if #{node_var}.key?(#{str}); #{node_var}[#{str}]
47
+ elsif #{node_var}.key?(#{sym}); #{node_var}[#{sym}]
48
+ elsif #{node_var}.key?(#{str}); #{node_var}[#{str}] # (string twice ok / predictable)
49
+ else :__missing__
50
+ end
51
+ RB
52
+ end
53
+
54
+ miss_action = build_miss_action(policy, mode, map_depth, preview_array, key: key, path_key: path_key)
55
+
56
+ <<~RB.chomp
57
+ raise TypeError, "Expected Hash at '#{path_key}' (#{mode})" unless #{node_var}.is_a?(Hash)
58
+ #{fetch}
59
+ if next_node == :__missing__
60
+ #{miss_action}
61
+ end
62
+ #{node_var} = next_node
63
+ RB
64
+ end
65
+
66
+ def array_guard_code(node_var:, mode:, policy:, path_key:, map_depth:)
67
+ miss_action = build_array_miss_action(policy, mode, map_depth, path_key)
68
+ <<~RB.chomp
69
+ if #{node_var}.nil?
70
+ #{miss_action}
71
+ end
72
+ unless #{node_var}.is_a?(Array)
73
+ raise TypeError, "Expected Array at '#{path_key}' (#{mode}); got \#{#{node_var}.class}"
74
+ end
75
+ RB
76
+ end
77
+
78
+ # ---------- missing behaviors ----------
79
+ def build_miss_action(policy, mode, map_depth, preview_array, key:, path_key:)
80
+ case policy
81
+ when :nil
82
+ if mode == :ravel
83
+ base = "out << nil"
84
+ cont = map_depth.positive? ? "next" : "return out"
85
+ "#{base}\n#{cont}"
86
+ elsif mode == :each_indexed
87
+ if map_depth.positive?
88
+ <<~RB.chomp
89
+ if block
90
+ block.call(nil, idx_vec.dup)
91
+ next
92
+ else
93
+ out << [nil, idx_vec.dup]
94
+ next
95
+ end
96
+ RB
97
+ else
98
+ <<~RB.chomp
99
+ if block
100
+ block.call(nil, idx_vec.dup)
101
+ return nil
102
+ else
103
+ out << [nil, idx_vec.dup]
104
+ return out
105
+ end
106
+ RB
107
+ end
108
+ else # :materialize, :read
109
+ # Important: for :materialize this is ALWAYS nil (never [])
110
+ return_val = 'nil'
111
+ map_depth.positive? ? "next #{return_val}" : "return #{return_val}"
112
+ end
113
+ when :skip
114
+ if mode == :materialize
115
+ return_val = preview_array ? '[]' : 'nil'
116
+ map_depth.positive? ? "next #{return_val}" : "return #{return_val}"
117
+ else
118
+ map_depth.positive? ? "next" : (mode == :each_indexed ? "if block; return nil; else; return out; end" : "return out")
119
+ end
120
+ else # :error
121
+ %(raise KeyError, "Missing key '#{key}' at '#{path_key}' (#{mode})")
122
+ end
123
+ end
124
+
125
+ def build_array_miss_action(policy, mode, map_depth, path_key)
126
+ case policy
127
+ when :nil
128
+ if mode == :materialize
129
+ map_depth.positive? ? "next nil" : "return nil"
130
+ elsif mode == :each_indexed
131
+ if map_depth.positive?
132
+ <<~RB.chomp
133
+ if block
134
+ block.call(nil, idx_vec.dup)
135
+ next
136
+ else
137
+ out << [nil, idx_vec.dup]
138
+ next
139
+ end
140
+ RB
141
+ else
142
+ <<~RB.chomp
143
+ if block
144
+ block.call(nil, idx_vec.dup)
145
+ return nil
146
+ else
147
+ out << [nil, idx_vec.dup]
148
+ return out
149
+ end
150
+ RB
151
+ end
152
+ else # :ravel / others
153
+ base = "out << nil"
154
+ cont = map_depth.positive? ? "next" : "return out"
155
+ "#{base}\n#{cont}"
156
+ end
157
+ when :skip
158
+ if mode == :materialize
159
+ map_depth.positive? ? "next []" : "return []"
160
+ elsif mode == :each_indexed
161
+ map_depth.positive? ? "next" : "if block; return nil; else; return out; end"
162
+ else # :ravel
163
+ map_depth.positive? ? "next" : "return out"
164
+ end
165
+ else
166
+ %(raise TypeError, "Missing array at '#{path_key}' (#{mode})")
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+ module Kumi::Core::Compiler::AccessEmit
3
+ module EachIndexed
4
+ extend Base
5
+ module_function
6
+ def build(plan)
7
+ policy = plan.on_missing || :error
8
+ key_policy = plan.key_policy || :indifferent
9
+ path_key = plan.path
10
+ segs = segment_ops(plan.operations)
11
+
12
+ code = +"lambda do |data, &block|\n"
13
+ code << " out = []\n"
14
+ code << " node0 = data\n"
15
+ code << " idx_vec = []\n"
16
+ nodev, depth, loop_depth = "node0", 0, 0
17
+
18
+ segs.each do |seg|
19
+ if seg == :array
20
+ code << " #{array_guard_code(node_var: nodev, mode: :each_indexed, policy: policy, path_key: path_key, map_depth: loop_depth)}\n"
21
+ code << " ary#{loop_depth} = #{nodev}\n"
22
+ code << " len#{loop_depth} = ary#{loop_depth}.length\n"
23
+ code << " i#{loop_depth} = -1\n"
24
+ code << " while (i#{loop_depth} += 1) < len#{loop_depth}\n"
25
+ code << " idx_vec[#{loop_depth}] = i#{loop_depth}\n"
26
+ child = "node#{depth + 1}"
27
+ code << " #{child} = ary#{loop_depth}[i#{loop_depth}]\n"
28
+ nodev = child; depth += 1; loop_depth += 1
29
+ else
30
+ seg.each do |(_, key, preview)|
31
+ code << fetch_hash_code(node_var: nodev, key: key, key_policy: key_policy,
32
+ preview_array: preview, mode: :each_indexed, policy: policy,
33
+ path_key: path_key, map_depth: loop_depth)
34
+ code << "\n"
35
+ end
36
+ end
37
+ end
38
+
39
+ code << " if block\n"
40
+ code << " block.call(#{nodev}, idx_vec.dup)\n"
41
+ code << " else\n"
42
+ code << " out << [#{nodev}, idx_vec.dup]\n"
43
+ code << " end\n"
44
+
45
+ while loop_depth.positive?
46
+ code << " end\n"
47
+ loop_depth -= 1
48
+ nodev = "node#{depth - 1}"
49
+ depth -= 1
50
+ end
51
+
52
+ code << " block ? nil : out\nend\n"
53
+ code
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+ module Kumi::Core::Compiler::AccessEmit
3
+ module Materialize
4
+ extend Base
5
+ module_function
6
+ def build(plan)
7
+ policy = plan.on_missing || :error
8
+ key_policy = plan.key_policy || :indifferent
9
+ path_key = plan.path
10
+ segs = segment_ops(plan.operations)
11
+
12
+ code = +"lambda do |data|\n"
13
+ nodev, depth, map_depth = "node0", 0, 0
14
+ code << " #{nodev} = data\n"
15
+
16
+ segs.each do |seg|
17
+ if seg == :array
18
+ code << " #{array_guard_code(node_var: nodev, mode: :materialize, policy: policy, path_key: path_key, map_depth: map_depth)}\n"
19
+ child = "node#{depth + 1}"
20
+ code << " #{nodev} = #{nodev}.map do |__e#{depth}|\n"
21
+ code << " #{child} = __e#{depth}\n"
22
+ nodev = child; depth += 1; map_depth += 1
23
+ else
24
+ seg.each do |(_, key, preview)|
25
+ code << " "
26
+ code << fetch_hash_code(node_var: nodev, key: key, key_policy: key_policy,
27
+ preview_array: preview, mode: :materialize, policy: policy,
28
+ path_key: path_key, map_depth: map_depth)
29
+ code << "\n"
30
+ end
31
+ end
32
+ end
33
+
34
+ while map_depth.positive?
35
+ code << " " * map_depth + "#{nodev}\n"
36
+ code << " " * (map_depth - 1) + "end\n"
37
+ nodev = "node#{depth - 1}"
38
+ depth -= 1
39
+ map_depth -= 1
40
+ end
41
+ code << " #{nodev}\nend\n"
42
+ code
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+ module Kumi::Core::Compiler::AccessEmit
3
+ module Ravel
4
+ extend Base
5
+ module_function
6
+ def build(plan)
7
+ policy = plan.on_missing || :error
8
+ key_policy = plan.key_policy || :indifferent
9
+ path_key = plan.path
10
+ segs = segment_ops(plan.operations)
11
+
12
+ code = +"lambda do |data|\n"
13
+ code << " out = []\n"
14
+ nodev, depth, loop_depth = "node0", 0, 0
15
+ code << " #{nodev} = data\n"
16
+
17
+ segs.each do |seg|
18
+ if seg == :array
19
+ code << " #{array_guard_code(node_var: nodev, mode: :ravel, policy: policy, path_key: path_key, map_depth: loop_depth)}\n"
20
+ code << " ary#{loop_depth} = #{nodev}\n"
21
+ code << " len#{loop_depth} = ary#{loop_depth}.length\n"
22
+ code << " i#{loop_depth} = -1\n"
23
+ code << " while (i#{loop_depth} += 1) < len#{loop_depth}\n"
24
+ child = "node#{depth + 1}"
25
+ code << " #{child} = ary#{loop_depth}[i#{loop_depth}]\n"
26
+ nodev = child; depth += 1; loop_depth += 1
27
+ else
28
+ seg.each do |(_, key, preview)|
29
+ code << " "
30
+ code << fetch_hash_code(node_var: nodev, key: key, key_policy: key_policy,
31
+ preview_array: preview, mode: :ravel, policy: policy,
32
+ path_key: path_key, map_depth: loop_depth)
33
+ code << "\n"
34
+ end
35
+ end
36
+ end
37
+
38
+ code << " out << #{nodev}\n"
39
+ while loop_depth.positive?
40
+ code << " end\n"
41
+ loop_depth -= 1
42
+ nodev = "node#{depth - 1}"
43
+ depth -= 1
44
+ end
45
+
46
+ code << " out\nend\n"
47
+ code
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+ module Kumi::Core::Compiler::AccessEmit
3
+ module Read
4
+ extend Base
5
+ module_function
6
+ def build(plan)
7
+ policy = plan.on_missing || :error
8
+ key_policy = plan.key_policy || :indifferent
9
+ path_key = plan.path
10
+ ops = plan.operations
11
+
12
+ body = ops.map do |op|
13
+ case op[:type]
14
+ when :enter_hash
15
+ fetch_hash_code(node_var: "node", key: op[:key], key_policy: key_policy,
16
+ preview_array: false, mode: :read, policy: policy,
17
+ path_key: path_key, map_depth: 0)
18
+ when :enter_array
19
+ %(raise TypeError, "Array encountered in :read accessor at '#{path_key}'")
20
+ end
21
+ end.join("\n ")
22
+
23
+ <<~RUBY
24
+ lambda do |data|
25
+ node = data
26
+ #{body}
27
+ node
28
+ end
29
+ RUBY
30
+ end
31
+ end
32
+ end