rubyduino 0.1.1 → 0.1.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aebef08a8012ff1994cfef5f943278222b9cc76c0147398d467d2a815121c37c
4
- data.tar.gz: d48692b143f9248dcae3cddc6db3b823ce9386e0a28e693133c88d28a4fea72d
3
+ metadata.gz: 06e43dd1d4e9f5f4d9dbfda78f6954733999175a658f50b5d76186e6c51fc544
4
+ data.tar.gz: 62a7ba189c79f9741001a6724af58026c7870aaa9da5775dcf54c824afcf53d3
5
5
  SHA512:
6
- metadata.gz: 3bbdc16bf822d3bc4ed389a64fbaa3fbc61d9dfa97604baf0c19d35a9ab0918040e1e2de5d1ccae958b2972338e18be6725eeefdd40291ab0b06fbeab067302b
7
- data.tar.gz: 54625b52317c6de6607655e81cbb1e9b19f493c9b1eb669f50edfe080c06b6a863ed07ab72d29e55af1a626a23d760103aa7142843f81499cd6aa7fb9879e989
6
+ metadata.gz: 9a83433f8069a0e13757f47666c6913307185d66f388eb014fa68bc54a9afd0117e4c7155bf2f8199ffa5a73c4e7de415096f8cdce2a0965f4679cd567cbf7a1
7
+ data.tar.gz: deb4d7cc643e4d93c172a525677fb7944fbc6fb2bab93fe2b48f7893ca2e0886153879fedd42ee7bf06605e4dc0ac5413cb16c767bd59a0ede37cde188dbf0c4
data/CHANGELOG.md CHANGED
@@ -7,3 +7,7 @@
7
7
  ## [0.1.1] - 2026-05-08
8
8
 
9
9
  - Fix avrdude discovery for installed command
10
+
11
+ ## [0.1.2] - 2026-05-08
12
+
13
+ - Update vendored Spinel revision
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Rubyduino
4
4
  module Spinel
5
- COMMIT = "a70208094343b9d4cd306218c909d99781661c2f"
5
+ COMMIT = "e7f714f213ca572912f7214f358a927f8e2152e5"
6
6
  ROOT = File.expand_path("../../vendor/spinel", __dir__)
7
7
  end
8
8
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rubyduino
4
- VERSION = "0.1.1"
4
+ VERSION = "0.1.2"
5
5
  end
@@ -122,7 +122,7 @@ PRISM_LIB = build/libprism.a
122
122
  CODEGEN_STAMP := build/stamps/spinel_codegen.rb.stamp
123
123
  PARSE_STAMP := build/stamps/spinel_parse.c.stamp
124
124
 
125
- .PHONY: all parse bootstrap codegen test retest clean-test-results regen-expected bench clean install uninstall deps
125
+ .PHONY: all parse bootstrap codegen test retest clean-test-results regen-expected bench optcarrot clean install uninstall deps
126
126
 
127
127
  all: parse regexp spinel_codegen$(EXE)
128
128
 
@@ -378,6 +378,34 @@ bench: spinel_parse$(EXE) $(SP_RT_LIB) spinel_codegen$(EXE)
378
378
  echo "Benchmarks: $$pass pass, $$fail fail, $$err error, $$skip skip"; \
379
379
  if [ $$fail -ne 0 ] || [ $$err -ne 0 ]; then exit 1; fi
380
380
 
