voxgig_struct 0.1.0 → 0.1.1

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.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -6
  3. data/voxgig_struct.rb +35 -16
  4. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4ac352065c6d43bd84bf62fd687157226920a7dcece0c2671af82e79bed18e44
4
- data.tar.gz: 46376fbb7414d3fc9d98ca4717f2c698cf7657da635e58d5178e00199769a215
3
+ metadata.gz: ff9e0e4d399722d0f1b625bc1c7ddc310a18db4affcdb6bc119fb300b30eae27
4
+ data.tar.gz: a51b33ce2ccd9f77a06092095017dc4f7aefb0be8e5a5981ab9a245d971913c9
5
5
  SHA512:
6
- metadata.gz: 3fadf7a2e1450899adee35501d6d75b51b1d4d33439d762e019d4f05a2e6e3595ed0980d80a69305d434f7ead1ed754dff2055aaa59cc24e17efe08df1506ec4
7
- data.tar.gz: 27c70e26883dbe8efc183bf018dc533875b4a1cc48d0c7ba4a662e9d907e782b795d27e968586fa791f3505d9c7b225551dfcaa2e523fc6c976d31077c03be92
6
+ metadata.gz: 81946c4f83d01daf2a0705961cce4afcd74cea7ac7d02568bb2cd5ffe2ece3ed5f21e1182b65fe5e8eb08e2ad1ab429aa1fd11e1c42a6897060d7af683db3a2e
7
+ data.tar.gz: 3fea471cd507d9e78623301686f5dd29fdc9300675ea4a02b7adf60fe66a0d869e7e2053ac39813832a87a6313b4c29186ba2c521cdae387f9ab441893402fe5
data/README.md CHANGED
@@ -468,7 +468,7 @@ VoxgigStruct.transform(
468
468
  )
469
469
  # { 'x' => { 'y' => [{ 'q' => 13, 'r' => 13, 'p' => 1 }, { 'q' => 23, 'r' => 23, 'p' => 1 }] } }
470
470
  ```
471
- <!-- => {"x": {"y": [{"q": 13, "r": 13, "p": 1}, {"q": 23, "r": 23, "p": 1}]}} -->
471
+ <!-- => {"x": {"y": [{"p": 1, "q": 13, "r": 13}, {"p": 1, "q": 23, "r": 23}]}} -->
472
472
 
473
473
  Putting a command in **key** position (or, for `$APPLY`, directly under a
474
474
  map) is an error — commands must be list values:
@@ -490,7 +490,7 @@ VoxgigStruct.validate(
490
490
  # { 'name' => 'Ada', 'age' => 36 }
491
491
  ```
492
492
 
493
- <!-- => {"name": "Ada", "age": 36} -->
493
+ <!-- => {"age": 36, "name": "Ada"} -->
494
494
 
495
495
  <!-- example: select#query -->
496
496
  ```ruby
@@ -502,7 +502,7 @@ VoxgigStruct.select(
502
502
  # [{ 'name' => 'Alice', 'age' => 30, '$KEY' => 'a' }]
503
503
  ```
504
504
 
505
- <!-- => [{"name": "Alice", "age": 30, "$KEY": "a"}] -->
505
+ <!-- => [{"$KEY": "a", "age": 30, "name": "Alice"}] -->
506
506
 
507
507
  ### Builders
508
508
 
@@ -519,7 +519,7 @@ VoxgigStruct.jt(1, 2, 3) # [1, 2, 3]
519
519
  ### `Injection` class
520
520
 
521
521
  Full implementation with `descend`, `child`, `setval` instance
522
- methods. Used internally by `inject`/`transform`/`validate`; you
522
+ methods. Used internally by `inject`/`transform`/`validate`; you
523
523
  need it when writing custom injectors.
524
524
 
525
525
  ### Injection helpers
@@ -603,7 +603,7 @@ Ruby has `nil`. The port distinguishes:
603
603
  ### Method naming
604
604
 
605
605
  Ruby method names match canonical lowercase (`getpath`, `setpath`,
606
- `getprop`), not Ruby's idiomatic snake_case. Parity beats style.
606
+ `getprop`), not Ruby's idiomatic snake_case. Parity beats style.
607
607
 
608
608
  ### Walk-based merge
609
609
 
@@ -640,7 +640,7 @@ portable to the Go / Rust / C / Lua / Zig ports.
640
640
  ### Sharp edges
641
641
 
642
642
  - **Catastrophic backtracking.** Onigmo has internal mitigations for
643
- some classic ReDoS shapes — `^(a+)+$` against 22 a's plus `!` runs
643
+ some classic ReDoS shapes — `^(a+)+$` against 22 repetitions of `a` plus `!` runs
644
644
  in microseconds here. Larger inputs or different shapes can still
645
645
  blow up; the safe rule is to stay inside the RE2 subset and avoid
646
646
  nested quantifiers.
data/voxgig_struct.rb CHANGED
@@ -5,8 +5,11 @@ module VoxgigStruct
5
5
  # --- Debug Logging Configuration ---
6
6
  DEBUG = false
7
7
 
8
- def self.log(msg)
9
- puts "[DEBUG] #{msg}" if DEBUG
8
+ # Lazy: the message block is only evaluated when DEBUG is on, so hot paths
9
+ # never pay to build (and `.inspect`-serialise whole nodes into) a debug
10
+ # string that would just be discarded.
11
+ def self.log
12
+ puts "[DEBUG] #{yield}" if DEBUG
10
13
  end
11
14
 
12
15
  # --- Helper to convert internal undefined marker to Ruby nil ---
