Merge: Safe collection access
authorJean Privat <jean@pryen.org>
Wed, 20 May 2015 00:30:31 +0000 (20:30 -0400)
committerJean Privat <jean@pryen.org>
Wed, 20 May 2015 00:30:31 +0000 (20:30 -0400)
A proposition to solve an old API issue related to covariance in collections (and fix #1353)

This PR updates the signatures of access methods in collections: `has`, `[]` and anything related that takes an `E` or a `K` in read-collections.
This make these classes covariant-safe.

Previously, the signature was to strictly accepts the bound, thus making the code unsafe but has the advantage to provide static hints

~~~nit
assert [1,2,3].has("Hello") # static error, expected int got string
~~~

But this behavior had issues because:

* unsafe code, but it is not because Nit *can* be unsafe that Nit *should* be unsafe.
* a perfect semantic answer is possible, eq returning false in the previous example. thus the existing behavior is not POLA

Because the philosophy of Nit is that static types should help the programmer without being annoying, this PR lift the constraint on parameters of all read access methods.

The semantic is now more aligned with the one of dynamically typed language where the behavior is decided by messages and not by the types of things.

This especially allows to use collections of thing when `==` does not implies the equality of static types (ex subclasses of Sequence).

~~~nit
var a = [1,2,3]
var l = new List[Int].from(a)
assert a == l # by the law of Sequence

var aa = [a]
assert aa.has(l) # before the PR, error; with the PR, true

var ma = new Map[Array[Int],String]
ma[a] = "hello"
assert ma.has_key(l) # before the PR, error; with the PR, true
~~~

Pull-Request: #1355
Reviewed-by: Alexis Laferrière <alexis.laf@xymus.net>
Reviewed-by: Lucas Bajolet <r4pass@hotmail.com>
Reviewed-by: Alexandre Terrasa <alexandre@moz-code.org>

49 files changed:
examples/calculator/.gitignore [new file with mode: 0644]
examples/calculator/Makefile
examples/calculator/src/calculator.nit [new file with mode: 0644]
examples/calculator/src/calculator_android.nit [deleted file]
examples/calculator/src/calculator_gtk.nit [deleted file]
examples/calculator/src/calculator_test.nit
lib/android/README.md
lib/android/examples/Makefile
lib/android/examples/src/ui_test.nit
lib/android/ui/ui.nit
lib/app/README.md [new file with mode: 0644]
lib/app/ui.nit [new file with mode: 0644]
lib/binary/binary.nit [new file with mode: 0644]
lib/bucketed_game.nit
lib/gtk/v3_4/gtk_core.nit
lib/gtk/v3_6.nit
lib/json_serialization.nit
lib/linux/ui.nit [new file with mode: 0644]
lib/serialization/serialization.nit
lib/socket/socket.nit
lib/socket/socket_c.nit
lib/standard/file.nit
lib/standard/kernel.nit
lib/standard/stream.nit
src/doc/doc_phases/doc_structure.nit
src/doc/html_templates/html_model.nit
src/doc/html_templates/html_templates.nit
src/frontend/serialization_phase.nit
src/location.nit
src/neo.nit
tests/sav/nitg-e/test_serialization.res
tests/sav/nitg-e/test_serialization_redef.res
tests/sav/nitg-e/test_serialization_redef_alt0.res
tests/sav/nitg-e/test_serialization_redef_alt1.res
tests/sav/nitg-e/test_serialization_redef_alt2.res
tests/sav/nitserial_args1.res
tests/sav/test_binary.res [new file with mode: 0644]
tests/sav/test_binary_alt1.res [new file with mode: 0644]
tests/sav/test_binary_alt2.res [new file with mode: 0644]
tests/sav/test_binary_alt3.res [new file with mode: 0644]
tests/sav/test_deserialization_serial.res
tests/sav/test_serialization.res
tests/sav/test_serialization_redef.res
tests/sav/test_serialization_redef_alt0.res
tests/sav/test_serialization_redef_alt1.res
tests/sav/test_serialization_redef_alt2.res
tests/test_binary.nit [new file with mode: 0644]
tests/test_deserialization.nit
tests/test_deserialization_serial.nit

diff --git a/examples/calculator/.gitignore b/examples/calculator/.gitignore
new file mode 100644 (file)
index 0000000..700b511
--- /dev/null
@@ -0,0 +1 @@
+res/
index dd66960..071fa2b 100644 (file)
@@ -1,12 +1,26 @@
-all:
-       mkdir -p bin/
+NITC=../../bin/nitc
+NITLS=../../bin/nitls
 
-       ../../bin/nitc --dir bin/ src/calculator_gtk.nit src/calculator_test.nit
+all: bin/calculator bin/calculator.apk bin/test
 
-android:
-       mkdir -p bin/ res/
+bin/calculator: $(shell ${NITLS} -M src/calculator.nit ../../lib/linux/ui.nit) ${NITC}
+       mkdir -p bin
+       ${NITC} -o $@ src/calculator.nit -m ../../lib/linux/ui.nit
+
+bin/calculator.apk: $(shell ${NITLS} -M src/calculator.nit ../../lib/android/ui/) ${NITC} ../../contrib/inkscape_tools/bin/svg_to_icons
+       mkdir -p bin res
        ../../contrib/inkscape_tools/bin/svg_to_icons art/icon.svg --android --out res/
-       ../../bin/nitc -o bin/calculator.apk src/calculator_android.nit
+       ${NITC} -o $@ src/calculator.nit -m ../../lib/android/ui/
+
+../../contrib/inkscape_tools/bin/svg_to_icons:
+       make -C ../../contrib/inkscape_tools/
 
-android-install: android
+android-install: bin/calculator.apk
        adb install -r bin/calculator.apk
+
+bin/test: $(shell ${NITLS} -M src/calculator_test.nit) ${NITC}
+       mkdir -p bin
+       ${NITC} -o $@ src/calculator_test.nit
+
+check: bin/test
+       bin/test
diff --git a/examples/calculator/src/calculator.nit b/examples/calculator/src/calculator.nit
new file mode 100644 (file)
index 0000000..2e2f437
--- /dev/null
@@ -0,0 +1,114 @@
+# 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.
+
+# Portable calculator UI
+module calculator is
+app_name "app.nit Calc."
+       app_version(0, 1, git_revision)
+       app_namespace "org.nitlanguage.calculator"
+
+       # Lock in portrait mode
+       android_manifest_activity """android:screenOrientation="portrait""""
+       android_api_target 15
+end
+
+import app::ui
+import app::data_store
+import android::aware
+
+import calculator_logic
+
+redef class App
+       redef fun on_create
+       do
+               # Create the main window
+               window = new CalculatorWindow
+               super
+       end
+end
+
+# The main (and only) window of this calculator
+class CalculatorWindow
+       super Window
+
+       # Calculator context, our business logic
+       private var context = new CalculatorContext
+
+       # Main window layout
+       private var layout = new VerticalLayout(parent=self)
+
+       # Main display, at the top of the screen
+       private var display = new TextInput(parent=layout)
+
+       # Maps operators as `String` to their `Button`
+       private var buttons = new HashMap[String, Button]
+
+       init
+       do
+               # All the button labels, row by row
+               var rows = [["7", "8", "9", "+"],
+                           ["4", "5", "6", "-"],
+                           ["1", "2", "3", "*"],
+                           ["0", ".", "C", "/"],
+                           ["="]]
+
+               for row in rows do
+                       var row_layout = new HorizontalLayout(parent=layout)
+
+                       for op in row do
+                               var but = new Button(parent=row_layout, text=op)
+                               but.observers.add self
+                               buttons[op] = but
+                       end
+               end
+       end
+
+       redef fun on_event(event)
+       do
+               if event isa ButtonPressEvent then
+
+                       var op = event.sender.text
+                       if op == "." then
+                               event.sender.enabled = false
+                               context.switch_to_decimals
+                       else if op.is_numeric then
+                               var n = op.to_i
+                               context.push_digit n
+                       else
+                               buttons["."].enabled = true
+                               context.push_op op.chars.first
+                       end
+
+                       display.text = context.display_text
+               end
+       end
+
+       redef fun on_save_state
+       do
+               app.data_store["context"] = context.to_json
+               super
+       end
+
+       redef fun on_restore_state
+       do
+               super
+
+               var save = app.data_store["context"]
+               if save == null then return
+               assert save isa String
+
+               self.context = new CalculatorContext.from_json(save)
+               display.text = context.display_text
+       end
+end
diff --git a/examples/calculator/src/calculator_android.nit b/examples/calculator/src/calculator_android.nit
deleted file mode 100644 (file)
index 1b1e686..0000000
+++ /dev/null
@@ -1,135 +0,0 @@
-# This file is part of NIT ( http://www.nitlanguage.org ).
-#
-# Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
-#
-# 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.
-
-# Android calculator application
-module calculator_android is
-       app_name "app.nit Calc."
-       app_version(0, 1, git_revision)
-       app_namespace "org.nitlanguage.calculator"
-
-       # Lock in portrait mode
-       android_manifest_activity """android:screenOrientation="portrait""""
-end
-
-# FIXME the next line should import `android` only when it uses nit_activity
-import android::log
-import android::ui
-
-import calculator_logic
-
-redef class Activity
-       super EventCatcher
-
-       private var context = new CalculatorContext
-
-       # The main display, at the top of the screen
-       private var display: EditText
-
-       # Maps operators as `String` to their `Button`
-       private var op2but = new HashMap[String, Button]
-
-       # Has this window been initialized?
-       private var inited = false
-
-       redef fun on_start
-       do
-               super
-
-               if inited then return
-               inited = true
-
-               # Setup UI
-               var layout = new NativeLinearLayout(native)
-               layout.set_vertical
-
-               # Display screen
-               var display = new EditText
-               layout.add_view_with_weight(display.native, 1.0)
-               display.text_size = 36.0
-               self.display = display
-
-               # Buttons; numbers and operators
-               var ops = [["7", "8", "9", "+"],
-                          ["4", "5", "6", "-"],
-                          ["1", "2", "3", "*"],
-                          ["0", ".", "C", "/"],
-                          ["="]]
-
-               for line in ops do
-                       var buts_layout = new NativeLinearLayout(native)
-                       buts_layout.set_horizontal
-                       layout.add_view_with_weight(buts_layout, 1.0)
-
-                       for op in line do
-                               var but = new Button
-                               but.event_catcher = self
-                               but.text = op
-                               but.text_size = 40
-                               buts_layout.add_view_with_weight(but.native, 1.0)
-                               op2but[op] = but
-                       end
-               end
-
-               native.content_view = layout
-       end
-
-       redef fun on_save_instance_state(state)
-       do
-               super
-
-               var nity = new Bundle.from(state)
-               nity["context"] = context.to_json
-       end
-
-       redef fun on_restore_instance_state(state)
-       do
-               super
-
-               var nity = new Bundle.from(state)
-               if not nity.has("context") then return
-
-               var json = nity.string("context")
-               if json == null then return
-
-               context = new CalculatorContext.from_json(json)
-       end
-
-       redef fun on_resume
-       do
-               display.text = context.display_text
-       end
-
-       redef fun catch_event(event)
-       do
-               if event isa ClickEvent then
-                       var sender = event.sender
-                       var op = sender.text
-
-                       if op == "." then
-                               sender.enabled = false
-                               context.switch_to_decimals
-                       else if op.is_numeric then
-                               var n = op.to_i
-                               context.push_digit n
-                       else
-                               op2but["."].enabled = true
-                               context.push_op op.chars.first
-                       end
-
-                       display.text = context.display_text
-               end
-       end
-end
diff --git a/examples/calculator/src/calculator_gtk.nit b/examples/calculator/src/calculator_gtk.nit
deleted file mode 100644 (file)
index 7586230..0000000
+++ /dev/null
@@ -1,113 +0,0 @@
-# This file is part of NIT ( http://www.nitlanguage.org ).
-#
-# Copyright 2013-2014 Alexis Laferrière <alexis.laf@xymus.net>
-#
-# 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.
-
-# GTK calculator
-module calculator_gtk
-
-import calculator_logic
-
-import gtk
-
-# GTK calculator UI
-class CalculatorGui
-       super GtkCallable
-
-       private var win: GtkWindow is noinit
-       private var container: GtkGrid is noinit
-
-       private var lbl_disp: GtkLabel is noinit
-       private var but_eq: GtkButton is noinit
-       private var but_dot: GtkButton is noinit
-
-       private var context = new CalculatorContext
-
-       redef fun signal(sender, op)
-       do
-               if op isa Char then # is an operation
-                       if op == '.' then
-                               but_dot.sensitive = false
-                               context.switch_to_decimals
-                       else
-                               but_dot.sensitive = true
-                               context.push_op op
-                       end
-               else if op isa Int then # is a number
-                       context.push_digit op
-               end
-
-               lbl_disp.text = context.display_text
-       end
-
-       init
-       do
-               init_gtk
-
-               win = new GtkWindow(new GtkWindowType.toplevel)
-               win.connect_destroy_signal_to_quit
-
-               container = new GtkGrid
-               win.add container
-
-               lbl_disp = new GtkLabel("_")
-               container.attach(lbl_disp, 0, 0, 5, 1)
-
-               # Digits
-               for n in [0..9] do
-                       var but = new GtkButton.with_label(n.to_s)
-                       but.request_size(64, 64)
-                       but.signal_connect("clicked", self, n)
-                       if n == 0 then
-                               container.attach(but, 0, 4, 1, 1)
-                       else container.attach(but, (n-1)%3, 3-(n-1)/3, 1, 1)
-               end
-
-               # Operators
-               var r = 1
-               for op in ['+', '-', '*', '/'] do
-                       var but = new GtkButton.with_label(op.to_s)
-                       but.request_size(64, 64)
-                       but.signal_connect("clicked", self, op)
-                       container.attach(but, 3, r, 1, 1)
-                       r+=1
-               end
-
-               # =
-               but_eq = new GtkButton.with_label("=")
-               but_eq.request_size(64, 64)
-               but_eq.signal_connect("clicked", self, '=')
-               container.attach(but_eq, 4, 3, 1, 2)
-
-               # .
-               but_dot = new GtkButton.with_label(".")
-               but_dot.request_size(64, 64)
-               but_dot.signal_connect("clicked", self, '.')
-               container.attach(but_dot, 1, 4, 1, 1)
-
-               # C
-               var but_c =  new GtkButton.with_label("C")
-               but_c.request_size(64, 64)
-               but_c.signal_connect("clicked", self, 'C')
-               container.attach(but_c, 2, 4, 1, 1)
-
-               win.show_all
-       end
-end
-
-# Do not show GUI in when testing
-if "NIT_TESTING".environ == "true" then exit 0
-
-var app = new CalculatorGui
-run_gtk
index 6937751..270854c 100644 (file)
@@ -29,7 +29,7 @@ context.push_op( '*' )
 context.push_digit( 2 )
 context.push_op( '=' )
 var r = context.result
-assert r == "30.00" else print r or else "-"
+assert r == 30 else print r or else "-"
 
 context = new CalculatorContext
 context.push_digit( 1 )
@@ -40,14 +40,14 @@ context.push_op( '*' )
 context.push_digit( 3 )
 context.push_op( '=' )
 r = context.result
-assert r == "42.30" else print r or else "-"
+assert r == 42.3 else print r or else "-"
 
 context.push_op( '+' )
 context.push_digit( 1 )
 context.push_digit( 1 )
 context.push_op( '=' )
 r = context.result
-assert r == "53.30" else print r or else "-"
+assert r == 53.3 else print r or else "-"
 
 context = new CalculatorContext
 context.push_digit( 4 )
@@ -58,7 +58,7 @@ context.push_op( '/' )
 context.push_digit( 3 )
 context.push_op( '=' )
 r = context.result
-assert r == "14.10" else print r or else "-"
+assert r == 14.1 else print r or else "-"
 
 #test multiple decimals
 context = new CalculatorContext
@@ -72,7 +72,7 @@ context.push_op( '+' )
 context.push_digit( 1 )
 context.push_op( '=' )
 r = context.result
-assert r == "51.123" else print r or else "-"
+assert r == 51.123 else print r or else "-"
 
 #test 'C' button
 context = new CalculatorContext
@@ -84,4 +84,4 @@ context.push_digit( 0 )
 context.push_op( '=' )
 context.push_op( 'C' )
 r = context.result
-assert r == "0.0" else print r or else "-"
+assert r == null else print r
index fb11c65..47c0e4b 100644 (file)
@@ -13,29 +13,15 @@ The tools `android`, `ndk-build` and `ant` must be in your PATH.
 
 # Configure your Android application
 
-The `app.nit` framework and this project offers some services to
-customized the generated Android application.
+The _app.nit_ framework and this project offers some services to
+customize the generated Android application.
 
-## Module annotations
+## Annotations
 
-* `app_version` specifies the version of the generated APK file.
-It takes 3 arguments: the major, minor and revision version numbers.
-The special function `git_revision` will use the prefix of the hash of the
-latest git commit. The default version is 1.0.
+* All _app.nit_ annotations are applied to Android projects:
+  `app_name`, `app_namespace` and `app_version`.
 
-    Example: `app_version(1, 0, git_revision)`
-
-* `app_name` takes a single argument, the visible name of the Android
-application. By default, the compiler would use the name of the target
-module. This name will be used as the name of the main activity and
-as the launcher name.
-
-    Example: `app_name "My App"`
-
-* `app_namespace` specifies the package used by the generated Java
-classes and the APK file. Once the application is published, this
-value should not be changed. By default, the compiler will use
-the package `org.nitlanguage.{module_name}`.
+    See: `../app/README.md`
 
 * Custom information can be added to the Android manifest file
 using the annotations `android_manifest`, `android_manifest_application`
@@ -47,8 +33,8 @@ and `android_manifest_activity`.
     android_manifest """<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>"""
     ~~~
 
-* The API version target can be specified with `min_api_version`,
-`max_api_version` and `target_api_version`. These take a single
+* The API version target can be specified with `android_api_min`,
+`android_api_max` and `android_api_target`. These take a single
 integer as argument. They are applied in the Android manifest as
 `minSdkVesion`, `targetSdkVersion` and `maxSdkVersion`.
 
index f0fd982..17fb8bf 100644 (file)
@@ -2,10 +2,9 @@ android:
        mkdir -p bin/ res/
        ../../../contrib/inkscape_tools/bin/svg_to_icons art/icon.svg --android --out res/
        ../../../bin/nitc --dir bin/ src/ui_test.nit
-       adb install -r bin/ui_test.apk
 
 install: android
-       adb install -r bin/ui.apk
+       adb install -r bin/ui_test.apk
 
 clean:
        rm -rf bin
index bdb06c5..4f63a92 100644 (file)
@@ -20,44 +20,37 @@ module ui_test is
        app_version(0, 1, git_revision)
        app_namespace "org.nitlanguage.ui_test"
        android_manifest_activity """android:theme="@android:style/Theme.Light""""
+       android_api_target 15
 end
 
-import android
 import android::ui
 import android::toast
 import android::notification
 
 redef class App
-
-       var but_notif: Button
-       var but_toast: Button
-
-       var notif: nullable Notification = null
-
-       var inited = false
-       redef fun init_window
+       redef fun on_create
        do
+               self.window = new Window
                super
+       end
+end
 
-               if inited then return
-               inited = true
+redef class Window
 
-               # Setup UI
-               var context = native_activity
-               var layout = new NativeLinearLayout(context)
-               layout.set_vertical
+       private var layout = new VerticalLayout(parent=self)
 
-               but_notif = new Button
-               but_notif.text = "Show Notification"
-               layout.add_view but_notif.native
+       private var but_notif = new Button(parent=layout, text="Show Notification")
+       private var but_toast = new Button(parent=layout, text="Show Toast")
 
-               but_toast = new Button
-               but_toast.text = "Show Toast"
-               layout.add_view but_toast.native
+       private var notif: nullable Notification = null
 
-               context.content_view = layout
+       init
+       do
+               but_notif.observers.add self
+               but_toast.observers.add self
        end
 
+       # Action when pressing `but_notif`
        fun act_notif
        do
                var notif = self.notif
@@ -72,14 +65,16 @@ redef class App
                end
        end
 
+       # Action when pressing `but_toast`
        fun act_toast
        do
-               toast("Sample toast from app.nit at {get_time}", false)
+               app.toast("Sample toast from app.nit at {get_time}", false)
        end
 
-       redef fun catch_event(event)
+       redef fun on_event(event)
        do
-               if event isa ClickEvent then
+               print "on_event {event}"
+               if event isa ButtonPressEvent then
                        var sender = event.sender
                        if sender == but_notif then
                                act_notif
index a37e572..f534e77 100644 (file)
@@ -1,7 +1,5 @@
 # This file is part of NIT (http://www.nitlanguage.org).
 #
-# Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
-#
 # 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
 # Views and services to use the Android native user interface
 module ui
 
+# Implementation note:
+#
+# We cannot rely on `Activity::on_restore_instance_state` to implement
+# `on_restore_state` is it only invoked if there is a bundled state,
+# and we don't use the Android bundled state.
+
 import native_ui
+import log
+import nit_activity
+
+import app::ui
+private import data_store
+
+redef class Control
+       # The Android element used to implement `self`
+       fun native: NATIVE is abstract
 
-# An event from the `app.nit` framework
-interface AppEvent
-       # Reaction to this event
-       fun react do end
+       # Type of `native`
+       type NATIVE: JavaObject
 end
 
-# A control click event
-class ClickEvent
-       super AppEvent
+redef class Window
+       redef var native = app.native_activity
 
-       # Sender of this event
-       var sender: Button
+       redef type NATIVE: NativeActivity
 
-       redef fun react do sender.click self
-end
+       redef fun add(item)
+       do
+               super
 
-# Receiver of events not handled directly by the sender
-interface EventCatcher
-       fun catch_event(event: AppEvent) do end
+               # FIXME abstract the Android restriction where `content_view` must be a layout
+               assert item isa Layout
+               native.content_view = item.native
+       end
 end
 
-redef class App
-       super EventCatcher
-end
+redef class View
+       redef type NATIVE: NativeView
 
-# An `Object` that raises events
-abstract class Eventful
-       var event_catcher: EventCatcher = app is lazy, writable
+       redef fun enabled=(enabled) do native.enabled = enabled or else true
+       redef fun enabled do return native.enabled
 end
 
-#
-## Nity classes and services
-#
+redef class Layout
+       redef type NATIVE: NativeViewGroup
 
-# An Android control with text
-abstract class TextView
-       super Finalizable
-       super Eventful
-
-       # Native Java variant to this Nity class
-       type NATIVE: NativeTextView
+       redef fun add(item)
+       do
+               super
 
-       # The native Java object encapsulated by `self`
-       var native: NATIVE is noinit
+               assert item isa View
 
-       # Get the text of this view
-       fun text: String
-       do
-               var jstr = native.text
-               var str = jstr.to_s
-               jstr.delete_local_ref
-               return str
+               # FIXME abstract the use either homogeneous or weight to balance views size in a layout
+               native.add_view_with_weight(item.native, 1.0)
        end
+end
 
-       # Set the text of this view
-       fun text=(value: Text)
-       do
-               var jstr = value.to_s.to_java_string
-               native.text = jstr
-               jstr.delete_local_ref
+redef class HorizontalLayout
+       redef var native do
+               var layout = new NativeLinearLayout(app.native_activity)
+               layout.set_horizontal
+               return layout
        end
+end
 
-       # Get whether this view is enabled or not
-       fun enabled: Bool do return native.enabled
-
-       # Set if this view is enabled
-       fun enabled=(val: Bool) do native.enabled = val
-
-       # Set the size of the text in this view at `dpi`
-       fun text_size=(dpi: Numeric) do native.text_size = dpi.to_f
-
-       private var finalized = false
-       redef fun finalize
-       do
-               if not finalized then
-                       native.delete_global_ref
-                       finalized = true
-               end
+redef class VerticalLayout
+       redef var native do
+               var layout = new NativeLinearLayout(app.native_activity)
+               layout.set_vertical
+               return layout
        end
 end
 
-# An Android button
-class Button
-       super TextView
+redef class TextView
+       redef type NATIVE: NativeTextView
 
-       redef type NATIVE: NativeButton
-
-       init
-       do
-               var native = new NativeButton(app.native_activity, self)
-               self.native = native.new_global_ref
+       redef fun text do return native.text.to_s
+       redef fun text=(value) do
+               if value == null then value = ""
+               native.text = value.to_java_string
        end
 
-       # Click event
-       #
-       # By default, this method calls `app.catch_event`. It can be specialized
-       # with custom behavior or the receiver of `catch_event` can be changed
-       # with `event_catcher=`.
-       fun click(event: AppEvent) do event_catcher.catch_event(event)
+       # Size of the text
+       fun text_size: Float do return native.text_size
 
-       private fun click_from_native do click(new ClickEvent(self))
+       # Size of the text
+       fun text_size=(text_size: nullable Float) do
+               if text_size != null then native.text_size = text_size
+       end
 end
 
-# An Android editable text field
-class EditText
-       super TextView
-
+redef class TextInput
        redef type NATIVE: NativeEditText
+       redef var native = (new NativeEditText(app.native_activity)).new_global_ref
+end
 
-       init
-       do
-               var native = new NativeEditText(app.activities.first.native)
-               self.native = native.new_global_ref
-       end
+redef class Button
+       super Finalizable
+
+       redef type NATIVE: NativeButton
+       redef var native = (new NativeButton(app.native_activity, self)).new_global_ref
+
+       private fun on_click do notify_observers new ButtonPressEvent(self)
+
+       redef fun finalize do native.delete_global_ref
 end
 
 redef class NativeButton
-       new (context: NativeActivity, sender_object: Object)
-       import Button.click_from_native in "Java" `{
+       private new (context: NativeActivity, sender_object: Button)
+       import Button.on_click in "Java" `{
                final int final_sender_object = sender_object;
 
                return new android.widget.Button(context){
                        @Override
                        public boolean onTouchEvent(android.view.MotionEvent event) {
                                if(event.getAction() == android.view.MotionEvent.ACTION_DOWN) {
-                                       Button_click_from_native(final_sender_object);
+                                       Button_on_click(final_sender_object);
                                        return true;
                                }
                                return false;
diff --git a/lib/app/README.md b/lib/app/README.md
new file mode 100644 (file)
index 0000000..22f4f77
--- /dev/null
@@ -0,0 +1,184 @@
+_app.nit_ is a framework to create cross-platform applications
+
+The framework provides services to manage common needs of modern mobile applications:
+
+* Life-cycle
+* User interface
+* Persistence
+* Package metadata
+* Compilation and packaging
+
+The features offered by _app.nit_ are common to all platforms, but
+may not be available on all devices.
+
+## Application Life-Cycle
+
+The _app.nit_ application life-cycle is compatible with all target platforms.
+It relies on the following sequence of events, represented here by their callback method name:
+
+1. `on_create`: The application is being created.
+   You should build the UI at this time.
+
+2. `on_start`: The app is starting or restarting, background activities may
+
+3. `on_resume`: The app enters the active state, it is in the foreground.
+
+4. `on_pause`: The app leaves the active state and the foreground.
+   It may still be visible in the background.
+   It may then go back to `on_resume` or `on_stop`.
+
+5. `on_stop`: The app is completely hidden.
+   It may then be destroyed (`on_destroy`) or go back to `on_start`.
+
+6. `on_destroy`: The app is being destroyed.
+
+Life-cycle events related to saving and restoring the application state are provided by two special callback methods:
+
+* `on_save_state`: The app may be destroyed soon, save its state for a future `on_restore_state`.
+  More on how it can be done in the `app::data_store` section.
+
+* `on_restore_state`: The app is launching, restore its state from a previous `on_save_state`.
+
+These events are synchronized to the native platforms applications
+The `App` instance is the first to be notified of these events.
+Other UI elements, from the `ui` submodule, are notified of the same events using a simple depth first visit.
+So all UI elements can react separately to live-cycle events.
+
+## User Interface
+
+The `app::ui` module defines an abstract API to build a portable graphical application.
+The API is composed of interactive `Control`s, visible `View`s and an active `Window`.
+
+Here is a subset of the most useful controls and views:
+
+* The classic pushable `Button` with text (usually rectangular).
+
+* `TextInput` is a field for the user to enter text.
+
+* `HorizontalLayout` and `VerticalLayout` organize other controls in order.
+
+Each control is notified of input events by callbacks to `on_event`.
+All controls have observers that are also notified of the events.
+So there is two ways  to customize the behavior on a given event:
+
+* Create a subclass of the wanted `Control`, let's say `Button`, and specialize `on_event`.
+
+* Add an observer to a `Button` instance, and implement `on_event` in the observer.
+
+### Usage Example
+
+The calculator example (at `../../examples/calculator/src/calculator.nit`) is a concrete,
+simple and complete use of the _app.nit_ portable UI.
+
+### Platform-specific UI
+
+You can go beyond the portable UI API of _app.nit_ by using the natives services of a platform.
+
+The suggested approach is to use platform specific modules to customize the application on a precise platform.
+This module redefine `Window::on_start` to call the native language of the platform and setup a native UI.
+
+_TODO complete description and add concrete examples_
+
+## Persistent State with data\_store
+
+_app.nit_ offers the submodule `app::data_store` to easily save the application state and user preferences.
+The service is accessible by the method `App::data_store`. The `DataStore` itself defines 2 methods:
+
+* `DataStore::[]=` saves and associates any serializable instances to a `String` key.
+Pass `null` to clear the value associated to a key.
+
+* `DataStore::[]` returns the object associated to a `String` key.
+It returns `null` if nothing is associated to the key.
+
+### Usage Example
+
+~~~
+import app::data_store
+
+redef class App
+       var user_name: String
+
+       redef fun on_save_state
+       do
+               app.data_store["first run"] = false
+               app.data_store["user name"] = user_name
+
+               super # call `on_save_state` on all attached instances of `AppComponent`
+       end
+
+       redef fun on_restore_state
+       do
+               var first_run = app.data_store["first run"]
+               if first_run != true then
+                       print "It's the first run of this application"
+               end
+
+               var user_name = app.data_store["user name"]
+               if user_name isa String then
+                       self.user_name = user_name
+               else self.user_name = "Undefined"
+
+               super
+       end
+end
+~~~
+
+## Metadata annotations
+
+The _app.nit_ framework defines three annotations to customize the application package.
+
+* `app_name` takes a single argument, the visible name of the application.
+  This name is used for launchers and window title.
+  By default, the name of the target module.
+
+* `app_namespace` specifies the full namespace (or package name) of the application package.
+  This value usually identify the application uniquely on application stores.
+  It should not change once the application has benn published.
+  By default, the namespace is `org.nitlanguage.{module_name}`.
+
+* `app_version` specifies the version of the application package.
+  This annotation expects at least one argument, usually we use three version numbers:
+  the major, minor and revision.
+  The special function `git_revision` will use the prefix of the hash of the latest git commit.
+  By default, the version is 0.1.
+
+### Usage Example
+
+~~~
+module my_module is
+    app_name "My App"
+    app_namespace "org.example.my_app"
+    app_version(1, 0, git_revision)
+end
+~~~
+
+## Compiling and Packaging an Application
+
+The Nit compiler detects the target platform from the importations and generates the appropriate application format and package.
+
+Applications using only the portable services of _app.nit_ require some special care at compilation.
+Such an application, let's say `calculator.nit`, does not depend on a specific platform and use the portable UI.
+The target platform must be specifed to the compiler for it to produce the correct application package.
+There is two main ways to achieve this goal:
+
+* The the mixin option (`-m path`) loads an additionnal module before compiling.
+  It can be used to load platform specific implementations of the _app.nit_ portable UI.
+
+  ~~~
+  # GNU/Linux version, using GTK
+  nitc calculator.nit -m NIT_DIR/lib/linux/ui.nit
+
+  # Android version
+  nitc calculator.nit -m NIT_DIR/lib/android/ui/
+  ~~~
+
+* A common alternative for larger projects is to use platform specific modules.
+  Continuing with the `calculator.nit` example, it can be accompagnied by the module `calculator_linux.nit`.
+  This module imports both `calculator` and `linux::ui`, and can also use other GNU/Linux specific code.
+
+  ~~~
+  module calculator_linux
+
+  import calculator
+  import linux::ui
+  ~~~
diff --git a/lib/app/ui.nit b/lib/app/ui.nit
new file mode 100644 (file)
index 0000000..2e30c76
--- /dev/null
@@ -0,0 +1,189 @@
+# 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.
+
+# Portable UI API for the _app.nit_ framework
+module ui
+
+import app_base
+
+redef class App
+       super AppComponent
+
+       # The current `Window` of this activity
+       #
+       # This attribute must be set by refinements of `App`.
+       var window: Window is writable
+
+       redef fun on_create do window.on_create
+
+       redef fun on_start do window.on_start
+
+       redef fun on_resume do window.on_resume
+
+       redef fun on_pause do window.on_pause
+
+       redef fun on_stop do window.on_stop
+
+       redef fun on_destroy do window.on_destroy
+
+       redef fun on_restore_state do window.on_restore_state
+
+       redef fun on_save_state do window.on_save_state
+end
+
+# An event created by an `AppComponent` and sent to `AppObserver`s
+interface AppEvent
+end
+
+# Observer of `AppEvent`s raised by `AppComponent`s
+interface AppObserver
+       # Notification of `event` raised by `sender`
+       #
+       # To be implemented in subclasses as needed.
+       fun on_event(event: AppEvent) do end
+end
+
+redef class AppComponent
+       super AppObserver
+
+       # All `AppObserver` notified of events raised by `self`
+       #
+       # By default, only `self` is an observer.
+       # Any other `AppObserver` can be added to this collection.
+       var observers = new HashSet[AppObserver].from([self: AppObserver])
+
+       # Propagate `event` to all `observers` by calling `AppObserver::on_event`
+       fun notify_observers(event: AppEvent)
+       do
+               for observer in observers do
+                       observer.on_event(event)
+               end
+       end
+end
+
+# A control implementing the UI
+class Control
+       super AppComponent
+
+       # Direct parent `Control` in the control tree
+       #
+       # If `null` then `self` is at the root of the tree, or not yet attached.
+       var parent: nullable CompositeControl = null is private writable(set_parent)
+
+       # Direct parent `Control` in the control tree
+       #
+       # Setting `parent` calls `remove` on the old parent and `add` on the new one.
+       fun parent=(parent: nullable CompositeControl)
+       is autoinit     do
+               var old_parent = self.parent
+               if old_parent != null and old_parent != parent then
+                       old_parent.remove self
+               end
+
+               if parent != null then parent.add self
+
+               set_parent parent
+       end
+end
+
+# A `Control` grouping other controls
+class CompositeControl
+       super Control
+
+       private var items = new HashSet[Control]
+
+       # Add `item` as a child of `self`
+       protected fun add(item: Control) do items.add item
+
+       # Remove `item` from `self`
+       protected fun remove(item: Control) do if has(item) then items.remove item
+
+       # Is `item` in `self`?
+       protected fun has(item: Control): Bool do return items.has(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
+
+       redef fun on_resume do for i in items do i.on_resume
+
+       redef fun on_pause do for i in items do i.on_pause
+
+       redef fun on_stop do for i in items do i.on_stop
+
+       redef fun on_destroy do for i in items do i.on_destroy
+
+       redef fun on_restore_state do for i in items do i.on_restore_state
+
+       redef fun on_save_state do for i in items do i.on_save_state
+end
+
+# A window, root of the `Control` tree
+class Window
+       super CompositeControl
+end
+
+# A viewable `Control`
+abstract class View
+       super Control
+
+       # Is this control enabled so the user can interact with it?
+       #
+       # By default, or if set to `null`, the control is enabled.
+       var enabled: nullable Bool is writable #, abstract FIXME with #1311
+end
+
+# A control with some `text`
+abstract class TextView
+       super View
+
+       # Main `Text` of this control
+       #
+       # By default, or if set to `null`, no text is shown.
+       var text: nullable Text is writable #, abstract FIXME with #1311
+end
+
+# A control for the user to enter custom `text`
+class TextInput
+       super TextView
+end
+
+# A pushable button, raises `ButtonPressEvent`
+class Button
+       super TextView
+end
+
+# A `Button` press event
+class ButtonPressEvent
+       super AppEvent
+
+       # The `Button` that raised this event
+       var sender: Button
+end
+
+# A layout to visually organize `Control`s
+abstract class Layout
+       super View
+       super CompositeControl
+end
+
+# An horizontal linear organization
+class HorizontalLayout
+       super Layout
+end
+
+# A vertical linear organization
+class VerticalLayout
+       super Layout
+end
diff --git a/lib/binary/binary.nit b/lib/binary/binary.nit
new file mode 100644 (file)
index 0000000..0084227
--- /dev/null
@@ -0,0 +1,327 @@
+# 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.
+
+# Add reading and writing binary services
+#
+# ~~~
+# var w = new FileWriter.open("/tmp/data.bin")
+# w.write "hello"
+# w.write_int64 123456789
+# w.write_byte 3
+# w.write_float 1.25
+# w.write_double 1.234567
+# w.write_bits(true, false, true)
+# assert w.last_error == null
+# w.close
+#
+# var r = new FileReader.open("/tmp/data.bin")
+# assert r.read(5) == "hello"
+# assert r.read_int64 == 123456789
+# assert r.read_byte == 3
+# assert r.read_float == 1.25
+# assert r.read_double == 1.234567
+#
+# var bits = r.read_bits
+# assert bits[0] and not bits[1] and bits[2]
+#
+# assert r.last_error == null
+# r.close
+# ~~~
+module binary
+
+in "C" `{
+       #include <inttypes.h>
+       #include <endian.h>
+
+       // Android compatibility
+       #ifndef be64toh
+               #define be64toh(val) betoh64(val)
+       #endif
+       #ifndef le64toh
+               #define le64toh(val) letoh64(val)
+       #endif
+`}
+
+# A stream of binary data
+abstract class BinaryStream
+       super Stream
+
+       # Use the big-endian convention? otherwise use little-endian.
+       #
+       # By default, `true` to use big-endian.
+       var big_endian = true is writable
+end
+
+redef abstract class Writer
+       super BinaryStream
+
+       # Write a boolean `value` on a byte, using 0 for `false` and 1 for `true`
+       fun write_bool(value: Bool) do write_byte if value then 1 else 0
+
+       # Write up to 8 `Bool` in a byte
+       #
+       # To be used with `BinaryReader::read_bits`.
+       #
+       # Ensure: `bits.length <= 8`
+       fun write_bits(bits: Bool...)
+       do
+               assert bits.length <= 8
+
+               var int = 0
+               for b in bits.length.times do
+                       if bits[b] then int += 2**b
+               end
+
+               write_byte int
+       end
+
+       # Write a floating point `value` on 32 bits
+       #
+       # Using this format may result in a loss of precision as it uses less bits
+       # than Nit `Float`.
+       fun write_float(value: Float)
+       do
+               for i in [0..4[ do write_byte value.float_byte_at(i, big_endian)
+       end
+
+       # Write a floating point `value` on 64 bits
+       fun write_double(value: Float)
+       do
+               for i in [0..8[ do write_byte value.double_byte_at(i, big_endian)
+       end
+
+       # Write `value` as a signed integer on 64 bits
+       #
+       # Using this format may result in a loss of precision as the length of a
+       # Nit `Int` may be more than 64 bits on some platforms.
+       fun write_int64(value: Int)
+       do
+               for i in [0..8[ do write_byte value.int64_byte_at(i, big_endian)
+       end
+
+       # TODO:
+       #
+       # fun write_int8
+       # fun write_uint8 # No need for this one, it is the current `read_char`
+       # fun write_int16
+       # fun write_uint16
+       # fun write_int32
+       # fun write_uint32
+       # fun write_uint64
+       # fun write_long_double?
+end
+
+redef abstract class Reader
+       super BinaryStream
+
+       # Read a single byte and return `true` if its value is different than 0
+       fun read_bool: Bool do return read_byte != 0
+
+       # Get an `Array` of 8 `Bool` by reading a single byte
+       #
+       # To be used with `BinaryWriter::write_bits`.
+       fun read_bits: Array[Bool]
+       do
+               var int = read_byte
+               if int == null then return new Array[Bool]
+               return [for b in 8.times do int.bin_and(2**b) > 0]
+       end
+
+       # Read a floating point on 32 bits and return it as a `Float`
+       #
+       # Using this format may result in a loss of precision as it uses less bits
+       # than Nit `Float`.
+       fun read_float: Float
+       do
+               if last_error != null then return 0.0
+
+               var b0 = read_byte
+               var b1 = read_byte
+               var b2 = read_byte
+               var b3 = read_byte
+
+               # Check for error, `last_error` is set by `read_byte`
+               if b0 == null or b1 == null or b2 == null or b3 == null then return 0.0
+
+               return native_read_float(b0, b1, b2, b3, big_endian)
+       end
+
+       # Utility for `read_float`
+       private fun native_read_float(b0, b1, b2, b3: Int, big_endian: Bool): Float `{
+               union {
+                       unsigned char b[4];
+                       float val;
+                       uint32_t conv;
+               } u;
+
+               u.b[0] = b0;
+               u.b[1] = b1;
+               u.b[2] = b2;
+               u.b[3] = b3;
+
+               if (big_endian)
+                       u.conv = be32toh(u.conv);
+               else u.conv = le32toh(u.conv);
+
+               return u.val;
+       `}
+
+       # Read a floating point on 64 bits and return it as a `Float`
+       fun read_double: Float
+       do
+               if last_error != null then return 0.0
+
+               var b0 = read_byte
+               var b1 = read_byte
+               var b2 = read_byte
+               var b3 = read_byte
+               var b4 = read_byte
+               var b5 = read_byte
+               var b6 = read_byte
+               var b7 = read_byte
+
+               # Check for error, `last_error` is set by `read_byte`
+               if b0 == null or b1 == null or b2 == null or b3 == null or
+                  b4 == null or b5 == null or b6 == null or b7 == null then return 0.0
+
+               return native_read_double(b0, b1, b2, b3, b4, b5, b6, b7, big_endian)
+       end
+
+       # Utility for `read_double`
+       private fun native_read_double(b0, b1, b2, b3, b4, b5, b6, b7: Int, big_endian: Bool): Float `{
+               union {
+                       unsigned char b[8];
+                       double val;
+                       uint64_t conv;
+               } u;
+
+               u.b[0] = b0;
+               u.b[1] = b1;
+               u.b[2] = b2;
+               u.b[3] = b3;
+               u.b[4] = b4;
+               u.b[5] = b5;
+               u.b[6] = b6;
+               u.b[7] = b7;
+
+               if (big_endian)
+                       u.conv = be64toh(u.conv);
+               else u.conv = le64toh(u.conv);
+
+               return u.val;
+       `}
+
+       # Read a signed integer on 64 bits and return is an `Int`
+       #
+       # Using this format may result in a loss of precision as the length of a
+       # Nit `Int` may be less than 64 bits on some platforms.
+       fun read_int64: Int
+       do
+               if last_error != null then return 0
+
+               var b0 = read_byte
+               var b1 = read_byte
+               var b2 = read_byte
+               var b3 = read_byte
+               var b4 = read_byte
+               var b5 = read_byte
+               var b6 = read_byte
+               var b7 = read_byte
+
+               # Check for error, `last_error` is set by `read_byte`
+               if b0 == null or b1 == null or b2 == null or b3 == null or
+                  b4 == null or b5 == null or b6 == null or b7 == null then return 0
+
+               return native_read_int64(b0, b1, b2, b3, b4, b5, b6, b7, big_endian)
+       end
+
+       # Utility for `read_int64`
+       private fun native_read_int64(b0, b1, b2, b3, b4, b5, b6, b7: Int, big_endian: Bool): Int `{
+               union {
+                       unsigned char b[8];
+                       int64_t val;
+                       uint64_t conv;
+               } u;
+
+               u.b[0] = b0;
+               u.b[1] = b1;
+               u.b[2] = b2;
+               u.b[3] = b3;
+               u.b[4] = b4;
+               u.b[5] = b5;
+               u.b[6] = b6;
+               u.b[7] = b7;
+
+               if (big_endian)
+                       u.conv = be64toh(u.conv);
+               else u.conv = le64toh(u.conv);
+
+               return u.val;
+       `}
+end
+
+redef class Int
+       # Utility for `BinaryWriter`
+       private fun int64_byte_at(index: Int, big_endian: Bool): Int `{
+               union {
+                       unsigned char bytes[8];
+                       int64_t val;
+                       uint64_t conv;
+               } u;
+
+               u.val = recv;
+
+               if (big_endian)
+                       u.conv = htobe64(u.conv);
+               else u.conv = htole64(u.conv);
+
+               return u.bytes[index];
+       `}
+end
+
+redef class Float
+       # Utility for `BinaryWriter`
+       private fun float_byte_at(index: Int, big_endian: Bool): Int `{
+               union {
+                       unsigned char bytes[4];
+                       float val;
+                       uint32_t conv;
+               } u;
+
+               u.val = recv;
+
+               if (big_endian)
+                       u.conv = htobe32(u.conv);
+               else u.conv = htole32(u.conv);
+
+               return u.bytes[index];
+       `}
+
+       # Utility for `BinaryWriter`
+       private fun double_byte_at(index: Int, big_endian: Bool): Int `{
+               union {
+                       unsigned char bytes[8];
+                       double val;
+                       uint64_t conv;
+               } u;
+
+               u.val = recv;
+
+               if (big_endian)
+                       u.conv = htobe64(u.conv);
+               else u.conv = htole64(u.conv);
+
+               return u.bytes[index];
+       `}
+end
index eba5d69..a621c54 100644 (file)
 # such as a forest with many individual trees.
 module bucketed_game
 
+import serialization
+
 # Something acting on the game
 class Turnable[G: Game]
+       auto_serializable
 
        # Execute `turn` for this instance.
        fun do_turn(turn: GameTurn[G]) is abstract
@@ -32,6 +35,8 @@ end
 # Something acting on the game from time to time
 class Bucketable[G: Game]
        super Turnable[G]
+       auto_serializable
+
        private var act_at: nullable Int = null
 
        # Cancel the previously registered acting turn
@@ -44,6 +49,7 @@ end
 # Optimized organization of `Bucketable` instances
 class Buckets[G: Game]
        super Turnable[G]
+       auto_serializable
 
        # Bucket type used in this implementation.
        type BUCKET: HashSet[Bucketable[G]]
@@ -112,10 +118,12 @@ end
 # Event raised at the first turn
 class FirstTurnEvent
        super GameEvent
+       auto_serializable
 end
 
 # Game logic on the client
 class ThinGame
+       auto_serializable
 
        # Game tick when `self` should act.
        #
@@ -125,17 +133,19 @@ end
 
 # Game turn on the client
 class ThinGameTurn[G: ThinGame]
+       auto_serializable
 
        # Game tick when `self` should act.
        var tick: Int is protected writable
 
-       # List of game events occured for `self`.
-       var events = new List[GameEvent] is protected writable
+       # Game events occurred for `self`.
+       var events = new Array[GameEvent] is protected writable
 end
 
 # Game turn on the full logic
 class GameTurn[G: Game]
        super ThinGameTurn[G]
+       auto_serializable
 
        # Game that `self` belongs to.
        var game: G
@@ -163,6 +173,7 @@ end
 # Full game logic
 class Game
        super ThinGame
+       auto_serializable
 
        # Game type used in this implementation.
        type G: Game
index 88842a4..88e373a 100644 (file)
@@ -389,11 +389,11 @@ extern class GtkBox `{ GtkBox * `}
        `}
 
        # Give the children of `self` equal space in the box?
-       fun omogeneous: Bool `{ return gtk_box_get_homogeneous(recv); `}
+       fun homogeneous: Bool `{ return gtk_box_get_homogeneous(recv); `}
 
        # Give the children of `self` equal space in the box?
-       fun omogeneous=(omogeneous: Bool) `{
-               gtk_box_set_homogeneous(recv, omogeneous);
+       fun homogeneous=(homogeneous: Bool) `{
+               gtk_box_set_homogeneous(recv, homogeneous);
        `}
 
        # Add `child` and pack it at the start of the box
index 036c2bb..ec2de93 100644 (file)
@@ -29,3 +29,52 @@ extern class GtkSearchEntry `{GtkSearchEntry *`}
                return (GtkSearchEntry *)gtk_search_entry_new();
        `}
 end
+
+redef extern class GtkEntry
+       # Purpose of this text field
+       #
+       # Can be used by on-screen keyboards and other input methods to adjust their behaviour.
+       fun input_purpose: GtkInputPurpose `{
+               return gtk_entry_get_input_purpose(recv);
+       `}
+
+       # Input purpose, tweaks the behavior of this widget
+       #
+       # Can be used by on-screen keyboards and other input methods to adjust their behaviour.
+       fun input_purpose=(purpose: GtkInputPurpose) `{
+               gtk_entry_set_input_purpose(recv, purpose);
+       `}
+end
+
+# Describe the purpose of an input widget
+extern class GtkInputPurpose `{ GtkInputPurpose `}
+       # Allow any character
+       new free_form `{ return GTK_INPUT_PURPOSE_FREE_FORM; `}
+
+       # Allow only alphabetic characters
+       new alpha `{ return GTK_INPUT_PURPOSE_ALPHA; `}
+
+       # Allow only digits
+       new digits `{ return GTK_INPUT_PURPOSE_DIGITS; `}
+
+       # Edited field expects numbers
+       new number `{ return GTK_INPUT_PURPOSE_NUMBER; `}
+
+       # Edited field expects phone number
+       new phone `{ return GTK_INPUT_PURPOSE_PHONE; `}
+
+       # Edited field expects URL
+       new url `{ return GTK_INPUT_PURPOSE_URL; `}
+
+       # Edited field expects email address
+       new email `{ return GTK_INPUT_PURPOSE_EMAIL; `}
+
+       # Edited field expects the name of a person
+       new name `{ return GTK_INPUT_PURPOSE_NAME; `}
+
+       # Like `free_form`, but characters are hidden
+       new password `{ return GTK_INPUT_PURPOSE_PASSWORD; `}
+
+       # Like `digits`, but characters are hidden
+       new pin `{ return GTK_INPUT_PURPOSE_PIN; `}
+end
index e8c76b4..b9667db 100644 (file)
@@ -42,7 +42,7 @@ class JsonSerializer
 
        redef fun serialize_reference(object)
        do
-               if refs_map.keys.has(object) then
+               if refs_map.has_key(object) then
                        # if already serialized, add local reference
                        var id = ref_id_for(object)
                        stream.write "\{\"__kind\": \"ref\", \"__id\": {id}\}"
@@ -53,12 +53,12 @@ class JsonSerializer
        end
 
        # Map of references to already serialized objects.
-       var refs_map = new HashMap[Serializable,Int]
+       private var refs_map = new StrictHashMap[Serializable,Int]
 
        # Get the internal serialized reference for this `object`.
        private fun ref_id_for(object: Serializable): Int
        do
-               if refs_map.keys.has(object) then
+               if refs_map.has_key(object) then
                        return refs_map[object]
                else
                        var id = refs_map.length
@@ -81,8 +81,8 @@ class JsonDeserializer
        # Depth-first path in the serialized object tree.
        var path = new Array[JsonObject]
 
-       # Map of refenrences to already deserialized objects.
-       var id_to_object = new HashMap[Int, Object]
+       # Map of references to already deserialized objects.
+       private var id_to_object = new StrictHashMap[Int, Object]
 
        # Last encountered object reference id.
        #
@@ -111,7 +111,7 @@ class JsonDeserializer
        redef fun notify_of_creation(new_object)
        do
                var id = just_opened_id
-               assert id != null
+               if id == null then return # Register `new_object` only once
                id_to_object[id] = new_object
        end
 
@@ -128,7 +128,7 @@ class JsonDeserializer
                                var id = object["__id"]
                                assert id isa Int
 
-                               assert id_to_object.keys.has(id)
+                               assert id_to_object.has_key(id)
                                return id_to_object[id]
                        end
 
@@ -142,7 +142,7 @@ class JsonDeserializer
                                var class_name = object["__class"]
                                assert class_name isa String
 
-                               assert not id_to_object.keys.has(id) else print "Error: Object with id '{id}' is deserialized twice."
+                               assert not id_to_object.has_key(id) else print "Error: Object with id '{id}' of {class_name} is deserialized twice."
 
                                # advance on path
                                path.push object
@@ -219,49 +219,46 @@ redef class NativeString
        redef fun serialize_to_json(v) do to_s.serialize_to_json(v)
 end
 
-redef class Array[E]
-       redef fun serialize_to_json(v)
+redef class Collection[E]
+       # Utility to serialize a normal Json array
+       private fun serialize_to_pure_json(v: JsonSerializer)
        do
-               if class_name == "Array[nullable Serializable]" then
-                       # Using class_name to the the exact type
-                       # We do not want Array[Int] or anything else here
                        v.stream.write "["
                        var is_first = true
                        for e in self do
                                if is_first then
                                        is_first = false
-                               else v.stream.write(", ")
+                               else v.stream.write ", "
 
                                if not v.try_to_serialize(e) then
                                        v.warn("element of type {e.class_name} is not serializable.")
                                end
                        end
                        v.stream.write "]"
-               else
-                       # Register as pseudo object
-                       var id = v.ref_id_for(self)
-                       v.stream.write "\{\"__kind\": \"obj\", \"__id\": {id}, \"__class\": \"{class_name}\""
-                       v.stream.write """, "__length": {{{length}}}, "__items": ["""
-                       var is_first = true
-                       for e in self do
-                               if is_first then
-                                       is_first = false
-                               else v.stream.write(", ")
+       end
+end
 
-                               if not v.try_to_serialize(e) then
-                                       v.warn("element of type {e.class_name} is not serializable.")
-                               end
-                       end
-                       v.stream.write "]"
-                       v.stream.write "\}"
-               end
+redef class SimpleCollection[E]
+       redef fun serialize_to_json(v)
+       do
+               # Register as pseudo object
+               var id = v.ref_id_for(self)
+               v.stream.write """{"__kind": "obj", "__id": """
+               v.stream.write id.to_s
+               v.stream.write """, "__class": """"
+               v.stream.write class_name
+               v.stream.write """", "__length": """
+               v.stream.write length.to_s
+               v.stream.write """, "__items": """
+               serialize_to_pure_json v
+               v.stream.write "\}"
        end
 
-       # Instanciate a new `Array` from its serialized representation.
-       init from_deserializer(v: Deserializer)
+       redef init from_deserializer(v: Deserializer)
        do
                if v isa JsonDeserializer then
                        v.notify_of_creation self
+                       init
 
                        var length = v.deserialize_attribute("__length").as(Int)
                        var arr = v.path.last["__items"].as(SequenceRead[nullable Object])
@@ -272,3 +269,112 @@ redef class Array[E]
                end
        end
 end
+
+redef class Array[E]
+       redef fun serialize_to_json(v)
+       do
+               if class_name == "Array[nullable Serializable]" then
+                       # Using class_name to get the exact type,
+                       # we do not want Array[Int] or anything else here.
+
+                       serialize_to_pure_json v
+               else super
+       end
+end
+
+redef class Map[K, V]
+       redef fun serialize_to_json(v)
+       do
+               # Register as pseudo object
+               var id = v.ref_id_for(self)
+
+               v.stream.write """{"__kind": "obj", "__id": """
+               v.stream.write id.to_s
+               v.stream.write """, "__class": """"
+               v.stream.write class_name
+               v.stream.write """", "__length": """
+               v.stream.write length.to_s
+               v.stream.write """, "__keys": """
+
+               keys.serialize_to_pure_json v
+
+               v.stream.write """, "__values": """
+               values.serialize_to_pure_json v
+               v.stream.write "\}"
+       end
+
+       # Instantiate a new `Array` from its serialized representation.
+       redef init from_deserializer(v: Deserializer)
+       do
+               init
+
+               if v isa JsonDeserializer then
+                       v.notify_of_creation self
+
+                       var length = v.deserialize_attribute("__length").as(Int)
+                       var keys = v.path.last["__keys"].as(SequenceRead[nullable Object])
+                       var values = v.path.last["__values"].as(SequenceRead[nullable Object])
+                       for i in length.times do
+                               var key = v.convert_object(keys[i])
+                               var value = v.convert_object(values[i])
+                               self[key] = value
+                       end
+               end
+       end
+end
+
+# Maps instances to a value, uses `is_same_instance`
+#
+# Warning: This class does not implement all the services from `Map`.
+private class StrictHashMap[K, V]
+       super Map[K, V]
+
+       # private
+       var map = new HashMap[K, Array[Couple[K, V]]]
+
+       redef var length = 0
+
+       redef fun is_empty do return length == 0
+
+       # private
+       fun node_at(key: K): nullable Couple[K, V]
+       do
+               if not map.keys.has(key) then return null
+
+               var arr = map[key]
+               for couple in arr do
+                       if couple.first.is_same_serialized(key) then
+                               return couple
+                       end
+               end
+
+               return null
+       end
+
+       redef fun [](key)
+       do
+               var node = node_at(key)
+               assert node != null
+               return node.second
+       end
+
+       redef fun []=(key, value)
+       do
+               var node = node_at(key)
+               if node != null then
+                       node.second = value
+                       return
+               end
+
+               var arr
+               if not map.keys.has(key) then
+                       arr = new Array[Couple[K, V]]
+                       map[key] = arr
+               else arr = map[key]
+
+               arr.add new Couple[K, V](key, value)
+               self.length += 1
+       end
+
+       redef fun has_key(key) do return node_at(key) != null
+end
diff --git a/lib/linux/ui.nit b/lib/linux/ui.nit
new file mode 100644 (file)
index 0000000..4f67eed
--- /dev/null
@@ -0,0 +1,139 @@
+# 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.
+
+# Implementation of the app.nit UI module for GNU/Linux
+module ui
+
+import app::ui
+import gtk
+
+import data_store
+
+redef class App
+       redef fun setup do init_gtk
+
+       # On GNU/Linux, we go through all the callbacks once,
+       # there is no complex life-cycle.
+       redef fun run
+       do
+               app.on_create
+               app.on_restore_state
+               app.on_start
+               app.on_resume
+
+               var window = window
+               window.native.show_all
+               run_gtk
+
+               app.on_pause
+               app.on_stop
+               app.on_save_state
+               app.on_destroy
+       end
+
+       # Spacing between GTK controls, default at 2
+       var control_spacing = 2 is writable
+end
+
+redef class Control
+       super GtkCallable
+       super Finalizable
+
+       # The GTK element used to implement `self`
+       fun native: NATIVE is abstract
+
+       # Type of `native`
+       type NATIVE: GtkWidget
+
+       redef fun finalize
+       do
+               var native = native
+               if not native.address_is_null then native.destroy
+       end
+end
+
+redef class CompositeControl
+       redef type NATIVE: GtkContainer
+
+       redef fun add(item)
+       do
+               super
+               native.add item.native
+       end
+end
+
+redef class Window
+       redef type NATIVE: GtkWindow
+       redef var native do
+               var win = new GtkWindow(new GtkWindowType.toplevel)
+               win.connect_destroy_signal_to_quit
+               return win
+       end
+end
+
+redef class View
+       redef fun enabled do return native.sensitive
+       redef fun enabled=(enabled) do native.sensitive = enabled or else true
+end
+
+redef class Layout
+       redef type NATIVE: GtkBox
+end
+
+redef class HorizontalLayout
+       redef var native = new GtkBox(new GtkOrientation.horizontal, app.control_spacing)
+
+       redef fun add(item)
+       do
+               super
+               native.homogeneous = true
+               native.set_child_packing(item.native, true, true, 0, new GtkPackType.start)
+       end
+end
+
+redef class VerticalLayout
+       redef var native = new GtkBox(new GtkOrientation.vertical, app.control_spacing)
+
+       redef fun add(item)
+       do
+               super
+
+               # FIXME abstract the use either homogeneous or weight to balance views size in a layout
+               native.homogeneous = true
+               native.set_child_packing(item.native, true, true, 0, new GtkPackType.start)
+       end
+end
+
+redef class Button
+       redef type NATIVE: GtkButton
+       redef var native = new GtkButton
+
+       redef fun text do return native.text
+       redef fun text=(value) do native.text = (value or else "").to_s
+
+       redef fun signal(sender, data) do notify_observers new ButtonPressEvent(self)
+
+       init do native.signal_connect("clicked", self, null)
+end
+
+redef class TextInput
+       redef type NATIVE: GtkEntry
+       redef var native = new GtkEntry
+
+       redef fun text do return native.text
+       redef fun text=(value) do
+               if value == null then value = ""
+               native.text = value.to_s
+       end
+end
index 31f98f0..6e5ce12 100644 (file)
@@ -63,7 +63,7 @@ interface Serializer
        fun serialize_attribute(name: String, value: nullable Object)
        do
                if not try_to_serialize(value) then
-                       warn("argument {value.class_name}::{name} is not serializable.")
+                       warn("argument {name} of type {value.class_name} is not serializable.")
                end
        end
 
@@ -134,6 +134,18 @@ interface Serializable
        # The subclass change the default behavior, which will accept references,
        # to force to always serialize copies of `self`.
        private fun serialize_to_or_delay(v: Serializer) do v.serialize_reference(self)
+
+       # Create an instance of this class from the `deserializer`
+       #
+       # This constructor is refined by subclasses to correctly build their instances.
+       init from_deserializer(deserializer: Deserializer) do end
+end
+
+redef interface Object
+       # Is `self` the same as `other` in a serialization context?
+       #
+       # Used to determine if an object has already been serialized.
+       fun is_same_serialized(other: nullable Object): Bool do return is_same_instance(other)
 end
 
 # Instances of this class are not delayed and instead serialized immediately
@@ -150,4 +162,5 @@ redef class Int super DirectSerializable end
 redef class Float super DirectSerializable end
 redef class NativeString super DirectSerializable end
 redef class String super DirectSerializable end
-redef class Array[E] super Serializable end
+redef class SimpleCollection[E] super Serializable end
+redef class Map[K, V] super Serializable end
index 681e239..b3cb2f9 100644 (file)
@@ -147,6 +147,12 @@ class TCPStream
                socket.write(msg.to_s)
        end
 
+       redef fun write_byte(value)
+       do
+               if closed then return
+               socket.write_byte value
+       end
+
        fun write_ln(msg: Text)
        do
                if end_reached then return
index 8758cdf..a574485 100644 (file)
@@ -132,6 +132,11 @@ extern class NativeSocket `{ int* `}
                return write(*recv, (char*)String_to_cstring(buffer), String_length(buffer));
        `}
 
+       # Write `value` as a single byte
+       fun write_byte(value: Int): Int `{
+               return write(*recv, &value, 1);
+       `}
+
        fun read: String import NativeString.to_s_with_length `{
                static char c[1024];
                int n = read(*recv, c, 1024);
index 1b81877..5324f01 100644 (file)
@@ -193,6 +193,26 @@ class FileWriter
                end
        end
 
+       redef fun write_byte(value)
+       do
+               if last_error != null then return
+               if not _is_writable then
+                       last_error = new IOError("Cannot write to non-writable stream")
+                       return
+               end
+               if _file.address_is_null then
+                       last_error = new IOError("Writing on a null stream")
+                       _is_writable = false
+                       return
+               end
+
+               var err = _file.write_byte(value)
+               if err != 1 then
+                       # Big problem
+                       last_error = new IOError("Problem writing a byte: {err}")
+               end
+       end
+
        redef fun close
        do
                super
@@ -1090,6 +1110,10 @@ end
 private extern class NativeFile `{ FILE* `}
        fun io_read(buf: NativeString, len: Int): Int is extern "file_NativeFile_NativeFile_io_read_2"
        fun io_write(buf: NativeString, len: Int): Int is extern "file_NativeFile_NativeFile_io_write_2"
+       fun write_byte(value: Int): Int `{
+               unsigned char b = (unsigned char)value;
+               return fwrite(&b, 1, 1, recv);
+       `}
        fun io_close: Int is extern "file_NativeFile_NativeFile_io_close_0"
        fun file_stat: NativeFileStat is extern "file_NativeFile_NativeFile_file_stat_0"
        fun fileno: Int `{ return fileno(recv); `}
index 3f32a62..85386b4 100644 (file)
@@ -63,7 +63,7 @@ interface Object
 
        # Have `self` and `other` the same value?
        #
-       # The exact meaning of "same value" is let to the subclasses.
+       # The exact meaning of "same value" is left to the subclasses.
        # Implicitly, the default implementation, is `is_same_instance`
        fun ==(other: nullable Object): Bool do return self.is_same_instance(other)
 
@@ -85,7 +85,7 @@ interface Object
        # Display class name on stdout (debug only).
        # This method MUST not be used by programs, it is here for debugging
        # only and can be removed without any notice
-       fun output_class_name is intern 
+       fun output_class_name is intern
 
        # The hash code of the object.
        # Assuming that a == b -> a.hash == b.hash
@@ -223,11 +223,11 @@ end
 # Something that can be cloned
 #
 # This interface introduces the `clone` method used to duplicate an instance
-# Its specific semantic is let to the subclasses.
+# Its specific semantic is left to the subclasses.
 interface Cloneable
        # Duplicate `self`
        #
-       # The specific semantic of this method is let to the subclasses;
+       # The specific semantic of this method is left to the subclasses;
        # Especially, if (and how) attributes are cloned (depth vs. shallow).
        #
        # As a rule of thumb, the principle of least astonishment should
index 6b6d3fe..7c4e606 100644 (file)
@@ -345,6 +345,9 @@ abstract class Writer
        # write a string
        fun write(s: Text) is abstract
 
+       # Write a single byte
+       fun write_byte(value: Int) is abstract
+
        # Can the stream be used to write
        fun is_writable: Bool is abstract
 end
index d392961..507a0a4 100644 (file)
@@ -16,6 +16,7 @@
 module doc_structure
 
 import doc_concerns
+import modelize
 
 # StructurePhase populates the DocPage content with section and article.
 #
@@ -177,6 +178,12 @@ redef class MClassPage
                mentity.intro_mmodule.mgroup.mproject.booster_rank = 0
                mentity.intro_mmodule.mgroup.booster_rank = 0
                mentity.intro_mmodule.booster_rank = 0
+               var constructors = new ConstructorsSection(mentity)
+               var minit = mentity.root_init
+               if minit != null then
+                       constructors.add_child new DefinitionArticle(minit)
+               end
+               section.add_child constructors
                section.add_child new ConcernsArticle(mentity, concerns)
                for mentity in concerns do
                        var ssection = new ConcernSection(mentity)
@@ -187,7 +194,12 @@ redef class MClassPage
                                        v.name_sorter.sort(group)
                                        for mprop in group do
                                                for mpropdef in mpropdefs_for(mprop, mentity) do
-                                                       ssection.add_child new DefinitionArticle(mpropdef)
+                                                       if mpropdef isa MMethodDef and mpropdef.mproperty.is_init then
+                                                               if mpropdef == minit then continue
+                                                               constructors.add_child new DefinitionArticle(mpropdef)
+                                                       else
+                                                               ssection.add_child new DefinitionArticle(mpropdef)
+                                                       end
                                                end
                                        end
                                end
@@ -289,6 +301,11 @@ class MEntityComposite
        var mentity: MEntity
 end
 
+# A list of constructors.
+class ConstructorsSection
+       super MEntitySection
+end
+
 # A Section about a Concern.
 #
 # Those sections are used to build the page summary.
index c112e25..cd6974c 100644 (file)
@@ -462,22 +462,50 @@ redef class MMethodDef
 
        # FIXME annotation should be handled in their own way
        redef fun html_modifiers do
+               if mproperty.is_init then
+                       var res = new Array[String]
+                       if mproperty.visibility != public_visibility then
+                               res.add mproperty.visibility.to_s
+                       end
+                       return res
+               end
                var res = super
                if is_abstract then
                        res.add "abstract"
                else if is_intern then
                        res.add "intern"
                end
+               res.add "fun"
+               return res
+       end
+
+       redef fun html_declaration do
                if mproperty.is_init then
-                       res.add "init"
-               else
-                       res.add "fun"
+                       var tpl = new Template
+                       tpl.add "<span>"
+                       tpl.add html_modifiers.join(" ")
+                       tpl.add " "
+                       tpl.add html_link
+                       tpl.add html_signature
+                       tpl.add "</span>"
+                       return tpl
                end
-               return res
+               return super
+       end
+
+       redef fun html_short_signature do
+               if mproperty.is_root_init and new_msignature != null then
+                       return new_msignature.html_short_signature
+               end
+               return msignature.html_short_signature
        end
 
-       redef fun html_short_signature do return msignature.html_short_signature
-       redef fun html_signature do return msignature.html_signature
+       redef fun html_signature do
+               if mproperty.is_root_init and new_msignature != null then
+                       return new_msignature.html_signature
+               end
+               return msignature.html_signature
+       end
 end
 
 redef class MVirtualTypeProp
index 4841f02..fb04bfe 100644 (file)
@@ -447,6 +447,13 @@ redef class MEntitySection
        redef var html_subtitle is lazy do return mentity.html_declaration
 end
 
+redef class ConstructorsSection
+       redef var html_id is lazy do return "article:{mentity.nitdoc_id}.constructors"
+       redef var html_title = "Constructors"
+       redef var html_subtitle = null
+       redef fun is_toc_hidden do return is_empty
+end
+
 redef class ConcernSection
        redef var html_id is lazy do return "concern:{mentity.nitdoc_id}"
        redef var html_title is lazy do return "in {mentity.nitdoc_name}"
index 976d532..90924f2 100644 (file)
@@ -99,14 +99,12 @@ private class SerializationPhasePreModel
        # Add a constructor to the automated nclassdef
        fun generate_deserialization_init(nclassdef: AStdClassdef)
        do
-               # Do not generate constructors for abstract classes
-               if nclassdef.n_classkind isa AAbstractClasskind then return
-
                var npropdefs = nclassdef.n_propdefs
 
                var code = new Array[String]
-               code.add "init from_deserializer(v: Deserializer)"
+               code.add "redef init from_deserializer(v: Deserializer)"
                code.add "do"
+               code.add "      super"
                code.add "      v.notify_of_creation self"
 
                for attribute in npropdefs do if attribute isa AAttrPropdef then
@@ -122,7 +120,7 @@ private class SerializationPhasePreModel
 
                        code.add ""
                        code.add "\tvar {name} = v.deserialize_attribute(\"{name}\")"
-                       code.add "\tassert {name} isa {type_name} else print \"Unsupported type for attribute '{name}', got '\{{name}.class_name\}' (ex {type_name})\""
+                       code.add "\tassert {name} isa {type_name} else print \"Unsupported type for `\{class_name\}::{name}`, got '\{{name}.class_name\}'; expected {type_name}\""
                        code.add "\tself.{name} = {name}"
                end
 
index b65511d..1a27697 100644 (file)
@@ -74,6 +74,67 @@ class Location
        # End of this location on `line_end`
        var column_end: Int
 
+       # Builds a location instance from its string representation.
+       #
+       # Examples:
+       #
+       # ~~~
+       # var loc = new Location.from_string("location.nit:82,2--105,8")
+       # assert loc.to_s == "location.nit:82,2--105,8"
+       #
+       # loc = new Location.from_string("location.nit")
+       # assert loc.to_s == "location.nit"
+       #
+       # loc = new Location.from_string("location.nit:82,2")
+       # assert loc.to_s == "location.nit:82,2--0,0"
+       #
+       # loc = new Location.from_string("location.nit:82--105")
+       # assert loc.to_s == "location.nit:82,0--105,0"
+       #
+       # loc = new Location.from_string("location.nit:82,2--105")
+       # assert loc.to_s == "location.nit:82,2--105,0"
+       #
+       # loc = new Location.from_string("location.nit:82--105,8")
+       # assert loc.to_s == "location.nit:82,0--105,8"
+       # ~~~
+       init from_string(string: String) do
+               self.line_start = 0
+               self.line_end = 0
+               self.column_start = 0
+               self.column_end = 0
+               # parses the location string and init position vars
+               var parts = string.split_with(":")
+               var filename = parts.shift
+               self.file = new SourceFile(filename, new FileReader.open(filename))
+               # split position
+               if parts.is_empty then return
+               var pos = parts.first.split_with("--")
+               # split start position
+               if pos.first.has(",") then
+                       var pos1 = pos.first.split_with(",")
+                       self.line_start = pos1[0].to_i
+                       if pos1.length > 1 then
+                               self.column_start = pos1[1].to_i
+                       end
+               else
+                       self.line_start = pos.first.to_i
+               end
+               # split end position
+               if pos.length <= 1 then return
+               if pos[1].has(",") then
+                       var pos2 = pos[1].split_with(",")
+                       if pos2.length > 1 then
+                               self.line_end = pos2[0].to_i
+                               self.column_end = pos2[1].to_i
+                       else
+                               self.line_end = self.line_start
+                               self.column_end = pos2[0].to_i
+                       end
+               else
+                       self.line_end = pos[1].to_i
+               end
+       end
+
        # The index in the start character in the source
        fun pstart: Int do return file.line_starts[line_start-1] + column_start-1
 
index f0e2911..5e28695 100644 (file)
@@ -869,21 +869,7 @@ class NeoModel
 
        # Get a `Location` from its string representation.
        private fun to_location(loc: String): Location do
-               #TODO filepath
-               var parts = loc.split_with(":")
-               var file = new SourceFile.from_string(parts[0], "")
-               if parts.length == 1 then
-                       return new Location(file, 0, 0, 0, 0)
-               end
-               var pos = parts[1].split_with("--")
-               var pos1 = pos[0].split_with(",")
-               var pos2 = pos[1].split_with(",")
-               var line_s = pos1[0].to_i
-               var line_e = pos2[0].to_i
-               var column_s = pos1[1].to_i
-               var column_e = 0
-               if pos2.length == 2 then pos2[1].to_i
-               return new Location(file, line_s, line_e, column_s, column_e)
+               return new Location.from_string(loc)
        end
 
        # Get a `MVisibility` from its string representation.
index ff615b1..4217115 100644 (file)
@@ -14,7 +14,7 @@
 <C: <A: true a 0.123 1234 asdf false> <B: <A: false b 123.123 2345 hjkl false> 1111 qwer>>
 
 # Json:
-{"__kind": "obj", "__id": 0, "__class": "C", "a": {"__kind": "obj", "__id": 1, "__class": "A", "b": true, "c": {"__kind": "char", "__val": "a"}, "f": 0.123, "i": 1234, "s": "asdf", "n": null, "array": {"__kind": "obj", "__id": 2, "__class": "Array", "__length": 3, "__items": [88, "hello", null]}}, "b": {"__kind": "obj", "__id": 3, "__class": "B", "b": false, "c": {"__kind": "char", "__val": "b"}, "f": 123.123, "i": 2345, "s": "hjkl", "n": null, "array": {"__kind": "ref", "__id": 2}, "ii": 1111, "ss": "qwer"}, "aa": {"__kind": "ref", "__id": 1}}
+{"__kind": "obj", "__id": 0, "__class": "C", "a": {"__kind": "obj", "__id": 1, "__class": "A", "b": true, "c": {"__kind": "char", "__val": "a"}, "f": 0.123, "i": 1234, "s": "asdf", "n": null, "array": {"__kind": "obj", "__id": 2, "__class": "Array", "__length": 3, "__items": [88, "hello", null]}}, "b": {"__kind": "obj", "__id": 3, "__class": "B", "b": false, "c": {"__kind": "char", "__val": "b"}, "f": 123.123, "i": 2345, "s": "hjkl", "n": null, "array": {"__kind": "obj", "__id": 4, "__class": "Array", "__length": 3, "__items": [88, "hello", null]}, "ii": 1111, "ss": "qwer"}, "aa": {"__kind": "ref", "__id": 1}}
 
 # Nit:
 <D: <B: <A: false b 123.123 2345 new line ->
index ff615b1..4217115 100644 (file)
@@ -14,7 +14,7 @@
 <C: <A: true a 0.123 1234 asdf false> <B: <A: false b 123.123 2345 hjkl false> 1111 qwer>>
 
 # Json:
-{"__kind": "obj", "__id": 0, "__class": "C", "a": {"__kind": "obj", "__id": 1, "__class": "A", "b": true, "c": {"__kind": "char", "__val": "a"}, "f": 0.123, "i": 1234, "s": "asdf", "n": null, "array": {"__kind": "obj", "__id": 2, "__class": "Array", "__length": 3, "__items": [88, "hello", null]}}, "b": {"__kind": "obj", "__id": 3, "__class": "B", "b": false, "c": {"__kind": "char", "__val": "b"}, "f": 123.123, "i": 2345, "s": "hjkl", "n": null, "array": {"__kind": "ref", "__id": 2}, "ii": 1111, "ss": "qwer"}, "aa": {"__kind": "ref", "__id": 1}}
+{"__kind": "obj", "__id": 0, "__class": "C", "a": {"__kind": "obj", "__id": 1, "__class": "A", "b": true, "c": {"__kind": "char", "__val": "a"}, "f": 0.123, "i": 1234, "s": "asdf", "n": null, "array": {"__kind": "obj", "__id": 2, "__class": "Array", "__length": 3, "__items": [88, "hello", null]}}, "b": {"__kind": "obj", "__id": 3, "__class": "B", "b": false, "c": {"__kind": "char", "__val": "b"}, "f": 123.123, "i": 2345, "s": "hjkl", "n": null, "array": {"__kind": "obj", "__id": 4, "__class": "Array", "__length": 3, "__items": [88, "hello", null]}, "ii": 1111, "ss": "qwer"}, "aa": {"__kind": "ref", "__id": 1}}
 
 # Nit:
 <D: <B: <A: false b 123.123 2345 new line ->
index 195a36c..53a12e3 100644 (file)
@@ -14,7 +14,7 @@
 <C: <A: true a 0.123 1234 asdf false> <B: <A: false b 123.123 2345 hjkl false> 1111 qwer>>
 
 # Json:
-{"__kind": "obj", "__id": 0, "__class": "C", "a": {"__kind": "obj", "__id": 1, "__class": "A", "b": true, "c": {"__kind": "char", "__val": "a"}, "f": 0.123, "i": 1234, "s": "asdf", "n": null, "array": {"__kind": "obj", "__id": 2, "__class": "Array", "__length": 3, "__items": [88, "hello", null]}, "iii": 6789, "sss": "redef"}, "b": {"__kind": "obj", "__id": 3, "__class": "B", "b": false, "c": {"__kind": "char", "__val": "b"}, "f": 123.123, "i": 2345, "s": "hjkl", "n": null, "array": {"__kind": "ref", "__id": 2}, "iii": 6789, "sss": "redef", "ii": 1111, "ss": "qwer"}, "aa": {"__kind": "ref", "__id": 1}}
+{"__kind": "obj", "__id": 0, "__class": "C", "a": {"__kind": "obj", "__id": 1, "__class": "A", "b": true, "c": {"__kind": "char", "__val": "a"}, "f": 0.123, "i": 1234, "s": "asdf", "n": null, "array": {"__kind": "obj", "__id": 2, "__class": "Array", "__length": 3, "__items": [88, "hello", null]}, "iii": 6789, "sss": "redef"}, "b": {"__kind": "obj", "__id": 3, "__class": "B", "b": false, "c": {"__kind": "char", "__val": "b"}, "f": 123.123, "i": 2345, "s": "hjkl", "n": null, "array": {"__kind": "obj", "__id": 4, "__class": "Array", "__length": 3, "__items": [88, "hello", null]}, "iii": 6789, "sss": "redef", "ii": 1111, "ss": "qwer"}, "aa": {"__kind": "ref", "__id": 1}}
 
 # Nit:
 <D: <B: <A: false b 123.123 2345 new line ->
index 040a8df..60bf2d1 100644 (file)
@@ -14,7 +14,7 @@
 <C: <A: true a 0.123 1234 asdf false> <B: <A: false b 123.123 2345 hjkl false> 1111 qwer>>
 
 # Json:
-{"__kind": "obj", "__id": 0, "__class": "C", "a": {"__kind": "obj", "__id": 1, "__class": "A", "b": true, "c": {"__kind": "char", "__val": "a"}, "f": 0.123, "i": 1234, "s": "asdf", "n": null, "array": {"__kind": "obj", "__id": 2, "__class": "Array", "__length": 3, "__items": [88, "hello", null]}}, "b": {"__kind": "obj", "__id": 3, "__class": "B", "b": false, "c": {"__kind": "char", "__val": "b"}, "f": 123.123, "i": 2345, "s": "hjkl", "n": null, "array": {"__kind": "ref", "__id": 2}, "ii": 1111, "ss": "qwer", "ffff": 6.789, "bbbb": false}, "aa": {"__kind": "ref", "__id": 1}}
+{"__kind": "obj", "__id": 0, "__class": "C", "a": {"__kind": "obj", "__id": 1, "__class": "A", "b": true, "c": {"__kind": "char", "__val": "a"}, "f": 0.123, "i": 1234, "s": "asdf", "n": null, "array": {"__kind": "obj", "__id": 2, "__class": "Array", "__length": 3, "__items": [88, "hello", null]}}, "b": {"__kind": "obj", "__id": 3, "__class": "B", "b": false, "c": {"__kind": "char", "__val": "b"}, "f": 123.123, "i": 2345, "s": "hjkl", "n": null, "array": {"__kind": "obj", "__id": 4, "__class": "Array", "__length": 3, "__items": [88, "hello", null]}, "ii": 1111, "ss": "qwer", "ffff": 6.789, "bbbb": false}, "aa": {"__kind": "ref", "__id": 1}}
 
 # Nit:
 <D: <B: <A: false b 123.123 2345 new line ->
index efb0ccc..32f461f 100644 (file)
@@ -14,7 +14,7 @@
 <C: <A: true a 0.123 1234 asdf false> <B: <A: false b 123.123 2345 hjkl false> 1111 qwer>>
 
 # Json:
-{"__kind": "obj", "__id": 0, "__class": "C", "a": {"__kind": "obj", "__id": 1, "__class": "A", "b": true, "c": {"__kind": "char", "__val": "a"}, "f": 0.123, "i": 1234, "s": "asdf", "n": null, "array": {"__kind": "obj", "__id": 2, "__class": "Array", "__length": 3, "__items": [88, "hello", null]}, "iii": 6789, "sss": "redef"}, "b": {"__kind": "obj", "__id": 3, "__class": "B", "b": false, "c": {"__kind": "char", "__val": "b"}, "f": 123.123, "i": 2345, "s": "hjkl", "n": null, "array": {"__kind": "ref", "__id": 2}, "iii": 6789, "sss": "redef", "ii": 1111, "ss": "qwer", "ffff": 6.789, "bbbb": false}, "aa": {"__kind": "ref", "__id": 1}}
+{"__kind": "obj", "__id": 0, "__class": "C", "a": {"__kind": "obj", "__id": 1, "__class": "A", "b": true, "c": {"__kind": "char", "__val": "a"}, "f": 0.123, "i": 1234, "s": "asdf", "n": null, "array": {"__kind": "obj", "__id": 2, "__class": "Array", "__length": 3, "__items": [88, "hello", null]}, "iii": 6789, "sss": "redef"}, "b": {"__kind": "obj", "__id": 3, "__class": "B", "b": false, "c": {"__kind": "char", "__val": "b"}, "f": 123.123, "i": 2345, "s": "hjkl", "n": null, "array": {"__kind": "obj", "__id": 4, "__class": "Array", "__length": 3, "__items": [88, "hello", null]}, "iii": 6789, "sss": "redef", "ii": 1111, "ss": "qwer", "ffff": 6.789, "bbbb": false}, "aa": {"__kind": "ref", "__id": 1}}
 
 # Nit:
 <D: <B: <A: false b 123.123 2345 new line ->
index b69dc33..22ebc32 100644 (file)
@@ -13,6 +13,9 @@ redef class Deserializer
                if name == "Array[nullable Object]" then return new Array[nullable Object].from_deserializer(self)
                if name == "Array[Serializable]" then return new Array[Serializable].from_deserializer(self)
                if name == "Array[String]" then return new Array[String].from_deserializer(self)
+               if name == "StrictHashMap[Serializable, Int]" then return new StrictHashMap[Serializable, Int].from_deserializer(self)
+               if name == "HashMap[Serializable, Array[Couple[Serializable, Int]]]" then return new HashMap[Serializable, Array[Couple[Serializable, Int]]].from_deserializer(self)
+               if name == "Array[Couple[Serializable, Int]]" then return new Array[Couple[Serializable, Int]].from_deserializer(self)
                return super
        end
 end
diff --git a/tests/sav/test_binary.res b/tests/sav/test_binary.res
new file mode 100644 (file)
index 0000000..f071d47
--- /dev/null
@@ -0,0 +1,7 @@
+no error
+hello
+77
+1.235
+1.235
+123456789
+no error
diff --git a/tests/sav/test_binary_alt1.res b/tests/sav/test_binary_alt1.res
new file mode 100644 (file)
index 0000000..eb0468f
--- /dev/null
@@ -0,0 +1,7 @@
+no error
+hello
+77
+144545136640.0
+0.0
+1571011930645069824
+no error
diff --git a/tests/sav/test_binary_alt2.res b/tests/sav/test_binary_alt2.res
new file mode 100644 (file)
index 0000000..eb0468f
--- /dev/null
@@ -0,0 +1,7 @@
+no error
+hello
+77
+144545136640.0
+0.0
+1571011930645069824
+no error
diff --git a/tests/sav/test_binary_alt3.res b/tests/sav/test_binary_alt3.res
new file mode 100644 (file)
index 0000000..f071d47
--- /dev/null
@@ -0,0 +1,7 @@
+no error
+hello
+77
+1.235
+1.235
+123456789
+no error
index 189c39d..58293c5 100644 (file)
 # Back in Nit:
 <E: 33.33>
 
+# Nit:
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+
+# Json:
+{"__kind": "obj", "__id": 0, "__class": "G", "hs": {"__kind": "obj", "__id": 1, "__class": "HashSet[Int]", "__length": 2, "__items": [-1, 0]}, "s": {"__kind": "obj", "__id": 2, "__class": "ArraySet[String]", "__length": 2, "__items": ["one", "two"]}, "hm": {"__kind": "obj", "__id": 3, "__class": "HashMap[String, Int]", "__length": 2, "__keys": ["one", "two"], "__values": [1, 2]}, "am": {"__kind": "obj", "__id": 4, "__class": "ArrayMap[String, String]", "__length": 2, "__keys": ["three", "four"], "__values": ["3", "4"]}}
+
+# Back in Nit:
+<G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
+
index 734ad5e..3281baf 100644 (file)
@@ -14,7 +14,7 @@
 <C: <A: true a 0.123 1234 asdf false> <B: <A: false b 123.123 2345 hjkl false> 1111 qwer>>
 
 # Json:
-{"__kind": "obj", "__id": 0, "__class": "C", "a": {"__kind": "obj", "__id": 1, "__class": "A", "b": true, "c": {"__kind": "char", "__val": "a"}, "f": 0.123, "i": 1234, "s": "asdf", "n": null, "array": {"__kind": "obj", "__id": 2, "__class": "Array[nullable Object]", "__length": 3, "__items": [88, "hello", null]}}, "b": {"__kind": "obj", "__id": 3, "__class": "B", "b": false, "c": {"__kind": "char", "__val": "b"}, "f": 123.123, "i": 2345, "s": "hjkl", "n": null, "array": {"__kind": "ref", "__id": 2}, "ii": 1111, "ss": "qwer"}, "aa": {"__kind": "ref", "__id": 1}}
+{"__kind": "obj", "__id": 0, "__class": "C", "a": {"__kind": "obj", "__id": 1, "__class": "A", "b": true, "c": {"__kind": "char", "__val": "a"}, "f": 0.123, "i": 1234, "s": "asdf", "n": null, "array": {"__kind": "obj", "__id": 2, "__class": "Array[nullable Object]", "__length": 3, "__items": [88, "hello", null]}}, "b": {"__kind": "obj", "__id": 3, "__class": "B", "b": false, "c": {"__kind": "char", "__val": "b"}, "f": 123.123, "i": 2345, "s": "hjkl", "n": null, "array": {"__kind": "obj", "__id": 4, "__class": "Array[nullable Object]", "__length": 3, "__items": [88, "hello", null]}, "ii": 1111, "ss": "qwer"}, "aa": {"__kind": "ref", "__id": 1}}
 
 # Nit:
 <D: <B: <A: false b 123.123 2345 new line ->
index 734ad5e..3281baf 100644 (file)
@@ -14,7 +14,7 @@
 <C: <A: true a 0.123 1234 asdf false> <B: <A: false b 123.123 2345 hjkl false> 1111 qwer>>
 
 # Json:
-{"__kind": "obj", "__id": 0, "__class": "C", "a": {"__kind": "obj", "__id": 1, "__class": "A", "b": true, "c": {"__kind": "char", "__val": "a"}, "f": 0.123, "i": 1234, "s": "asdf", "n": null, "array": {"__kind": "obj", "__id": 2, "__class": "Array[nullable Object]", "__length": 3, "__items": [88, "hello", null]}}, "b": {"__kind": "obj", "__id": 3, "__class": "B", "b": false, "c": {"__kind": "char", "__val": "b"}, "f": 123.123, "i": 2345, "s": "hjkl", "n": null, "array": {"__kind": "ref", "__id": 2}, "ii": 1111, "ss": "qwer"}, "aa": {"__kind": "ref", "__id": 1}}
+{"__kind": "obj", "__id": 0, "__class": "C", "a": {"__kind": "obj", "__id": 1, "__class": "A", "b": true, "c": {"__kind": "char", "__val": "a"}, "f": 0.123, "i": 1234, "s": "asdf", "n": null, "array": {"__kind": "obj", "__id": 2, "__class": "Array[nullable Object]", "__length": 3, "__items": [88, "hello", null]}}, "b": {"__kind": "obj", "__id": 3, "__class": "B", "b": false, "c": {"__kind": "char", "__val": "b"}, "f": 123.123, "i": 2345, "s": "hjkl", "n": null, "array": {"__kind": "obj", "__id": 4, "__class": "Array[nullable Object]", "__length": 3, "__items": [88, "hello", null]}, "ii": 1111, "ss": "qwer"}, "aa": {"__kind": "ref", "__id": 1}}
 
 # Nit:
 <D: <B: <A: false b 123.123 2345 new line ->
index 11c2a78..797b30d 100644 (file)
@@ -14,7 +14,7 @@
 <C: <A: true a 0.123 1234 asdf false> <B: <A: false b 123.123 2345 hjkl false> 1111 qwer>>
 
 # Json:
-{"__kind": "obj", "__id": 0, "__class": "C", "a": {"__kind": "obj", "__id": 1, "__class": "A", "b": true, "c": {"__kind": "char", "__val": "a"}, "f": 0.123, "i": 1234, "s": "asdf", "n": null, "array": {"__kind": "obj", "__id": 2, "__class": "Array[nullable Object]", "__length": 3, "__items": [88, "hello", null]}, "iii": 6789, "sss": "redef"}, "b": {"__kind": "obj", "__id": 3, "__class": "B", "b": false, "c": {"__kind": "char", "__val": "b"}, "f": 123.123, "i": 2345, "s": "hjkl", "n": null, "array": {"__kind": "ref", "__id": 2}, "iii": 6789, "sss": "redef", "ii": 1111, "ss": "qwer"}, "aa": {"__kind": "ref", "__id": 1}}
+{"__kind": "obj", "__id": 0, "__class": "C", "a": {"__kind": "obj", "__id": 1, "__class": "A", "b": true, "c": {"__kind": "char", "__val": "a"}, "f": 0.123, "i": 1234, "s": "asdf", "n": null, "array": {"__kind": "obj", "__id": 2, "__class": "Array[nullable Object]", "__length": 3, "__items": [88, "hello", null]}, "iii": 6789, "sss": "redef"}, "b": {"__kind": "obj", "__id": 3, "__class": "B", "b": false, "c": {"__kind": "char", "__val": "b"}, "f": 123.123, "i": 2345, "s": "hjkl", "n": null, "array": {"__kind": "obj", "__id": 4, "__class": "Array[nullable Object]", "__length": 3, "__items": [88, "hello", null]}, "iii": 6789, "sss": "redef", "ii": 1111, "ss": "qwer"}, "aa": {"__kind": "ref", "__id": 1}}
 
 # Nit:
 <D: <B: <A: false b 123.123 2345 new line ->
index cb9461b..2809790 100644 (file)
@@ -14,7 +14,7 @@
 <C: <A: true a 0.123 1234 asdf false> <B: <A: false b 123.123 2345 hjkl false> 1111 qwer>>
 
 # Json:
-{"__kind": "obj", "__id": 0, "__class": "C", "a": {"__kind": "obj", "__id": 1, "__class": "A", "b": true, "c": {"__kind": "char", "__val": "a"}, "f": 0.123, "i": 1234, "s": "asdf", "n": null, "array": {"__kind": "obj", "__id": 2, "__class": "Array[nullable Object]", "__length": 3, "__items": [88, "hello", null]}}, "b": {"__kind": "obj", "__id": 3, "__class": "B", "b": false, "c": {"__kind": "char", "__val": "b"}, "f": 123.123, "i": 2345, "s": "hjkl", "n": null, "array": {"__kind": "ref", "__id": 2}, "ii": 1111, "ss": "qwer", "ffff": 6.789, "bbbb": false}, "aa": {"__kind": "ref", "__id": 1}}
+{"__kind": "obj", "__id": 0, "__class": "C", "a": {"__kind": "obj", "__id": 1, "__class": "A", "b": true, "c": {"__kind": "char", "__val": "a"}, "f": 0.123, "i": 1234, "s": "asdf", "n": null, "array": {"__kind": "obj", "__id": 2, "__class": "Array[nullable Object]", "__length": 3, "__items": [88, "hello", null]}}, "b": {"__kind": "obj", "__id": 3, "__class": "B", "b": false, "c": {"__kind": "char", "__val": "b"}, "f": 123.123, "i": 2345, "s": "hjkl", "n": null, "array": {"__kind": "obj", "__id": 4, "__class": "Array[nullable Object]", "__length": 3, "__items": [88, "hello", null]}, "ii": 1111, "ss": "qwer", "ffff": 6.789, "bbbb": false}, "aa": {"__kind": "ref", "__id": 1}}
 
 # Nit:
 <D: <B: <A: false b 123.123 2345 new line ->
index f5aff78..e176977 100644 (file)
@@ -14,7 +14,7 @@
 <C: <A: true a 0.123 1234 asdf false> <B: <A: false b 123.123 2345 hjkl false> 1111 qwer>>
 
 # Json:
-{"__kind": "obj", "__id": 0, "__class": "C", "a": {"__kind": "obj", "__id": 1, "__class": "A", "b": true, "c": {"__kind": "char", "__val": "a"}, "f": 0.123, "i": 1234, "s": "asdf", "n": null, "array": {"__kind": "obj", "__id": 2, "__class": "Array[nullable Object]", "__length": 3, "__items": [88, "hello", null]}, "iii": 6789, "sss": "redef"}, "b": {"__kind": "obj", "__id": 3, "__class": "B", "b": false, "c": {"__kind": "char", "__val": "b"}, "f": 123.123, "i": 2345, "s": "hjkl", "n": null, "array": {"__kind": "ref", "__id": 2}, "iii": 6789, "sss": "redef", "ii": 1111, "ss": "qwer", "ffff": 6.789, "bbbb": false}, "aa": {"__kind": "ref", "__id": 1}}
+{"__kind": "obj", "__id": 0, "__class": "C", "a": {"__kind": "obj", "__id": 1, "__class": "A", "b": true, "c": {"__kind": "char", "__val": "a"}, "f": 0.123, "i": 1234, "s": "asdf", "n": null, "array": {"__kind": "obj", "__id": 2, "__class": "Array[nullable Object]", "__length": 3, "__items": [88, "hello", null]}, "iii": 6789, "sss": "redef"}, "b": {"__kind": "obj", "__id": 3, "__class": "B", "b": false, "c": {"__kind": "char", "__val": "b"}, "f": 123.123, "i": 2345, "s": "hjkl", "n": null, "array": {"__kind": "obj", "__id": 4, "__class": "Array[nullable Object]", "__length": 3, "__items": [88, "hello", null]}, "iii": 6789, "sss": "redef", "ii": 1111, "ss": "qwer", "ffff": 6.789, "bbbb": false}, "aa": {"__kind": "ref", "__id": 1}}
 
 # Nit:
 <D: <B: <A: false b 123.123 2345 new line ->
diff --git a/tests/test_binary.nit b/tests/test_binary.nit
new file mode 100644 (file)
index 0000000..a38619b
--- /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 binary
+
+var path = "/tmp/bin"
+var writer = new FileWriter.open(path)
+#alt1# writer.big_endian = false
+#alt3# writer.big_endian = false
+writer.write "hello"
+writer.write_byte 77
+writer.write_float 1.23456789
+writer.write_double 1.23456789
+writer.write_int64 123456789
+print writer.last_error or else "no error"
+writer.close
+
+var reader = new FileReader.open(path)
+#alt2# reader.big_endian = false
+#alt3# reader.big_endian = false
+print reader.read(5)
+print reader.read_byte or else "null"
+print reader.read_float
+print reader.read_double
+print reader.read_int64
+print reader.last_error or else "no error"
+reader.close
index deb07b7..f26bcbf 100644 (file)
@@ -98,7 +98,7 @@ class E
        redef fun to_s do return "<E: a: {a.join(", ")}; b: {b.join(", ")}>"
 end
 
-# Class with generics
+# Parameterized class
 class F[N: Numeric]
        auto_serializable
 
@@ -108,6 +108,31 @@ class F[N: Numeric]
        redef fun to_s do return "<E: {n}>"
 end
 
+# Other collections
+class G
+       auto_serializable
+
+       var hs = new HashSet[Int]
+       var s: Set[String] = new ArraySet[String]
+       var hm = new HashMap[String, Int]
+       var am = new ArrayMap[String, String]
+
+       init
+       do
+               hs.add -1
+               hs.add 0
+               s.add "one"
+               s.add "two"
+               hm["one"] = 1
+               hm["two"] = 2
+               am["three"] = 3.to_s
+               am["four"] = 4.to_s
+       end
+
+       redef fun to_s do return "<G: hs: {hs.join(", ")}; s: {s.join(", ")}; "+
+               "hm: {hm.join(", ", ". ")}; am: {am.join(", ", ". ")}>"
+end
+
 var a = new A(true, 'a', 0.1234, 1234, "asdf", null)
 var b = new B(false, 'b', 123.123, 2345, "hjkl", 12, 1111, "qwer")
 var c = new C(a, b)
@@ -116,9 +141,10 @@ d.d = d
 var e = new E
 var fi = new F[Int](2222)
 var ff = new F[Float](33.33)
+var g = new G
 
 # Default works only with Nit serial
-var tests = new Array[Serializable].with_items(a, b, c, d, e, fi, ff)
+var tests = [a, b, c, d, e, fi, ff, g: Serializable]
 
 # Alt1 should work without nitserial
 #alt1# tests = new Array[Serializable].with_items(a, b, c, d)
index 1279538..7d1d443 100644 (file)
@@ -22,16 +22,24 @@ import serialization
 redef class Deserializer
        redef fun deserialize_class(name)
        do
-                       if name == "Array[Object]" then return new Array[Object].from_deserializer(self)
-                       if name == "Array[nullable Serializable]" then return new Array[nullable Serializable].from_deserializer(self)
-                       if name == "F[Int]" then return new F[Int].from_deserializer(self)
-                       if name == "F[Float]" then return new F[Float].from_deserializer(self)
-                       if name == "Array[Serializable]" then return new Array[Serializable].from_deserializer(self)
-                       if name == "Array[String]" then return new Array[String].from_deserializer(self)
-                       if name == "Array[HashMap[String, nullable Object]]" then return new Array[HashMap[String, nullable Object]].from_deserializer(self)
-                       if name == "Array[Match]" then return new Array[Match].from_deserializer(self)
-                       if name == "Array[nullable Object]" then return new Array[nullable Object].from_deserializer(self)
-                       if name == "Array[FlatBuffer]" then return new Array[FlatBuffer].from_deserializer(self)
+               # Module: test_deserialization
+               if name == "Array[Object]" then return new Array[Object].from_deserializer(self)
+               if name == "Array[nullable Serializable]" then return new Array[nullable Serializable].from_deserializer(self)
+               if name == "F[Int]" then return new F[Int].from_deserializer(self)
+               if name == "F[Float]" then return new F[Float].from_deserializer(self)
+               if name == "HashSet[Int]" then return new HashSet[Int].from_deserializer(self)
+               if name == "ArraySet[String]" then return new ArraySet[String].from_deserializer(self)
+               if name == "HashMap[String, Int]" then return new HashMap[String, Int].from_deserializer(self)
+               if name == "ArrayMap[String, String]" then return new ArrayMap[String, String].from_deserializer(self)
+               if name == "Array[Serializable]" then return new Array[Serializable].from_deserializer(self)
+               if name == "Array[String]" then return new Array[String].from_deserializer(self)
+               if name == "HashMap[Serializable, Int]" then return new HashMap[Serializable, Int].from_deserializer(self)
+               if name == "Array[JsonObject]" then return new Array[JsonObject].from_deserializer(self)
+               if name == "HashMap[Int, Object]" then return new HashMap[Int, Object].from_deserializer(self)
+               if name == "Array[Node]" then return new Array[Node].from_deserializer(self)
+               if name == "Array[LRState]" then return new Array[LRState].from_deserializer(self)
+               if name == "Array[Couple[String, String]]" then return new Array[Couple[String, String]].from_deserializer(self)
+               if name == "Array[nullable Jsonable]" then return new Array[nullable Jsonable].from_deserializer(self)
                return super
        end
 end