381
+ # ---- Optcarrot integration test ----
382
+ #
383
+ # End-to-end pipeline: clone optcarrot's `experiment/spinel` branch,
384
+ # pack `lib/optcarrot/*.rb` into a single Ruby file via the upstream
385
+ # `tools/pack-for-spinel.rb`, compile through spinel, run the
386
+ # resulting binary against `examples/Lan_Master.nes`, and verify the
387
+ # output contains `fps: <num>` and `checksum: 59662` (the canonical
388
+ # 180-frame checksum for `--benchmark`).
389
+
390
+ OPTCARROT_DIR := build/optcarrot
391
+ OPTCARROT_REPO := https://github.com/mame/optcarrot.git
392
+ OPTCARROT_BRANCH := experiment/spinel
393
+
394
+ optcarrot: spinel_parse$(EXE) $(SP_RT_LIB) spinel_codegen$(EXE)
395
+ @if [ ! -d $(OPTCARROT_DIR) ]; then \
396
+ git clone --depth=1 --branch=$(OPTCARROT_BRANCH) $(OPTCARROT_REPO) $(OPTCARROT_DIR); \
397
+ fi
398
+ @ruby $(OPTCARROT_DIR)/tools/pack-for-spinel.rb > build/optcarrot-single.rb
399
+ @./spinel build/optcarrot-single.rb -o build/optcarrot-single
400
+ @out=$$($(TIMEOUT60) ./build/optcarrot-single 2>&1); \
401
+ echo "$$out"; \
402
+ if echo "$$out" | grep -qE "^fps: [0-9.]+$$" && echo "$$out" | grep -q "^checksum: 59662$$"; then \
403
+ echo "Optcarrot: OK"; \
404
+ else \
405
+ echo "Optcarrot: FAIL — expected 'fps: <num>' and 'checksum: 59662'"; \
406
+ exit 1; \
407
+ fi
408
+
381
409
  # ---- Install ----
382
410
 
383
411
  PREFIX ?= /usr/local
@@ -2934,6 +2934,18 @@ class Compiler
2934
2934
  end
2935
2935
  end
2936
2936
 
2937
+ # `<obj>.class` returns the class name as a string (Spinel
2938
+ # collapses Class/Module objects to their textual name; the
2939
+ # only consumers in practice are interpolation
2940
+ # `"#<#{self.class}>"` and `.to_s.split` chains, both of
2941
+ # which work fine on a plain string).
2942
+ if recv >= 0 && mname == "class"
2943
+ rt = infer_type(recv)
2944
+ if is_obj_type(rt) == 1
2945
+ return "string"
2946
+ end
2947
+ end
2948
+
2937
2949
  # `recv.__sp_ieval_<N>(...)`: the rewritten form of an
2938
2950
  # `recv.instance_eval { ... }` call. v1 only fired on top-level call
2939
2951
  # sites, where the call's value was always discarded — so its return
@@ -3994,6 +4006,16 @@ class Compiler
3994
4006
  end
3995
4007
  return "int_array"
3996
4008
  end
4009
+ # `replace(other)` returns the receiver, not a fresh array;
4010
+ # the inferred result must therefore preserve the receiver's
4011
+ # array type so that an expression-form `c = a.replace(b)`
4012
+ # still tags `c` as `int_array` (or whatever `a` is) rather
4013
+ # than falling through to `int`.
4014
+ if mname == "replace"
4015
+ if recv >= 0
4016
+ return infer_type(recv)
4017
+ end
4018
+ end
3997
4019
  if mname == "pop"
3998
4020
  if recv >= 0
3999
4021
  rt = infer_type(recv)
@@ -4643,7 +4665,7 @@ class Compiler
4643
4665
  if mname == "read" || mname == "binread"
4644
4666
  return "string"
4645
4667
  end
4646
- if mname == "exist?"
4668
+ if mname == "exist?" || mname == "readable?"
4647
4669
  return "bool"
4648
4670
  end
4649
4671
  if mname == "join"
@@ -17819,6 +17841,14 @@ class Compiler
17819
17841
 
17820
17842
  # ---- Forward declarations ----
17821
17843
  def emit_forward_decls
17844
+ # ARGV (sp_argv) is referenced from any function that uses
17845
+ # `ARGV.length` / `ARGV[i]`, so the declaration has to precede
17846
+ # all function bodies — not just main()'s. emit_main keeps the
17847
+ # runtime initialization (`sp_argv.len = argc - 1; ...`) where
17848
+ # main() can fill it in from `argc` / `argv`.
17849
+ emit_raw("typedef struct{const char**data;mrb_int len;}sp_Argv;")
17850
+ emit_raw("static sp_Argv sp_argv;")
17851
+ emit_raw("")
17822
17852
  # Emit block helper functions accumulated during collection
17823
17853
  if @block_funcs != ""
17824
17854
  emit_raw(@block_funcs)
