Merge: Benitlux & sqlite3: fix closing SQLite statements and malformed superstring
authorJean Privat <jean@pryen.org>
Thu, 14 Apr 2016 01:03:03 +0000 (21:03 -0400)
committerJean Privat <jean@pryen.org>
Thu, 14 Apr 2016 01:03:03 +0000 (21:03 -0400)
Fix statements that were left open and did not remove the lock on the database. It now relies on iterators to close the statement after the end of the loop. This requires to actually complete the loop and not return before it is complete. A shortcut is to use `statement.iterator.to_a` when the result is expected to be short, like with a single row.

Note that I did not use `with` because it has the same problem with a `return` skipping the call to `finish`. Neither did I use `Finalizable` because it is invoked by the GC which may be much later.

Also fix one malformed superstring and improve the style of another one.

Pull-Request: #2009
Reviewed-by: Jean Privat <jean@pryen.org>

12 files changed:
contrib/tnitter/src/tnitter_app.nit
lib/app/ui.nit
lib/core/bytes.nit
lib/gtk/v3_10.nit
lib/linux/ui.nit
src/interpreter/debugger.nit
src/interpreter/naive_interpreter.nit
src/parser/parser_nodes.nit
src/semantize/scope.nit
tests/base_with_return.nit [new file with mode: 0644]
tests/sav/base_with_return.res [new file with mode: 0644]
tests/sav/nitc-common/fixme/base_with_return.res [new file with mode: 0644]

index b8a955a..fccad93 100644 (file)
@@ -73,8 +73,7 @@ class TnitterWindow
        # Update the screen to show the new `posts`
        fun apply_update(posts: Array[Post])
        do
-               layout.remove list_posts
-               list_posts = new ListLayout(parent=layout)
+               list_posts.clear
                for post in posts do
                        var line = new VerticalLayout(parent=list_posts)
                        var author = new LabelAuthor(parent=line, text="@"+post.user)
index 727b716..6597022 100644 (file)
@@ -118,6 +118,9 @@ class CompositeControl
        # Is `item` in `self`?
        fun has(item: Control): Bool do return items.has(item)
 
+       # Remove all items from `self`
+       fun clear do for item in items.to_a do remove item
+
        redef fun on_create do for i in items do i.on_create
 
        redef fun on_start do for i in items do i.on_start
index 1e081b8..232f8c7 100644 (file)
@@ -251,7 +251,15 @@ class Bytes
                return slice(from, length)
        end
 
-       # Returns self as a hexadecimal digest
+       # Returns self as an hexadecimal digest.
+       #
+       # Also known as plain hexdump or postscript hexdump.
+       #
+       # ~~~
+       # var b = "abcd".to_bytes
+       # assert b.hexdigest == "61626364"
+       # assert b.hexdigest.hexdigest_to_bytes == b
+       # ~~~
        fun hexdigest: String do
                var elen = length * 2
                var ns = new NativeString(elen)
@@ -265,6 +273,61 @@ class Bytes
                return new FlatString.full(ns, elen, 0, elen)
        end
 
+       # Return self as a C hexadecimal digest where bytes are prefixed by `\x`
+       #
+       # The output is compatible with literal stream of bytes for most languages
+       # including C and Nit.
+       #
+       # ~~~
+       # var b = "abcd".to_bytes
+       # assert b.chexdigest == "\\x61\\x62\\x63\\x64"
+       # assert b.chexdigest.unescape_to_bytes == b
+       # ~~~
+       fun chexdigest: String do
+               var elen = length * 4
+               var ns = new NativeString(elen)
+               var i = 0
+               var oi = 0
+               while i < length do
+                       ns[oi] = 0x5Cu8 # b'\\'
+                       ns[oi+1] = 0x78u8 # b'x'
+                       self[i].add_digest_at(ns, oi+2)
+                       i += 1
+                       oi += 4
+               end
+               return new FlatString.full(ns, elen, 0, elen)
+       end
+
+
+       # Returns self as a stream of bits (0 and 1)
+       #
+       # ~~~
+       # var b = "abcd".to_bytes
+       # assert b.binarydigest == "01100001011000100110001101100100"
+       # assert b.binarydigest.binarydigest_to_bytes == b
+       # ~~~
+       fun binarydigest: String do
+               var elen = length * 8
+               var ns = new NativeString(elen)
+               var i = 0
+               var oi = 0
+               while i < length do
+                       var c = self[i]
+                       var b = 128u8
+                       while b > 0u8 do
+                               if c & b == 0u8 then
+                                       ns[oi] = 0x30u8 # b'0'
+                               else
+                                       ns[oi] = 0x31u8 # b'1'
+                               end
+                               oi += 1
+                               b = b >> 1
+                       end
+                       i += 1
+               end
+               return new FlatString.full(ns, elen, 0, elen)
+       end
+
        #     var b = new Bytes.with_capacity(1)
        #     b[0] = 101u8
        #     assert b.to_s == "e"
