nexus-standard 1.0.1 → 1.1.0

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 +33 -0
  3. data/nxs.rb +179 -0
  4. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ae59a874713e9affb3dfa1c5cbe77ed0af65e2c5b120a26a30d91cdc6d9c9f2b
4
- data.tar.gz: 726d12f95a732a9faa8bfdb3a50d696b93da476c8dfd08bfc5dcfdcd460757c6
3
+ metadata.gz: 715afe41048d9af9f236d0d07833c900181aa85ca9572b0c739ce1bfe701c5df
4
+ data.tar.gz: 976755cd67a6a2d74660d090be9408fdab3e0f04c5ed3065e67d2733ce7aa519
5
5
  SHA512:
6
- metadata.gz: c39e3ef8fb6ab67c77d54a62a136d6e0b6659bab924e1a147869d6056ac18bef60fa7557be8c186f2d875c02e5eba153a4309151480d1afcb370346c12c554ad
7
- data.tar.gz: 60f80e81992088466ebff384ca40efaa670dfba0408e4b8bde2cde901a826ee7cf16917660387031ed14c4dde91027337d42b751a2e930606813b2c2588216b7
6
+ metadata.gz: e1cf8eba02ffa4db83adfdfbbbc0f96c27e4b70872e9bedfca288ae4d560346c3b4853fd11c0dc6bb908c97e043202bbe680a8d8e0686f2419bc3cc7648a5c1e
7
+ data.tar.gz: 7e49fe80a28de4ab2cc4829e3877c8b8b1e1e40b45e93e0b2a68ca554226643eb07daf546221b63ef0fec59f0d1acd71beb13197ef8f5e2488646804b2468737
data/README.md CHANGED
@@ -96,6 +96,39 @@ ruby bench_c.rb ../js/fixtures # C extension vs JSON
96
96
  | `ext/nxs/extconf.rb` | Extension build configuration |
97
97
  | `ext/build.sh` | Compiles the C extension |
98
98
 
99
+ ## Query engine
100
+
101
+ ```ruby
102
+ require_relative 'nxs'
103
+
104
+ data = File.binread("data.nxb")
105
+ reader = Nxs::Reader.new(data)
106
+
107
+ # Count matching records
108
+ n = reader.where(Nxs::Eq.new("active", true) & Nxs::Gt.new("score", 80.0)).count
109
+
110
+ # Iterate — yields Nxs::Object
111
+ reader.where(Nxs::Eq.new("active", true)).each do |obj|
112
+ puts obj.get_str("username")
113
+ end
114
+
115
+ # First match or nil
116
+ first = reader.where(Nxs::Gt.new("score", 99.0)).first
117
+
118
+ # All records
119
+ reader.all.each { |obj| ... }
120
+ ```
121
+
122
+ ### Predicates
123
+
124
+ | Class | Matches |
125
+ |-------|---------|
126
+ | `Eq.new(key, value)` | equality — String, Integer, Float, boolean |
127
+ | `Gt.new(key, v)` / `Lt.new(key, v)` | numeric comparison |
128
+ | `p1 & p2` / `p1 \| p2` / `~p` | And / Or / Not via operator overloads |
129
+
130
+ `Query` includes `Enumerable` — all `map`, `select`, `reject` etc. are available.
131
+
99
132
  ---
100
133
 
101
134
  For the format specification see [`SPEC.md`](../SPEC.md). For cross-language examples see [`GETTING_STARTED.md`](../GETTING_STARTED.md).
data/nxs.rb CHANGED
@@ -251,6 +251,185 @@ module Nxs
251
251
  end
252
252
  end
253
253
 
