Merge: lib/popcorn: introduce AuthHandler to check user session
authorJean Privat <jean@pryen.org>
Tue, 16 Aug 2016 21:37:29 +0000 (17:37 -0400)
committerJean Privat <jean@pryen.org>
Tue, 16 Aug 2016 21:37:29 +0000 (17:37 -0400)
### AuthHandler allows access to session user

 Inherit this handler to access to session user from your custom handler.

 For example, you need a profile handler that checks if the user is logged
 before returning it in json format.
 ~~~nit
 import popcorn::pop_auth

 class ProfileHandler
super AuthHandler

redef fun get(req, res) do
var user = check_session_user(req, res)
if user == null then return
res.json user.json
end
 end
 ~~~

 By using `check_session_user`, we delegate to the `AuthHandler` the responsability
 to set the HTTP 403 error.
 We then check is the user is not null before pursuing.

Signed-off-by: Alexandre Terrasa <alexandre@moz-code.org>

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

25 files changed:
contrib/benitlux/src/client/client.nit
contrib/nitcc/.gitignore
contrib/nitcc/examples/blob.nit [new file with mode: 0644]
contrib/nitcc/examples/blob.sablecc [new file with mode: 0644]
contrib/nitcc/src/Makefile
lib/binary/serialization.nit
lib/core/environ.nit
lib/core/exec.nit
lib/core/file.nit
lib/core/kernel.nit
lib/core/text/native.nit
lib/json/serialization.nit
lib/mongodb/queries.nit
lib/nitcc_runtime.nit
lib/popcorn/pop_repos.nit
lib/serialization/README.md
lib/serialization/serialization.nit
src/compiler/abstract_compiler.nit
src/frontend/serialization_phase.nit
tests/sav/nitce/test_binary_deserialization_alt1.res
tests/sav/nitce/test_json_deserialization_alt1.res
tests/sav/nitce/test_json_deserialization_alt3.res
tests/sav/nituml_args3.res
tests/sav/nituml_args4.res
wallet [deleted file]

index e2fc2a9..75ba6a9 100644 (file)
@@ -30,11 +30,6 @@ import user_views
 redef class Deserializer
        redef fun deserialize_class(name)
        do
-               if name == "Array[Beer]" then return new Array[Beer].from_deserializer(self)
-               if name == "Array[User]" then return new Array[User].from_deserializer(self)
-               if name == "Array[BeerBadge]" then return new Array[BeerBadge].from_deserializer(self)
-               if name == "Array[BeerAndRatings]" then return new Array[BeerAndRatings].from_deserializer(self)
-               if name == "Array[String]" then return new Array[String].from_deserializer(self)
                if name == "Array[UserAndFollowing]" then return new Array[UserAndFollowing].from_deserializer(self)
                return super
        end
index 8d6951d..f7a4467 100644 (file)
@@ -13,3 +13,4 @@ nitcc1
 nitcc
 calc
 minilang
+blob
diff --git a/contrib/nitcc/examples/blob.nit b/contrib/nitcc/examples/blob.nit
new file mode 100644 (file)
index 0000000..696197f
--- /dev/null
@@ -0,0 +1,85 @@
+# 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.
+
+# Example of the hijack of a lexer to inject custom behavior.
+# see `blob.sablecc` for the grammar
+import blob_test_parser
+
+redef class Lexer_blob
+
+       # Two context, *in blob* (custom), and *not in blob* (normal).
+       # The initial state is *in blob*.
+       var in_blob = true
+
+       # Refine the `next_token` to hijack the lexer.
+       redef fun next_token
+       do
+               if not in_blob then
+                       # Normal lexer
+                       var res = super
+                       # Watch for tokens that trigger a context change
+                       if res isa Nendmark then in_blob = true
+                       return res
+               end
+
+               # Custom lexer
+               # Manage pos, line and col manually
+               # TODO: improve the lexer API
+
+               var pos = pos_start
+               var line = line_start
+               var col = col_start
+               var text = stream
+               var len = text.length
+
+               # Need to count three '{' or the end of text
+               var cpt = 0
+               while pos < len do
+                       var c = text[pos]
+                       if c == '{' then
+                               cpt += 1
+                               if cpt == 3 then
+                                       # Got them, backtrack them.
+                                       pos -= 3
+                                       col -= 3
+                                       break
+                               end
+                       else
+                               cpt = 0
+                       end
+
+                       # Next char, count lines.
+                       pos += 1
+                       col += 1
+                       if c == '\n' then
+                               line += 1
+                               col = 1
+                       end
+               end
+
+               # Create manually the `blob token`
+               var token = new Nblob
+               var position = new Position(pos_start, pos, line_start, line, col_start, col)
+               token.position = position
+               token.text = text.substring(pos_start, pos-pos_start+1)
+
+               # Prepare for the next token
+               pos_start = pos + 1
+               line_start = line
+               col_start = col + 1
+               in_blob = false
+
+               return token
+       end
+end
diff --git a/contrib/nitcc/examples/blob.sablecc b/contrib/nitcc/examples/blob.sablecc
new file mode 100644 (file)
index 0000000..a2dee60
--- /dev/null
@@ -0,0 +1,22 @@
+/* Special lexer that will be hijacked. See blob.nit */
+Grammar blob;
+
+Lexer
+// These tokens are recognized by the genuine lexer
+d = '0'..'9';
+int = d+;
+white = #9..#13 | ' ';
+// Need to name this token, we will use it to change context
+endmark = '}}}';
+
+// Special token that the genuine lexer is expect to not recognize.
+// But that muse be known by the parser or the application.
+// TODO: Maybe add a special keyword?
+//       blob = Phony;
+blob = #0;
+
+Parser
+Ignored white;
+ps = p*;
+// Parser do not know that `blob` is phony.
+p = blob | '{{{' int endmark;
index 52356e6..ed5ba5d 100644 (file)
@@ -1,6 +1,6 @@
 NITC=../../../bin/nitc
 
-all: nitcc calc minilang
+all: nitcc calc minilang blob
 
 nitcc_parser_gen: nitcc_parser_gen.nit
        @echo "*** Compile the nitcc bootstrap parser generator -- level 0"
@@ -33,6 +33,12 @@ minilang: nitcc ../examples/minilang.sablecc ../examples/minilang.nit
        ${NITC} ../examples/minilang.nit -v
        printf "10\n42\n" | ./minilang ../examples/minilang.minilang
 
+blob: nitcc ../examples/blob.sablecc ../examples/blob.nit
+       @echo "*** Example program, blob"
+       cd ../examples && ../src/nitcc blob.sablecc
+       ${NITC} ../examples/blob.nit -v
+       ./blob -e "abc {{{ 1 }}} de {{{ 2 }}} { 3 }"
+
 check: tests
 tests:
        cd ../tests && ./run
@@ -42,7 +48,7 @@ clean:
                *.dot *.out \
                nitcc_lexer.nit nitcc_parser.nit nitcc_test_parser.nit nitcc_parser_gen \
                nitcc0 nitcc1 \