@@ -583,17 +646,56 @@ redef class Text
        # Returns a new `Bytes` instance with the digest as content
        #
        #     assert "0B1F4D".hexdigest_to_bytes == [0x0Bu8, 0x1Fu8, 0x4Du8]
+       #     assert "0B1F4D".hexdigest_to_bytes.hexdigest == "0B1F4D"
+       #
+       # Characters that are not hexadecimal digits are ignored.
+       #
+       #     assert "z0B1 F4\nD".hexdigest_to_bytes.hexdigest == "0B1F4D"
+       #     assert "\\x0b1 \\xf4d".hexdigest_to_bytes.hexdigest == "0B1F4D"
+       #
+       # When the number of hexadecimal digit is not even, then a leading 0 is
+       # implicitly considered to fill the left byte (the most significant one).
        #
-       # REQUIRE: `self` is a valid hexdigest and hexdigest.length % 2 == 0
+       #     assert "1".hexdigest_to_bytes.hexdigest == "01"
+       #     assert "FFF".hexdigest_to_bytes.hexdigest == "0FFF"
+       #
+       # `Bytes::hexdigest` is a loosely reverse method since its
+       # results contain only pairs of uppercase hexadecimal digits.
+       #
+       #     assert "ABCD".hexdigest_to_bytes.hexdigest == "ABCD"
+       #     assert "a b c".hexdigest_to_bytes.hexdigest == "0ABC"
        fun hexdigest_to_bytes: Bytes do
                var b = bytes
-               var pos = 0
                var max = bytelen
-               var ret = new Bytes.with_capacity(max / 2)
+
+               var dlength = 0 # Number of hex digits
+               var pos = 0
                while pos < max do
-                       ret.add((b[pos].hexdigit_to_byteval << 4) |
-                       b[pos + 1].hexdigit_to_byteval)
-                       pos += 2
+                       var c = b[pos]
+                       if c.is_valid_hexdigit then dlength += 1
+                       pos += 1
+               end
+
+               # Allocate the result buffer
+               var ret = new Bytes.with_capacity((dlength+1) / 2)
+
+               var i = (dlength+1) % 2 # current hex digit (1=high, 0=low)
+               var byte = 0u8 # current accumulated byte value
+
+               pos = 0
+               while pos < max do
+                       var c = b[pos]
+                       if c.is_valid_hexdigit then
+                               byte = byte << 4 | c.hexdigit_to_byteval
+                               i -= 1
+                               if i < 0 then
+                                       # Last digit known: store and restart
+                                       ret.add byte
+                                       i = 1
+                                       byte = 0u8
+                               end
+                       end
+                       pos += 1
                end
                return ret
        end
@@ -615,6 +717,12 @@ redef class Text
        # Return a `Bytes` instance where Nit escape sequences are transformed.
        #
        #     assert "B\\n\\x41\\u0103D3".unescape_to_bytes.hexdigest == "420A41F0908F93"
+       #
+       # `Bytes::chexdigest` is a loosely reverse methods since its result is only made
+       # of `"\x??"` escape sequences.
+       #
+       #     assert "\\x41\\x42\\x43".unescape_to_bytes.chexdigest == "\\x41\\x42\\x43"
+       #     assert "B\\n\\x41\\u0103D3".unescape_to_bytes.chexdigest == "\\x42\\x0A\\x41\\xF0\\x90\\x8F\\x93"
        fun unescape_to_bytes: Bytes do
                var res = new Bytes.with_capacity(self.bytelen)
                var was_slash = false
@@ -662,6 +770,70 @@ redef class Text
                end
                return res
        end