254
+ # ── Query engine ─────────────────────────────────────────────────────────────
255
+
256
+ # Base predicate — supports & | ~ operator overloading.
257
+ class Predicate
258
+ def &(other) = And.new(self, other)
259
+ def |(other) = Or.new(self, other)
260
+ def ~@ = Not.new(self)
261
+ def call(_record) = raise NotImplementedError, "#{self.class}#call not implemented"
262
+ end
263
+
264
+ # Eq(key, value) — equality for String, Integer, Float, or boolean.
265
+ class Eq < Predicate
266
+ def initialize(key, value)
267
+ super()
268
+ @key = key
269
+ @value = value
270
+ end
271
+
272
+ def call(record) = record[@key] == @value
273
+ end
274
+
275
+ # Gt(key, number) — numeric greater-than.
276
+ class Gt < Predicate
277
+ def initialize(key, value)
278
+ super()
279
+ @key = key
280
+ @value = value
281
+ end
282
+
283
+ def call(record)
284
+ v = record[@key]
285
+ v.is_a?(Numeric) && v > @value
286
+ end
287
+ end
288
+
289
+ # Lt(key, number) — numeric less-than.
290
+ class Lt < Predicate
291
+ def initialize(key, value)
292
+ super()
293
+ @key = key
294
+ @value = value
295
+ end
296
+
297
+ def call(record)
298
+ v = record[@key]
299
+ v.is_a?(Numeric) && v < @value
300
+ end
301
+ end
302
+
303
+ # And(p1, p2) — conjunction.
304
+ class And < Predicate
305
+ def initialize(a, b)
306
+ super()
307
+ @a = a
308
+ @b = b
309
+ end
310
+
311
+ def call(record) = @a.call(record) && @b.call(record)
312
+ end
313
+
314
+ # Or(p1, p2) — disjunction.
315
+ class Or < Predicate
316
+ def initialize(a, b)
317
+ super()
318
+ @a = a
319
+ @b = b
320
+ end
321
+
322
+ def call(record) = @a.call(record) || @b.call(record)
323
+ end
324
+
325
+ # Not(p) — negation.
326
+ class Not < Predicate
327
+ def initialize(inner)
328
+ super()
329
+ @inner = inner
330
+ end
331
+
332
+ def call(record) = !@inner.call(record)
333
+ end
334
+
335
+ # ── Record proxy ─────────────────────────────────────────────────────────────
336
+
337
+ # Thin hash-like wrapper around Nxs::Object so predicates can use record[key].
338
+ # Values are fetched lazily and memoised per field access.
339
+ class RecordProxy
340
+ def initialize(obj, reader)
341
+ @obj = obj
342
+ @reader = reader
343
+ @cache = {}
344
+ end
345
+
346
+ # Reset to a new underlying object, clearing the field cache.
347
+ # Used by Query#each to reuse a single RecordProxy instance across iterations.
348
+ def reset(obj)
349
+ @obj = obj
350
+ @cache = {}
351
+ end
352
+
353
+ def [](key)
354
+ return @cache[key] if @cache.key?(key)
355
+
356
+ slot = @reader.key_index[key]
357
+ unless slot
358
+ @cache[key] = nil
359
+ return nil
360
+ end
361
+
362
+ sigil = @reader.key_sigils[slot]
363
+ val = case sigil
364
+ when 0x22 then @obj.get_str(key) # '"' string
365
+ when 0x3D then @obj.get_i64(key) # '=' i64
366
+ when 0x7E then @obj.get_f64(key) # '~' f64
367
+ when 0x3F then @obj.get_bool(key) # '?' bool
368
+ end
369
+ @cache[key] = val
370
+ end
371
+ end
372
+
373
+ # ── Query ─────────────────────────────────────────────────────────────────────
374
+
375
+ # Lazy filtered view over a Reader. Created via reader.where(pred) or reader.all.
376
+ #
377
+ # Includes Enumerable, so map/select/min/max etc. all work automatically.
378
+ # count and first are overridden for clarity (Enumerable would work too).
379
+ class Query
380
+ include Enumerable
381
+
382
+ def initialize(reader, pred = nil)
383
+ @reader = reader
384
+ @pred = pred
385
+ end
386
+
387
+ # Yield each matching Nxs::Object to the block.
388
+ def each
389
+ n = @reader.record_count
390
+ pred = @pred
391
+ proxy = RecordProxy.new(nil, @reader)
392
+ i = 0
393
+ while i < n
394
+ obj = @reader.record(i)
395
+ if pred.nil?
396
+ yield obj
397
+ else
398
+ proxy.reset(obj)
399
+ yield obj if pred.call(proxy)
400
+ end
401
+ i += 1
402
+ end
403
+ end
404
+
405
+ # Number of matching records (no block form; delegates to Enumerable when block given).
406
+ def count(&blk)
407
+ return super if blk
408
+
409
+ n = 0
410
+ each { n += 1 }
411
+ n
412
+ end
413
+
414
+ # First matching record, or nil.
415
+ def first
416
+ find { true }
417
+ end
418
+ end
419
+
420
+ # ── Reader extensions ────────────────────────────────────────────────────────
421
+
422
+ class Reader
423
+ # Returns a Query filtered by pred.
424
+ def where(pred) = Query.new(self, pred)
425
+
426
+ # Returns a Query over all records.
427
+ def all = Query.new(self)
428
+
429
+ # Expose key_sigils for RecordProxy
430
+ attr_reader :key_sigils
431
+ end
432
+
254
433
  # ── Object ───────────────────────────────────────────────────────────────────
255
434
 
256
435
  class Object
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nexus-standard
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Micael Malta
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-05-01 00:00:00.000000000 Z
11
+ date: 2026-05-18 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |
14
14
  Pure-Ruby reader for NXB files produced by the NXS compiler. Provides