-               calc minilang \
+               calc minilang blob \
                ../examples/*.dot ../examples/*.out ../examples/*_lexer.nit ../examples/*_parser.nit ../examples/*_test_parser.nit \
                2>/dev/null || true
 
index a530ebf..fff7fb5 100644 (file)
@@ -153,7 +153,7 @@ class BinaryDeserializer
                return new Couple[String, nullable Object](next_attribute_name, next_object)
        end
 
-       redef fun deserialize_attribute(name)
+       redef fun deserialize_attribute(name, static_type)
        do
                if unclaimed_attributes.last.keys.has(name) then
                        # Pick in already deserialized attributes
index 4774878..4747a6e 100644 (file)
@@ -58,7 +58,13 @@ end
 
 redef class NativeString
        private fun get_environ: NativeString `{ return getenv(self); `}
-       private fun setenv(value: NativeString) `{ setenv(self, value, 1); `}
+       private fun setenv(value: NativeString) `{
+#ifdef _WIN32
+               _putenv_s(self, value);
+#else
+               setenv(self, value, 1);
+#endif
+       `}
 end
 
 redef class Sys
index c1edff8..8cee011 100644 (file)
@@ -23,8 +23,10 @@ in "C" `{
        #include <errno.h>
        #include <stdio.h>
        #include <unistd.h>
-       #include <sys/wait.h>
        #include <signal.h>
+#ifndef _WIN32
+       #include <sys/wait.h>
+#endif
 `}
 
 in "C Header" `{
@@ -113,6 +115,10 @@ class Process
 
        private var data: NativeProcess
        private fun basic_exec_execute(prog, args: NativeString, argc: Int, pipeflag: Int): NativeProcess `{
+#ifdef _WIN32
+               // FIXME use a higher level abstraction to support WIN32
+               return -1;
+#else
                se_exec_data_t* result = NULL;
                int id;
                int in_fd[2];
@@ -207,6 +213,7 @@ class Process
                }
 
                return result;
+#endif
        `}
 end
 
@@ -337,10 +344,12 @@ redef class NativeString
        # See the posix function system(3).
        fun system: Int `{
                int status = system(self);
+#ifndef _WIN32
                if (WIFSIGNALED(status) && WTERMSIG(status) == SIGINT) {
                        // system exited on SIGINT: in my opinion the user wants the main to be discontinued
                        kill(getpid(), SIGINT);
                }
+#endif
                return status;
        `}
 end
@@ -354,6 +363,10 @@ private extern class NativeProcess `{ se_exec_data_t* `}
        fun err_fd: Int `{ return self->err_fd; `}
 
        fun is_finished: Bool `{
+#ifdef _WIN32
+               // FIXME use a higher level abstraction to support WIN32
+               return 0;
+#else
                int result = (int)0;
                int status;
                if (self->running) {
@@ -369,14 +382,18 @@ private extern class NativeProcess `{ se_exec_data_t* `}
                        result = (int)1;
                }
                return result;
+#endif
        `}
 
        fun wait `{
+#ifndef _WIN32
+               // FIXME use a higher level abstraction to support WIN32
                int status;
                if (self->running) {
                        waitpid(self->id, &status, 0);
                        self->status = WEXITSTATUS(status);
                        self->running = 0;
                }
+#endif
        `}
 end
index 79ddf45..23d3ce3 100644 (file)
@@ -28,8 +28,16 @@ in "C Header" `{
        #include <sys/stat.h>
        #include <unistd.h>
        #include <stdio.h>
-       #include <poll.h>
        #include <errno.h>
+#ifndef _WIN32
+       #include <poll.h>
+#endif
+`}
+
+in "C" `{
+#ifdef _WIN32
+       #include <windows.h>
+#endif
 `}
 
 # `Stream` used to interact with a File or FileDescriptor
@@ -188,8 +196,12 @@ class FileReader
        end
 
        private fun native_poll_in(fd: Int): Int `{
+#ifndef _WIN32
                struct pollfd fds = {(int)fd, POLLIN, 0};
                return poll(&fds, 1, 0);
+#else
+               return 0;
+#endif
        `}
 end
 
@@ -950,13 +962,24 @@ redef class String
        #     assert "".basename("")                            == ""
        fun basename(extension: nullable String): String
        do
-               var l = length - 1 # Index of the last char
-               while l > 0 and self.chars[l] == '/' do l -= 1 # remove all trailing `/`
-               if l == 0 then return "/"
-               var pos = chars.last_index_of_from('/', l)
                var n = self
-               if pos >= 0 then
-                       n = substring(pos+1, l-pos)
+               if is_windows then
+                       var l = length - 1 # Index of the last char
+                       while l > 0 and (self.chars[l] == '/' or chars[l] == '\\') do l -= 1 # remove all trailing `/`
+                       if l == 0 then return "/"
+                       var pos = chars.last_index_of_from('/', l)
+                       pos = pos.max(last_index_of_from('\\', l))
+                       if pos >= 0 then
+                               n = substring(pos+1, l-pos)
+                       end
+               else
+                       var l = length - 1 # Index of the last char
+                       while l > 0 and self.chars[l] == '/' do l -= 1 # remove all trailing `/`
+                       if l == 0 then return "/"
+                       var pos = chars.last_index_of_from('/', l)
+                       if pos >= 0 then
+                               n = substring(pos+1, l-pos)
+                       end
                end
 
                if extension != null then
@@ -1319,15 +1342,29 @@ redef class FlatString
        end
 
        redef fun basename(extension) do
-               var l = last_byte
-               var its = _items
-               var min = _first_byte
-               var sl = '/'.ascii
-               while l > min and its[l] == sl do l -= 1
-               if l == min then return "/"
-               var ns = l
-               while ns >= min and its[ns] != sl do ns -= 1
-               var bname = new FlatString.with_infos(its, l - ns, ns + 1)
+               var bname
+               if is_windows then
+                       var l = last_byte
+                       var its = _items
+                       var min = _first_byte
+                       var sl = '/'.ascii
+                       var ls = '\\'.ascii
+                       while l > min and (its[l] == sl or its[l] == ls) do l -= 1
+                       if l == min then return "\\"
+                       var ns = l
+                       while ns >= min and its[ns] != sl and its[ns] != ls do ns -= 1
+                       bname = new FlatString.with_infos(its, l - ns, ns + 1)
+               else
+                       var l = last_byte
+                       var its = _items
+                       var min = _first_byte
+                       var sl = '/'.ascii
+                       while l > min and its[l] == sl do l -= 1
+                       if l == min then return "/"
+                       var ns = l
+                       while ns >= min and its[ns] != sl do ns -= 1
+                       bname = new FlatString.with_infos(its, l - ns, ns + 1)
+               end
 
                return if extension != null then bname.strip_extension(extension) else bname
        end
@@ -1335,11 +1372,16 @@ end
 
 redef class NativeString
        private fun file_exists: Bool `{
+#ifdef _WIN32
+               DWORD attribs = GetFileAttributesA(self);
+               return attribs != INVALID_FILE_ATTRIBUTES;
+#else
                FILE *hdl = fopen(self,"r");
                if(hdl != NULL){
                        fclose(hdl);
                }
                return hdl != NULL;
+#endif
        `}
 
        private fun file_stat: NativeFileStat `{
@@ -1353,15 +1395,26 @@ redef class NativeString
        `}
 
        private fun file_lstat: NativeFileStat `{
+#ifdef _WIN32
+               // FIXME use a higher level abstraction to support WIN32
+               return NULL;
+#else
                struct stat* stat_element;
                int res;
                stat_element = malloc(sizeof(struct stat));
                res = lstat(self, stat_element);
                if (res == -1) return NULL;
                return stat_element;
+#endif
        `}
 
-       private fun file_mkdir(mode: Int): Bool `{ return !mkdir(self, mode); `}
+       private fun file_mkdir(mode: Int): Bool `{
+#ifdef _WIN32
+               return !mkdir(self);
+#else
+               return !mkdir(self, mode);
+#endif
+       `}
 
        private fun rmdir: Bool `{ return !rmdir(self); `}
 
@@ -1371,7 +1424,16 @@ redef class NativeString
 
        private fun file_chdir: Bool `{ return !chdir(self); `}
 
-       private fun file_realpath: NativeString `{ return realpath(self, NULL); `}
+       private fun file_realpath: NativeString `{
+#ifdef _WIN32
+               DWORD len = GetFullPathName(self, 0, NULL, NULL);
+               char *buf = malloc(len+1); // FIXME don't leak memory
+               len = GetFullPathName(self, len+1, buf, NULL);
+               return buf;
+#else
+               return realpath(self, NULL);
+#endif
+       `}
 end
 
 # This class is system dependent ... must reify the vfs
@@ -1408,10 +1470,22 @@ private extern class NativeFileStat `{ struct stat * `}
        fun is_fifo: Bool `{ return S_ISFIFO(self->st_mode); `}
 
        # Returns true if the type is a link
-       fun is_lnk: Bool `{ return S_ISLNK(self->st_mode); `}
+       fun is_lnk: Bool `{
+#ifdef _WIN32
+       return 0;
+#else
+       return S_ISLNK(self->st_mode);
+#endif
+       `}
 
        # Returns true if the type is a socket
-       fun is_sock: Bool `{ return S_ISSOCK(self->st_mode); `}
+       fun is_sock: Bool `{
+#ifdef _WIN32
+       return 0;
+#else
+       return S_ISSOCK(self->st_mode);
+#endif
+       `}
 end
 
 # Instance of this class are standard FILE * pointers
@@ -1528,6 +1602,9 @@ redef class Sys
 
        private fun intern_poll(in_fds: Array[Int], out_fds: Array[Int]): nullable Int
        import Array[Int].length, Array[Int].[], Int.as(nullable Int) `{
+#ifndef _WIN32
+               // FIXME use a higher level abstraction to support WIN32
+
                int in_len, out_len, total_len;
                struct pollfd *c_fds;
                int i;
@@ -1572,6 +1649,7 @@ redef class Sys
                }
                else if ( result < 0 )
                        fprintf( stderr, "Error in Stream:poll: %s\n", strerror( errno ) );
+#endif
 
                return null_Int();
        `}
index fe45072..18dd7ac 100644 (file)
@@ -1073,3 +1073,12 @@ interface Task
        # Main method of this task
        fun main do end
 end
+
+# Is this program currently running in a Windows OS?
+fun is_windows: Bool `{
+#ifdef _WIN32
+       return 1;
+#else
+       return 0;
+#endif
+`}
index ebb5661..590cff8 100644 (file)
@@ -22,6 +22,9 @@ in "C" `{
        #include <libkern/OSByteOrder.h>
        #define be32toh(x) OSSwapBigToHostInt32(x)
 #endif
+#ifdef _WIN32
+       #define be32toh(val) _byteswap_ulong(val)
+#endif
 
 #ifdef __pnacl__
        #define be16toh(val) (((val) >> 8) | ((val) << 8))
index e2c3adf..8861e19 100644 (file)
 #}"""
 # ~~~
 #
-# ## JSON to Nit objects
+# ## Read JSON to create Nit objects
 #
-# The `JsonDeserializer` support reading JSON code with minimal metadata
-# to easily create Nit object from client-side code or configuration files.
-# Each JSON object must define the `__class` attribute with the corresponding
-# Nit class and the expected attributes with its name in Nit followed by its value.
+# The `JsonDeserializer` supports reading JSON code with or without metadata.
+# It can create Nit objects from a remote service returning JSON data
+# or to read local configuration files as Nit objects.
+# However, it needs to know which Nit class to recreate from each JSON object.
+# The class is either declared or inferred:
+#
+# 1. The JSON object defines a `__class` key with the name of the Nit class as value.
+#    This attribute is generated by the `JsonSerializer` with other metadata,
+#    it can also be specified by other external tools.
+# 2. A refinement of `JsonDeserializer::class_name_heuristic` identifies the Nit class.
+# 3. If all else fails, `JsonDeserializer` uses the static type of the attribute.
 #
 # ### Usage Example
 #
 # ~~~nitish
 # import json::serialization
 #
-# class MeetupConfig
+# class Triangle
 #     serialize
 #
-#     var description: String
-#     var max_participants: nullable Int
-#     var answers: Array[FlatString]
+#     var corners = new Array[Point]
+#     redef var to_s is serialize_as("name")
 # end
 #
-# var json_code = """
-# {"__class": "MeetupConfig", "description": "My Awesome Meetup", "max_participants": null, "answers": ["Pepperoni", "Chicken"]}"""
-# var deserializer = new JsonDeserializer(json_code)
+# class Point
+#     serialize
 #
-# var meet = deserializer.deserialize
+#     var x: Int
+#     var y: Int
+# end
 #
-# # Check for errors
+# # Metadata on each JSON object tells the deserializer what is its Nit type,
+# # and it supports special types such as generic collections.
+# var json_with_metadata = """{
+#     "__class": "Triangle",
+#     "corners": {"__class": "Array[Point]",
+#                 "__items": [{"__class": "Point", "x": 0, "y": 0},
+#                             {"__class": "Point", "x": 3, "y": 0},
+#                             {"__class": "Point", "x": 2, "y": 2}]},
+#     "name": "some triangle"
+# }"""
+#
+# var deserializer = new JsonDeserializer(json_with_metadata)
+# var object = deserializer.deserialize
 # assert deserializer.errors.is_empty
+# assert object != null
+# print object
+#
+# # However most non-Nit services won't add the metadata and instead produce plain JSON.
+# # Without a "__class", the deserializer relies on `class_name_heuristic` and the static type.
+# # The type of the root object to deserialize can be specified by calling
+# # its deserialization constructor `from_deserializer`.
+# var plain_json = """{
+#     "corners": [{"x": 0, "y": 0},
+#                 {"x": 3, "y": 0},
+#                 {"x": 2, "y": 2}],
+#     "name": "the same triangle"
+# }"""
 #
-# assert meet isa MeetupConfig
-# assert meet.description == "My Awesome Meetup"
-# assert meet.max_participants == null
-# assert meet.answers == ["Pepperoni", "Chicken"]
+# deserializer = new JsonDeserializer(plain_json)
+# object = new Triangle.from_deserializer(deserializer)
+# assert deserializer.errors.is_empty # If false, `obj` is invalid
+# print object
 # ~~~
 module serialization
 
@@ -256,7 +288,7 @@ class JsonDeserializer
                self.root = root
        end
 
-       redef fun deserialize_attribute(name)
+       redef fun deserialize_attribute(name, static_type)
        do
                assert not path.is_empty # This is an internal error, abort
                var current = path.last
@@ -269,7 +301,7 @@ class JsonDeserializer
                var value = current[name]
 
                attributes_path.add name
-               var res = convert_object(value)
+               var res = convert_object(value, static_type)
                attributes_path.pop
                return res
        end
@@ -283,8 +315,8 @@ class JsonDeserializer
                cache[id] = new_object
        end
 
-       # Convert from simple Json object to Nit object
-       private fun convert_object(object: nullable Object): nullable Object
+       # Convert the simple JSON `object` to a Nit object
+       private fun convert_object(object: nullable Object, static_type: nullable String): nullable Object
        do
                if object isa JsonParseError then
                        errors.add object
@@ -339,6 +371,14 @@ class JsonDeserializer
                                if class_name == null then
                                        # Fallback to custom heuristic
                                        class_name = class_name_heuristic(object)
+
+                                       if class_name == null and static_type != null then
+                                               # Fallack to the static type, strip the `nullable` prefix
+                                               var prefix = "nullable "
+                                               if static_type.has(prefix) then
+                                                       class_name = static_type.substring_from(prefix.length)
+                                               else class_name = static_type
+                                       end
                                end
 
                                if class_name == null then
@@ -387,6 +427,23 @@ class JsonDeserializer
 
                # Simple JSON array without serialization metadata
                if object isa Array[nullable Object] then
+                       # Can we use the static type?
+                       if static_type != null then
+                               var prefix = "nullable "
+                               var class_name = if static_type.has(prefix) then
+                                               static_type.substring_from(prefix.length)
+                                       else static_type
+
+                               opened_array = object
+                               var value = deserialize_class(class_name)
+                               opened_array = null
+                               return value
+                       end
+
+                       # This branch should rarely be used:
+                       # when an array is the root object which is accepted but illegal in standard JSON,
+                       # or in strange custom deserialization hacks.
+
                        var array = new Array[nullable Object]
                        var types = new HashSet[String]
                        var has_nullable = false
@@ -434,13 +491,16 @@ class JsonDeserializer
                                return typed_array
                        end
 
-                       # Uninferable type, return as `Array[nullable Object]`
+                       # Uninferrable type, return as `Array[nullable Object]`
                        return array
                end
 
                return object
        end
 
+       # Current array open for deserialization, used by `SimpleCollection::from_deserializer`
+       private var opened_array: nullable Array[nullable Object] = null
+
        redef fun deserialize
        do
                errors.clear
@@ -676,15 +736,34 @@ redef class SimpleCollection[E]
                        v.notify_of_creation self
                        init
 
-                       var arr = v.path.last.get_or_null("__items")
-                       if not arr isa SequenceRead[nullable Object] then
-                               # If there is nothing, we consider that it is an empty collection.
-                               if arr != null then v.errors.add new Error("Deserialization Error: invalid format in {self.class_name}")
-                               return
+                       var open_array: nullable SequenceRead[nullable Object] = v.opened_array
+                       if open_array == null then
+                               # With metadata
+                               var arr = v.path.last.get_or_null("__items")
+                               if not arr isa SequenceRead[nullable Object] then
+                                       # If there is nothing, we consider that it is an empty collection.
+                                       if arr != null then v.errors.add new Error("Deserialization Error: invalid format in {self.class_name}")
+                                       return
+                               end
+                               open_array = arr
+                       end
+
+                       # Try to get the name of the single parameter type assuming it is E.
+                       # This does not work in non-generic subclasses,
+                       # when the first parameter is not E, or
+                       # when there is more than one parameter. (The last one could be fixed)
+                       var class_name = class_name
+                       var items_type = null
+                       var bracket_index = class_name.index_of('[')
+                       if bracket_index != -1 then
+                               var start = bracket_index + 1
+                               var ending = class_name.last_index_of(']')
+                               items_type = class_name.substring(start, ending-start)
                        end
 
-                       for o in arr do
-                               var obj = v.convert_object(o)
+                       # Fill array
+                       for o in open_array do
+                               var obj = v.convert_object(o, items_type)
                                if obj isa E then
                                        add obj
                                else v.errors.add new AttributeTypeError(self, "items", obj, "E")
index c7bb18f..26d7375 100644 (file)
@@ -289,7 +289,12 @@ class MongoPipeline
        # ~~~json
        # { $skip: { <number> } }
        # ~~~
-       fun skip(number: Int): MongoPipeline do return add_stage("skip", number)
+       #
+       # If `number == null` then no skip stage is generated
+       fun skip(number: nullable Int): MongoPipeline do
+               if number == null then return self
+               return add_stage("skip", number)
+       end
 
        # Apply limit
        #
@@ -300,7 +305,12 @@ class MongoPipeline
        # ~~~json
        # { $limit: { <number> } }
        # ~~~
-       fun limit(number: Int): MongoPipeline do return add_stage("limit", number)
+       #
+       # If `number == null` then no limit stage is generated
+       fun limit(number: nullable Int): MongoPipeline do
+               if number == null then return self
+               return add_stage("limit", number)
+       end
 
        # Apply group
        #
index ac75d8b..a09bc3c 100644 (file)
@@ -165,16 +165,36 @@ abstract class Lexer
        fun lex: CircularArray[NToken]
        do
                var res = new CircularArray[NToken]
+               loop
+                       var t = next_token
+                       if t != null then res.add t
+                       if t isa NEof or t isa NError then break
+               end
+               return res
+       end
+
+       # Cursor current position (in chars, starting from 0)
+       var pos_start = 0
+
+       # Cursor current line (starting from 1)
+       var line_start = 1
+
+       # Cursor current column (in chars, starting from 1)
+       var col_start = 1
+
+       # Move the cursor and return the next token.
+       #
+       # Returns a `NEof` and the end.
+       # Returns `null` if the token is ignored.
+       fun next_token: nullable NToken
+       do
                var state = start_state
-               var pos = 0
-               var pos_start = 0
-               var pos_end = 0
-               var line = 1
-               var line_start = 1
-               var line_end = 0
-               var col = 1
-               var col_start = 1
-               var col_end = 0
+               var pos = pos_start
+               var pos_end = pos_start - 1
+               var line = line_start
+               var line_end = line_start - 1
+               var col = col_start
+               var col_end = col_start - 1
                var last_state: nullable DFAState = null
                var text = stream
                var length = text.length
@@ -195,39 +215,30 @@ abstract class Lexer
                                next = state.trans(c)
                        end
                        if next == null then
+                               var token
                                if pos_start < length then
                                        if last_state == null then
-                                               var token = new NLexerError
+                                               token = new NLexerError
                                                var position = new Position(pos_start, pos, line_start, line, col_start, col)
                                                token.position = position
                                                token.text = text.substring(pos_start, pos-pos_start+1)
-                                               res.push token
-                                               break
-                                       end
-                                       if not last_state.is_ignored then
+                                       else if not last_state.is_ignored then
                                                var position = new Position(pos_start, pos_end, line_start, line_end, col_start, col_end)
-                                               var token = last_state.make_token(position, text)
-                                               if token != null then res.push(token)
+                                               token = last_state.make_token(position, text)
+                                       else
+                                               token = null
                                        end
-                               end
-                               if pos >= length then
-                                       var token = new NEof
+                               else
+                                       token = new NEof
                                        var position = new Position(pos, pos, line, line, col, col)
                                        token.position = position
                                        token.text = ""
-                                       res.push token
-                                       break
                                end
-                               state = start_state
                                pos_start = pos_end + 1
-                               pos = pos_start
                                line_start = line_end
-                               line = line_start
                                col_start = col_end
-                               col = col_start
 
-                               last_state = null
-                               continue
+                               return token
                        end
                        state = next
                        pos += 1
@@ -237,7 +248,6 @@ abstract class Lexer
                                col = 1
                        end
                end
-               return res
        end
 end
 
index c950ea4..3a83346 100644 (file)
@@ -149,7 +149,10 @@ interface Repository[E: Serializable]
        # Find all instances based on `query`
        #
        # Using `query` == null will retrieve all the document in the repository.
-       fun find_all(query: nullable QUERY): Array[E] is abstract
+       fun find_all(query: nullable QUERY, skip, limit: nullable Int): Array[E] is abstract
+
+       # Count instances that matches `query`
+       fun count(query: nullable QUERY): Int is abstract
 
        # Save an `instance`
        fun save(instance: E): Bool is abstract
@@ -160,6 +163,9 @@ interface Repository[E: Serializable]
        # Remove the instance based on `query`
        fun remove(query: nullable QUERY): Bool is abstract
 
+       # Remove all the instances matching on `query`
+       fun remove_all(query: nullable QUERY): Bool is abstract
+
        # Remove all instances
        fun clear: Bool is abstract
 
@@ -216,17 +222,14 @@ end
 #
 # ~~~
 # import popcorn
-# import popcorn::pop_routes
+# import popcorn::pop_repos
 #
 # # First, let's create a User abstraction:
 #
 # # Serializable user representation.
 # class User
+#      super RepoObject
 #      serialize
-#      super Jsonable
-#
-#      # User uniq ID
-#      var id: String = (new MongoObjectId).id is serialize_as "_id"
 #
 #      # User login
 #      var login: String
@@ -235,9 +238,6 @@ end
 #      var password: String is writable
 #
 #      redef fun to_s do return login
-#      redef fun ==(o) do return o isa SELF and id == o.id
-#      redef fun hash do return id.hash
-#      redef fun to_json do return serialize_to_json
 # end
 #
 # # We then need to subclass the `MongoRepository` to provide User specific services:
@@ -293,14 +293,18 @@ class MongoRepository[E: Serializable]
                return deserialize(res.to_json)
        end
 
-       redef fun find_all(query) do
+       redef fun find_all(query, skip, limit) do
                var res = new Array[E]
-               for e in collection.find_all(query or else new JsonObject) do
+               for e in collection.find_all(query or else new JsonObject, skip, limit) do
                        res.add deserialize(e.to_json).as(E)
                end
                return res
        end
 
+       redef fun count(query) do
+               return collection.count(query or else new JsonObject)
+       end
+
        redef fun save(item) do
                var json = serialize(item).as(String)
                var obj = json.parse_json.as(JsonObject)
@@ -317,6 +321,10 @@ class MongoRepository[E: Serializable]
                return collection.remove(query or else new JsonObject)
        end
 
+       redef fun remove_all(query) do
+               return collection.remove_all(query or else new JsonObject)
+       end
+
        redef fun clear do return collection.drop
 
        # Perform an aggregation query over the repo.
@@ -331,6 +339,62 @@ class MongoRepository[E: Serializable]
        end
 end
 
+# Base serializable entity that can go into a JsonRepository
+#
+# Provide boiler plate implementation of all object serializable to json.
+#
+# `id` is used as a primary key for `find_by_id`.
+#
+# Subclassing RepoObject makes it easy to create a serializable class:
+# ~~~
+# import popcorn::pop_repos
+#
+# class Album
+#      super RepoObject
+#      serialize
+#
+#      var title: String
+#      var price: Float
+# end
+# ~~~
+#
+# Do not forget the `serialize` annotation else the fields will not be serialized.
+#
+# It is also possible to redefine the `id` primary key to use your own:
+# ~~~
+# import popcorn::pop_repos
+#
+# class Order
+#      super RepoObject
+#      serialize
+#
+#      redef var id = "order-{get_time}"
+#
+#      # ...
+#
+# end
+# ~~~
+abstract class RepoObject
+       super Jsonable
+       serialize
+
+       # `self` unique id.
+       #
+       # This attribute is serialized under the key `_id` to be used
+       # as primary key by MongoDb
+       var id: String = (new MongoObjectId).id is writable, serialize_as "_id"
+
+       # Base object comparison on ID
+       #
+       # Because multiple deserialization can exists of the same instance,
+       # we use the ID to determine if two object are the same.
+       redef fun ==(o) do return o isa SELF and id == o.id
+
+       redef fun hash do return id.hash
+       redef fun to_s do return id
+       redef fun to_json do return serialize_to_json
+end
+
 # JsonObject can be used as a `RepositoryQuery`.
 #
 # See `mongodb` lib.
index 787f51c..47eef73 100644 (file)
@@ -260,22 +260,12 @@ assert couple == deserialize_couple
 
 The serialization has some limitations:
 
-* Not enough classes from the standard library are supported.
-  This only requires someone to actually code the support.
-  It should not be especially hard for most classes, some can
-  simply declare the `serialize` annotation.
-
-* A limitation of the Json parser prevents deserializing from files
+* A limitation of the JSON parser prevents deserializing from files
   with more than one object.
   This could be improved in the future, but for now you should
-  serialize a single object to each filesand use different instances of
+  serialize a single object to each files and use different instances of
   serializer and deserializer each time.
 
-* The `serialize` annotation does not handle very well
-  complex constructors. This could be improved in the compiler.
-  For now, you may prefer to use `serialize` on simple classes,
-  of by using custom `Serializable`.
-
 * The serialization uses only the short name of a class, not its qualified name.
   This will cause problem when different classes using the same name.
   This could be solved partially in the compiler and the library.
@@ -283,7 +273,7 @@ The serialization has some limitations:
   the different programs sharing the serialized data.
 
 * The serialization support in the compiler need some help to
-  deal with generic types. The solution is to use `nitserial`,
+  deal with generic types. A solution is to use `nitserial`,
   the next section explores this subject.
 
 ## Dealing with generic types
index 435f8df..7ecc7d2 100644 (file)
@@ -97,8 +97,11 @@ abstract class Deserializer
 
        # Deserialize the attribute with `name` from the object open for deserialization
        #
+       # The `static_type` can be used as last resort if the deserialized object
+       # desn't have any metadata declaring the dynamic type.
+       #
        # Internal method to be implemented by the engines.
-       fun deserialize_attribute(name: String): nullable Object is abstract
+       fun deserialize_attribute(name: String, static_type: nullable String): nullable Object is abstract
 
        # Register a newly allocated object (even if not completely built)
        #
index cddc8d3..105b1fb 100644 (file)
@@ -510,7 +510,7 @@ endif
                self.toolcontext.info(command, 2)
 
                var res
-               if self.toolcontext.verbose_level >= 3 then
+               if self.toolcontext.verbose_level >= 3 or is_windows then
                        res = sys.system("{command} 2>&1")
                else
                        res = sys.system("{command} 2>&1 >/dev/null")
@@ -665,6 +665,9 @@ abstract class AbstractCompiler
                self.header.add_decl("  #include <libkern/OSByteOrder.h>")
                self.header.add_decl("  #define be32toh(x) OSSwapBigToHostInt32(x)")
                self.header.add_decl("#endif")
+               self.header.add_decl("#ifdef _WIN32")
+               self.header.add_decl("  #define be32toh(val) _byteswap_ulong(val)")
+               self.header.add_decl("#endif")
                self.header.add_decl("#ifdef __pnacl__")
                self.header.add_decl("  #define be16toh(val) (((val) >> 8) | ((val) << 8))")
                self.header.add_decl("  #define be32toh(val) ((be16toh((val) << 16) | (be16toh((val) >> 16))))")
@@ -878,11 +881,17 @@ extern void nitni_global_ref_decr( struct nitni_ref *ref );
                v.add_decl("\}")
 
                v.add_decl("void sig_handler(int signo)\{")
+               v.add_decl "#ifdef _WIN32"
+               v.add_decl "PRINT_ERROR(\"Caught signal : %s\\n\", signo);"
+               v.add_decl "#else"
                v.add_decl("PRINT_ERROR(\"Caught signal : %s\\n\", strsignal(signo));")
+               v.add_decl "#endif"
                v.add_decl("show_backtrace();")
                # rethrows
                v.add_decl("signal(signo, SIG_DFL);")
+               v.add_decl "#ifndef _WIN32"
                v.add_decl("kill(getpid(), signo);")
+               v.add_decl "#endif"
                v.add_decl("\}")
 
                v.add_decl("void fatal_exit(int status) \{")
@@ -908,7 +917,9 @@ extern void nitni_global_ref_decr( struct nitni_ref *ref );
                v.add("signal(SIGTERM, sig_handler);")
                v.add("signal(SIGSEGV, sig_handler);")
                v.add "#endif"
+               v.add "#ifndef _WIN32"
                v.add("signal(SIGPIPE, SIG_IGN);")
+               v.add "#endif"
 
                v.add("glob_argc = argc; glob_argv = argv;")
                v.add("catchStack.cursor = -1;")
index bb5cb8d..6633e13 100644 (file)
@@ -22,6 +22,7 @@ module serialization_phase
 private import parser_util
 import modelize
 private import annotation
+intrude import literal
 
 redef class ToolContext
 
@@ -34,9 +35,7 @@ redef class ToolContext
 
        # The second phase of the serialization
        var serialization_phase_post_model: Phase = new SerializationPhasePostModel(self,
-               [modelize_class_phase, serialization_phase_pre_model])
-
-       private fun place_holder_type_name: String do return "PlaceHolderTypeWhichShouldNotExist"
+               [modelize_property_phase, serialization_phase_pre_model])
 end
 
 redef class ANode
@@ -45,8 +44,6 @@ redef class ANode
 
        # Is this node annotated to not be made serializable?
        private fun is_noserialize: Bool do return false
-
-       private fun accept_precise_type_visitor(v: PreciseTypeVisitor) do visit_all(v)
 end
 
 redef class ADefinition
@@ -121,7 +118,7 @@ private class SerializationPhasePreModel
                if not node isa AModuledecl then
                        var up_serialize = false
                        var up: nullable ANode = node
-                       loop
+                       while up != null do
                                up = up.parent
                                if up == null then
                                        break
@@ -160,7 +157,7 @@ private class SerializationPhasePreModel
                        # Add services
                        var per_attribute = not serialize_by_default
                        generate_serialization_method(nclassdef, per_attribute)
-                       generate_deserialization_init(nclassdef, per_attribute)
+                       generate_deserialization_init(nclassdef)
                end
        end
 
@@ -170,18 +167,16 @@ private class SerializationPhasePreModel
                nmodule.inits_to_retype.clear
 
                # collect all classes
-               var auto_serializable_nclassdefs = new Array[AStdClassdef]
-               for nclassdef in nmodule.n_classdefs do
-                       if nclassdef isa AStdClassdef and nclassdef.how_serialize != null then
-                               auto_serializable_nclassdefs.add nclassdef
-                       end
-               end
-
+               var auto_serializable_nclassdefs = nmodule.auto_serializable_nclassdefs
                if not auto_serializable_nclassdefs.is_empty then
                        generate_deserialization_method(nmodule, auto_serializable_nclassdefs)
                end
        end
 
+       # Implement `core_serialize_to` on `nclassdef`
+       #
+       # Are attributes serialized on demand `per_attribute` with `serialize`?
+       # Otherwise they are serialized by default, and we check instead for `noserialize`.
        fun generate_serialization_method(nclassdef: AClassdef, per_attribute: Bool)
        do
                var npropdefs = nclassdef.n_propdefs
@@ -206,8 +201,10 @@ private class SerializationPhasePreModel
                npropdefs.push(toolcontext.parse_propdef(code.join("\n")))
        end
 
-       # Add a constructor to the automated nclassdef
-       fun generate_deserialization_init(nclassdef: AClassdef, per_attribute: Bool)
+       # Add an empty constructor to the automated nclassdef
+       #
+       # Will be filled by `SerializationPhasePostModel`.
+       fun generate_deserialization_init(nclassdef: AClassdef)
        do
                var npropdefs = nclassdef.n_propdefs
 
@@ -221,6 +218,75 @@ private class SerializationPhasePreModel
                        end
                end
 
+               var code = """
+redef init from_deserializer(v: Deserializer) do abort"""
+
+               var npropdef = toolcontext.parse_propdef(code).as(AMethPropdef)
+               npropdefs.add npropdef
+               nclassdef.parent.as(AModule).inits_to_retype.add npropdef
+       end
+
+       # Add an empty `Deserializer::deserialize_class_intern`
+       #
+       # Will be filled by `SerializationPhasePostModel`.
+       fun generate_deserialization_method(nmodule: AModule, nclassdefs: Array[AStdClassdef])
+       do
+               var code = new Array[String]
+
+               var deserializer_nclassdef = nmodule.deserializer_nclassdef
+               var deserializer_npropdef
+               if deserializer_nclassdef == null then
+                       # create the class
+                       code.add "redef class Deserializer"
+                       deserializer_npropdef = null
+               else
+                       deserializer_npropdef = deserializer_nclassdef.deserializer_npropdef
+               end
+
+               if deserializer_npropdef == null then
+                       # create the property
+                       code.add "      redef fun deserialize_class_intern(name) do abort"
+               else
+                       toolcontext.error(deserializer_npropdef.location, "Error: `Deserializer::deserialize_class_intern` is generated and must not be defined, use `deserialize_class` instead.")
+                       return
+               end
+
+               if deserializer_nclassdef == null then
+                       code.add "end"
+                       nmodule.n_classdefs.add toolcontext.parse_classdef(code.join("\n"))
+               else
+                       deserializer_nclassdef.n_propdefs.add(toolcontext.parse_propdef(code.join("\n")))
+               end
+       end
+end
+
+private class SerializationPhasePostModel
+       super Phase
+
+       # Fill the deserialization init `from_deserializer` and `Deserializer.deserialize_class_intern`
+       redef fun process_nmodule(nmodule)
+       do
+               for npropdef in nmodule.inits_to_retype do
+                       var nclassdef = npropdef.parent
+                       assert nclassdef isa AStdClassdef
+
+                       var serialize_by_default = nclassdef.how_serialize
+                       assert serialize_by_default != null
+
+                       var per_attribute = not serialize_by_default
+                       fill_deserialization_init(nclassdef, npropdef, per_attribute)
+               end
+
+               # collect all classes
+               var auto_serializable_nclassdefs = nmodule.auto_serializable_nclassdefs
+               if not auto_serializable_nclassdefs.is_empty then
+                       fill_deserialization_method(nmodule, auto_serializable_nclassdefs)
+               end
+       end
+
+       # Fill the constructor to the generated `init_npropdef` of `nclassdef`
+       fun fill_deserialization_init(nclassdef: AClassdef, init_npropdef: AMethPropdef, per_attribute: Bool)
+       do
                var code = new Array[String]
                code.add """
 redef init from_deserializer(v: Deserializer)
@@ -229,35 +295,28 @@ do
        v.notify_of_creation self
 """
 
-               for attribute in npropdefs do if attribute isa AAttrPropdef then
+               for attribute in nclassdef.n_propdefs do
+                       if not attribute isa AAttrPropdef then continue
 
                        # Is `attribute` to be skipped?
                        if (per_attribute and not attribute.is_serialize) or
                                attribute.is_noserialize then continue
 
-                       var n_type = attribute.n_type
-                       var type_name
-                       var type_name_pretty
-                       if n_type == null then
-                               # Use a place holder, we will replace it with the inferred type after the model phases
-                               type_name = toolcontext.place_holder_type_name
-                               type_name_pretty = "Unknown type"
-                       else
-                               type_name = n_type.type_name
-                               type_name_pretty = type_name
-                       end
+                       var mtype = attribute.mtype
+                       if mtype == null then continue
+                       var type_name = mtype.to_s
                        var name = attribute.name
 
                        if type_name == "nullable Object" then
                                # Don't type check
                                code.add """
-       var {{{name}}} = v.deserialize_attribute("{{{attribute.serialize_name}}}")
+       var {{{name}}} = v.deserialize_attribute("{{{attribute.serialize_name}}}", "{{{type_name}}}")
 """
                        else code.add """
-       var {{{name}}} = v.deserialize_attribute("{{{attribute.serialize_name}}}")
+       var {{{name}}} = v.deserialize_attribute("{{{attribute.serialize_name}}}", "{{{type_name}}}")
        if not {{{name}}} isa {{{type_name}}} then
                # Check if it was a subjectent error
-               v.errors.add new AttributeTypeError(self, "{{{attribute.serialize_name}}}", {{{name}}}, "{{{type_name_pretty}}}")
+               v.errors.add new AttributeTypeError(self, "{{{attribute.serialize_name}}}", {{{name}}}, "{{{type_name}}}")
 
                # Clear subjacent error
                if v.keep_going == false then return
@@ -269,94 +328,93 @@ do
 
                code.add "end"
 
+               # Replace the body of the constructor
                var npropdef = toolcontext.parse_propdef(code.join("\n")).as(AMethPropdef)
-               npropdefs.add npropdef
-               nclassdef.parent.as(AModule).inits_to_retype.add npropdef
+               init_npropdef.n_block = npropdef.n_block
+
+               # Run the literal phase on the generated code
+               var v = new LiteralVisitor(toolcontext)
+               v.enter_visit(npropdef.n_block)
        end
 
-       # Added to the abstract serialization service
-       fun generate_deserialization_method(nmodule: AModule, nclassdefs: Array[AStdClassdef])
+       # Fill the abstract serialization service
+       fun fill_deserialization_method(nmodule: AModule, nclassdefs: Array[AStdClassdef])
        do
-               var code = new Array[String]
-
                var deserializer_nclassdef = nmodule.deserializer_nclassdef
-               var deserializer_npropdef
-               if deserializer_nclassdef == null then
-                       # create the class
-                       code.add "redef class Deserializer"
-                       deserializer_npropdef = null
-               else
-                       deserializer_npropdef = deserializer_nclassdef.deserializer_npropdef
-               end
+               if deserializer_nclassdef == null then return
+               var deserializer_npropdef = deserializer_nclassdef.deserializer_npropdef
+               if deserializer_npropdef == null then return
 
-               if deserializer_npropdef == null then
-                       # create the property
-                       code.add "      redef fun deserialize_class_intern(name)"
-                       code.add "      do"
-               else
-                       toolcontext.error(deserializer_npropdef.location, "Error: `Deserializer::deserialize_class_intern` is generated and must not be defined, use `deserialize_class` instead.")
-                       return
-               end
+               # Collect local types expected to be deserialized
+               var types_to_deserialize = new Set[String]
 
+               ## Local serializable standard class without parameters
                for nclassdef in nclassdefs do
-                       var name = nclassdef.n_qid.n_id.text
-                       if nclassdef.n_formaldefs.is_empty and
-                          nclassdef.n_classkind isa AConcreteClasskind then
+                       var mclass = nclassdef.mclass
+                       if mclass == null then continue
 
-                               code.add "              if name == \"{name}\" then return new {name}.from_deserializer(self)"
+                       if mclass.arity == 0 and mclass.kind == concrete_kind then
+                               types_to_deserialize.add mclass.name
                        end
                end
 
-               code.add "              return super"
-               code.add "      end"
+               ## Static parametized types on serializable attributes
+               for nclassdef in nmodule.n_classdefs do
+                       if not nclassdef isa AStdClassdef then continue
 
-               if deserializer_nclassdef == null then
-                       code.add "end"
-                       nmodule.n_classdefs.add toolcontext.parse_classdef(code.join("\n"))
-               else
-                       deserializer_nclassdef.n_propdefs.add(toolcontext.parse_propdef(code.join("\n")))
-               end
-       end
-end
+                       for attribute in nclassdef.n_propdefs do
+                               if not attribute isa AAttrPropdef then continue
 
-private class SerializationPhasePostModel
-       super Phase
+                               var serialize_by_default = nclassdef.how_serialize
+                               if serialize_by_default == null then continue
+                               var per_attribute = not serialize_by_default
 
-       redef fun process_nmodule(nmodule)
-       do
-               for npropdef in nmodule.inits_to_retype do
-                       var mpropdef = npropdef.mpropdef
-                       if mpropdef == null then continue # skip error
-                       var v = new PreciseTypeVisitor(npropdef, mpropdef.mclassdef, toolcontext)
-                       npropdef.accept_precise_type_visitor v
-               end
-       end
-end
+                               # Is `attribute` to be skipped?
+                               if (per_attribute and not attribute.is_serialize) or
+                                       attribute.is_noserialize then continue
 
-# Visitor on generated constructors to replace the expected type of deserialized attributes
-private class PreciseTypeVisitor
-       super Visitor
+                               var mtype = attribute.mtype
+                               if mtype == null then continue
+                               if mtype isa MNullableType then mtype = mtype.mtype
 
-       var npropdef: AMethPropdef
-       var mclassdef: MClassDef
-       var toolcontext: ToolContext
+                               if mtype isa MClassType and mtype.mclass.arity > 0 and
+                                  mtype.mclass.kind == concrete_kind and not mtype.need_anchor then
 
-       redef fun visit(n) do n.accept_precise_type_visitor(self)
-end
+                                       # Check is a `Serializable`
+                                       var mmodule = nmodule.mmodule
+                                       if mmodule == null then continue
 
-redef class AIsaExpr
-       redef fun accept_precise_type_visitor(v)
-       do
-               if n_type.collect_text != v.toolcontext.place_holder_type_name then return
-
-               var attr_name = "_" + n_expr.collect_text
-               for mattrdef in v.mclassdef.mpropdefs do
-                       if mattrdef isa MAttributeDef and mattrdef.name == attr_name then
-                               var new_ntype = v.toolcontext.parse_something(mattrdef.static_mtype.to_s)
-                               n_type.replace_with new_ntype
-                               break
+                                       var greaters = mtype.mclass.in_hierarchy(mmodule).greaters
+                                       var is_serializable = false
+                                       for sup in greaters do if sup.name == "Serializable" then
+                                               is_serializable = true
+                                               break
+                                       end
+
+                                       if is_serializable then types_to_deserialize.add mtype.to_s
+                               end
                        end
                end
+
+               # Build implementation code
+               var code = new Array[String]
+               code.add "redef fun deserialize_class_intern(name)"
+               code.add "do"
+
+               for name in types_to_deserialize do
+                       code.add "      if name == \"{name}\" then return new {name}.from_deserializer(self)"
+               end
+
+               code.add "      return super"
+               code.add "end"
+
+               # Replace the body of the constructor
+               var npropdef = toolcontext.parse_propdef(code.join("\n")).as(AMethPropdef)
+               deserializer_npropdef.n_block = npropdef.n_block
+
+               # Run the literal phase on the generated code
+               var v = new LiteralVisitor(toolcontext)
+               v.enter_visit(npropdef.n_block)
        end
 end
 
@@ -387,9 +445,9 @@ redef class AModule
        private fun deserializer_nclassdef: nullable AStdClassdef
        do
                for nclassdef in n_classdefs do
-                       if nclassdef isa AStdClassdef and nclassdef.n_qid.n_id.text == "Deserializer" then
-                               return nclassdef
-                       end
+                       if not nclassdef isa AStdClassdef then continue
+                       var n_qid = nclassdef.n_qid
+                       if n_qid != null and n_qid.n_id.text == "Deserializer" then return nclassdef
                end
 
                return null
@@ -397,7 +455,22 @@ redef class AModule
 
        private var inits_to_retype = new Array[AMethPropdef]
 
-       redef fun is_serialize do return n_moduledecl != null and n_moduledecl.is_serialize
+       redef fun is_serialize
+       do
+               var n_moduledecl = n_moduledecl
+               return n_moduledecl != null and n_moduledecl.is_serialize
+       end
+
+       # `AStdClassdef` marked as serializable, itself or one of theur attribute
+       private var auto_serializable_nclassdefs: Array[AStdClassdef] is lazy do
+               var array = new Array[AStdClassdef]
+               for nclassdef in n_classdefs do
+                       if nclassdef isa AStdClassdef and nclassdef.how_serialize != null then
+                               array.add nclassdef
+                       end
+               end
+               return array
+       end
 end
 
 redef class AStdClassdef
index 730ae5c..f2e2aae 100644 (file)
@@ -20,7 +20,7 @@
 <D: <B: <A: false b 123.123 2345 new line ->
 <- false p4ssw0rd> 1111        f"\r\/> true>
 
-Deserialization Error: Doesn't know how to deserialize class "Array", Deserialization Error: Wrong type on `E::a` expected `Unknown type`, got `null`, Deserialization Error: Doesn't know how to deserialize class "Array", Deserialization Error: Wrong type on `E::b` expected `Unknown type`, got `null`
+Deserialization Error: Doesn't know how to deserialize class "Array", Deserialization Error: Wrong type on `E::a` expected `Array[Object]`, got `null`, Deserialization Error: Doesn't know how to deserialize class "Array", Deserialization Error: Wrong type on `E::b` expected `Array[nullable Serializable]`, got `null`
 # Src:
 <E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
 # Dst:
@@ -28,7 +28,7 @@ Deserialization Error: Doesn't know how to deserialize class "Array", Deserializ
 
 Deserialization Error: Doesn't know how to deserialize class "F"
 Deserialization Error: Doesn't know how to deserialize class "F"
-Deserialization Error: Doesn't know how to deserialize class "HashSet", Deserialization Error: Wrong type on `G::hs` expected `Unknown type`, got `null`, Deserialization Error: Doesn't know how to deserialize class "ArraySet", Deserialization Error: Wrong type on `G::s` expected `Set[String]`, got `null`, Deserialization Error: Doesn't know how to deserialize class "HashMap", Deserialization Error: Wrong type on `G::hm` expected `Unknown type`, got `null`, Deserialization Error: Doesn't know how to deserialize class "ArrayMap", Deserialization Error: Wrong type on `G::am` expected `Unknown type`, got `null`
+Deserialization Error: Doesn't know how to deserialize class "HashSet", Deserialization Error: Wrong type on `G::hs` expected `HashSet[Int]`, got `null`, Deserialization Error: Doesn't know how to deserialize class "ArraySet", Deserialization Error: Wrong type on `G::s` expected `Set[String]`, got `null`, Deserialization Error: Doesn't know how to deserialize class "HashMap", Deserialization Error: Wrong type on `G::hm` expected `HashMap[String, Int]`, got `null`, Deserialization Error: Doesn't know how to deserialize class "ArrayMap", Deserialization Error: Wrong type on `G::am` expected `ArrayMap[String, String]`, got `null`
 # Src:
 <G: hs: -1, 0; s: one, two; hm: one. 1, two. 2; am: three. 3, four. 4>
 # Dst:
index 9c63bff..cb74989 100644 (file)
@@ -46,9 +46,9 @@
 <E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
 
 Deserialization Error: Doesn't know how to deserialize class "Array"
-Deserialization Error: Wrong type on `E::a` expected `Unknown type`, got `null`
+Deserialization Error: Wrong type on `E::a` expected `Array[Object]`, got `null`
 Deserialization Error: Doesn't know how to deserialize class "Array"
-Deserialization Error: Wrong type on `E::b` expected `Unknown type`, got `null`
+Deserialization Error: Wrong type on `E::b` expected `Array[nullable Serializable]`, got `null`
 # Nit:
 <E: 2222>
 
@@ -79,10 +79,10 @@ Deserialization Error: Doesn't know how to deserialize class "F"
 <G: hs: ; s: ; hm: ; am: >
 
 Deserialization Error: Doesn't know how to deserialize class "HashSet"
-Deserialization Error: Wrong type on `G::hs` expected `Unknown type`, got `null`
+Deserialization Error: Wrong type on `G::hs` expected `HashSet[Int]`, got `null`
 Deserialization Error: Doesn't know how to deserialize class "ArraySet"
 Deserialization Error: Wrong type on `G::s` expected `Set[String]`, got `null`
 Deserialization Error: Doesn't know how to deserialize class "HashMap"
-Deserialization Error: Wrong type on `G::hm` expected `Unknown type`, got `null`
+Deserialization Error: Wrong type on `G::hm` expected `HashMap[String, Int]`, got `null`
 Deserialization Error: Doesn't know how to deserialize class "ArrayMap"
-Deserialization Error: Wrong type on `G::am` expected `Unknown type`, got `null`
+Deserialization Error: Wrong type on `G::am` expected `ArrayMap[String, String]`, got `null`
index fe41ba6..3a3aec0 100644 (file)
 <E: a: hello, 1234, 123.4; b: hella, 2345, 234.5>
 
 Deserialization Error: Doesn't know how to deserialize class "Array"
-Deserialization Error: Wrong type on `E::a` expected `Unknown type`, got `null`
+Deserialization Error: Wrong type on `E::a` expected `Array[Object]`, got `null`
 Deserialization Error: Doesn't know how to deserialize class "Array"
-Deserialization Error: Wrong type on `E::b` expected `Unknown type`, got `null`
+Deserialization Error: Wrong type on `E::b` expected `Array[nullable Serializable]`, got `null`
 # Nit:
 <E: 2222>
 
@@ -197,10 +197,10 @@ Deserialization Error: Doesn't know how to deserialize class "F"
 <G: hs: ; s: ; hm: ; am: >
 
 Deserialization Error: Doesn't know how to deserialize class "HashSet"
-Deserialization Error: Wrong type on `G::hs` expected `Unknown type`, got `null`
+Deserialization Error: Wrong type on `G::hs` expected `HashSet[Int]`, got `null`
 Deserialization Error: Doesn't know how to deserialize class "ArraySet"
 Deserialization Error: Wrong type on `G::s` expected `Set[String]`, got `null`
 Deserialization Error: Doesn't know how to deserialize class "HashMap"
-Deserialization Error: Wrong type on `G::hm` expected `Unknown type`, got `null`
+Deserialization Error: Wrong type on `G::hm` expected `HashMap[String, Int]`, got `null`
 Deserialization Error: Doesn't know how to deserialize class "ArrayMap"
-Deserialization Error: Wrong type on `G::am` expected `Unknown type`, got `null`
+Deserialization Error: Wrong type on `G::am` expected `ArrayMap[String, String]`, got `null`
index a43ae0d..5662865 100644 (file)
@@ -16,7 +16,7 @@ Object [
 ]
 
 Sys [
- label = "{Sys||+ main()\l+ run()\l+ errno(): Int\l+ exit(exit_value: Int)\l}"
+ label = "{Sys||+ main()\l+ run()\l+ errno(): Int\l+ exit(exit_value: Int)\l+ is_windows(): Bool\l}"
 ]
 Object -> Sys [dir=back arrowtail=open style=dashed];
 
index 4bd5666..e6a1e8c 100644 (file)
@@ -16,7 +16,7 @@ Object [
 ]
 
 Sys [
- label = "{Sys||+ main()\l+ run()\l+ errno(): Int\l+ exit(exit_value: Int)\l}"
+ label = "{Sys||+ main()\l+ run()\l+ errno(): Int\l+ exit(exit_value: Int)\l+ is_windows(): Bool\l}"
 ]
 Object -> Sys [dir=back arrowtail=open style=dashed];
 
diff --git a/wallet b/wallet
deleted file mode 100755 (executable)
index 567234d..0000000
Binary files a/wallet and /dev/null differ