+
+       # Return a `Bytes` by reading 0 and 1.
+       #
+       #     assert "1010101100001101".binarydigest_to_bytes.hexdigest == "AB0D"
+       #
+       # Note that characters that are neither 0 or 1 are just ignored.
+       #
+       #     assert "a1B01 010\n1100あ001101".binarydigest_to_bytes.hexdigest == "AB0D"
+       #     assert "hello".binarydigest_to_bytes.is_empty
+       #
+       # When the number of bits is not divisible by 8, then leading 0 are
+       # implicitly considered to fill the left byte (the most significant one).
+       #
+       #     assert "1".binarydigest_to_bytes.hexdigest == "01"
+       #     assert "1111111".binarydigest_to_bytes.hexdigest == "7F"
+       #     assert "1000110100".binarydigest_to_bytes.hexdigest == "0234"
+       #
+       # `Bytes::binarydigest` is a loosely reverse method since its
+       # results contain only 1 and 0 by blocks of 8.
+       #
+       #     assert "1010101100001101".binarydigest_to_bytes.binarydigest == "1010101100001101"
+       #     assert "1".binarydigest_to_bytes.binarydigest == "00000001"
+       fun binarydigest_to_bytes: Bytes
+       do
+               var b = bytes
+               var max = bytelen
+
+               # Count bits
+               var bitlen = 0
+               var pos = 0
+               while pos < max do
+                       var c = b[pos]
+                       pos += 1
+                       if c == 0x30u8 or c == 0x31u8 then bitlen += 1 # b'0' or b'1'
+               end
+
+               # Allocate (and take care of the padding)
+               var ret = new Bytes.with_capacity((bitlen+7) / 8)
+
+               var i = (bitlen+7) % 8 # current bit (7th=128, 0th=1)
+               var byte = 0u8 # current accumulated byte value
+
+               pos = 0
+               while pos < max do
+                       var c = b[pos]
+                       pos += 1
+                       if c == 0x30u8 then # b'0'
+                               byte = byte << 1
+                       else if c == 0x31u8 then # b'1'
+                               byte = byte << 1 | 1u8
+                       else
+                               continue
+                       end
+
+                       i -= 1
+                       if i < 0 then
+                               # Last bit known: store and restart
+                               ret.add byte
+                               i = 7
+                               byte = 0u8
+                       end
+               end
+               return ret
+       end
 end
 
 redef class FlatText
index f47b46e..37b5dd5 100644 (file)
@@ -70,7 +70,7 @@ end
 
 # A single row of a `GtkListBox`
 extern class GtkListBoxRow `{ GtkListBoxRow* `}
-       super GtkWidget
+       super GtkBin
 
        new `{ return (GtkListBoxRow*)gtk_list_box_row_new(); `}
 
index 23c1f91..8f9fae4 100644 (file)
@@ -186,6 +186,9 @@ redef class ListLayout
        # Container inside `native`
        var native_list_box = new GtkListBox
 
+       # `GtkListBoxRow` used to contains children `View`s
+       var native_rows = new Map[View, GtkListBoxRow]
+
        init do
                native_list_box.selection_mode = new GtkSelectionMode.none
                native.add native_list_box
@@ -199,13 +202,32 @@ redef class ListLayout
        redef fun add(item)
        do
                super
-               if item isa View then native_list_box.add item.native
+               if item isa View then
+                       var native_row = new GtkListBoxRow
+                       #native_row.activable = false # TODO with GTK 3.14
+                       #native_row.selectable = false
+                       native_row.add item.native
+
+                       native_rows[item] = native_row
+                       native_list_box.add native_row
+                       native_row.show
+               end
        end
 
        redef fun remove(item)
        do
                super
-               if item isa View then native_list_box.remove item.native
+               if item isa View then
+                       var native_row = native_rows.get_or_null(item)
+                       if native_row == null then
+                               print_error "Error: {self} does not contains {item}"
+                               return
+                       end
+
+                       native_list_box.remove native_row
+                       native_rows.keys.remove item
+                       native_row.destroy
+               end
        end
 end
 
index cf4e815..25ca6c2 100644 (file)
@@ -1418,10 +1418,8 @@ redef class AMethPropdef
                                curr_instances[i] = currFra.map[i]
                        end
                end
-               if v.returnmark == f then
-                       v.returnmark = null
+               if v.is_escape(self.return_mark) then
                        var res = v.escapevalue
-                       v.escapevalue = null
                        return res
                end
                return null
index 9236d99..7fc2355 100644 (file)
@@ -113,17 +113,13 @@ class NaiveInterpreter
                return self.modelbuilder.force_get_primitive_method(current_node, name, recv.mclass, self.mainmodule)
        end
 
-       # Is a return executed?
-       # Set this mark to skip the evaluation until the end of the specified method frame
-       var returnmark: nullable FRAME = null
-
-       # Is a break or a continue executed?
+       # Is a return, a break or a continue executed?
        # Set this mark to skip the evaluation until a labeled statement catch it with `is_escape`
        var escapemark: nullable EscapeMark = null
 
        # Is a return or a break or a continue executed?
        # Use this function to know if you must skip the evaluation of statements