@@ -20806,9 +20836,6 @@ class Compiler
20806
20836
 
20807
20837
  def emit_main
20808
20838
  stmts = get_body_stmts(@root_id)
20809
- emit_raw("typedef struct{const char**data;mrb_int len;}sp_Argv;")
20810
- emit_raw("static sp_Argv sp_argv;")
20811
- emit_raw("")
20812
20839
  emit_raw("int main(int argc,char**argv){")
20813
20840
  emit_raw(" sp_argv.len=argc-1;sp_argv.data=(const char**)malloc(sizeof(const char*)*(argc>1?argc-1:1));{int _i;for(_i=0;_i<sp_argv.len;_i++)sp_argv.data[_i]=sp_str_dup_external(argv[_i+1]);}")
20814
20841
  if @needs_rand == 1
@@ -21303,7 +21330,16 @@ class Compiler
21303
21330
  exit(1)
21304
21331
  end
21305
21332
  if t == "IntegerNode"
21306
- return @nd_value[nid].to_s
21333
+ # Suffix with `LL` so the literal is `long long` rather than
21334
+ # `int` in the emitted C. Without it, an expression of int
21335
+ # literals whose product exceeds int32 (e.g. APU mixer
21336
+ # constant `24329 * 256 * 500 = 3,114,112,000`) overflows
21337
+ # at constant-folding time and the C compiler emits a wrong
21338
+ # value. Plain literals at runtime sites get implicitly
21339
+ # promoted to mrb_int (= long long) on assignment, but
21340
+ # constant-init expressions are evaluated by the C compiler
21341
+ # using the literals' declared type.
21342
+ return @nd_value[nid].to_s + "LL"
21307
21343
  end
21308
21344
  if t == "FloatNode"
21309
21345
  return @nd_content[nid]
@@ -26285,6 +26321,37 @@ class Compiler
26285
26321
  end
26286
26322
  return "sp_IntArray_sort(" + rc + ")"
26287
26323
  end
26324
+ # `pack("C*")` — the only format Spinel implements: each
26325
+ # int element is written as a byte into a freshly allocated
26326
+ # NUL-terminated string. Sufficient for the optcarrot save-
26327
+ # RAM `@wrk.pack("C*")` shape and the symmetric inverse of
26328
+ # `String#bytes` we already support. Other format strings
26329
+ # fall through to the unresolved-call warning.
26330
+ if mname == "pack" && recv_type == "int_array"
26331
+ args_id = @nd_arguments[nid]
26332
+ if args_id >= 0
26333
+ a_pk = get_args(args_id)
26334
+ if a_pk.length == 1 && @nd_type[a_pk[0]] == "StringNode" && @nd_content[a_pk[0]] == "C*"
26335
+ tmp = new_temp
26336
+ ntmp = new_temp
26337
+ itmp = new_temp
26338
+ emit(" mrb_int " + ntmp + " = sp_IntArray_length(" + rc + ");")
26339
+ emit(" char *" + tmp + " = sp_str_alloc(" + ntmp + ");")
26340
+ emit(" for (mrb_int " + itmp + " = 0; " + itmp + " < " + ntmp + "; " + itmp + "++) " + tmp + "[" + itmp + "] = (char)sp_IntArray_get(" + rc + ", " + itmp + ");")
26341
+ emit(" " + tmp + "[" + ntmp + "] = 0;")
26342
+ return tmp
26343
+ end
26344
+ end
26345
+ end
26346
+ # `replace(other)` in expression position: the stmt-form
26347
+ # arm in compile_*_stmt only fires when the call's value is
26348
+ # discarded; in expression position (e.g. last stmt of a
26349
+ # method body, or rvalue of an assignment) we still need to
26350
+ # emit the side effect *and* yield the receiver as the
26351
+ # value. Use the comma operator so the result is `rc`.
26352
+ if mname == "replace" && recv_type == "int_array"
26353
+ return "(sp_IntArray_replace(" + rc + ", " + compile_arg0(nid) + "), " + rc + ")"
26354
+ end
26288
26355
  # take_while / drop_while: block-driven prefix scan. take_while
26289
26356
  # collects elements from the front while the block stays truthy;
