# close the stream
fun close is abstract
+
+ # Pre-work hook.
+ #
+ # Used to inform `self` that operations will start.
+ # Specific streams can use this to prepare some resources.
+ #
+ # Is automatically invoked at the beginning of `with` structures.
+ #
+ # Do nothing by default.
+ fun start do end
+
+ # Post-work hook.
+ #
+ # Used to inform `self` that the operations are over.
+ # Specific streams can use this to free some resources.
+ #
+ # Is automatically invoked at the end of `with` structures.
+ #
+ # call `close` by default.
+ fun finish do close
end
# A `Stream` that can be read from
super Stream
# Decoder used to transform input bytes to UTF-8
- var decoder: Decoder = utf8_decoder is writable
+ var decoder: Codec = utf8_codec is writable
# Reads a character. Returns `null` on EOF or timeout
fun read_char: nullable Char is abstract
fun read_bytes(i: Int): Bytes
do
if last_error != null then return new Bytes.empty
- var s = new NativeString(i)
+ var s = new CString(i)
var buf = new Bytes(s, 0, 0)
while i > 0 and not eof do
var c = read_byte
var rets = ""
var pos = 0
var str = s.items.clean_utf8(slen)
- slen = str.bytelen
+ slen = str.byte_length
var sits = str.items
var remsp = slen
while pos < slen do
# if this is the best size or not
var chunksz = 129
if chunksz > remsp then
- rets += new FlatString.with_infos(sits, remsp, pos, pos + remsp - 1)
+ rets += new FlatString.with_infos(sits, remsp, pos)
break
end
var st = sits.find_beginning_of_char_at(pos + chunksz - 1)
- var bytelen = st - pos
- rets += new FlatString.with_infos(sits, bytelen, pos, st - 1)
+ var byte_length = st - pos
+ rets += new FlatString.with_infos(sits, byte_length, pos)
pos = st
- remsp -= bytelen
+ remsp -= byte_length
end
if rets isa Concat then return rets.balance
return rets
super Stream
# The coder from a nit UTF-8 String to the output file
- var coder: Coder = utf8_coder is writable
+ var coder: Codec = utf8_codec is writable
# Writes bytes from `s`
fun write_bytes(s: Bytes) is abstract
# Write a single byte
fun write_byte(value: Byte) is abstract
+ # Writes a single char
+ fun write_char(c: Char) do write(c.to_s)
+
# Can the stream be used to write
fun is_writable: Bool is abstract
end
end
end
+redef class Bytes
+ super Writable
+ redef fun write_to(s) do s.write_bytes(self)
+
+ redef fun write_to_string do return to_s
+end
+
redef class Text
super Writable
redef fun write_to(stream) do stream.write(self)
while c < full_len do c = c * 2 + 2
_buffer_capacity = c
end
- var nns = new NativeString(_buffer_capacity)
+ var nns = new CString(_buffer_capacity)
bf.items.copy_to(nns, bf.length, 0, 0)
_buffer.copy_to(nns, remsp, _buffer_pos, bf.length)
_buffer = nns
end
# The buffer
- private var buffer: NativeString = new NativeString(0)
+ private var buffer: CString = new CString(0)
# The current position in the buffer
private var buffer_pos = 0
# Allocate a `_buffer` for a given `capacity`.
protected fun prepare_buffer(capacity: Int)
do
- _buffer = new NativeString(capacity)
+ _buffer = new CString(capacity)
_buffer_pos = 0 # need to read
_buffer_length = 0
_buffer_capacity = capacity
super Writer
end
-# `Stream` that can be used to write to a `String`
+# Write to `bytes` in memory
#
-# Mainly used for compatibility with Writer type and tests.
-class StringWriter
+# ~~~
+# var writer = new BytesWriter
+#
+# writer.write "Strings "
+# writer.write_char '&'
+# writer.write_byte 0x20u8
+# writer.write_bytes "bytes".to_bytes
+#
+# assert writer.to_s == "\\x53\\x74\\x72\\x69\\x6E\\x67\\x73\\x20\\x26\\x20\\x62\\x79\\x74\\x65\\x73"
+# assert writer.bytes.to_s == "Strings & bytes"
+# ~~~
+#
+# As with any binary data, UTF-8 code points encoded on two bytes or more
+# can be constructed byte by byte.
+#
+# ~~~
+# writer = new BytesWriter
+#
+# # Write just the character first half
+# writer.write_byte 0xC2u8
+# assert writer.to_s == "\\xC2"
+# assert writer.bytes.to_s == "�"
+#
+# # Complete the character
+# writer.write_byte 0xA2u8
+# assert writer.to_s == "\\xC2\\xA2"
+# assert writer.bytes.to_s == "¢"
+# ~~~
+class BytesWriter
super Writer
- private var content = new Array[String]
- redef fun to_s do return content.plain_to_s
- redef fun is_writable do return not closed
+ # Written memory
+ var bytes = new Bytes.empty
- redef fun write_bytes(b) do
- content.add(b.to_s)
- end
+ redef fun to_s do return bytes.chexdigest
redef fun write(str)
do
- assert not closed
- content.add(str.to_s)
+ if closed then return
+ str.append_to_bytes bytes
+ end
+
+ redef fun write_char(c)
+ do
+ if closed then return
+ bytes.add_char c
+ end
+
+ redef fun write_byte(value)
+ do
+ if closed then return
+ bytes.add value
+ end
+
+ redef fun write_bytes(b)
+ do
+ if closed then return
+ bytes.append b
end
# Is the stream closed?
protected var closed = false
redef fun close do closed = true
+ redef fun is_writable do return not closed
end
-# `Stream` used to read from a `String`
+# `Stream` writing to a `String`
#
-# Mainly used for compatibility with Reader type and tests.
-class StringReader
+# This class has the same behavior as `BytesWriter`
+# except for `to_s` which decodes `bytes` to a string.
+#
+# ~~~
+# var writer = new StringWriter
+#
+# writer.write "Strings "
+# writer.write_char '&'
+# writer.write_byte 0x20u8
+# writer.write_bytes "bytes".to_bytes
+#
+# assert writer.to_s == "Strings & bytes"
+# ~~~
+class StringWriter
+ super BytesWriter
+
+ redef fun to_s do return bytes.to_s
+end
+
+# Read from `bytes` in memory
+#
+# ~~~
+# var reader = new BytesReader(b"a…b")
+# assert reader.read_char == 'a'
+# assert reader.read_byte == 0xE2u8 # 1st byte of '…'
+# assert reader.read_byte == 0x80u8 # 2nd byte of '…'
+# assert reader.read_char == '�' # Reads the last byte as an invalid char
+# assert reader.read_all_bytes == b"b"
+# ~~~
+class BytesReader
super Reader
- # The string to read from.
- var source: String
+ # Source data to read
+ var bytes: Bytes
- # The current position in the string (bytewise).
- private var cursor: Int = 0
+ # The current position in `bytes`
+ private var cursor = 0
- redef fun read_char do
- if cursor < source.length then
- # Fix when supporting UTF-8
- var c = source[cursor]
- cursor += 1
- return c
- else
- return null
- end
- end
+ redef fun read_char
+ do
+ if cursor >= bytes.length then return null
- redef fun read_byte do
- if cursor < source.length then
- var c = source.bytes[cursor]
- cursor += 1
- return c
- else
- return null
- end
+ var len = bytes.items.length_of_char_at(cursor)
+ var char = bytes.items.char_at(cursor)
+ cursor += len
+ return char
end
- redef fun close do
- source = ""
+ redef fun read_byte
+ do
+ if cursor >= bytes.length then return null
+
+ var c = bytes[cursor]
+ cursor += 1
+ return c
end
- redef fun read_all_bytes do
- var nslen = source.length - cursor
- var nns = new NativeString(nslen)
- source.copy_to_native(nns, nslen, cursor, 0)
- return new Bytes(nns, nslen, nslen)
+ redef fun close do bytes = new Bytes.empty
+
+ redef fun read_all_bytes
+ do
+ var res = bytes.slice_from(cursor)
+ cursor = bytes.length
+ return res
end
- redef fun eof do return cursor >= source.bytelen
+ redef fun eof do return cursor >= bytes.length
+end
+
+# `Stream` reading from a `String` source
+#
+# This class has the same behavior as `BytesReader`
+# except for its constructor accepting a `String`.
+#
+# ~~~
+# var reader = new StringReader("a…b")
+# assert reader.read_char == 'a'
+# assert reader.read_byte == 0xE2u8 # 1st byte of '…'
+# assert reader.read_byte == 0x80u8 # 2nd byte of '…'
+# assert reader.read_char == '�' # Reads the last byte as an invalid char
+# assert reader.read_all == "b"
+# ~~~
+class StringReader
+ super BytesReader
+
+ autoinit source
+
+ # Source data to read
+ var source: String
+
+ init do bytes = source.to_bytes
+
+ redef fun close
+ do
+ source = ""
+ super
+ end
end