Module: Spinel::Json
- Defined in:
- lib/fresco/runtime/runtime.rb
Overview
Hand-rolled JSON encoder. Scalars and Arrays dispatch through the polymorphic ‘encode`; Hashes are handled by `encode_hash`, which is pinned to `Hash<Symbol, *>` (`SymPolyHash`) via a default-arg type hint and a single-call-site discipline.
Spinel limitation: ‘.each` lowering on a polymorphic-typed (`sp_RbVal`) hash only emits array-tag branches — a Hash there silently iterates zero times. The analyzer also widens `encode_hash`’s parameter to ‘sp_RbVal` if there’s any call site where the argument is polymorphic. So ‘encode_hash` is called only from `Response.json` (where `body` is a concrete SymPolyHash), and `encode`’s ‘value.is_a?(Hash)` branch recurses through it via an implicit `(sp_SymPolyHash *)` cast that Spinel inserts at the call site. Nested hashes within Arrays / Hash values therefore only encode their first level correctly when they happen to be SymPolyHash; deeper nesting through a polymorphic-typed value will need either a Spinel codegen change or per-shape encoders.
Class Method Summary collapse
- .encode(value) ⇒ Object
-
.encode_hash(h = { __t: nil }) ⇒ Object
Hash branch lives in its own function so the ‘h = { __t: nil }` default pins `h` to `SymPolyHash` for Spinel’s analyzer.
-
.encode_recursive_hash(h = { __t: nil }) ⇒ Object
CRuby-correct, Spinel-degraded sibling of ‘encode_hash`.
-
.encode_string(s) ⇒ Object
Char-by-char concat (plan quirk: stick to the existing parsers’ shape rather than ‘gsub` chains).
Class Method Details
.encode(value) ⇒ Object
857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 |
# File 'lib/fresco/runtime/runtime.rb', line 857 def self.encode(value) return "null" if value.nil? return "true" if value == true return "false" if value == false return value.to_s if value.is_a?(Integer) return value.to_s if value.is_a?(Float) return encode_string(value) if value.is_a?(String) return encode_string(value.to_s) if value.is_a?(Symbol) if value.is_a?(Array) out = "[" i = 0 while i < value.length out += "," if i > 0 out += encode(value[i]) i += 1 end return out + "]" end # Polymorphic Hash recursion path. Under CRuby this iterates # correctly; under Spinel the `.each` lowering on a RbVal-typed # hash only emits array-tag branches and silently produces # `"{}"`. The two implementations diverge here on purpose — # CRuby (bin/dev) reads payloads as written, while Spinel-built # binaries should pre-encode nested hashes with `encode_hash` # and concatenate the outer JSON manually if they need it. return encode_recursive_hash(value) if value.is_a?(Hash) encode_string(value.to_s) end |
.encode_hash(h = { __t: nil }) ⇒ Object
Hash branch lives in its own function so the ‘h = { __t: nil }` default pins `h` to `SymPolyHash` for Spinel’s analyzer. Inline iteration on a polymorphic ‘value` inside `encode` would lower to array-only branches; passing through a typed parameter forces a real hash walk. The default `{ __t: nil }` is never visible —the function is only ever called with an actual hash from the `value.is_a?(Hash)` branch in `encode`.
911 912 913 914 915 916 917 918 919 920 921 |
# File 'lib/fresco/runtime/runtime.rb', line 911 def self.encode_hash(h = { __t: nil }) out = "{" first = true h.each do |k, v| out += "," unless first first = false encoded_value = encode(v) out += encode_string(k.to_s) + ":" + encoded_value end out + "}" end |
.encode_recursive_hash(h = { __t: nil }) ⇒ Object
CRuby-correct, Spinel-degraded sibling of ‘encode_hash`. Lives behind a different name so `encode_hash` stays single-caller and pinned to `SymPolyHash`; this one is the polymorphic recursion target from `encode` and Spinel widens its parameter to RbVal, breaking the iteration. Kept for CRuby parity so `bin/dev` and unit tests still behave as expected.
892 893 894 895 896 897 898 899 900 901 902 |
# File 'lib/fresco/runtime/runtime.rb', line 892 def self.encode_recursive_hash(h = { __t: nil }) out = "{" first = true h.each do |k, v| out += "," unless first first = false encoded_value = encode(v) out += encode_string(k.to_s) + ":" + encoded_value end out + "}" end |
.encode_string(s) ⇒ Object
Char-by-char concat (plan quirk: stick to the existing parsers’ shape rather than ‘gsub` chains). gsub-with-string-replacement interprets backslash backreferences under CRuby — encoding the JSON `\` escape requires four-deep escaping that doesn’t carry to Spinel. The walk sidesteps all of that and stays monomorphic. Control chars below 0x20 other than the named escapes pass through unescaped; an MVP encoder for app-built payloads doesn’t need the full u00XX table.
931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 |
# File 'lib/fresco/runtime/runtime.rb', line 931 def self.encode_string(s) out = "\"" i = 0 n = s.length while i < n c = s[i] if c == "\\" out += "\\\\" elsif c == "\"" out += "\\\"" elsif c == "\n" out += "\\n" elsif c == "\r" out += "\\r" elsif c == "\t" out += "\\t" elsif c == "\b" out += "\\b" elsif c == "\f" out += "\\f" else out += c end i += 1 end out + "\"" end |