26290
26357
  # drop_while skips them and returns the rest. Mirrors the
@@ -26539,6 +26606,14 @@ class Compiler
26539
26606
  if mname == "difference"
26540
26607
  return "sp_FloatArray_difference(" + rc + ", " + compile_arg0(nid) + ")"
26541
26608
  end
26609
+ if mname == "sort"
26610
+ # Non-bang: yield a fresh sorted copy. Mirror the
26611
+ # `sp_FloatArray_shuffle` pattern (new + replace) so the
26612
+ # source array is left untouched.
26613
+ tmp = new_temp
26614
+ emit(" sp_FloatArray *" + tmp + " = sp_FloatArray_new(); sp_FloatArray_replace(" + tmp + ", " + rc + "); sp_FloatArray_sort_bang(" + tmp + ");")
26615
+ return tmp
26616
+ end
26542
26617
  end
26543
26618
  if is_ptr_array_type(recv_type) == 1
26544
26619
  elem_type = ptr_array_elem_type(recv_type)
@@ -27529,9 +27604,32 @@ class Compiler
27529
27604
  if mname == "exist?"
27530
27605
  return "sp_file_exist(" + compile_arg0(nid) + ")"
27531
27606
  end
27607
+ if mname == "readable?"
27608
+ # Reuse the exist check: on every platform Spinel
27609
+ # targets, a fopen-able file is also readable from
27610
+ # the same process. Distinguishing the two would
27611
+ # need access(2), which isn't worth a runtime fn for
27612
+ # the dead-code optcarrot battery-save path that
27613
+ # surfaced this.
27614
+ return "sp_file_exist(" + compile_arg0(nid) + ")"
27615
+ end
27532
27616
  if mname == "delete"
27533
27617
  return "(sp_file_delete(" + compile_arg0(nid) + "), 0)"
27534
27618
  end
27619
+ if mname == "binwrite"
27620
+ # NOTE: `sp_file_write` uses fputs, so an embedded NUL
27621
+ # in the payload truncates the write — fine for the
27622
+ # optcarrot save-RAM dead-code path that surfaced
27623
+ # this, not safe for general binary use.
27624
+ args_id_bw = @nd_arguments[nid]
27625
+ if args_id_bw >= 0
27626
+ a_bw = get_args(args_id_bw)
27627
+ if a_bw.length >= 2
27628
+ return "(sp_file_write(" + compile_expr(a_bw[0]) + ", " + compile_expr(a_bw[1]) + "), 0)"
27629
+ end
27630
+ end
27631
+ return "0"
27632
+ end
27535
27633
  if mname == "join"
27536
27634
  args_id = @nd_arguments[nid]
27537
27635
  if args_id >= 0
@@ -27864,6 +27962,16 @@ class Compiler
27864
27962
  if @cls_is_value_type[ci] == 1
27865
27963
  arrow = "."
27866
27964
  end
27965
+ # `<obj>.class` — collapse to the class name as a string
27966
+ # literal. Spinel doesn't allocate runtime Class objects,
27967
+ # but the typical consumers (interpolation in inspect,
27968
+ # `.to_s.split("::").last` shape) all treat the result as
27969
+ # a string. Underscore-flattened nested module names
27970
+ # (`Optcarrot_NES`) render as-is rather than the original
27971
+ # `Optcarrot::NES` form; the loss is cosmetic.
27972
+ if mname == "class"
27973
+ return c_string_literal(cname)
27974
+ end
27867
27975
  # attr_reader
27868
27976
  readers = @cls_attr_readers[ci].split(";")
27869
27977
  j = 0
