ruby_wasm_ic_minimal 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: cc18f5b18fe43d51c78dccd18e8065cb1dc22d3ed9dbfcec7e03e34c84cbeb99
4
+ data.tar.gz: 310d256e55b950d1091d0093f90707a189bccde91063171dda295294e4a4a985
5
+ SHA512:
6
+ metadata.gz: 8d1d198fd172fc2fda92b76d7da1e1ed8c3de301cc90b5ccd25f3e661bbf448c3cdb546388b58d8d2d2da040d2501fa5b041d92f8d81896abdac74c5fa5b3ff2
7
+ data.tar.gz: 4173adeef1c2ee9435dcc27bbb1c110f07aaad4a9f4d744737f8f4ce898a7bb4220612c791f935ad809561115c69685679b0ad5b3070f1e2c2b21187e25b738f
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 the ruby_wasm_builder authors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,64 @@
1
+ # ruby_wasm_ic_minimal
2
+
3
+ Pure-Ruby stand-ins for four things a **`minimal`** Ruby canister lacks, for
4
+ canisters packed by [ruby_wasm_builder](https://gitlab.com/phrug/ruby_wasm_builder)
5
+ on its `minimal` Build. That Build links no C extension and defines no
6
+ `Encoding` constant, and Rack and Roda need all four:
7
+
8
+ | file | stands in for | why Rack needs it |
9
+ | --- | --- | --- |
10
+ | `encoding_constants.rb` | `Encoding::UTF_8`, `US_ASCII`, `ASCII_8BIT` (and `BINARY`, `ASCII`) | it decodes every query parameter as UTF-8 |
11
+ | `stringio.rb` | `StringIO` (a C extension in CRuby) | `rack.input`, `Rack::MockRequest` |
12
+ | `strscan.rb` | `StringScanner` (a C extension) | the multipart parser, loaded by `Rack::Request` |
13
+ | `date.rb` | `date` (its work is the C extension `date_core`) | only because `time` requires it; **a stub** |
14
+
15
+ `stringio.rb` and `strscan.rb` are checked against CRuby's C extensions on
16
+ MRI, operation by operation, and `StringScanner` keeps Ruby 3.3's meaning (a
17
+ `String` in `scan_until` is a `TypeError`). `date.rb` is a stub: Rack only
18
+ formats and reads HTTP dates, which need nothing from `Date`; `Time.parse`,
19
+ `Time.strptime` and `Date` itself raise `NotImplementedError` when called.
20
+
21
+ ## Only in a `minimal` canister
22
+
23
+ **Do not put this gem in a Gemfile.** Each file shadows a real library by its
24
+ name, and `date.rb` is a stub. So every file refuses to load anywhere but a
25
+ `minimal` canister, raising `LoadError`. A `minimal` canister is one where the
26
+ builder's wrapper gem has defined `IC::DATA_DIR` and the core knows no more
27
+ than its three built-in encodings (`Encoding.list.size <= 3`). On the `full`
28
+ Build, which links the real libraries and every encoding, it refuses too:
29
+ carry none of it there.
30
+
31
+ ## Carrying it
32
+
33
+ Copy its `lib/` into your Application and put it first on the load path,
34
+ before Rack is required:
35
+
36
+ ```bash
37
+ gem fetch ruby_wasm_ic_minimal && gem unpack ruby_wasm_ic_minimal-*.gem
38
+ cp -r ruby_wasm_ic_minimal-*/lib my_app/vendor/ruby_wasm_ic_minimal
39
+ ```
40
+
41
+ ```ruby
42
+ # app.rb
43
+ here = __dir__
44
+ $LOAD_PATH.unshift(File.join(here, "vendor/ruby_wasm_ic_minimal"), File.join(here, "vendor/rack"))
45
+ require "encoding_constants"
46
+ require "stringio"
47
+ require "rack"
48
+ ```
49
+
50
+ `require "ruby_wasm_ic_minimal"` loads all four at once. Otherwise Rack's own
51
+ `require "strscan"` and `time`'s `require "date"` find them by name.
52
+
53
+ ## Development
54
+
55
+ The gem lives in the ruby_wasm_builder repository, `gems/ruby_wasm_ic_minimal/`. Its
56
+ specs are in that repository's `spec/gems/`, and run with the builder's suite,
57
+ including the 0.7.0 `roda_on_minimal` Script played as `minimal` with this
58
+ gem in place of the Challenge's own `carried/` files.
59
+
60
+ **Not yet observed on a canister:** that `Encoding.list.size` is 3 or less on
61
+ `minimal`. The first deploy that carries this gem checks it; if the guard
62
+ refuses there, the rule is what needs changing.
63
+
64
+ MIT licensed.
data/lib/date.rb ADDED
@@ -0,0 +1,22 @@
1
+ # date.rb (ruby_wasm_ic_minimal): a stand-in for CRuby's `date`, whose work is done by the
2
+ # C extension date_core, which the `minimal` Build does not link. `time`
3
+ # requires `date` at load, and Rack requires `time`, so without this file
4
+ # `require "rack"` fails. Rack only formats and reads HTTP dates
5
+ # (Time#httpdate, Time.httpdate), and those need nothing from Date.
6
+ #
7
+ # What needs a real Date fails here, loudly and only when called:
8
+ # Time.parse and Time.strptime (through Date._parse and Date._strptime), and
9
+ # Date itself. Under `full` this file is not carried.
10
+ require_relative "ruby_wasm_ic_minimal/guard"
11
+ RubyWasmIcMinimal.guard!("date")
12
+ class Date
13
+ def self.unavailable(what)
14
+ raise NotImplementedError, "#{what} needs the date C extension, which the minimal Build lacks"
15
+ end
16
+
17
+ def self._parse(*) = unavailable("Date._parse (Time.parse)")
18
+ def self._strptime(*) = unavailable("Date._strptime (Time.strptime)")
19
+ def self.new(*) = unavailable("Date.new")
20
+ def self.today(*) = unavailable("Date.today")
21
+ def self.parse(*) = unavailable("Date.parse")
22
+ end
@@ -0,0 +1,16 @@
1
+ # encoding_constants.rb (ruby_wasm_ic_minimal): the Encoding constants a canister lacks.
2
+ # The `minimal` Build has three encodings, UTF-8, US-ASCII and ASCII-8BIT,
3
+ # and no constant naming any of them (ADR-0060). Rack names
4
+ # Encoding::UTF_8 to decode every query parameter, and `cgi/util` names it as
5
+ # it loads. Each is found here from a literal, without naming a constant:
6
+ # a "\u" escape is always UTF-8, a Symbol's text US-ASCII, String#b binary.
7
+ # Only these three: there is no transcoding to any other (ADR-0053).
8
+ require_relative "ruby_wasm_ic_minimal/guard"
9
+ RubyWasmIcMinimal.guard!("encoding_constants")
10
+ class Encoding
11
+ UTF_8 = "\u0000".encoding unless const_defined?(:UTF_8, false)
12
+ US_ASCII = :a.to_s.encoding unless const_defined?(:US_ASCII, false)
13
+ ASCII_8BIT = "".b.encoding unless const_defined?(:ASCII_8BIT, false)
14
+ BINARY = ASCII_8BIT unless const_defined?(:BINARY, false)
15
+ ASCII = US_ASCII unless const_defined?(:ASCII, false)
16
+ end
@@ -0,0 +1,24 @@
1
+ # The guard every stand-in in ruby_wasm_ic_minimal requires first. A stand-in shadows a
2
+ # real library by its name (`date`, `stringio`, `strscan`), and `date.rb` here
3
+ # is a stub, so loading one anywhere but a `minimal` canister would quietly
4
+ # take the real library away. The guard refuses that instead, with LoadError.
5
+ #
6
+ # A `minimal` canister is one where the wrapper gem has defined IC::DATA_DIR
7
+ # (every canister packed by ruby_wasm_builder) and the core knows no more than
8
+ # its three built-in encodings (UTF-8, US-ASCII, ASCII-8BIT). It counts the
9
+ # encodings rather than looking for Encoding::UTF_8, which this gem's own
10
+ # encoding_constants.rb defines. Decided once, at the first stand-in loaded,
11
+ # and kept in RubyWasmIcMinimal::ON_MINIMAL; a test harness may set that constant
12
+ # first (the builder's MRI player does, for `minimal: true`).
13
+ module RubyWasmIcMinimal
14
+ unless const_defined?(:ON_MINIMAL, false)
15
+ ON_MINIMAL = !defined?(IC::DATA_DIR).nil? && Encoding.list.size <= 3
16
+ end
17
+
18
+ def self.guard!(feature)
19
+ return if ON_MINIMAL
20
+
21
+ raise LoadError, "ruby_wasm_ic_minimal's #{feature} is a stand-in only for a minimal canister, and this is not " \
22
+ "one; remove ruby_wasm_ic_minimal from $LOAD_PATH so the real #{feature} loads"
23
+ end
24
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyWasmIcMinimal
4
+ VERSION = "0.1.1"
5
+ end
@@ -0,0 +1,10 @@
1
+ # ruby_wasm_ic_minimal: pure-Ruby stand-ins for what Rack and Roda need and a `minimal`
2
+ # canister lacks, for canisters packed by ruby_wasm_builder on its `minimal`
3
+ # Build only. Requiring this file loads all four; each can be required alone
4
+ # by its library's own name. Each refuses to load anywhere else (see
5
+ # ruby_wasm_ic_minimal/guard.rb).
6
+ require_relative "ruby_wasm_ic_minimal/version"
7
+ require_relative "encoding_constants"
8
+ require_relative "stringio"
9
+ require_relative "strscan"
10
+ require_relative "date"
data/lib/stringio.rb ADDED
@@ -0,0 +1,270 @@
1
+ # stringio.rb (ruby_wasm_ic_minimal): a pure-Ruby StringIO for the `minimal` Build, where
2
+ # CRuby's is a C extension it does not link. Rack names it for request bodies
3
+ # (rack.input) and in Rack::MockRequest; an Application may use it as any
4
+ # in-memory IO.
5
+ #
6
+ # The common reading and writing API, by bytes, with CRuby's meaning. Not
7
+ # here: the encoding conversions (set_encoding only tags), fcntl, and the
8
+ # *_nonblock methods.
9
+ require_relative "ruby_wasm_ic_minimal/guard"
10
+ RubyWasmIcMinimal.guard!("stringio")
11
+ class StringIO
12
+ include Enumerable
13
+
14
+ attr_reader :string, :lineno
15
+ attr_writer :lineno
16
+
17
+ def self.open(*args)
18
+ io = new(*args)
19
+ return io unless block_given?
20
+
21
+ begin
22
+ yield io
23
+ ensure
24
+ io.close
25
+ end
26
+ end
27
+
28
+ def initialize(string = +"", mode = nil)
29
+ @string = string
30
+ @pos = 0
31
+ @lineno = 0
32
+ @closed_read = @closed_write = false
33
+ @readonly = string.frozen? || mode.to_s.start_with?("r") && !mode.to_s.include?("+")
34
+ @string.replace("") if mode.to_s.start_with?("w") && !@string.frozen?
35
+ @pos = @string.bytesize if mode.to_s.start_with?("a")
36
+ end
37
+
38
+ def string=(s)
39
+ @string = s
40
+ @pos = 0
41
+ @lineno = 0
42
+ s
43
+ end
44
+
45
+ def pos = @pos
46
+ alias tell pos
47
+
48
+ def pos=(n)
49
+ raise Errno::EINVAL, "negative position" if n.negative?
50
+
51
+ @pos = n
52
+ end
53
+
54
+ def seek(offset, whence = IO::SEEK_SET)
55
+ base = case whence
56
+ when IO::SEEK_SET, :SET then 0
57
+ when IO::SEEK_CUR, :CUR then @pos
58
+ when IO::SEEK_END, :END then @string.bytesize
59
+ else raise Errno::EINVAL, "invalid whence"
60
+ end
61
+ self.pos = base + offset
62
+ 0
63
+ end
64
+
65
+ def rewind
66
+ @pos = 0
67
+ @lineno = 0
68
+ 0
69
+ end
70
+
71
+ def size = @string.bytesize
72
+ alias length size
73
+ def eof? = @pos >= @string.bytesize
74
+ alias eof eof?
75
+
76
+ def read(length = nil, buffer = nil)
77
+ readable!
78
+ if length.nil?
79
+ out = (@string.byteslice(@pos, @string.bytesize - @pos) || "").dup
80
+ @pos = [@pos, @string.bytesize].max
81
+ out.force_encoding(@string.encoding)
82
+ else
83
+ raise ArgumentError, "negative length #{length} given" if length.negative?
84
+
85
+ out = (@string.byteslice(@pos, length) || "").b
86
+ @pos += out.bytesize
87
+ return (buffer&.replace("") && nil) if length.positive? && out.empty?
88
+ end
89
+ buffer ? buffer.replace(out) : out
90
+ end
91
+
92
+ def read_nonblock(length, buffer = nil, exception: true)
93
+ out = read(length, buffer)
94
+ return out unless out.nil?
95
+ raise EOFError, "end of file reached" if exception
96
+
97
+ nil
98
+ end
99
+
100
+ def readpartial(length, buffer = nil)
101
+ out = read(length, buffer)
102
+ raise EOFError, "end of file reached" if out.nil?
103
+
104
+ out
105
+ end
106
+
107
+ def getc
108
+ readable!
109
+ return nil if eof?
110
+
111
+ ch = (@string.byteslice(@pos, @string.bytesize - @pos) || "").force_encoding(@string.encoding)[0]
112
+ @pos += ch.bytesize
113
+ ch
114
+ end
115
+
116
+ def getbyte
117
+ readable!
118
+ return nil if eof?
119
+
120
+ b = @string.getbyte(@pos)
121
+ @pos += 1
122
+ b
123
+ end
124
+
125
+ def each_byte
126
+ return enum_for(:each_byte) unless block_given?
127
+
128
+ while (b = getbyte)
129
+ yield b
130
+ end
131
+ self
132
+ end
133
+
134
+ def ungetc(s)
135
+ readable!
136
+ return nil if s.nil?
137
+
138
+ s = s.chr if s.is_a?(Integer)
139
+ if @pos.zero?
140
+ @string.prepend(s)
141
+ else
142
+ @pos -= s.bytesize
143
+ @string.bytesplice(@pos, s.bytesize, s)
144
+ end
145
+ nil
146
+ end
147
+ alias ungetbyte ungetc
148
+
149
+ def gets(sep = $/, limit = nil, chomp: false)
150
+ readable!
151
+ sep, limit = nil, sep if sep.is_a?(Integer)
152
+ return nil if eof?
153
+
154
+ rest = (@string.byteslice(@pos, @string.bytesize - @pos) || "").force_encoding(@string.encoding)
155
+ line =
156
+ if sep.nil?
157
+ rest
158
+ elsif sep.empty?
159
+ rest.sub!(/\A\n+/) { |nl| @pos += nl.bytesize; "" }
160
+ (i = rest.index(/\n\n+/)) ? rest[0, i] + rest[i..][/\A\n+/] : rest
161
+ else
162
+ (i = rest.index(sep)) ? rest[0, i + sep.length] : rest
163
+ end
164
+ line = line.byteslice(0, limit) if limit && limit >= 0 && line.bytesize > limit
165
+ @pos += line.bytesize
166
+ @lineno += 1
167
+ $_ = chomp && sep ? line.chomp(sep.empty? ? "\n" : sep) : line
168
+ end
169
+
170
+ def readline(*args, **kw) = gets(*args, **kw) || raise(EOFError, "end of file reached")
171
+
172
+ def each_line(sep = $/, limit = nil, chomp: false)
173
+ return enum_for(:each_line, sep, limit, chomp: chomp) unless block_given?
174
+
175
+ while (line = gets(sep, limit, chomp: chomp))
176
+ yield line
177
+ end
178
+ self
179
+ end
180
+ alias each each_line
181
+
182
+ def readlines(*args, **kw) = each_line(*args, **kw).to_a
183
+
184
+ def write(*objs)
185
+ writable!
186
+ objs.sum do |obj|
187
+ s = obj.to_s
188
+ if @pos > @string.bytesize
189
+ @string << ("\0" * (@pos - @string.bytesize))
190
+ end
191
+ @string.bytesplice(@pos, [s.bytesize, @string.bytesize - @pos].min, s.b.force_encoding(@string.encoding))
192
+ @pos += s.bytesize
193
+ s.bytesize
194
+ end
195
+ end
196
+ alias syswrite write
197
+ alias write_nonblock write
198
+
199
+ def <<(obj)
200
+ write(obj)
201
+ self
202
+ end
203
+
204
+ def print(*objs)
205
+ objs = [$_] if objs.empty?
206
+ objs.each { write(_1) }
207
+ write($\) if $\
208
+ nil
209
+ end
210
+
211
+ def puts(*objs)
212
+ return write("\n") && nil if objs.empty?
213
+
214
+ objs.flatten.each do |o|
215
+ s = o.nil? ? "" : o.to_s
216
+ write(s.end_with?("\n") ? s : "#{s}\n")
217
+ end
218
+ nil
219
+ end
220
+
221
+ def printf(fmt, *args) = write(format(fmt, *args)) && nil
222
+ def putc(c) = (write(c.is_a?(Integer) ? (c & 0xff).chr : c.to_s[0]) && c)
223
+
224
+ def truncate(n)
225
+ writable!
226
+ @string.replace(n < @string.bytesize ? @string.byteslice(0, n) : @string + ("\0" * (n - @string.bytesize)))
227
+ 0
228
+ end
229
+
230
+ def close
231
+ @closed_read = @closed_write = true
232
+ nil
233
+ end
234
+
235
+ def close_read = (@closed_read = true) && nil
236
+ def close_write = (@closed_write = true) && nil
237
+ def closed? = @closed_read && @closed_write
238
+ def closed_read? = @closed_read
239
+ def closed_write? = @closed_write
240
+
241
+ def binmode
242
+ @string.force_encoding("".b.encoding) unless @string.frozen?
243
+ self
244
+ end
245
+
246
+ def set_encoding(*) = self
247
+ def external_encoding = @string.encoding
248
+ def internal_encoding = nil
249
+ def sync = true
250
+ def sync=(value)
251
+ value
252
+ end
253
+ def flush = self
254
+ def fsync = 0
255
+ def fileno = nil
256
+ def isatty = false
257
+ alias tty? isatty
258
+ def pid = nil
259
+ def fcntl(*) = raise(NotImplementedError, "fcntl")
260
+
261
+ private
262
+
263
+ def readable!
264
+ raise IOError, "not opened for reading" if @closed_read
265
+ end
266
+
267
+ def writable!
268
+ raise IOError, "not opened for writing" if @closed_write || @readonly
269
+ end
270
+ end
data/lib/strscan.rb ADDED
@@ -0,0 +1,197 @@
1
+ # strscan.rb (ruby_wasm_ic_minimal): a pure-Ruby StringScanner for the `minimal` Build,
2
+ # where CRuby's is a C extension it does not link. Rack's multipart parser
3
+ # requires it at load, so Rack::Request cannot load without it, and it is what
4
+ # reads a multipart body.
5
+ #
6
+ # The common API, with CRuby's meaning: positions are byte offsets, a pattern
7
+ # is matched only at the scan pointer (scan, skip, match?, check; a Regexp
8
+ # or a String) or anywhere after it (*_until, exist?; a Regexp only, as in
9
+ # Ruby 3.3). As in CRuby's default mode, the text begins at the pointer: \A
10
+ # and ^ match there, which Rack's boundary pattern depends on. Not here: the
11
+ # fixed_anchor: mode, named_captures, scan_byte and peek_byte. Checked
12
+ # against CRuby's on MRI, operation by operation.
13
+ require_relative "ruby_wasm_ic_minimal/guard"
14
+ RubyWasmIcMinimal.guard!("strscan")
15
+ class StringScanner
16
+ class Error < StandardError; end
17
+
18
+ attr_reader :string
19
+
20
+ def initialize(string, _dup = false, fixed_anchor: false)
21
+ raise ArgumentError, "fixed_anchor: true is not supported here" if fixed_anchor
22
+
23
+ @string = string
24
+ @pos = 0
25
+ clear
26
+ end
27
+
28
+ def string=(s)
29
+ @string = s
30
+ @pos = 0
31
+ clear
32
+ s
33
+ end
34
+
35
+ def concat(s)
36
+ @string << s
37
+ self
38
+ end
39
+ alias << concat
40
+
41
+ def pos = @pos
42
+
43
+ def pos=(n)
44
+ n += @string.bytesize if n.negative?
45
+ raise RangeError, "index out of range" unless n.between?(0, @string.bytesize)
46
+
47
+ @pos = n
48
+ end
49
+ alias pointer pos
50
+ alias pointer= pos=
51
+
52
+ def charpos = @string.byteslice(0, @pos).length
53
+
54
+ def reset
55
+ @pos = 0
56
+ clear
57
+ self
58
+ end
59
+
60
+ def terminate
61
+ @pos = @string.bytesize
62
+ clear
63
+ self
64
+ end
65
+ alias clear_terminate terminate
66
+
67
+ def eos? = @pos >= @string.bytesize
68
+ def rest = @string.byteslice(@pos, @string.bytesize - @pos) || ""
69
+ def rest? = !eos?
70
+ def rest_size = @string.bytesize - @pos
71
+ def beginning_of_line? = @pos.zero? || @string.getbyte(@pos - 1) == 10
72
+ alias bol? beginning_of_line?
73
+
74
+ # At the pointer: advance, return the match.
75
+ def scan(pattern) = go(pattern, anchored: true, advance: true, string: true)
76
+ # At the pointer: advance, return its length.
77
+ def skip(pattern) = go(pattern, anchored: true, advance: true, string: false)
78
+ # At the pointer: do not advance, return its length.
79
+ def match?(pattern) = go(pattern, anchored: true, advance: false, string: false)
80
+ # At the pointer: do not advance, return the match.
81
+ def check(pattern) = go(pattern, anchored: true, advance: false, string: true)
82
+ # Anywhere after it: advance past the match, return what was passed over.
83
+ def scan_until(pattern) = go(pattern, anchored: false, advance: true, string: true)
84
+ def skip_until(pattern) = go(pattern, anchored: false, advance: true, string: false)
85
+ def check_until(pattern) = go(pattern, anchored: false, advance: false, string: true)
86
+ def exist?(pattern) = go(pattern, anchored: false, advance: false, string: false)
87
+
88
+ def search_full(pattern, advance, return_string)
89
+ go(pattern, anchored: false, advance: advance, string: return_string)
90
+ end
91
+
92
+ def scan_full(pattern, advance, return_string)
93
+ go(pattern, anchored: true, advance: advance, string: return_string)
94
+ end
95
+
96
+ def getch
97
+ return nil if eos?
98
+
99
+ ch = rest[0]
100
+ set_match(@pos, @pos + ch.bytesize, nil)
101
+ @pos += ch.bytesize
102
+ ch
103
+ end
104
+
105
+ def get_byte
106
+ return nil if eos?
107
+
108
+ b = @string.byteslice(@pos, 1)
109
+ set_match(@pos, @pos + 1, nil)
110
+ @pos += 1
111
+ b
112
+ end
113
+
114
+ def peek(n) = @string.byteslice(@pos, n) || ""
115
+
116
+ def unscan
117
+ raise Error, "unscan error: nothing to unscan" unless @last
118
+
119
+ @pos = @last
120
+ clear
121
+ self
122
+ end
123
+
124
+ def matched? = !@m_end.nil?
125
+ def matched = matched? ? @string.byteslice(@m_start, @m_end - @m_start) : nil
126
+ def matched_size = matched? ? @m_end - @m_start : nil
127
+ def pre_match = matched? ? @string.byteslice(0, @m_start) : nil
128
+ def post_match = matched? ? @string.byteslice(@m_end, @string.bytesize - @m_end) : nil
129
+
130
+ def [](i)
131
+ return nil unless matched?
132
+ return (i.zero? ? matched : nil) unless @md
133
+
134
+ @md[i]
135
+ end
136
+
137
+ def size = matched? ? (@md ? @md.size : 1) : nil
138
+ def captures = @md ? @md.captures : (matched? ? [] : nil)
139
+ def values_at(*is) = matched? ? is.map { self[_1] } : nil
140
+
141
+ def inspect
142
+ return "#<StringScanner fin>" if eos?
143
+
144
+ "#<StringScanner #{@pos}/#{@string.bytesize} @ #{peek(5).inspect}>"
145
+ end
146
+
147
+ private
148
+
149
+ def clear
150
+ @m_start = @m_end = @md = @last = nil
151
+ end
152
+
153
+ def set_match(from, to, md)
154
+ @last = @pos
155
+ @m_start = from
156
+ @m_end = to
157
+ @md = md
158
+ end
159
+
160
+ def go(pattern, anchored:, advance:, string:)
161
+ start = @pos
162
+ if pattern.is_a?(String)
163
+ # As Ruby 3.3's strscan: a String only where the match is at the pointer.
164
+ raise TypeError, "wrong argument type String (expected Regexp)" unless anchored
165
+ return fail_match unless @string.byteslice(start, pattern.bytesize) == pattern
166
+
167
+ from = start
168
+ to = from + pattern.bytesize
169
+ md = nil
170
+ else
171
+ # As CRuby's (fixed_anchor: false): the text begins at the pointer, so
172
+ # \A and ^ match there and nothing before it is seen.
173
+ md = regexp(pattern, anchored).match(rest)
174
+ return fail_match unless md
175
+
176
+ from = start + md.byteoffset(0)[0]
177
+ to = start + md.byteoffset(0)[1]
178
+ end
179
+ set_match(from, to, md)
180
+ @pos = to if advance
181
+ length = to - start
182
+ return length unless string
183
+
184
+ anchored ? @string.byteslice(from, to - from) : @string.byteslice(start, length)
185
+ end
186
+
187
+ def fail_match
188
+ clear
189
+ nil
190
+ end
191
+
192
+ def regexp(re, anchored)
193
+ return re unless anchored
194
+
195
+ (@anchored ||= {})[re] ||= Regexp.new("\\A(?:#{re.source})", re.options)
196
+ end
197
+ end
metadata ADDED
@@ -0,0 +1,51 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby_wasm_ic_minimal
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - graial
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: This gem contains the four carried files required for running rack and
13
+ roda on the minimal ruby.wasm build.
14
+ email:
15
+ - graial@paglalayag.net
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - LICENSE
21
+ - README.md
22
+ - lib/date.rb
23
+ - lib/encoding_constants.rb
24
+ - lib/ruby_wasm_ic_minimal.rb
25
+ - lib/ruby_wasm_ic_minimal/guard.rb
26
+ - lib/ruby_wasm_ic_minimal/version.rb
27
+ - lib/stringio.rb
28
+ - lib/strscan.rb
29
+ homepage: https://github.com/graial/ruby_wasm_ic_minimal
30
+ licenses:
31
+ - MIT
32
+ metadata: {}
33
+ rdoc_options: []
34
+ require_paths:
35
+ - lib
36
+ required_ruby_version: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 3.3.3
41
+ required_rubygems_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ requirements: []
47
+ rubygems_version: 3.6.7
48
+ specification_version: 4
49
+ summary: Four carried files required for running rack and roda on the minimal ruby.wasm
50
+ build
51
+ test_files: []