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
nitcc
calc
minilang
+blob
--- /dev/null
+# 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
--- /dev/null
+/* 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;
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"
${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
*.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
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
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
#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" `{
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];
}
return result;
+#endif
`}
end
# 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
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) {
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
#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
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
# 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
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
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 `{
`}
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); `}
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
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
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;
}
else if ( result < 0 )
fprintf( stderr, "Error in Stream:poll: %s\n", strerror( errno ) );
+#endif
return null_Int();
`}
# 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
+`}
#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))
#}"""
# ~~~
#
-# ## 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
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
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
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
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
# 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
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
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")
# ~~~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
#
# ~~~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
#
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
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
col = 1
end
end
- return res
end
end
# 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
# 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
#
# ~~~
# 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
# 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:
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)
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.
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.
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.
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
# 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)
#
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")
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))))")
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) \{")
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;")
private import parser_util
import modelize
private import annotation
+intrude import literal
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
# 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
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
# 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
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
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
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)
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
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
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
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
<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:
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:
<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>
<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`
<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>
<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`
]
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];
]
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];