@@ -0,0 +1,20 @@
1
+ # `File.readable?` and `File.binwrite` — class-method stubs in
2
+ # the `rcname == "File"` dispatch block, plus `IntArray#pack("C*")`
3
+ # which is the symmetric inverse of `String#bytes`.
4
+ #
5
+ # Surfaced via optcarrot's battery-save dead code path:
6
+ # `return unless File.readable?(sav)` and
7
+ # `File.binwrite(sav, @wrk.pack("C*"))`.
8
+ #
9
+ # `readable?` reuses the exist check (close enough on POSIX).
10
+ # `binwrite` reuses `sp_file_write` — fputs-based, so embedded
11
+ # NULs truncate; acceptable for the NUL-free use sites that
12
+ # exist in practice.
13
+
14
+ path = "/tmp/spinel_file_class_test"
15
+
16
+ File.binwrite(path, [72, 105, 33].pack("C*")) # "Hi!"
17
+ puts File.readable?(path)
18
+ puts File.read(path)
19
+ File.delete(path)
20
+ puts File.exist?(path)
@@ -0,0 +1,3 @@
1
+ true
2
+ Hi!
3
+ false
@@ -0,0 +1,18 @@
1
+ # `Array#sort` (non-bang) on a float array. Surfaced via
2
+ # optcarrot's `@fps_history.sort[(@fps_history.length * 0.05).floor]`
3
+ # p95 calculation. Mirrors the existing int_array sort path —
4
+ # yields a fresh sorted array; the source stays untouched.
5
+
6
+ a = [3.5, 1.25, 2.0, 0.75, 4.125]
7
+ b = a.sort
8
+ puts b.length
9
+
10
+ i = 0
11
+ while i < b.length
12
+ puts b[i]
13
+ i = i + 1
14
+ end
15
+
16
+ # Source unchanged.
17
+ puts a[0]
18
+ puts a[1]
@@ -0,0 +1,8 @@
1
+ 5
2
+ 0.75
3
+ 1.25
4
+ 2.0
5
+ 3.5
6
+ 4.125
7
+ 3.5
8
+ 1.25
@@ -0,0 +1,23 @@
1
+ # `Array#replace` in *expression* position on an int_array.
2
+ # The stmt-form arm has long supported this; the expr-form was
3
+ # missing — unresolved-call warning + literal `0` emitted.
4
+ # Surfaced via optcarrot's `def load_battery; ...; @wrk.replace(sav.bytes); end`,
5
+ # where the call sits at the tail of the method (its value is
6
+ # the implicit return).
7
+
8
+ a = [1, 2, 3]
9
+ b = [10, 20, 30, 40]
10
+
11
+ # Expression position: assigned. Should yield the (mutated) `a`.
12
+ c = a.replace(b)
13
+
14
+ # `a` mutated in place
15
+ puts a[0]
16
+ puts a[1]
17
+ puts a[2]
18
+ puts a[3]
19
+ puts a.length
20
+
21
+ # `c` is the same array (replace returns the receiver)
22
+ puts c[0]
23
+ puts c.length
@@ -0,0 +1,7 @@
1
+ 10
2
+ 20
3
+ 30
4
+ 40
5
+ 4
6
+ 10
7
+ 4
@@ -0,0 +1,29 @@
1
+ # Spinel's integers are mrb_int (long long); Ruby integers don't
2
+ # overflow in the same range. But emitting a plain `24329 * 256 *
3
+ # 500` in C lets the C compiler fold the constant using `int`
4
+ # arithmetic, overflowing at 3,114,112,000 to a wrong negative
5
+ # result on 32-bit-int platforms.
6
+ #
7
+ # Surfaced via optcarrot's `APU::Mixer::TND_1 = 100 * 24329 * 256
8
+ # / 500`-shaped constant expressions whose intermediate products
9
+ # clear int32 max. Same hazard for any constant-init expression
10
+ # of integer literals whose folded result exceeds INT_MAX.
11
+ #
12
+ # Fix: emit the `LL` suffix on every IntegerNode literal so
13
+ # operands carry `long long` type at the C level. Runtime
14
+ # expressions assigned to `mrb_int` slots already promoted
15
+ # implicitly; only constant-init contexts (where the C compiler
16
+ # folds before any assignment) saw the overflow.
17
+
18
+ # Each chain straddles int32: max 2^31 - 1 = 2,147,483,647.
19
+ A = 24329 * 256 * 500 # 3,114,112,000
20
+ B = 100000 * 100000 # 10,000,000,000
21
+ C = 65535 * 65535 # 4,294,836,225
22
+ D = 2 ** 40 # 1,099,511,627,776
23
+ E = 1_000_000_000 + 1_000_000_000 # 2,000,000,000 — sum overflow
24
+
25
+ puts A
26
+ puts B
27
+ puts C
28
+ puts D
29
+ puts E
@@ -0,0 +1,5 @@
1
+ 3114112000
2
+ 10000000000
3
+ 4294836225
4
+ 1099511627776
5
+ 2000000000
@@ -0,0 +1,24 @@
1
+ # `<user_obj>.class` collapses to the class name as a string.
2
+ # Spinel doesn't allocate runtime Class objects, so the only
3
+ # coherent representation for `obj.class` is the textual name.
4
+ # The two real-world consumers in optcarrot are:
5
+ # 1. `"#<#{ self.class }>"`-style interpolation in `inspect`
6
+ # 2. `self.class.to_s.split("::").last`-style introspection
7
+ # Both work fine if `.class` simply yields the class name as a
8
+ # string (the second loses the `::` namespace because Spinel
9
+ # flattens nested modules with `_`, but that's a cosmetic loss).
10
+
11
+ class Foo
12
+ def to_s
13
+ "<" + self.class + ">"
14
+ end
15
+ end
16
+
17
+ class Bar
18
+ def label
19
+ self.class.to_s
20
+ end
21
+ end
22
+
23
+ puts Foo.new.to_s # => <Foo>
24
+ puts Bar.new.label # => Bar
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubyduino
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joseph Schito
@@ -345,6 +345,10 @@ files:
345
345
  - vendor/spinel/test/fiber_yield_across_method_call.rb.expected