-       fun is_escaping: Bool do return returnmark != null or escapemark != null
+       fun is_escaping: Bool do return escapemark != null
 
        # The value associated with the current return/break/continue, if any.
        # Set the value when you set a escapemark.
@@ -848,10 +844,8 @@ redef class AMethPropdef
                var f = v.new_frame(self, mpropdef, args)
                var res = call_commons(v, mpropdef, args, f)
                v.frames.shift
-               if v.returnmark == f then
-                       v.returnmark = null
+               if v.is_escape(self.return_mark) then
                        res = v.escapevalue
-                       v.escapevalue = null
                        return res
                end
                return res
@@ -1545,10 +1539,9 @@ redef class AAttrPropdef
                        val = v.expr(nexpr)
                else if nblock != null then
                        v.stmt(nblock)
-                       assert v.returnmark == f
+                       assert v.escapemark == return_mark
                        val = v.escapevalue
-                       v.returnmark = null
-                       v.escapevalue = null
+                       v.escapemark = null
                else
                        abort
                end
@@ -1693,19 +1686,6 @@ redef class AEscapeExpr
        end
 end
 
-redef class AReturnExpr
-       redef fun stmt(v)
-       do
-               var ne = self.n_expr
-               if ne != null then
-                       var i = v.expr(ne)
-                       if i == null then return
-                       v.escapevalue = i
-               end
-               v.returnmark = v.frame
-       end
-end
-
 redef class AAbortExpr
        redef fun stmt(v)
        do
index 9fe32f6..ba5b8f3 100644 (file)
@@ -1776,13 +1776,10 @@ end
 
 # A `return` statement. eg `return x`
 class AReturnExpr
-       super AExpr
+       super AEscapeExpr
 
        # The `return` keyword
        var n_kwreturn: nullable TKwreturn = null is writable
-
-       # The return value, if any
-       var n_expr: nullable AExpr = null is writable
 end
 
 # A `yield` statement. eg `yield x`
index 5f9c748..a0205a5 100644 (file)
@@ -71,6 +71,9 @@ private class ScopeVisitor
        # The tool context used to display errors
        var toolcontext: ToolContext
 
+       # The analysed property
+       var propdef: APropdef
+
        var selfvariable = new Variable("self")
 
        init
@@ -248,10 +251,13 @@ redef class ANode
 end
 
 redef class APropdef
+       # The break escape mark associated with the return
+       var return_mark: nullable EscapeMark
+
        # Entry point of the scope analysis
        fun do_scope(toolcontext: ToolContext)
        do
-               var v = new ScopeVisitor(toolcontext)
+               var v = new ScopeVisitor(toolcontext, self)
                v.enter_visit(self)
                v.shift_scope
        end
@@ -326,6 +332,21 @@ redef class ABreakExpr
        end
 end
 
+redef class AReturnExpr
+       redef fun accept_scope_visitor(v)
+       do
+               super
+
+               var escapemark = v.propdef.return_mark
+               if escapemark == null then
+                       escapemark = new EscapeMark
+                       v.propdef.return_mark = escapemark
+               end
+
+               escapemark.escapes.add(self)
+               self.escapemark = escapemark
+       end
+end
 
 redef class ADoExpr
        # The break escape mark associated with the 'do' block
diff --git a/tests/base_with_return.nit b/tests/base_with_return.nit
new file mode 100644 (file)
index 0000000..2fc6f9d
--- /dev/null
@@ -0,0 +1,38 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import core::kernel
+
+class A
+       var a = 5
+       fun start do
+               var a = new A
+               1.output
+       end
+       fun finish do
+               var a = new A
+               3.output
+       end
+end
+
+fun foo
+do
+       with new A do
+               2.output
+               return
+       end
+end
+
+foo
+4.output
diff --git a/tests/sav/base_with_return.res b/tests/sav/base_with_return.res
new file mode 100644 (file)
index 0000000..94ebaf9
--- /dev/null
@@ -0,0 +1,4 @@
+1
+2
+3
+4
diff --git a/tests/sav/nitc-common/fixme/base_with_return.res b/tests/sav/nitc-common/fixme/base_with_return.res
new file mode 100644 (file)
index 0000000..e8a01cd
--- /dev/null
@@ -0,0 +1,3 @@
+1
+2
+4