├── .gitignore ├── mrbgem.rake ├── .ci_build_config.rb ├── README.md ├── Rakefile ├── LICENCE.txt ├── .github └── workflows │ └── ci.yml ├── mrblib └── stringio.rb ├── test └── stringio.rb └── src └── stringio.c /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | mruby-* 3 | -------------------------------------------------------------------------------- /mrbgem.rake: -------------------------------------------------------------------------------- 1 | MRuby::Gem::Specification.new('mruby-stringio') do |spec| 2 | spec.license = 'MIT' 3 | spec.author = 'ksss ' 4 | spec.summary = 'StringIO class' 5 | 6 | spec.add_dependency('mruby-enumerator', core: 'mruby-enumerator') 7 | end 8 | -------------------------------------------------------------------------------- /.ci_build_config.rb: -------------------------------------------------------------------------------- 1 | def gem_config(conf) 2 | conf.gem '../' 3 | end 4 | 5 | MRuby::Build.new do |conf| 6 | toolchain :gcc 7 | conf.enable_test 8 | if ENV['DISABLE_PRESYM'] == 'true' 9 | conf.disable_presym 10 | end 11 | 12 | gem_config(conf) 13 | end 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | mruby-stringio 2 | === 3 | 4 | [![CI](https://github.com/ksss/mruby-stringio/workflows/CI/badge.svg)](https://github.com/ksss/mruby-stringio/actions) 5 | 6 | 7 | ```ruby 8 | io = StringIO.new 9 | io.puts "Hello World" 10 | io.string #=> "Hello World\n" 11 | ``` 12 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | mruby_version = ENV["MRUBY_VERSION"] || 'master' 2 | mruby_dir = "mruby-#{mruby_version}" 3 | 4 | file 'mruby-head' do 5 | sh "git clone --depth 1 --no-single-branch https://github.com/mruby/mruby.git" 6 | sh "mv mruby mruby-head" 7 | end 8 | 9 | file mruby_dir => 'mruby-head' do 10 | sh "cp -a mruby-head #{mruby_dir}" 11 | cd mruby_dir do 12 | sh "git checkout #{mruby_version}" 13 | end 14 | end 15 | 16 | file "#{mruby_dir}/ci_build_config.rb" => [mruby_dir, ".ci_build_config.rb"] do 17 | sh "cp #{File.expand_path(".ci_build_config.rb")} #{mruby_dir}/ci_build_config.rb" 18 | end 19 | 20 | desc "run test with mruby" 21 | task :test => "#{mruby_dir}/ci_build_config.rb" do 22 | cd mruby_dir do 23 | sh "rake -E 'STDOUT.sync=true' test all MRUBY_CONFIG=ci_build_config.rb" 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /LICENCE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 ksss 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | ubuntu-latest-gcc: 13 | strategy: 14 | matrix: 15 | mruby-version: 16 | - 2.1.2 17 | - 2.0.1 18 | include: 19 | - mruby-version: 3.0.0 20 | disable-presym: 'true' 21 | - mruby-version: 3.0.0 22 | disable-presym: 'false' 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v2 26 | - uses: ruby/setup-ruby@v1 27 | with: 28 | ruby-version: '2.7' 29 | - name: Build and test 30 | env: 31 | CC: gcc 32 | CXX: g++ 33 | MRUBY_VERSION: ${{ matrix.mruby-version }} 34 | DISABLE_PRESYM: ${{ matrix.disable-presym }} 35 | run: rake test 36 | 37 | ubuntu-latest-clang: 38 | strategy: 39 | matrix: 40 | mruby-version: 41 | - 2.1.2 42 | - 2.0.1 43 | include: 44 | - mruby-version: 3.0.0 45 | disable-presym: 'true' 46 | - mruby-version: 3.0.0 47 | disable-presym: 'false' 48 | runs-on: ubuntu-latest 49 | steps: 50 | - uses: actions/checkout@v2 51 | - uses: ruby/setup-ruby@v1 52 | with: 53 | ruby-version: '2.7' 54 | - name: Build and test 55 | env: 56 | CC: clang 57 | CXX: clang++ 58 | MRUBY_VERSION: ${{ matrix.mruby-version }} 59 | DISABLE_PRESYM: ${{ matrix.disable-presym }} 60 | run: rake test 61 | 62 | ubuntu-latest-mingw: 63 | strategy: 64 | matrix: 65 | mruby-version: 66 | - 2.1.2 67 | - 2.0.1 68 | include: 69 | - mruby-version: 3.0.0 70 | disable-presym: 'true' 71 | - mruby-version: 3.0.0 72 | disable-presym: 'false' 73 | runs-on: ubuntu-latest 74 | steps: 75 | - uses: actions/checkout@v2 76 | - uses: ruby/setup-ruby@v1 77 | with: 78 | ruby-version: '2.7' 79 | - name: apt 80 | run: sudo apt install mingw-w64 81 | - name: Build and test 82 | env: 83 | TARGET: windows-x86_64 84 | MRUBY_VERSION: ${{ matrix.mruby-version }} 85 | DISABLE_PRESYM: ${{ matrix.disable-presym }} 86 | run: rake test 87 | 88 | windows-mingw: 89 | strategy: 90 | matrix: 91 | mruby-version: 92 | - 2.1.2 93 | - 2.0.1 94 | include: 95 | - mruby-version: 3.0.0 96 | disable-presym: 'true' 97 | - mruby-version: 3.0.0 98 | disable-presym: 'false' 99 | runs-on: windows-latest 100 | steps: 101 | - uses: actions/checkout@v2 102 | - uses: ruby/setup-ruby@v1 103 | with: 104 | ruby-version: '2.7' 105 | - name: Build and test 106 | env: 107 | CFLAGS: -g -O1 -Wall -Wundef 108 | MRUBY_VERSION: ${{ matrix.mruby-version }} 109 | DISABLE_PRESYM: ${{ matrix.disable-presym }} 110 | run: rake test 111 | -------------------------------------------------------------------------------- /mrblib/stringio.rb: -------------------------------------------------------------------------------- 1 | class StringIO 2 | include Enumerable 3 | 4 | READABLE = 0x0001 5 | WRITABLE = 0x0002 6 | READWRITE = READABLE | WRITABLE 7 | BINMODE = 0x0004 8 | APPEND = 0x0040 9 | CREATE = 0x0080 10 | TRUNC = 0x0800 11 | DEFAULT_RS = "\n" 12 | 13 | class << self 14 | def open(string = "", mode = "r+", &block) 15 | instance = new string, mode 16 | block.call instance if block 17 | instance 18 | ensure 19 | instance.string = nil 20 | instance.close unless instance.closed? 21 | end 22 | end 23 | 24 | attr_accessor :string 25 | 26 | alias_method :tell, :pos 27 | 28 | def print(*strings) 29 | strings.each do |string| 30 | str = string.to_s 31 | write str 32 | end 33 | nil 34 | end 35 | 36 | def puts(*strings) 37 | if strings.length == 0 38 | write DEFAULT_RS 39 | return nil 40 | end 41 | 42 | strings.each do |string| 43 | str = string.to_s 44 | write str 45 | if str.length == 0 || str[str.length-1] != DEFAULT_RS 46 | write DEFAULT_RS 47 | end 48 | end 49 | nil 50 | end 51 | 52 | def read_nonblock(*args) 53 | option = { exception: true } 54 | if Hash === args.last 55 | option = args.pop 56 | if !option.key?(:exception) 57 | option[:exception] = true 58 | end 59 | end 60 | 61 | if args.length == 0 62 | raise ArgumentError, "wrong number of arguments (given 0, expected 1..2)" 63 | end 64 | 65 | str = read(*args) 66 | if str.nil? 67 | if option[:exception] 68 | raise EOFError, "end of file reached" 69 | else 70 | nil 71 | end 72 | end 73 | str 74 | end 75 | 76 | def write_nonblock(*args) 77 | case args.last 78 | when Hash, NilClass 79 | args.pop 80 | end 81 | write(*args) 82 | end 83 | 84 | def sysread(*args) 85 | str = read(*args) 86 | if str == nil 87 | raise EOFError, "end of file reached" 88 | end 89 | str 90 | end 91 | 92 | def readchar 93 | c = getc 94 | raise EOFError, 'end of file reached' if c.nil? 95 | c 96 | end 97 | 98 | def ungetc(str) 99 | raise FrozenError, "can't modify frozen String" if frozen? 100 | raise IOError, "not modifiable string" unless !string.frozen? 101 | raise IOError, "not opened for reading" unless (@flags & READABLE) == READABLE 102 | raise TypeError, "expect String, got #{str.class}" unless str.is_a?(String) 103 | raise "Unsupported: Please send PR" unless pos < string.length 104 | s = pos - str.length 105 | s = 0 if s < 0 106 | len = pos 107 | len = str.length if str.length < pos 108 | self.pos = s 109 | string[s, len] = str 110 | nil 111 | end 112 | 113 | def each(*args, &block) 114 | return to_enum :each unless block 115 | while line = gets(*args) 116 | block.call(line) 117 | end 118 | end 119 | alias each_line each 120 | 121 | def fileno 122 | nil 123 | end 124 | 125 | def tty? 126 | false 127 | end 128 | alias_method :isatty, :tty? 129 | 130 | def sync 131 | true 132 | end 133 | 134 | def sync=(val) 135 | val 136 | end 137 | 138 | def fsync 139 | 0 140 | end 141 | 142 | def flush 143 | self 144 | end 145 | end 146 | -------------------------------------------------------------------------------- /test/stringio.rb: -------------------------------------------------------------------------------- 1 | assert 'StringIO#initialize' do 2 | assert_kind_of StringIO, StringIO.new 3 | assert_kind_of StringIO, StringIO.new('str') 4 | assert_kind_of StringIO, StringIO.new('str', 'r+') 5 | assert_raise(ArgumentError) { StringIO.new('', 'x') } 6 | assert_raise(ArgumentError) { StringIO.new('', 'r+x') } 7 | assert_raise(TypeError) { StringIO.new(nil) } 8 | assert_raise(TypeError) { StringIO.new('', nil) } 9 | 10 | frozen = ''.freeze 11 | assert_kind_of StringIO, StringIO.new(frozen) 12 | assert_raise(RuntimeError) { StringIO.new(frozen, 'r+') } 13 | 14 | o = Object.new 15 | def o.to_str 16 | nil 17 | end 18 | assert_raise(TypeError) { StringIO.new(o) } 19 | # def o.to_str 20 | # '' 21 | # end 22 | # assert_kind_of StringIO, StringIO.new(o) 23 | end 24 | 25 | assert 'StringIO#dup' do 26 | begin 27 | f1 = StringIO.new("1234") 28 | assert_equal("1", f1.getc) 29 | f2 = f1.dup 30 | assert_equal("2", f2.getc) 31 | assert_equal("3", f1.getc) 32 | assert_equal("4", f2.getc) 33 | assert_equal(nil, f1.getc) 34 | assert_equal(true, f2.eof?) 35 | f1.close 36 | assert_equal(false, f2.closed?) 37 | ensure 38 | f1.close unless f1.closed? 39 | f2.close unless f2.closed? 40 | end 41 | end 42 | 43 | assert 'StringIO#pos' do 44 | f = StringIO.new("foo\nbar\nbaz\n") 45 | assert_equal([0, "foo\n"], [f.pos, f.gets]) 46 | assert_equal([4, "bar\n"], [f.pos, f.gets]) 47 | assert_raise(RuntimeError) { f.pos = -1 } 48 | f.pos = 1 49 | assert_equal([1, "oo\n"], [f.pos, f.gets]) 50 | assert_equal([4, "bar\n"], [f.pos, f.gets]) 51 | assert_equal([8, "baz\n"], [f.pos, f.gets]) 52 | assert_equal([12, nil], [f.pos, f.gets]) 53 | end 54 | 55 | assert 'StringIO#string' do 56 | s = "foo" 57 | strio = StringIO.new(s, 'w') 58 | assert_true s.equal?(strio.string) 59 | end 60 | 61 | assert 'StringIO#close' do 62 | strio = StringIO.new 63 | assert_nil strio.close 64 | assert_raise(IOError){ strio.close } 65 | end 66 | 67 | assert 'StringIO#closed?' do 68 | strio = StringIO.new 69 | assert_false strio.closed? 70 | strio.close 71 | assert_true strio.closed? 72 | end 73 | 74 | assert 'StringIO.open' do 75 | ret = [] 76 | strio = StringIO.open("foo") do |io| 77 | ret << io 78 | end 79 | assert_kind_of StringIO, strio 80 | assert_true strio.equal?(ret[0]) 81 | assert_true strio.closed? 82 | assert_equal nil, strio.string 83 | end 84 | 85 | assert 'StringIO#rewind' do 86 | strio = StringIO.new("test") 87 | strio.pos = 2 88 | 89 | assert_equal 0, strio.rewind 90 | assert_equal 0, strio.pos 91 | end 92 | 93 | assert 'StringIO#size' do 94 | strio = StringIO.new("1234") 95 | assert_equal(4, strio.size) 96 | end 97 | 98 | assert 'StringIO#each' do 99 | f = StringIO.new("foo\nbar\nbaz\n") 100 | assert_equal(["foo\n", "bar\n", "baz\n"], f.each.to_a) 101 | end 102 | 103 | assert 'StringIO#write' do 104 | begin 105 | s = "" 106 | strio = StringIO.new(s, "w") 107 | assert_equal("", s) 108 | assert_equal 3, strio.write("foo") 109 | assert_equal("foo", s) 110 | assert_nil strio.close 111 | assert_equal("foo", s) 112 | 113 | strio = StringIO.new(s, "a") 114 | o = Object.new 115 | def o.to_s; "baz"; end 116 | strio.write(o) 117 | strio.close 118 | assert_equal("foobaz", s) 119 | assert_raise(IOError) { strio.write("") } 120 | ensure 121 | strio.close unless strio.closed? 122 | end 123 | end 124 | 125 | assert 'StringIO#write_nonblock' do 126 | assert_equal 1, StringIO.new.write_nonblock("a") 127 | assert_equal 1, StringIO.new.write_nonblock("a", exception: true) 128 | assert_equal 1, StringIO.new.write_nonblock("a", nil) 129 | end 130 | 131 | assert 'StringiO#print' do 132 | strio = StringIO.new("test") 133 | assert_nil strio.print("b") 134 | assert_equal "best", strio.string 135 | end 136 | 137 | assert 'StringIO#read' do 138 | strio = StringIO.new("test") 139 | assert_equal "test", strio.read 140 | 141 | strio.rewind 142 | assert_equal "t", strio.read(1) 143 | assert_equal "es", strio.read(2) 144 | assert_equal "t", strio.read(3) 145 | 146 | strio.rewind 147 | assert_raise(ArgumentError) { strio.read(-1) } 148 | assert_raise(ArgumentError) { strio.read(1, 2, 3) } 149 | assert_equal "test", strio.read 150 | 151 | strio.rewind 152 | assert_equal "test", strio.read(strio.size) 153 | 154 | strio.rewind 155 | assert_equal "test", strio.read(nil, nil) 156 | 157 | strio.rewind 158 | s = "" 159 | strio.read(nil, s) 160 | assert_equal "test", s 161 | 162 | strio.rewind 163 | s = "0123456789" 164 | strio.read(nil, s) 165 | assert_equal "test", s 166 | end 167 | 168 | assert 'StringIO#read_nonblock' do 169 | f = StringIO.new("\u3042\u3044") 170 | assert_raise(ArgumentError) { f.read_nonblock(-1) } 171 | assert_raise(ArgumentError) { f.read_nonblock(1, 2, 3) } 172 | assert_equal("\u3042\u3044", f.read_nonblock(100)) 173 | assert_raise(EOFError) { f.read_nonblock(10) } 174 | f.rewind 175 | assert_equal("\u3042\u3044", f.read_nonblock(f.size)) 176 | end 177 | 178 | assert 'read_nonblock_no_exceptions' do 179 | f = StringIO.new("\u3042\u3044") 180 | assert_raise(ArgumentError) { f.read_nonblock(-1, exception: false) } 181 | assert_raise(ArgumentError) { f.read_nonblock(1, 2, 3, exception: false) } 182 | assert_raise(ArgumentError) { f.read_nonblock } 183 | assert_equal("\u3042\u3044", f.read_nonblock(100, exception: false)) 184 | assert_raise(EOFError) { f.read_nonblock(10, typo: false) } 185 | assert_equal(nil, f.read_nonblock(10, exception: false)) 186 | f.rewind 187 | assert_equal("\u3042\u3044", f.read_nonblock(f.size)) 188 | f.rewind 189 | # not empty buffer 190 | s = '0123456789' 191 | assert_equal("\u3042\u3044", f.read_nonblock(f.size, s)) 192 | end 193 | 194 | assert 'StringIO#sysread' do 195 | strio = StringIO.new("test") 196 | assert_equal "tes", strio.sysread(3) 197 | assert_equal "t", strio.sysread(10) 198 | assert_raise(EOFError){ strio.sysread(10) } 199 | end 200 | 201 | assert 'StringIO#getc' do 202 | strio = StringIO.new("abc") 203 | assert_equal "a", strio.getc 204 | assert_equal "b", strio.getc 205 | assert_equal "c", strio.getc 206 | assert_equal nil, strio.getc 207 | end 208 | 209 | assert 'StringIO#gets' do 210 | io = StringIO.new("this>is>an>example") 211 | assert_equal "this>", io.gets(">") 212 | assert_equal "is>", io.gets(">") 213 | assert_equal "an>", io.gets(">") 214 | assert_equal "example", io.gets(">") 215 | assert_equal nil, io.gets(">") 216 | 217 | io = StringIO.new("this>>is>>an>>example") 218 | assert_equal "this>>", io.gets(">>") 219 | assert_equal "is>>", io.gets(">>") 220 | assert_equal "an>>", io.gets(">>") 221 | assert_equal "example", io.gets(">>") 222 | assert_equal nil, io.gets(">>") 223 | end 224 | 225 | assert 'gets pos and lineno' do 226 | io = StringIO.new("this is\nan example\nfor StringIO#gets") 227 | io.gets 228 | assert_equal 8, io.pos 229 | assert_equal 1, io.lineno 230 | io.gets 231 | assert_equal 19, io.pos 232 | assert_equal 2, io.lineno 233 | io.gets 234 | assert_equal 36, io.pos 235 | assert_equal 3, io.lineno 236 | end 237 | 238 | assert 'gets1' do 239 | assert_equal(nil, StringIO.new("").gets) 240 | assert_equal("\n", StringIO.new("\n").gets) 241 | assert_equal("a\n", StringIO.new("a\n").gets) 242 | assert_equal("a\n", StringIO.new("a\nb\n").gets) 243 | assert_equal("a", StringIO.new("a").gets) 244 | assert_equal("a\n", StringIO.new("a\nb").gets) 245 | assert_equal("abc\n", StringIO.new("abc\n\ndef\n").gets) 246 | assert_equal("abc\n\ndef\n", StringIO.new("abc\n\ndef\n").gets(nil)) 247 | assert_equal("abc\n\n", StringIO.new("abc\n\ndef\n").gets("")) 248 | assert_raise(TypeError){StringIO.new("").gets(1, 1)} 249 | assert_nothing_raised {StringIO.new("").gets(nil, nil)} 250 | end 251 | 252 | assert 'overwrite' do 253 | stringio = StringIO.new 254 | responses = ['', 'just another ruby', 'hacker'] 255 | responses.each do |resp| 256 | stringio.puts(resp) 257 | stringio.rewind 258 | end 259 | assert_equal("hacker\nother ruby\n", stringio.string) 260 | end 261 | 262 | assert 'StringIO#seek' do 263 | begin 264 | f = StringIO.new("1234") 265 | assert_raise(RuntimeError) { f.seek(-1) } 266 | f.seek(-1, 2) 267 | assert_equal("4", f.getc) 268 | assert_raise(RuntimeError) { f.seek(0, 3) } 269 | f.close 270 | assert_raise(IOError) { f.seek(1) } 271 | ensure 272 | f.close unless f.closed? 273 | end 274 | end 275 | 276 | assert 'gets2' do 277 | f = StringIO.new("foo\nbar\nbaz\n") 278 | assert_equal("fo", f.gets(2)) 279 | # o = Object.new 280 | # def o.to_str; "z"; end 281 | # assert_equal("o\nbar\nbaz", f.gets(o)) 282 | 283 | f = StringIO.new("foo\nbar\nbaz\n") 284 | assert_equal("foo\nbar\nbaz", f.gets("az")) 285 | f = StringIO.new("a" * 10000 + "zz!") 286 | assert_equal("a" * 10000 + "zz", f.gets("zz")) 287 | f = StringIO.new("a" * 10000 + "zz!") 288 | assert_equal("a" * 10000 + "zz!", f.gets("zzz")) 289 | 290 | assert_equal("a", StringIO.new("a").gets(1)) 291 | assert_equal("a", StringIO.new("a").gets(nil, 1)) 292 | end 293 | 294 | assert 'seek_beyond_eof' do 295 | io = StringIO.new 296 | n = 10 297 | io.seek(n) 298 | io.print "last" 299 | assert_equal("\0" * n + "last", io.string) 300 | end 301 | 302 | assert 'eof?' do 303 | io = StringIO.new("test") 304 | assert_false io.eof? 305 | assert_false io.eof 306 | io.seek(3) 307 | assert_false io.eof? 308 | assert_false io.eof 309 | io.seek(4) 310 | assert_true io.eof? 311 | assert_true io.eof 312 | end 313 | 314 | assert 'StringIO#tty?' do 315 | assert_false StringIO.new.tty? 316 | assert_false StringIO.new.isatty 317 | end 318 | 319 | assert 'StringIO#sync' do 320 | s = StringIO.new 321 | assert_true s.sync 322 | assert_false s.sync = false 323 | assert_true s.sync 324 | assert_true s.sync = true 325 | end 326 | 327 | assert 'StringIO#fsync' do 328 | assert_equal 0, StringIO.new.fsync 329 | end 330 | 331 | assert 'StringIO#flush' do 332 | s = StringIO.new 333 | assert_equal s, s.flush 334 | end 335 | 336 | assert 'StringIO#reopen' do 337 | begin 338 | f = StringIO.new("foo\nbar\nbaz\n") 339 | assert_equal("foo\n", f.gets) 340 | f.reopen("qux\nquux\nquuux\n") 341 | assert_equal("qux\n", f.gets) 342 | 343 | f2 = StringIO.new("") 344 | f2.reopen(f) 345 | assert_equal("quux\n", f2.gets) 346 | ensure 347 | f.close unless f.closed? 348 | end 349 | end 350 | 351 | assert 'reopen with dup' do 352 | f = StringIO.new("foo\nbar\nbaz\n") 353 | assert_equal("foo\n", f.gets) 354 | f.dup.reopen("qux\nquux\nquuux\n") 355 | assert_equal("qux\n", f.gets) 356 | end 357 | 358 | assert 'StringIO#readchar' do 359 | f = StringIO.new('1234') 360 | a = '' 361 | assert_equal '1', a.replace(a + f.readchar) 362 | assert_equal '12', a.replace(a + f.readchar) 363 | assert_equal '123', a.replace(a + f.readchar) 364 | assert_equal '1234', a.replace(a + f.readchar) 365 | assert_raise(EOFError) { f.readchar } 366 | end 367 | 368 | assert 'StringIO#puts' do 369 | f = StringIO.new 370 | f.puts(1, 2, 3, 4) 371 | assert_equal("1\n2\n3\n4\n", f.string) 372 | 373 | f = StringIO.new 374 | f.puts('') 375 | assert_equal("\n", f.string) 376 | 377 | f = StringIO.new 378 | f.puts 379 | assert_equal("\n", f.string) 380 | 381 | f = StringIO.new('', 'r') 382 | assert_raise(IOError) { f.puts } 383 | end 384 | 385 | assert 'StringIO#ungetc' do 386 | s = "1234" 387 | f = StringIO.new(s, "r") 388 | f.ungetc("x") 389 | assert_equal("x", f.getc) 390 | assert_equal("1", f.getc) 391 | 392 | s = "1234" 393 | f = StringIO.new(s, "r") 394 | assert_equal("1", f.getc) 395 | f.ungetc("y") 396 | assert_equal("y", f.getc) 397 | assert_equal("2", f.getc) 398 | 399 | s = "12" 400 | f = StringIO.new(s) 401 | f.pos = 1 402 | f.ungetc('aaa') 403 | assert_equal("aaa2", f.string) 404 | 405 | s = "1" 406 | f = StringIO.new(s) 407 | f.pos = 1 408 | assert_raise(RuntimeError) { f.ungetc('aaa') } 409 | 410 | s = StringIO.new("".freeze) 411 | assert_raise(IOError) {s.ungetc("x")} 412 | 413 | s = StringIO.new("", "w") 414 | assert_raise(IOError) {s.ungetc("x")} 415 | 416 | s = StringIO.new("").freeze 417 | assert_raise(FrozenError) {s.ungetc("x")} 418 | end 419 | -------------------------------------------------------------------------------- /src/stringio.c: -------------------------------------------------------------------------------- 1 | /* 2 | original is https://github.com/ruby/ruby/blob/trunk/ext/stringio/stringio.c 3 | */ 4 | 5 | #include 6 | #include 7 | #include "mruby.h" 8 | #include "mruby/string.h" 9 | #include "mruby/variable.h" 10 | #include "mruby/error.h" 11 | #include "mruby/data.h" 12 | #include "mruby/class.h" 13 | #include "mruby/object.h" 14 | 15 | #if MRUBY_RELEASE_NO >= 30000 16 | #include "mruby/presym.h" 17 | #else 18 | #define MRB_IVSYM(s) mrb_intern_lit(mrb, "@"#s) 19 | #endif 20 | 21 | #define FMODE_READABLE 0x0001 22 | #define FMODE_WRITABLE 0x0002 23 | #define FMODE_READWRITE (FMODE_READABLE|FMODE_WRITABLE) 24 | #define FMODE_BINMODE 0x0004 25 | #define FMODE_APPEND 0x0040 26 | 27 | #define stringio_iv_get(name) mrb_iv_get(mrb, self, MRB_IVSYM(name)) 28 | #define E_IOERROR (mrb_class_get(mrb, "IOError")) 29 | #define StringIO(self) get_strio(mrb, self) 30 | 31 | // For compatibility before https://github.com/mruby/mruby/pull/3340 32 | #ifndef MRB_FROZEN_P 33 | #ifdef mrb_frozen_p 34 | #define MRB_FROZEN_P(o) mrb_frozen_p(o) 35 | #else 36 | #define MRB_FROZEN_P(o) RSTR_FROZEN_P(o) 37 | #endif 38 | #endif 39 | 40 | struct StringIO { 41 | mrb_int pos; 42 | mrb_int lineno; 43 | int count; 44 | }; 45 | 46 | static struct StringIO * 47 | stringio_alloc(mrb_state *mrb) 48 | { 49 | struct StringIO *ptr = (struct StringIO *)mrb_malloc(mrb, sizeof(struct StringIO)); 50 | ptr->pos = 0; 51 | ptr->lineno = 0; 52 | ptr->count = 1; 53 | return ptr; 54 | } 55 | 56 | static void 57 | stringio_free(mrb_state *mrb, void *p) { 58 | struct StringIO *ptr = ((struct StringIO *)p); 59 | if (--ptr->count <= 0) { 60 | mrb_free(mrb, p); 61 | } 62 | } 63 | 64 | static const struct mrb_data_type mrb_stringio_type = { "StringIO", stringio_free }; 65 | 66 | static struct StringIO * 67 | get_strio(mrb_state *mrb, mrb_value self) 68 | { 69 | struct StringIO *ptr = mrb_data_get_ptr(mrb, self, &mrb_stringio_type); 70 | if (!ptr) mrb_raise(mrb, E_IOERROR, "uninitialized stream"); 71 | return ptr; 72 | } 73 | 74 | static void 75 | check_modifiable(mrb_state *mrb, mrb_value self) 76 | { 77 | mrb_value string = stringio_iv_get(string); 78 | if (MRB_FROZEN_P(mrb_str_ptr(string))) { 79 | mrb_raise(mrb, E_IOERROR, "not modifiable string"); 80 | } 81 | } 82 | 83 | static void 84 | mrb_syserr_fail(mrb_state *mrb, mrb_int no, const char *mesg) { 85 | struct RClass *sce; 86 | if (mrb_class_defined(mrb, "SystemCallError")) { 87 | sce = mrb_class_get(mrb, "SystemCallError"); 88 | if (mesg) { 89 | mrb_funcall(mrb, mrb_obj_value(sce), "_sys_fail", 2, mrb_fixnum_value(no), mrb_str_new_cstr(mrb, mesg)); 90 | } else { 91 | mrb_funcall(mrb, mrb_obj_value(sce), "_sys_fail", 1, mrb_fixnum_value(no)); 92 | } 93 | } else { 94 | mrb_raise(mrb, E_RUNTIME_ERROR, mesg); 95 | } 96 | } 97 | 98 | /* Boyer-Moore search: copied from https://github.com/ruby/ruby/ext/stringio/stringio.c */ 99 | static void 100 | bm_init_skip(long *skip, const char *pat, long m) 101 | { 102 | int c; 103 | 104 | for (c = 0; c < (1 << CHAR_BIT); c++) { 105 | skip[c] = m; 106 | } 107 | while (--m) { 108 | skip[(unsigned char)*pat++] = m; 109 | } 110 | } 111 | 112 | static long 113 | bm_search(const char *little, long llen, const char *big, long blen, const long *skip) 114 | { 115 | long i, j, k; 116 | 117 | i = llen - 1; 118 | while (i < blen) { 119 | k = i; 120 | j = llen - 1; 121 | while (j >= 0 && big[k] == little[j]) { 122 | k--; 123 | j--; 124 | } 125 | if (j < 0) return k + 1; 126 | i += skip[(unsigned char)big[i]]; 127 | } 128 | return -1; 129 | } 130 | 131 | static mrb_int 132 | modestr_fmode(mrb_state *mrb, const char *modestr) 133 | { 134 | mrb_int fmode = 0; 135 | const char *m = modestr; 136 | 137 | switch (*m++) { 138 | case 'r': 139 | fmode |= FMODE_READABLE; 140 | break; 141 | case 'w': 142 | fmode |= FMODE_WRITABLE; 143 | break; 144 | case 'a': 145 | fmode |= FMODE_WRITABLE | FMODE_APPEND; 146 | break; 147 | default: 148 | goto error; 149 | } 150 | 151 | while (*m) { 152 | switch (*m++) { 153 | case '+': 154 | fmode |= FMODE_READWRITE; 155 | break; 156 | default: 157 | goto error; 158 | } 159 | } 160 | return fmode; 161 | 162 | error: 163 | mrb_raisef(mrb, E_ARGUMENT_ERROR, "invalid access mode %S", mrb_str_new_static(mrb, modestr, strlen(modestr))); 164 | return -1; 165 | } 166 | 167 | static mrb_value 168 | strio_substr(mrb_state *mrb, mrb_value self, long pos, long len) 169 | { 170 | mrb_value str = stringio_iv_get(string); 171 | long rlen = RSTRING_LEN(str) - pos; 172 | 173 | if (len > rlen) len = rlen; 174 | if (len < 0) len = 0; 175 | if (len == 0) return mrb_str_new(mrb, 0, 0); 176 | return mrb_str_new(mrb, RSTRING_PTR(str)+pos, len); 177 | } 178 | 179 | static void 180 | strio_extend(mrb_state *mrb, mrb_value self, long pos, long len) 181 | { 182 | long olen; 183 | mrb_value string = stringio_iv_get(string); 184 | 185 | olen = RSTRING_LEN(string); 186 | if (pos + len > olen) { 187 | mrb_str_resize(mrb, string, pos + len); 188 | if (pos > olen) 189 | memset(RSTRING_PTR(string) + olen, 0, sizeof(char) * (pos - olen)); 190 | } else { 191 | mrb_str_modify(mrb, mrb_str_ptr(string)); 192 | } 193 | } 194 | 195 | static void 196 | strio_init(mrb_state *mrb, mrb_value self, mrb_int argc, mrb_value *argv) 197 | { 198 | mrb_int flags; 199 | struct StringIO *ptr; 200 | mrb_value string = mrb_nil_value(); 201 | mrb_value mode = mrb_nil_value(); 202 | switch (argc) { 203 | case 0: 204 | break; 205 | case 1: 206 | string = argv[0]; 207 | break; 208 | case 2: 209 | string = argv[0]; 210 | mode = argv[1]; 211 | break; 212 | default: 213 | mrb_raisef(mrb, E_ARGUMENT_ERROR, "wrong number of arguments (given %S, expected 1..2)", argc); 214 | break; 215 | } 216 | if (mrb_nil_p(string)) { 217 | string = mrb_str_new(mrb, 0, 0); 218 | } 219 | if (mrb_nil_p(mode)) { 220 | flags = MRB_FROZEN_P(mrb_str_ptr(string)) ? FMODE_READABLE : FMODE_READWRITE; 221 | } else { 222 | flags = modestr_fmode(mrb, RSTRING_PTR(mrb_string_type(mrb, mode))); 223 | } 224 | 225 | if (argc == 2 && (flags & FMODE_WRITABLE) && MRB_FROZEN_P(mrb_str_ptr(string))) { 226 | mrb_syserr_fail(mrb, EACCES, 0); 227 | } 228 | 229 | ptr = (struct StringIO*)DATA_PTR(self); 230 | if (ptr) { 231 | /* reopen */ 232 | mrb_funcall(mrb, mrb_iv_get(mrb, self, MRB_IVSYM(string)), "replace", 1, string); 233 | } else { 234 | /* initialize */ 235 | ptr = stringio_alloc(mrb); 236 | mrb_iv_set(mrb, self, MRB_IVSYM(string), string); 237 | } 238 | ptr->lineno = 0; 239 | ptr->pos = 0; 240 | mrb_iv_set(mrb, self, MRB_IVSYM(flags), mrb_fixnum_value(flags)); 241 | mrb_data_init(self, ptr, &mrb_stringio_type); 242 | } 243 | 244 | static mrb_value 245 | stringio_initialize(mrb_state *mrb, mrb_value self) 246 | { 247 | mrb_value string = mrb_nil_value(); 248 | mrb_value mode = mrb_nil_value(); 249 | mrb_int argc; 250 | mrb_value argv[2]; 251 | 252 | argc = mrb_get_args(mrb, "|SS", &string, &mode); 253 | argv[0] = string; 254 | argv[1] = mode; 255 | strio_init(mrb, self, argc, argv); 256 | return self; 257 | } 258 | 259 | static mrb_value 260 | stringio_copy(mrb_state *mrb, mrb_value copy, mrb_value orig) 261 | { 262 | struct StringIO *ptr; 263 | mrb_value flags; 264 | mrb_value string; 265 | 266 | orig = mrb_convert_type(mrb, orig, MRB_TT_DATA, "StringIO", "to_strio"); 267 | if (© == &orig) return copy; 268 | ptr = StringIO(orig); 269 | if (mrb_data_check_get_ptr(mrb, copy, &mrb_stringio_type)) { 270 | stringio_free(mrb, DATA_PTR(copy)); 271 | } 272 | mrb_data_init(copy, ptr, &mrb_stringio_type); 273 | 274 | string = mrb_iv_get(mrb, orig, MRB_IVSYM(string)); 275 | mrb_iv_set(mrb, copy, MRB_IVSYM(string), string); 276 | 277 | flags = mrb_iv_get(mrb, orig, MRB_IVSYM(flags)); 278 | mrb_iv_set(mrb, copy, MRB_IVSYM(flags), flags); 279 | 280 | ++ptr->count; 281 | return copy; 282 | } 283 | 284 | static mrb_value 285 | stringio_initialize_copy(mrb_state *mrb, mrb_value copy) 286 | { 287 | mrb_value orig; 288 | 289 | mrb_get_args(mrb, "o", &orig); 290 | return stringio_copy(mrb, copy, orig); 291 | } 292 | 293 | static mrb_value 294 | stringio_get_lineno(mrb_state *mrb, mrb_value self) 295 | { 296 | struct StringIO *ptr = StringIO(self); 297 | return mrb_fixnum_value(ptr->lineno); 298 | } 299 | 300 | static mrb_value 301 | stringio_set_lineno(mrb_state *mrb, mrb_value self) 302 | { 303 | struct StringIO *ptr = StringIO(self); 304 | mrb_int lineno = 0; 305 | mrb_get_args(mrb, "i", &lineno); 306 | ptr->lineno = lineno; 307 | return mrb_fixnum_value(lineno); 308 | } 309 | 310 | static mrb_value 311 | stringio_get_pos(mrb_state *mrb, mrb_value self) 312 | { 313 | struct StringIO *ptr = StringIO(self); 314 | return mrb_fixnum_value(ptr->pos); 315 | } 316 | 317 | static mrb_value 318 | stringio_set_pos(mrb_state *mrb, mrb_value self) 319 | { 320 | struct StringIO *ptr = StringIO(self); 321 | mrb_int pos = 0; 322 | 323 | mrb_get_args(mrb, "i", &pos); 324 | if (pos < 0) { 325 | mrb_syserr_fail(mrb, EINVAL, 0); 326 | } 327 | ptr->pos = pos; 328 | return mrb_fixnum_value(pos); 329 | } 330 | 331 | static mrb_value 332 | stringio_rewind(mrb_state *mrb, mrb_value self) 333 | { 334 | struct StringIO *ptr = StringIO(self); 335 | ptr->pos = 0; 336 | ptr->lineno = 0; 337 | return mrb_fixnum_value(0); 338 | } 339 | 340 | static mrb_value 341 | stringio_closed_p(mrb_state *mrb, mrb_value self) 342 | { 343 | mrb_int flags; 344 | 345 | StringIO(self); 346 | flags = mrb_fixnum(stringio_iv_get(flags)); 347 | return ((flags & FMODE_READWRITE) == 0) ? mrb_true_value() : mrb_false_value(); 348 | } 349 | 350 | static mrb_value 351 | stringio_close(mrb_state *mrb, mrb_value self) 352 | { 353 | mrb_int flags; 354 | 355 | StringIO(self); 356 | flags = mrb_fixnum(stringio_iv_get(flags)); 357 | if ((flags & FMODE_READWRITE) == 0) 358 | mrb_raise(mrb, E_IOERROR, "closed stream"); 359 | flags &= ~FMODE_READWRITE; 360 | mrb_iv_set(mrb, self, MRB_IVSYM(flags), mrb_fixnum_value(flags)); 361 | return mrb_nil_value(); 362 | } 363 | 364 | static mrb_value 365 | stringio_read(mrb_state *mrb, mrb_value self) 366 | { 367 | mrb_int argc; 368 | mrb_int clen; 369 | struct StringIO *ptr = StringIO(self); 370 | mrb_value rlen = mrb_nil_value(); 371 | mrb_value rstr = mrb_nil_value(); 372 | mrb_value string = stringio_iv_get(string); 373 | mrb_int flags = mrb_fixnum(stringio_iv_get(flags)); 374 | 375 | if ((flags & FMODE_READABLE) != FMODE_READABLE) 376 | mrb_raise(mrb, E_IOERROR, "not opened for reading"); 377 | 378 | argc = mrb_get_args(mrb, "|oo", &rlen, &rstr); 379 | switch (argc) { 380 | case 2: 381 | if (!mrb_nil_p(rstr)) { 382 | mrb_str_modify(mrb, mrb_str_ptr(rstr)); 383 | } 384 | /* fall through */ 385 | case 1: 386 | if (!mrb_nil_p(rlen)) { 387 | clen = mrb_fixnum(rlen); 388 | if (clen < 0) { 389 | mrb_raisef(mrb, E_ARGUMENT_ERROR, "negative length %S given", rlen); 390 | } 391 | if (clen > 0 && ptr->pos >= RSTRING_LEN(string)) { 392 | if (!mrb_nil_p(rstr)) mrb_str_resize(mrb, rstr, 0); 393 | return mrb_nil_value(); 394 | } 395 | break; 396 | } 397 | /* fall through */ 398 | case 0: 399 | clen = RSTRING_LEN(string); 400 | if (clen <= ptr->pos) { 401 | if (mrb_nil_p(rstr)) { 402 | rstr = mrb_str_new(mrb, 0, 0); 403 | } else { 404 | mrb_str_resize(mrb, rstr, 0); 405 | } 406 | return rstr; 407 | } else { 408 | clen -= ptr->pos; 409 | } 410 | break; 411 | default: 412 | mrb_raisef(mrb, E_ARGUMENT_ERROR, "wrong number of arguments (given %S, expected 0..2)", mrb_fixnum_value(argc)); 413 | break; 414 | } 415 | if (mrb_nil_p(rstr)) { 416 | rstr = strio_substr(mrb, self, ptr->pos, clen); 417 | } else { 418 | long rest = RSTRING_LEN(string) - ptr->pos; 419 | if (clen > rest) clen = rest; 420 | mrb_str_resize(mrb, rstr, clen); 421 | memcpy(RSTRING_PTR(rstr), RSTRING_PTR(string) + ptr->pos, sizeof(char) * clen); 422 | } 423 | ptr->pos += RSTRING_LEN(rstr); 424 | return rstr; 425 | } 426 | 427 | static mrb_value 428 | stringio_write(mrb_state *mrb, mrb_value self) 429 | { 430 | struct StringIO *ptr = StringIO(self); 431 | mrb_int len, olen; 432 | mrb_value str = mrb_nil_value(); 433 | mrb_value string = stringio_iv_get(string); 434 | mrb_int flags = mrb_fixnum(stringio_iv_get(flags)); 435 | 436 | if ((flags & FMODE_WRITABLE) != FMODE_WRITABLE) 437 | mrb_raise(mrb, E_IOERROR, "not opened for writing"); 438 | 439 | mrb_get_args(mrb, "o", &str); 440 | 441 | if (!mrb_string_p(str)) 442 | str = mrb_obj_as_string(mrb, str); 443 | 444 | len = RSTRING_LEN(str); 445 | if (len == 0) 446 | return mrb_fixnum_value(0); 447 | check_modifiable(mrb, self); 448 | olen = RSTRING_LEN(string); 449 | if (flags & FMODE_APPEND) { 450 | ptr->pos = olen; 451 | } 452 | if (ptr->pos == olen) { 453 | mrb_str_append(mrb, string, str); 454 | } else { 455 | strio_extend(mrb, self, ptr->pos, len); 456 | memmove(RSTRING_PTR(string) + ptr->pos, RSTRING_PTR(str), len); 457 | } 458 | ptr->pos += len; 459 | return mrb_fixnum_value(len); 460 | } 461 | 462 | static mrb_value 463 | stringio_getc(mrb_state *mrb, mrb_value self) 464 | { 465 | struct StringIO *ptr = StringIO(self); 466 | mrb_value string = stringio_iv_get(string); 467 | mrb_value ret; 468 | mrb_int flags = mrb_fixnum(stringio_iv_get(flags)); 469 | 470 | if ((flags & FMODE_READABLE) == 0) 471 | mrb_raise(mrb, E_IOERROR, "not opened for reading"); 472 | 473 | if (ptr->pos >= RSTRING_LEN(string)) { 474 | return mrb_nil_value(); 475 | } 476 | 477 | ret = mrb_str_new(mrb, RSTRING_PTR(string) + ptr->pos, 1); 478 | ptr->pos += 1; 479 | return ret; 480 | } 481 | 482 | static mrb_value 483 | stringio_gets(mrb_state *mrb, mrb_value self) 484 | { 485 | struct StringIO *ptr = StringIO(self); 486 | mrb_value string = stringio_iv_get(string); 487 | mrb_int flags = mrb_fixnum(stringio_iv_get(flags)); 488 | mrb_int argc; 489 | mrb_value *argv; 490 | mrb_value mrb_rs = mrb_str_new_lit(mrb, "\n"); 491 | mrb_value str = mrb_nil_value(); 492 | mrb_value lim = mrb_nil_value(); 493 | const char *s, *e, *p; 494 | long n, limit = 0; 495 | 496 | if ((flags & FMODE_READABLE) != FMODE_READABLE) 497 | mrb_raise(mrb, E_IOERROR, "not opened for reading"); 498 | 499 | mrb_get_args(mrb, "*", &argv, &argc); 500 | switch (argc) { 501 | case 0: 502 | str = mrb_rs; 503 | break; 504 | case 1: 505 | str = argv[0]; 506 | if (!mrb_nil_p(str) && !mrb_string_p(str)) { 507 | mrb_value tmp = mrb_check_string_type(mrb, str); 508 | if (mrb_nil_p(tmp)) { 509 | limit = mrb_fixnum(str); 510 | if (limit == 0) return mrb_str_new(mrb, 0, 0); 511 | str = mrb_rs; 512 | } else { 513 | str = tmp; 514 | } 515 | } 516 | break; 517 | case 2: 518 | str = argv[0]; 519 | lim = argv[1]; 520 | if (!mrb_nil_p(str)) str = mrb_string_type(mrb, str); 521 | if (!mrb_nil_p(lim)) limit = mrb_fixnum(lim); 522 | break; 523 | } 524 | 525 | if (ptr->pos >= (n = RSTRING_LEN(string))) { 526 | return mrb_nil_value(); 527 | } 528 | s = RSTRING_PTR(string); 529 | e = s + RSTRING_LEN(string); 530 | s += ptr->pos; 531 | if (limit > 0 && s + limit < e) { 532 | e = s + limit; 533 | } 534 | if (mrb_nil_p(str)) { 535 | str = strio_substr(mrb, self, ptr->pos, e - s); 536 | } 537 | else if ((n = RSTRING_LEN(str)) == 0) { 538 | p = s; 539 | while (*p == '\n') { 540 | if (++p == e) { 541 | return mrb_nil_value(); 542 | } 543 | } 544 | s = p; 545 | while ((p = memchr(p, '\n', e - p)) && (p != e)) { 546 | if (*++p == '\n') { 547 | e = p + 1; 548 | break; 549 | } 550 | } 551 | str = strio_substr(mrb, self, s - RSTRING_PTR(string), e - s); 552 | } 553 | else if (n == 1) { 554 | if ((p = memchr(s, RSTRING_PTR(str)[0], e - s)) != 0) { 555 | e = p + 1; 556 | } 557 | str = strio_substr(mrb, self, ptr->pos, e - s); 558 | } 559 | else { 560 | if (n < e - s) { 561 | if (e - s < 1024) { 562 | for (p = s; p + n <= e; ++p) { 563 | if (memcmp(p, RSTRING_PTR(str), sizeof(char) * n) == 0) { 564 | e = p + n; 565 | break; 566 | } 567 | } 568 | } else { 569 | long skip[1 << CHAR_BIT], pos; 570 | p = RSTRING_PTR(str); 571 | bm_init_skip(skip, p, n); 572 | if ((pos = bm_search(p, n, s, e - s, skip)) >= 0) { 573 | e = s + pos + n; 574 | } 575 | } 576 | } 577 | str = strio_substr(mrb, self, ptr->pos, e - s); 578 | } 579 | ptr->pos = e - RSTRING_PTR(string); 580 | ptr->lineno++; 581 | return str; 582 | } 583 | 584 | static mrb_value 585 | stringio_seek(mrb_state *mrb, mrb_value self) 586 | { 587 | struct StringIO *ptr = StringIO(self); 588 | mrb_value whence = mrb_nil_value(); 589 | mrb_int offset = 0; 590 | mrb_value string = stringio_iv_get(string); 591 | mrb_int flags = mrb_fixnum(stringio_iv_get(flags)); 592 | 593 | mrb_get_args(mrb, "i|o", &offset, &whence); 594 | if ((flags & FMODE_READWRITE) == 0) { 595 | mrb_raise(mrb, E_IOERROR, "closed stream"); 596 | } 597 | switch (mrb_nil_p(whence) ? 0 : mrb_fixnum(whence)) { 598 | case SEEK_SET: 599 | break; 600 | case SEEK_CUR: 601 | offset += ptr->pos; 602 | break; 603 | case SEEK_END: 604 | offset += RSTRING_LEN(string); 605 | break; 606 | default: 607 | mrb_syserr_fail(mrb, EINVAL, "invalid whence"); 608 | } 609 | if (offset < 0) { 610 | mrb_syserr_fail(mrb, EINVAL, 0); 611 | } 612 | ptr->pos = offset; 613 | return mrb_fixnum_value(0); 614 | } 615 | 616 | static mrb_value 617 | stringio_size(mrb_state *mrb, mrb_value self) 618 | { 619 | mrb_value string = stringio_iv_get(string); 620 | if (mrb_nil_p(string)) { 621 | mrb_raise(mrb, E_IOERROR, "not opened"); 622 | } 623 | return mrb_fixnum_value(RSTRING_LEN(string)); 624 | } 625 | 626 | static mrb_value 627 | stringio_eof_p(mrb_state *mrb, mrb_value self) 628 | { 629 | mrb_value string = stringio_iv_get(string); 630 | struct StringIO *ptr = StringIO(self); 631 | return (RSTRING_LEN(string) <= ptr->pos) ? mrb_true_value() : mrb_false_value(); 632 | } 633 | 634 | static mrb_value 635 | stringio_reopen(mrb_state *mrb, mrb_value self) 636 | { 637 | mrb_int argc; 638 | mrb_value *argv; 639 | 640 | mrb_get_args(mrb, "*", &argv, &argc); 641 | if (argc == 1 && !(mrb_type(*argv) == MRB_TT_STRING)) { 642 | return stringio_copy(mrb, self, *argv); 643 | } 644 | strio_init(mrb, self, argc, argv); 645 | return self; 646 | } 647 | 648 | void 649 | mrb_mruby_stringio_gem_init(mrb_state* mrb) 650 | { 651 | struct RClass *stringio = mrb_define_class(mrb, "StringIO", mrb->object_class); 652 | MRB_SET_INSTANCE_TT(stringio, MRB_TT_DATA); 653 | mrb_define_method(mrb, stringio, "initialize", stringio_initialize, MRB_ARGS_ANY()); 654 | mrb_define_method(mrb, stringio, "initialize_copy", stringio_initialize_copy, MRB_ARGS_ANY()); 655 | mrb_define_method(mrb, stringio, "lineno", stringio_get_lineno, MRB_ARGS_NONE()); 656 | mrb_define_method(mrb, stringio, "lineno=", stringio_set_lineno, MRB_ARGS_REQ(1)); 657 | mrb_define_method(mrb, stringio, "pos", stringio_get_pos, MRB_ARGS_NONE()); 658 | mrb_define_method(mrb, stringio, "pos=", stringio_set_pos, MRB_ARGS_REQ(1)); 659 | mrb_define_method(mrb, stringio, "rewind", stringio_rewind, MRB_ARGS_NONE()); 660 | mrb_define_method(mrb, stringio, "closed?", stringio_closed_p, MRB_ARGS_NONE()); 661 | mrb_define_method(mrb, stringio, "close", stringio_close, MRB_ARGS_NONE()); 662 | mrb_define_method(mrb, stringio, "read", stringio_read, MRB_ARGS_ANY()); 663 | mrb_define_method(mrb, stringio, "write", stringio_write, MRB_ARGS_REQ(1)); 664 | mrb_define_alias(mrb, stringio, "syswrite", "write"); 665 | mrb_define_method(mrb, stringio, "getc", stringio_getc, MRB_ARGS_ANY()); 666 | mrb_define_method(mrb, stringio, "gets", stringio_gets, MRB_ARGS_ANY()); 667 | mrb_define_method(mrb, stringio, "seek", stringio_seek, MRB_ARGS_ANY()); 668 | mrb_define_method(mrb, stringio, "size", stringio_size, MRB_ARGS_NONE()); 669 | mrb_define_alias(mrb, stringio, "length", "size"); 670 | mrb_define_method(mrb, stringio, "eof?", stringio_eof_p, MRB_ARGS_NONE()); 671 | mrb_define_alias(mrb, stringio, "eof", "eof?"); 672 | mrb_define_method(mrb, stringio, "reopen", stringio_reopen, MRB_ARGS_ANY()); 673 | 674 | struct RClass *io = mrb_define_class(mrb, "IO", mrb->object_class); 675 | /* Set I/O position from the beginning */ 676 | mrb_define_const(mrb, io, "SEEK_SET", mrb_fixnum_value(SEEK_SET)); 677 | /* Set I/O position from the current position */ 678 | mrb_define_const(mrb, io, "SEEK_CUR", mrb_fixnum_value(SEEK_CUR)); 679 | /* Set I/O position from the end */ 680 | mrb_define_const(mrb, io, "SEEK_END", mrb_fixnum_value(SEEK_END)); 681 | 682 | struct RClass *io_error = mrb_define_class(mrb, "IOError", mrb->eStandardError_class); 683 | mrb_define_class(mrb, "EOFError", io_error); 684 | } 685 | 686 | void 687 | mrb_mruby_stringio_gem_final(mrb_state* mrb) 688 | { 689 | } 690 | --------------------------------------------------------------------------------