346
346
  - vendor/spinel/test/file_basename_gc.rb
347
347
  - vendor/spinel/test/file_basename_gc.rb.expected
348
+ - vendor/spinel/test/file_class_methods_int_recv.rb
349
+ - vendor/spinel/test/file_class_methods_int_recv.rb.expected
350
+ - vendor/spinel/test/float_array_sort.rb
351
+ - vendor/spinel/test/float_array_sort.rb.expected
348
352
  - vendor/spinel/test/forward_call_class_method_inherited_init.rb
349
353
  - vendor/spinel/test/forward_call_class_method_inherited_init.rb.expected
350
354
  - vendor/spinel/test/forward_call_class_method_inherited_init_int_array.rb
@@ -407,6 +411,8 @@ files:
407
411
  - vendor/spinel/test/inspect.rb.expected
408
412
  - vendor/spinel/test/instance_eval_trampoline.rb
409
413
  - vendor/spinel/test/instance_eval_trampoline.rb.expected
414
+ - vendor/spinel/test/int_array_replace_expr.rb
415
+ - vendor/spinel/test/int_array_replace_expr.rb.expected
410
416
  - vendor/spinel/test/int_bracket_skip_sym_idx.rb
411
417
  - vendor/spinel/test/int_bracket_skip_sym_idx.rb.expected
412
418
  - vendor/spinel/test/int_keyed_hash_lookup_as_array.rb
@@ -417,6 +423,8 @@ files:
417
423
  - vendor/spinel/test/integer_div.rb.expected
418
424
  - vendor/spinel/test/integer_div_by_zero.rb
419
425
  - vendor/spinel/test/integer_div_by_zero.rb.expected
426
+ - vendor/spinel/test/integer_literal_no_int32_overflow.rb
427
+ - vendor/spinel/test/integer_literal_no_int32_overflow.rb.expected
420
428
  - vendor/spinel/test/interp_method_widening.rb
421
429
  - vendor/spinel/test/interp_method_widening.rb.expected
422
430
  - vendor/spinel/test/interp_symbol.rb
@@ -560,6 +568,8 @@ files:
560
568
  - vendor/spinel/test/nullable.rb.expected
561
569
  - vendor/spinel/test/obj_array.rb
562
570
  - vendor/spinel/test/obj_array.rb.expected
571
+ - vendor/spinel/test/obj_class_returns_string.rb
572
+ - vendor/spinel/test/obj_class_returns_string.rb.expected
563
573
  - vendor/spinel/test/object_new_sentinel.rb
564
574
  - vendor/spinel/test/object_new_sentinel.rb.expected
565
575
  - vendor/spinel/test/object_truthy.rb