@@ -202,34 +205,34 @@ module VoxgigStruct
202
205
  # --- Internal getprop ---
203
206
  # Returns the value if found; otherwise returns alt (default is UNDEF)
204
207
  def self._getprop(val, key, alt = UNDEF)
205
- log("(_getprop) called with val=#{val.inspect} and key=#{key.inspect}")
208
+ log { "(_getprop) called with val=#{val.inspect} and key=#{key.inspect}" }
206
209
  return alt if val.nil? || key.nil?
207
210
 
208
211
  if islist(val)
209
212
  key = key.to_i if key.to_s =~ /\A\d+\z/
210
213
  unless key.is_a?(Numeric) && key >= 0 && key < val.size
211
- log("(_getprop) index #{key.inspect} out of bounds; returning alt")
214
+ log { "(_getprop) index #{key.inspect} out of bounds; returning alt" }
212
215
  return alt
213
216
  end
214
217
  result = val[key]
215
- log("(_getprop) returning #{result.inspect} from array for key #{key}")
218
+ log { "(_getprop) returning #{result.inspect} from array for key #{key}" }
216
219
  result
217
220
  elsif ismap(val)
218
221
  key_str = key.to_s
219
222
  if val.key?(key_str)
220
223
  result = val[key_str]
221
- log("(_getprop) found key #{key_str.inspect} in hash, returning #{result.inspect}")
224
+ log { "(_getprop) found key #{key_str.inspect} in hash, returning #{result.inspect}" }
222
225
  result
223
226
  elsif key.is_a?(String) && val.key?(key.to_sym)
224
227
  result = val[key.to_sym]
225
- log("(_getprop) found symbol key #{key.to_sym.inspect} in hash, returning #{result.inspect}")
228
+ log { "(_getprop) found symbol key #{key.to_sym.inspect} in hash, returning #{result.inspect}" }
226
229
  result
227
230
  else
228
- log("(_getprop) key #{key.inspect} not found; returning alt")
231
+ log { "(_getprop) key #{key.inspect} not found; returning alt" }
229
232
  alt
230
233
  end
231
234
  else
232
- log('(_getprop) value is not a node; returning alt')
235
+ log { '(_getprop) value is not a node; returning alt' }
233
236
  alt
234
237
  end
235
238
  end
@@ -280,7 +283,7 @@ module VoxgigStruct
280
283
  end
281
284
 
282
285
  def self.setprop(parent, key, val = :no_val_provided)
283
- log(">>> setprop called with parent=#{parent.inspect}, key=#{key.inspect}, val=#{val.inspect}")
286
+ log { ">>> setprop called with parent=#{parent.inspect}, key=#{key.inspect}, val=#{val.inspect}" }
284
287
  return parent unless iskey(key)
285
288
 
286
289
  if ismap(parent)
@@ -305,7 +308,7 @@ module VoxgigStruct
305
308
  parent.unshift(val)
306
309
  end
307
310
  end
308
- log("<<< setprop result: #{parent.inspect}")
311
+ log { "<<< setprop result: #{parent.inspect}" }
309
312
  parent
310
313
  end
311
314
 
@@ -942,8 +945,8 @@ module VoxgigStruct
942
945
  part = stringify(getpath(inj_meta, part[6..-2]))
943
946
  end
944
947
 
945
- # $$ escapes $
946
- part = part.gsub('$$', '$') if part.is_a?(String)
948
+ # $$ escapes $ (skip the gsub allocation for the common no-`$$` part)
949
+ part = part.gsub('$$', '$') if part.is_a?(String) && part.include?('$$')
947
950
 
948
951
  if part == S_MT
949
952
  ascends = 0
@@ -1789,8 +1792,21 @@ module VoxgigStruct
1789
1792
  setprop(parent, n[0], clone(childtm))
1790
1793
  end
1791
1794
  parent.slice!(inj.dparent.length..-1) if parent.length > inj.dparent.length
1792
- inj.keyI = 0
1793
- return getprop(inj.dparent, 0)
1795
+
1796
+ # NOTE: modifying inj! This extends the child value loop in inject
1797
+ # to cover every cloned child.
1798
+ (size(keys)...size(parent)).each do |ckeyI|
1799
+ keys << strkey(ckeyI)
1800
+ end
1801
+
1802
+ # Restart the child value loop at the first element (the loop
1803
+ # increments keyI on resume) so that the first element is also
1804
+ # validated against the child template.
1805
+ inj.keyI = -1
1806
+
1807
+ # SKIP leaves the cloned child template in place at the first
1808
+ # element so the resumed loop can validate it.
1809
+ return SKIP
1794
1810
  end
1795
1811
 
1796
1812
  nil
@@ -1920,7 +1936,10 @@ module VoxgigStruct
1920
1936
  pkeys = keysof(pval)
1921
1937
 
1922
1938
  if pkeys.length.positive? && getprop(pval, '`$OPEN`') != true
1923
- badkeys = ckeys.reject { |ck| haskey(pval, ck) }
1939
+ # Literal presence: the shape DECLARES ck even when its value is nil.
1940
+ # Canonical uses `NONE === _lookup`; `haskey` is Group A (value-based)
1941
+ # and would drop records with a nil field from an open ($AND) select.
1942
+ badkeys = ckeys.reject { |ck| pval.is_a?(Hash) && pval.key?(strkey(ck)) }
1924
1943
  if badkeys.length.positive?
1925
1944
  inj.errs << "Unexpected keys at field #{pathify(inj.path, 1)}#{S_VIZ}#{join(badkeys, ', ')}"
1926
1945
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: voxgig_struct
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Voxgig