stream: Introduce a push-back reader.
authorJean-Christophe Beaupré <jcbrinfo@users.noreply.github.com>
Fri, 3 Oct 2014 14:38:34 +0000 (10:38 -0400)
committerJean-Christophe Beaupré <jcbrinfo@users.noreply.github.com>
Mon, 6 Oct 2014 16:20:29 +0000 (12:20 -0400)
May be useful when implementing backtracking algorithms.

Signed-off-by: Jean-Christophe Beaupré <jcbrinfo@users.noreply.github.com>

contrib/pep8analysis/src/pep8analysis_web.nit
lib/io/io.nit [new file with mode: 0644]
lib/io/push_back_reader.nit [new file with mode: 0644]
lib/io/test_push_back_reader.nit [new file with mode: 0644]

index 259fd09..18ee374 100644 (file)
@@ -108,15 +108,6 @@ redef class AnalysisManager
        fun show_graph(content: String) do "show_graph('{content.escape_to_c}');".run_js
 end
 
-class StringIStream
-       super BufferedIStream
-
-       init(str: String) do _buffer = new FlatBuffer.from(str)
-
-       redef fun fill_buffer do end_reached = true
-       redef var end_reached: Bool = false
-end
-
 redef class NativeString
        fun run_analysis do manager.run to_s
 end
diff --git a/lib/io/io.nit b/lib/io/io.nit
new file mode 100644 (file)
index 0000000..c48b171
--- /dev/null
@@ -0,0 +1,14 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This file is free software, which comes along with NIT. This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. You can modify it is you want, provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You are allowed to redistribute it and sell it, alone or is a part of
+# another product.
+
+# Additional services for streams.
+module io
+
+import push_back_reader
diff --git a/lib/io/push_back_reader.nit b/lib/io/push_back_reader.nit
new file mode 100644 (file)
index 0000000..257c662
--- /dev/null
@@ -0,0 +1,110 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This file is free software, which comes along with NIT. This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. You can modify it is you want, provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You are allowed to redistribute it and sell it, alone or is a part of
+# another product.
+
+# Input stream that permits to push bytes back to the stream.
+module io::push_back_reader
+
+# Input stream that permits to push bytes back to the stream.
+interface PushBackReader
+       super IStream
+
+       # Push the specified byte back to the stream.
+       #
+       # The specified byte is usually the last read byte that is not
+       # “unread” already.
+       fun unread_char(c: Char) is abstract
+
+       # Push the specified string back to the stream.
+       #
+       # The specified string is usually the last read string that is not
+       # “unread” already.
+       fun unread(s: String) do
+               for c in s.chars.reverse_iterator do unread_char(c)
+       end
+end
+
+# Decorates an input stream to permit to push bytes back to the input stream.
+class PushBackDecorator
+       super PushBackReader
+
+       # The parent stream.
+       var parent: IStream
+
+       # The stack of the pushed-back bytes.
+       #
+       # Bytes are in the reverse order they will reappear in the stream.
+       # `unread` pushes bytes after already pushed-back bytes.
+       #
+       # TODO: With optimized bulk array copy operations, a reversed stack (like in
+       # OpenJDK) would be more efficient.
+       private var buf: Sequence[Char] = new Array[Char]
+
+       redef fun read_char: Int do
+               if buf.length <= 0 then return parent.read_char
+               return buf.pop.ascii
+       end
+
+       redef fun read(i: Int): String do
+               if i <= 0 then return ""
+               if buf.length <= 0 then return parent.read(i)
+               var s = new FlatBuffer.with_capacity(i)
+
+               loop
+                       s.chars.push(buf.pop)
+                       i -= 1
+                       if i <= 0 then
+                               return s.to_s
+                       else if buf.length <= 0 then
+                               s.append(parent.read(i))
+                               return s.to_s
+                       end
+               end
+       end
+
+       redef fun read_all: String do
+               if buf.length <= 0 then return parent.read_all
+               var s = new FlatBuffer
+
+               loop
+                       s.chars.push(buf.pop)
+                       if buf.length <= 0 then
+                               s.append(parent.read_all)
+                               return s.to_s
+                       end
+               end
+       end
+
+       redef fun append_line_to(s: Buffer) do
+               if buf.length > 0 then
+                       var c: Char
+
+                       loop
+                               c = buf.pop
+                               s.chars.push(c)
+                               if c == '\n' then return
+                               if buf.length <= 0 then break
+                       end
+               end
+               parent.append_line_to(s)
+       end
+
+       redef fun eof: Bool do return buf.length <= 0 and parent.eof
+
+       redef fun close do
+               buf.clear
+               parent.close
+       end
+
+       redef fun unread_char(c: Char) do buf.push(c)
+
+       redef fun unread(s: String) do
+               for c in s.chars.reverse_iterator do buf.push(c)
+       end
+end
diff --git a/lib/io/test_push_back_reader.nit b/lib/io/test_push_back_reader.nit
new file mode 100644 (file)
index 0000000..5bace8f
--- /dev/null
@@ -0,0 +1,158 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This file is free software, which comes along with NIT. This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. You can modify it is you want, provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You are allowed to redistribute it and sell it, alone or is a part of
+# another product.
+
+# Test suites for module `push_back_reader`
+module test_push_back_reader is test_suite
+
+import test_suite
+import io::push_back_reader
+
+class TestPushBackDecorator
+       super TestSuite
+
+       private fun sample: PushBackDecorator do
+               return new PushBackDecorator(new StringIStream("""
+abcd
+
+efg
+"""))
+       end
+
+       fun test_read_char do
+               var subject = sample
+
+               assert 'a' == subject.read_char.ascii
+       end
+
+       fun test_read_char_eof do
+               var subject = new PushBackDecorator(new StringIStream(""))
+
+               assert -1 == subject.read_char
+       end
+
+       fun test_unread_read_char do
+               var subject = sample
+
+               subject.unread_char('z')
+               assert 'z' == subject.read_char.ascii
+               assert 'a' == subject.read_char.ascii
+       end
+
+       fun test_read_partial do
+               var subject = sample
+
+               assert "abcd" == subject.read(4)
+       end
+
+       fun test_read_too_much do
+               var subject = sample
+               var exp = """
+abcd
+
+efg
+"""
+               assert exp == subject.read(100)
+       end
+
+       fun test_unread_read do
+               var subject = sample
+
+               subject.unread("a")
+               assert "aab" == subject.read(3)
+       end
+
+       fun test_unread_read_mixed do
+               var subject = sample
+
+               subject.unread("a")
+               assert "aab" == subject.read(3)
+       end
+
+       fun test_read_all do
+               var subject = sample
+               var exp = """
+abcd
+
+efg
+"""
+               assert exp == subject.read_all
+       end
+
+       fun test_unread_read_all do
+               var subject = sample
+               var exp = """
+fooabcd
+
+efg
+"""
+               subject.unread("foo")
+               assert exp == subject.read_all
+       end
+
+       fun test_read_line do
+               var subject = sample
+
+               assert "abcd\n" == subject.read_line
+               assert "\n" == subject.read_line
+       end
+
+       fun test_unread_read_line do
+               var subject = sample
+
+               subject.unread("a\nb")
+               assert "a\n" == subject.read_line
+               assert "babcd\n" == subject.read_line
+       end
+
+       fun test_eof do
+               var subject = sample
+
+               assert not subject.eof
+               subject.read_all
+               assert subject.eof
+       end
+
+       fun test_eof_empty do
+               var subject = new PushBackDecorator(new StringIStream(""))
+
+               assert subject.eof
+       end
+
+       fun test_close do
+               var subject = sample
+
+               subject.close
+               assert subject.eof
+       end
+
+       fun test_unread_close do
+               var subject = sample
+
+               subject.unread("foo")
+               subject.close
+               assert subject.eof
+       end
+
+       fun test_unread_char_order do
+               var subject = sample
+
+               subject.unread_char('z')
+               subject.unread_char('y')
+               assert "yzab" == subject.read(4)
+       end
+
+       fun test_unread_order do
+               var subject = sample
+
+               subject.unread("bar")
+               subject.unread("foo")
+               assert "foobarab" == subject.read(8)
+       end
+end