Merge: Nitiwiki: a quick wiki builder based on markdown files
authorJean Privat <jean@pryen.org>
Tue, 25 Nov 2014 01:05:56 +0000 (20:05 -0500)
committerJean Privat <jean@pryen.org>
Tue, 25 Nov 2014 01:05:56 +0000 (20:05 -0500)
Introduces `nitiwiki` a contrib tool that generate an html wiki structure based on markdown files.
Behavior is very similar to ikiwiki (but simplified).

Features:

* automatic wiki structure from folders hierarchy
* automatic site menu
* automatic sitemap
* automatic summaries
* easy and rapid templating
* customizable section templates and menus
* rsync synchronization
* git synchronization

See http://moz-code.org/nitiwiki/ for online documentation. This is actually the `README.md` file rendered by nitiwiki. The full wiki can be found in `contrib/examples/nitiwiki`

Another example can be found at http://moz-code.org/uqam/. This is my TA site, entirely based on nitiwiki.

Pull-Request: #765
Reviewed-by: Jean Privat <jean@pryen.org>
Reviewed-by: Alexis Laferrière <alexis.laf@xymus.net>
Reviewed-by: Lucas Bajolet <r4pass@hotmail.com>

78 files changed:
benchmarks/strings/array_to_s_vars/array_to_s_manual.nit
contrib/github_merge.nit
contrib/github_search_for_jni/src/github_search_for_jni.nit
contrib/nitcc/examples/json.sablecc
examples/rosettacode/ab.nit
lib/android/assets_and_resources.nit
lib/android/audio.nit
lib/android/bundle/bundle.nit
lib/android/intent/intent_api10.nit
lib/android/platform.nit
lib/android/shared_preferences/shared_preferences_api10.nit
lib/android/vibration.nit
lib/app/data_store.nit
lib/c.nit
lib/counter.nit
lib/dummy_array.nit
lib/github_api.nit
lib/hash_debug.nit
lib/java/io.nit
lib/java/java.nit
lib/json/dynamic.nit
lib/json/error.nit [new file with mode: 0644]
lib/json/json.nit
lib/json/json_lexer.nit
lib/json/static.nit
lib/json_serialization.nit
lib/markdown/markdown.nit
lib/markdown/test_markdown.nit
lib/mnit_android/android_app.nit
lib/neo4j/curl_json.nit
lib/neo4j/error.nit [new file with mode: 0644]
lib/neo4j/json_store.nit
lib/neo4j/jsonable.nit [deleted file]
lib/neo4j/neo4j.nit
lib/pthreads/extra.nit [new file with mode: 0644]
lib/pthreads/pthreads.nit
lib/standard/collection/array.nit
lib/standard/collection/hash_collection.nit
lib/standard/kernel.nit
lib/standard/math.nit
lib/standard/string.nit
lib/string_experimentations/utf8.nit
lib/string_experimentations/utf8_noindex.nit
lib/trees/abstract_tree.nit
lib/trees/bintree.nit
lib/trees/rbtree.nit
src/compiler/android_annotations.nit
src/compiler/android_platform.nit
src/compiler/global_compiler.nit
src/doc/doc_pages.nit
src/interpreter/naive_interpreter.nit
src/modelize/modelize_property.nit
tests/base_self_type.nit [new file with mode: 0644]
tests/sav/ab.res
tests/sav/base_self_type.res [new file with mode: 0644]
tests/sav/base_self_type_alt1.res [new file with mode: 0644]
tests/sav/base_self_type_alt2.res [new file with mode: 0644]
tests/sav/base_self_type_alt3.res [new file with mode: 0644]
tests/sav/error_class_glob.res
tests/sav/nitg-e/base_self_type.res [new file with mode: 0644]
tests/sav/nitg-e/base_self_type_alt2.res [new file with mode: 0644]
tests/sav/nitg-e/fixme/base_gen_reassign_alt4.res
tests/sav/nitg-e/fixme/base_gen_reassign_alt5.res
tests/sav/nitg-e/fixme/base_gen_reassign_alt6.res
tests/sav/nitserial_args1.res
tests/sav/nituml_args3.res
tests/sav/nituml_args4.res
tests/sav/test_c_alt1.res
tests/sav/test_c_alt2.res
tests/sav/test_c_alt3.res
tests/sav/test_c_alt4.res
tests/sav/test_json_static.res
tests/sav/test_new_native_alt1.res
tests/test_gen.nit
tests/test_json_static.nit
tests/test_native_array.nit
tests/test_neo4j.nit
tests/test_neo4j_batch.nit

index 843eb93..da6b667 100644 (file)
@@ -21,8 +21,6 @@ redef class NativeArray[E]
 end
 
 redef class Array[E]
-       super StringCapable
-
        redef fun to_s: String do
                var l = length
                var its = _items
@@ -35,7 +33,7 @@ redef class Array[E]
                        na[i] = tmp
                        i += 1
                end
-               var ns = calloc_string(sl + 1)
+               var ns = new NativeString(sl + 1)
                ns[sl] = '\0'
                i = 0
                var off = 0
index c30732f..0d5e052 100644 (file)
@@ -20,14 +20,14 @@ import template
 
 redef class Object
        # Factorize cast
-       fun json_as_a: Array[nullable Object] do return self.as(Array[nullable Object])
+       fun json_as_a: JsonArray do return self.as(JsonArray)
        # Factorize cast
-       fun json_as_map: Map[String, nullable Object] do return self.as(Map[String, nullable Object])
+       fun json_as_map: JsonObject do return self.as(JsonObject)
 end
 
 redef class GithubCurl
        # Get a given pull request (PR)
-       fun getpr(number: Int): Map[String, nullable Object]
+       fun getpr(number: Int): JsonObject
        do
                var pr = get_and_check("https://api.github.com/repos/privat/nit/pulls/{number}")
                var prm = pr.json_as_map
@@ -46,7 +46,7 @@ redef class GithubCurl
        end
 
        # Get reviewers of a PR
-       fun getrev(pr: Map[String, nullable Object]): Array[String]
+       fun getrev(pr: JsonObject): Array[String]
        do
                var number = pr["number"].as(Int)
                var user = pr["user"].json_as_map["login"].as(String)
index d12f288..35b19b2 100644 (file)
@@ -19,13 +19,12 @@ module github_search_for_jni
 
 import github_api
 
-# The proprieties introduced by this redef are to be used only on HashMap
+# The proprieties introduced by this redef are to be used only on a JSON object
 # representing a Github repository.
-redef class HashMap[K, V]
+redef class JsonObject
        # The repository has at least 50% Java code
        fun has_lots_of_java: Bool
        do
-               assert self isa HashMap[String, nullable Object]
                var java_count = 0
                if keys.has("Java") then java_count = self["Java"].as(Int)
 
@@ -43,7 +42,6 @@ redef class HashMap[K, V]
        # The repository has at least 100 lines of C code
        fun has_some_c: Bool
        do
-               assert self isa HashMap[String, nullable Object]
                var c_count = 0
                if keys.has("C") then c_count = self["C"].as(Int)
                return c_count > 100
@@ -64,14 +62,14 @@ var per_page = 100
 loop
        # Get a page of the main query
        var uri = "https://api.github.com/search/repositories?q={main_query}&page={page}&per_page={per_page}&sort=stars"
-       var obj = curl.get_and_check(uri).as(HashMap[String, nullable Object])
+       var obj = curl.get_and_check(uri).as(JsonObject)
 
        # Main object has "total_count" and "items"
-       var items = obj["items"].as(Array[nullable Object])
+       var items = obj["items"].as(JsonArray)
 
        # "items" is an array of Json objects
        for item in items do
-               assert item isa HashMap[String, nullable Object]
+               assert item isa JsonObject
 
                # Each item has "name" and "languages_url"
                assert item.keys.has("name")
@@ -79,7 +77,7 @@ loop
 
                # Download the language list
                var lang_url = item["languages_url"].as(String)
-               var langs = curl.get_and_check(lang_url).as(HashMap[String, nullable Object])
+               var langs = curl.get_and_check(lang_url).as(JsonObject)
 
                # The project is of interest if it has lots of Java and at least some C
                var may_be_of_interest = langs.has_lots_of_java and langs.has_some_c
index d1e9c6b..0de8914 100644 (file)
@@ -9,7 +9,18 @@ frac = '.' d+;
 exp = e d+;
 e = ('e'|'E') ('+'|'-')?;
 
-string = '"' (Any-'\\'-'"' | '\\'Any)* '"';
+hexdig = '0'..'9' | 'a'..'z' | 'A'..'Z';
+string = '"' (Any - '\\' - '"' | '\\' (
+               '\\' |
+               '"' |
+               '/' |
+               'b' |
+               'f' |
+               'n' |
+               'r' |
+               't' |
+               'u' hexdig hexdig hexdig hexdig
+       ))* '"';
 
 blank = (' '|'\n'|'\t')+;
 
index 9138c0c..ce18fc0 100644 (file)
@@ -10,4 +10,8 @@
 module ab
 
 var words = gets.split(" ")
+if words.length != 2 then
+       print "Expected two numbers"
+       return
+end
 print words[0].to_i + words[1].to_i
index 9470dd8..82ae3d1 100644 (file)
@@ -46,7 +46,6 @@ in "Java" `{
 # This is a low-level class, use `AssetManager` instead
 extern class NativeAssetManager in "Java" `{ android.content.res.AssetManager `}
        super JavaObject
-       redef type SELF: NativeAssetManager
 
        fun close in "Java" `{ recv.close(); `}
 
@@ -172,7 +171,6 @@ end
 # This is a low-level class, use `ResourcesManager` instead
 extern class NativeResources in "Java" `{ android.content.res.Resources `}
        super JavaObject
-       redef type SELF: NativeResources
 
        fun get_assets:NativeAssetManager in "Java" `{ return recv.getAssets(); `}
        fun get_color(id: Int): Int in "Java" `{ return recv.getColor((int)id); `}
@@ -283,7 +281,6 @@ end
 # An android Bitmap, get an instance using the AssetManager or the ResourceManager
 extern class NativeBitmap in "Java" `{ android.graphics.Bitmap `}
        super JavaObject
-       redef type SELF: NativeBitmap
 
        # Create a NativeBitmap from a NativeInputStream retrieved with `open` function of the AssetManager
        # Called by the AssetManager
@@ -299,7 +296,6 @@ end
 # Android AssetFileDescriptor, can be retrieve by AssetManager and used to load a sound in a SoundPool
 extern class NativeAssetFileDescriptor in "Java" `{ android.content.res.AssetFileDescriptor `}
        super JavaObject
-       redef type SELF: NativeAssetFileDescriptor
 
        fun close in "Java" `{
                try {
index e640621..366c7d4 100644 (file)
@@ -38,7 +38,6 @@ in "Java" `{
 # AudioManager of the application, used to manage the audio mode
 extern class NativeAudioManager in "Java" `{ android.media.AudioManager `}
        super JavaObject
-       redef type SELF: NativeAudioManager
 
        fun mode: Int in "Java" `{ return recv.getMode(); `}
        fun mode=(i: Int) in "Java" `{ recv.setMode((int)i); `}
@@ -61,7 +60,6 @@ end
 # This is a low-level class, use `MediaPlater` instead
 extern class NativeMediaPlayer in "Java" `{ android.media.MediaPlayer `}
        super JavaObject
-       redef type SELF: NativeMediaPlayer
 
        new in "Java" `{ return new MediaPlayer(); `}
        fun start in "Java" `{ recv.start(); `}
@@ -108,7 +106,6 @@ end
 # This is a low-level class, use `SoundPool`instead
 extern class NativeSoundPool in "Java" `{ android.media.SoundPool `}
        super JavaObject
-       redef type SELF: NativeSoundPool
 
        new(max_streams, stream_type, src_quality: Int) in "Java" `{
                return new SoundPool((int)max_streams, (int)stream_type, (int)src_quality);
index 9528e05..5a083d2 100644 (file)
@@ -31,7 +31,6 @@ in "Java" `{
 
 extern class NativeBundle in "Java" `{ android.os.Bundle `}
        super JavaObject
-       redef type SELF: NativeBundle
 
        fun clone: JavaObject in "Java" `{ return recv.clone(); `}
        fun size: Int in "Java" `{ return recv.size(); `}
index d07a817..20c85dc 100644 (file)
@@ -33,7 +33,6 @@ in "Java" `{
 
 extern class NativeIntent in "Java" `{ android.content.Intent `}
        super JavaObject
-       redef type SELF: NativeIntent
 
        fun add_category(category: JavaString) in "Java" `{ recv.addCategory(category); `}
        fun add_flags(flags: Int) in "Java" `{ recv.addFlags((int)flags); `}
index dab001d..fca03e0 100644 (file)
@@ -21,6 +21,8 @@ module platform is
        new_annotation max_api_version
        new_annotation target_api_version
        new_annotation android_manifest
+       new_annotation android_manifest_application
+       new_annotation android_manifest_activity
 end
 
 import java
index 8635f86..404aaea 100644 (file)
@@ -33,7 +33,6 @@ in "Java" `{
 
 extern class NativeSharedPreferences in "Java" `{ android.content.SharedPreferences `}
        super JavaObject
-       redef type SELF: NativeSharedPreferences
        
        fun contains(key: JavaString): Bool in "Java" `{ return recv.contains(key); `}
        fun get_all: HashMap[JavaString, JavaObject] import HashMap[JavaString, JavaObject],
@@ -113,7 +112,6 @@ end
 
 extern class NativeSharedPreferencesEditor in "Java" `{ android.content.SharedPreferences$Editor `}
        super JavaObject
-       redef type SELF: NativeSharedPreferencesEditor
 
        fun clear: NativeSharedPreferencesEditor in "Java" `{ return recv.clear(); `}
        fun commit: Bool in "Java" `{ return recv.commit(); `}
index 6226285..f56dbef 100644 (file)
@@ -26,7 +26,6 @@ import native_app_glue
 # Handle to an Android vibrator
 extern class Vibrator in "Java" `{ android.os.Vibrator `}
        super JavaObject
-       redef type SELF: Vibrator
 
        # Vibrate for `n` miliseconds
        fun vibrate(n: Int) in "Java" `{ recv.vibrate(n); `}
index afb6a1d..13891fc 100644 (file)
@@ -34,5 +34,5 @@ interface DataStore
        fun [](key: String): nullable Object is abstract
 
        # Store `value` at `key`
-       fun []=(key: String, value: Serializable) is abstract
+       fun []=(key: String, value: nullable Serializable) is abstract
 end
index fdc7e99..6c843f3 100644 (file)
--- a/lib/c.nit
+++ b/lib/c.nit
@@ -29,7 +29,6 @@ end
 # A native C array, as in a pointer to the first element of the array
 extern class NativeCArray `{ void * `}
        type E: nullable Object
-       type SELF: NativeCArray
 
        fun [](index: E): E is abstract
        fun []=(index: E, val: E) is abstract
@@ -79,7 +78,6 @@ end
 extern class NativeCIntArray `{ int* `}
        super NativeCArray
        redef type E: Int
-       redef type SELF: NativeCIntArray
 
        new(size: Int) `{ return calloc(size, sizeof(int)); `}
        redef fun [](index) `{ return recv[index]; `}
@@ -91,7 +89,6 @@ end
 redef class NativeString
        super NativeCArray
        redef type E: Char
-       redef type SELF: NativeString
 
        redef fun +(offset) `{ return recv + offset; `}
 end
index 670fc59..3d1555b 100644 (file)
@@ -19,10 +19,33 @@ import poset
 
 # A counter counts occurrences of things
 # Use this instead of a `HashMap[E, Int]`
+#
+# ~~~
+# var c = new Counter[String].from(["a", "a", "b", "b", "b", "c"])
+# assert c["a"]   == 2
+# assert c["b"]   == 3
+# assert c["c"]   == 1
+# assert c["d"]   == 0
+# ~~~
+#
+# The counter class can also be used to gather statistical informations.
+#
+# ~~~~
+# assert c.length == 3   # because 3 distinct values
+# assert c.max    == "b" # because "b" has the most count (3)
+# assert c.avg    == 2.0 # because it is the mean of the counts
+# ~~~~
 class Counter[E: Object]
        super Map[E, Int]
 
        # Total number of counted occurrences
+       #
+       # ~~~
+       # var c = new Counter[String]
+       # assert c.sum == 0
+       # c.inc_all(["a", "a", "b", "b", "b", "c"])
+       # assert c.sum == 6
+       # ~~~
        var sum: Int = 0
 
        private var map = new HashMap[E, Int]
@@ -64,7 +87,24 @@ class Counter[E: Object]
                sum += 1
        end
 
+       # Count one more for each element of `es`
+       fun inc_all(es: Collection[E])
+       do
+               for e in es do inc(e)
+       end
+
+       # A new Counter initialized with `inc_all`.
+       init from(es: Collection[E])
+       do
+               inc_all(es)
+       end
+
        # Return an array of elements sorted by occurrences
+       #
+       # ~~~
+       # var c = new Counter[String].from(["a", "a", "b", "b", "b", "c"])
+       # assert c.sort == ["c", "a", "b"]
+       # ~~~
        fun sort: Array[E]
        do
                var res = map.keys.to_a
@@ -77,7 +117,7 @@ class Counter[E: Object]
        # @toimplement by default just call `to_s` on the element
        protected fun element_to_s(e: E): String
        do
-               do return e.to_s
+               return e.to_s
        end
 
        # Display statistical information
@@ -130,7 +170,14 @@ class Counter[E: Object]
                end
        end
 
-       # Return the element with the highest value
+       # Return the element with the highest value (aka. the mode)
+       #
+       # ~~~
+       # var c = new Counter[String].from(["a", "a", "b", "b", "b", "c"])
+       # assert c.max == "b"
+       # ~~~
+       #
+       # If more than one max exists, the first one is returned.
        fun max: nullable E do
                var max: nullable Int = null
                var elem: nullable E = null
@@ -144,6 +191,13 @@ class Counter[E: Object]
        end
 
        # Return the couple with the lowest value
+       #
+       # ~~~
+       # var c = new Counter[String].from(["a", "a", "b", "b", "b", "c"])
+       # assert c.min == "c"
+       # ~~~
+       #
+       # If more than one min exists, the first one is returned.
        fun min: nullable E do
                var min: nullable Int = null
                var elem: nullable E = null
@@ -156,13 +210,24 @@ class Counter[E: Object]
                return elem
        end
 
-       # Values average
+       # Values average (aka. arithmetic mean)
+       #
+       # ~~~
+       # var c = new Counter[String].from(["a", "a", "b", "b", "b", "c"])
+       # assert c.avg == 2.0
+       # ~~~
        fun avg: Float do
                if values.is_empty then return 0.0
                return (sum / values.length).to_f
        end
 
        # The standard derivation of the counter values
+       #
+       # ~~~
+       # var c = new Counter[String].from(["a", "a", "b", "b", "b", "c"])
+       # assert c.std_dev > 0.81
+       # assert c.std_dev < 0.82
+       # ~~~
        fun std_dev: Float do
                var avg = self.avg
                var sum = 0.0
index 21da4bd..88e7008 100644 (file)
@@ -12,7 +12,6 @@
 
 class DummyArray
        super Set[Int]
-       super ArrayCapable[Int]
        private var capacity: Int
        redef var length: Int
        private var keys: NativeArray[Int]
@@ -79,8 +78,8 @@ class DummyArray
        init(capacity: Int)
        do
                _capacity = capacity
-               _keys = calloc_array(capacity)
-               _values = calloc_array(capacity)
+               _keys = new NativeArray[Int](capacity)
+               _values = new NativeArray[Int](capacity)
        end
 end
 
index 2394592..39fca84 100644 (file)
@@ -45,7 +45,7 @@ class GithubCurl
 
        # Get the requested URI, and check the HTTP response. Then convert to JSON
        # and check for Github errors.
-       fun get_and_check(uri: String): nullable Object
+       fun get_and_check(uri: String): nullable Jsonable
        do
                var request = new CurlHTTPRequest(uri, self)
                request.user_agent = user_agent
@@ -53,8 +53,8 @@ class GithubCurl
                var response = request.execute
 
                if response isa CurlResponseSuccess then
-                       var obj = response.body_str.json_to_nit_object
-                       if obj isa HashMap[String, nullable Object] then
+                       var obj = response.body_str.parse_json
+                       if obj isa JsonObject then
                                if obj.keys.has("message") then
                                        print "Message from Github API: {obj["message"] or else ""}"
                                        print "Requested URI: {uri}"
index b618729..8426097 100644 (file)
@@ -121,7 +121,7 @@ do
        super
 end
 
-redef class HashCollection[K,N]
+redef class HashCollection[K]
        redef fun node_at_idx(i,k)
        do
                sys.gt_count += 1
index 04245a5..4ab4eab 100644 (file)
@@ -32,7 +32,6 @@ in "Java" `{
 
 extern class NativeFile in "Java" `{ java.io.File `}
        super JavaObject
-       redef type SELF: NativeFile
 
        fun can_execute: Bool in "Java" `{ return recv.canExecute(); `}
        fun can_read: Bool in "Java" `{ return recv.canRead(); `}
@@ -77,7 +76,6 @@ end
 
 extern class NativeFileInputStream in "Java" `{ java.io.FileInputStream `}
        super JavaObject
-       redef type SELF: NativeFileInputStream
 
        fun available: Int in "Java" `{
                try {
@@ -114,7 +112,6 @@ end
 
 extern class NativeFileOutputStream in "Java" `{ java.io.FileOutputStream `}
        super JavaObject
-       redef type SELF: NativeFileOutputStream
 
        fun close in "Java" `{
                try {
@@ -141,7 +138,7 @@ end
 
 extern class NativeFileDescriptor in "Java" `{ java.io.FileDescriptor `}
        super JavaObject
-       redef type SELF: NativeFileDescriptor
+
        fun sync in "Java" `{
                try{
                        recv.sync();
@@ -154,7 +151,6 @@ end
 
 extern class NativeInputStream in "Java" `{ java.io.InputStream `}
        super JavaObject
-       redef type SELF: NativeInputStream
 
        fun available: Int in "Java" `{
                try {
index 0b6a652..c3db430 100644 (file)
@@ -98,8 +98,6 @@ end
 extern class JavaString in "Java" `{ java.lang.String `}
        super JavaObject
 
-       redef type SELF: JavaString
-
        # Get the string from Java and copy it to Nit memory
        fun to_cstring: NativeString import sys, Sys.jni_env `{
                Sys sys = JavaString_sys(recv);
@@ -140,7 +138,6 @@ redef class Text
 end
 
 redef extern class JavaObject
-       type SELF: JavaObject
 
        # Returns a global reference to the Java object behind this reference
        #
index ec3ba25..d15ceb7 100644 (file)
@@ -20,8 +20,8 @@
 # to get the underlying Json data. It can also be used as any Json types.
 module dynamic
 
+import error
 private import static
-import standard
 
 class JsonValue
        var value: nullable Object
@@ -123,14 +123,14 @@ class JsonValue
        #
        #     assert """{"a": 123}""".to_json_value.is_map
        #     assert not "123".to_json_value.is_map
-       fun is_map: Bool do return value isa HashMap[String, nullable Object]
+       fun is_map: Bool do return value isa MapRead[String, nullable Object]
 
        # Get this value as a `Map[String, JsonValue]`
        #
        # require: `self.is_map`
        fun to_map: Map[String, JsonValue] do
                var value = value
-               assert value isa HashMap[String, nullable Object]
+               assert value isa MapRead[String, nullable Object]
 
                var map = new HashMap[String, JsonValue]
                for k, v in value do map[k] = new JsonValue(v)
@@ -145,7 +145,7 @@ class JsonValue
        #     assert "[1, 2, 3, 4, 5]".to_json_value.is_array
        #     assert "[null, true, false, 0.0, 1, \"str\"]".to_json_value.is_array
        #     assert """["a", "b", "c"]""".to_json_value.is_array
-       fun is_array: Bool do return value isa Array[nullable Object]
+       fun is_array: Bool do return value isa SequenceRead[nullable Object]
 
        # Get this value as an `Array[JsonValue]`
        #
@@ -155,13 +155,42 @@ class JsonValue
        fun to_a: Array[JsonValue]
        do
                var value = value
-               assert value isa Array[nullable Object]
+               assert value isa SequenceRead[nullable Object]
 
                var a = new Array[JsonValue]
                for e in value do a.add(new JsonValue(e))
                return a
        end
 
+       ### Error
+
+       # Is this value an error?
+       #
+       #     assert "[]".to_json_value[0].is_error
+       #     assert "[".to_json_value.is_error
+       #     assert not "[]".to_json_value.is_error
+       fun is_error: Bool do return value isa Error
+
+       # Get this value as a `Error`.
+       #
+       # require: `self.is_error`
+       fun to_error: Error do return value.as(Error)
+
+       ### JsonParseError
+
+       # Is this value a parse error?
+       #
+       #     assert "[".to_json_value.is_parse_error
+       #     assert not "[]".to_json_value.is_parse_error
+       fun is_parse_error: Bool do return value isa JsonParseError
+
+       # Get this value as a `JsonParseError`.
+       #
+       # require: `self.is_parse_error`
+       fun to_parse_error: JsonParseError do return value.as(JsonParseError)
+
+       ### Children access
+
        # Iterator over the values of the array `self`
        #
        # require: `self.is_array`
@@ -181,17 +210,39 @@ class JsonValue
        #     assert """{"a": 123}""".to_json_value["a"].to_i == 123
        #     assert """{"123": "a"}""".to_json_value[123].to_s == "a"
        #     assert """{"John Smith": 1980}""".to_json_value[["John ", "Smith"]].to_i == 1980
+       #     assert """{"a": 123}""".to_json_value["b"].is_error
        #
        #     assert """["a", "b", "c"]""".to_json_value[0].to_s == "a"
-       fun [](key: Object): JsonValue
-       do
+       #     assert """["a", "b", "c"]""".to_json_value[3].is_error
+       fun [](key: Object): JsonValue do
                var value = value
-               if value isa HashMap[String, nullable Object] then
-                       return new JsonValue(value[key.to_s])
-               else if value isa Array[nullable Object] then
-                       assert key isa Int
-                       return new JsonValue(value[key])
-               else abort
+               var result: nullable Object
+               if is_error then
+                       return self
+               else if value isa MapRead[String, nullable Object] then
+                       key = key.to_s
+                       if value.has_key(key) then
+                               result = value[key]
+                       else
+                               result = new JsonKeyError("Key `{key}` not found.", self, key)
+                       end
+               else if value isa SequenceRead[nullable Object] then
+                       if key isa Int then
+                               if key < value.length and key >= 0 then
+                                       result = value[key]
+                               else
+                                       result = new JsonKeyError("Index `{key}` out of bounds.",
+                                                       self, key)
+                               end
+                       else
+                               result = new JsonKeyError("Invalid key type. Expecting `Int`. Got `{key.class_name}`.",
+                                               self, key)
+                       end
+               else
+                       result = new JsonKeyError("Invalid `[]` access on a `{json_type}` JsonValue.",
+                                       self, key)
+               end
+               return new JsonValue(result)
        end
 
        # Advanced query to get a value within the map `self` or it's children.
@@ -203,24 +254,78 @@ class JsonValue
        #     assert """{"a": {"t": true, "f": false}}""".to_json_value.get("a").is_map
        #     assert """{"a": {"t": true, "f": false}}""".to_json_value.get("a.t").to_bool
        #     assert not """{"a": {"t": true, "f": false}}""".to_json_value.get("a.f").to_bool
+       #     assert """{"a": {"t": true, "f": false}}""".to_json_value.get("a.t.t").is_error
        #     assert """{"a": {"b": {"c": {"d": 123}}}}""".to_json_value.get("a.b.c.d").to_i == 123
-       fun get(query: String): JsonValue
-       do
+       #     assert """{"a": {"b": {"c": {"d": 123}}}}""".to_json_value.get("a.z.c.d").is_error
+       fun get(query: String): JsonValue do
                var keys = query.split(".")
                var value = value
-               for key in keys do
-                       assert value isa HashMap[String, nullable Object]
-                       value = value[key]
+               if is_error then return self
+               for i in [0..keys.length[ do
+                       var key = keys[i]
+                       if value isa MapRead[String, nullable Object] then
+                               if value.has_key(key) then
+                                       value = value[key]
+                               else
+                                       var sub_query = sub_query_to_s(keys, i)
+                                       var e = new JsonKeyError("Key `{key}` not found.",
+                                                       self, sub_query)
+                                       return new JsonValue(e)
+                               end
+                       else
+                               var sub_query = sub_query_to_s(keys, i)
+                               var val_type = (new JsonValue(value)).json_type
+                               var e = new JsonKeyError("Value at `{sub_query}` is not a map. Got type `{val_type}`",
+                                               self, sub_query)
+                               return new JsonValue(e)
+                       end
                end
                return new JsonValue(value)
        end
+
+       # Concatenate all keys up to `last` for debugging purposes.
+       #
+       # Note: This method deletes elements in `keys`.
+       private fun sub_query_to_s(keys: Array[String], last: Int): String do
+               last += 1
+               for j in [last..keys.length[ do keys.pop
+               return keys.join(".")
+       end
+
+       # Return a human-readable description of the type.
+       #
+       # For debugging purpose only.
+       fun json_type: String do
+               if is_array then return "array"
+               if is_bool then return "bool"
+               if is_float then return "float"
+               if is_int then return "int"
+               if is_null then return "null"
+               if is_map then return "map"
+               if is_string then return "string"
+               if is_parse_error then return "parse_error"
+               if is_error then return "error"
+               return "undefined"
+       end
 end
 
-redef class String
+# Keyed access failed.
+class JsonKeyError
+       super Error
+
+       # The value on which the access was requested.
+       var json_value: JsonValue
+
+       # The requested key.
+       #
+       # In the case of `JsonValue.get`, the sub-query that failed.
+       var key: Object
+end
+
+redef class Text
        # Parse `self` to obtain a `JsonValue`
-       fun to_json_value: JsonValue
-       do
-               var value = json_to_nit_object
+       fun to_json_value: JsonValue do
+               var value = parse_json
                return new JsonValue(value)
        end
 end
diff --git a/lib/json/error.nit b/lib/json/error.nit
new file mode 100644 (file)
index 0000000..dab1689
--- /dev/null
@@ -0,0 +1,31 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This file is free software, which comes along with NIT. This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. You can modify it is you want, provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You are allowed to redistribute it and sell it, alone or is a part of
+# another product.
+
+# Errors related to JSON parsing.
+module json::error
+
+import nitcc_runtime
+
+# Ill-formed JSON.
+class JsonParseError
+       super Error
+
+       # The location of the error in the original text.
+       var position: nullable Position
+
+       redef fun to_s do
+               var p = position
+               if p isa Position then
+                       return "[{p}] {super}"
+               else
+                       return super
+               end
+       end
+end
index 9353350..b458d01 100644 (file)
@@ -1,6 +1,7 @@
 # This file is part of NIT ( http://www.nitlanguage.org ).
 #
 # Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
+# Copyright 2014 Jean-Christophe Beaupré <jcbrinfo@users.noreply.github.com>
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# Offers two APIs to manipulate read Json strings.
+# Provides two APIs to manipulate JSON strings.
+#
+# Both `dynamic` and `static` modules provide at least a method to parse a
+# value written in JSON, but only `static` provide a method to translate a
+# value into JSON.
 #
 # The `dynamic` module provides a simple interface to get information
-# from a Json document. You must be careful as all services are provided on
+# from a JSON document. You must be careful as all services are provided on
 # each nodes and a wrongful use can `abort`.
 #
-# The `static` module converts a Json string to a nullable Nit object. The object
-# must then be type checked before it can be used.
+# The `static` module provides a common interface for anything that can be
+# translated into a JSON document. The provided parsing method return a
+# nullable Nit object that must then be type-checked before it can be used.
 module json
 
 import static
index f597c82..3273525 100644 (file)
@@ -36,6 +36,10 @@ redef class Object
        private fun dfastate_28: DFAState28 do return once new DFAState28
        private fun dfastate_29: DFAState29 do return once new DFAState29
        private fun dfastate_30: DFAState30 do return once new DFAState30
+       private fun dfastate_31: DFAState31 do return once new DFAState31
+       private fun dfastate_32: DFAState32 do return once new DFAState32
+       private fun dfastate_33: DFAState33 do return once new DFAState33
+       private fun dfastate_34: DFAState34 do return once new DFAState34
 end
 class MyNToken
        super NToken
@@ -391,6 +395,75 @@ private class DFAState30
        super DFAState
        redef fun trans(char) do
                var c = char.ascii
-               return dfastate_2
+               if c <= 33 then return null
+               if c <= 34 then return dfastate_2
+               if c <= 46 then return null
+               if c <= 47 then return dfastate_2
+               if c <= 91 then return null
+               if c <= 92 then return dfastate_2
+               if c <= 97 then return null
+               if c <= 98 then return dfastate_2
+               if c <= 101 then return null
+               if c <= 102 then return dfastate_2
+               if c <= 109 then return null
+               if c <= 110 then return dfastate_2
+               if c <= 113 then return null
+               if c <= 114 then return dfastate_2
+               if c <= 115 then return null
+               if c <= 116 then return dfastate_2
+               if c <= 117 then return dfastate_31
+               return null
+       end
+end
+private class DFAState31
+       super DFAState
+       redef fun trans(char) do
+               var c = char.ascii
+               if c <= 47 then return null
+               if c <= 57 then return dfastate_32
+               if c <= 64 then return null
+               if c <= 90 then return dfastate_32
+               if c <= 96 then return null
+               if c <= 122 then return dfastate_32
+               return null
+       end
+end
+private class DFAState32
+       super DFAState
+       redef fun trans(char) do
+               var c = char.ascii
+               if c <= 47 then return null
+               if c <= 57 then return dfastate_33
+               if c <= 64 then return null
+               if c <= 90 then return dfastate_33
+               if c <= 96 then return null
+               if c <= 122 then return dfastate_33
+               return null
+       end
+end
+private class DFAState33
+       super DFAState
+       redef fun trans(char) do
+               var c = char.ascii
+               if c <= 47 then return null
+               if c <= 57 then return dfastate_34
+               if c <= 64 then return null
+               if c <= 90 then return dfastate_34
+               if c <= 96 then return null
+               if c <= 122 then return dfastate_34
+               return null
+       end
+end
+private class DFAState34
+       super DFAState
+       redef fun trans(char) do
+               var c = char.ascii
+               if c <= 47 then return null
+               if c <= 57 then return dfastate_2
+               if c <= 64 then return null
+               if c <= 90 then return dfastate_2
+               if c <= 96 then return null
+               if c <= 122 then return dfastate_2
+               return null
        end
 end
index 51800fc..29e5c22 100644 (file)
@@ -1,6 +1,8 @@
 # This file is part of NIT ( http://www.nitlanguage.org ).
 #
 # Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
+# Copyright 2014 Alexandre Terrasa <alexandre@moz-concept.com>
+# Copyright 2014 Jean-Christophe Beaupré <jcbrinfo@users.noreply.github.com>
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
 
 # Static interface to get Nit objects from a Json string.
 #
-# `String::json_to_nit_object` returns an equivalent Nit object from
+# `Text::parse_json` returns an equivalent Nit object from
 # the Json source. This object can then be type checked by the usual
 # languages features (`isa` and `as`).
 module static
 
-import standard
+import error
 private import json_parser
 private import json_lexer
 
+# Something that can be translated to JSON.
+interface Jsonable
+       # Encode `self` in JSON.
+       #
+       # SEE: `append_json`
+       fun to_json: String is abstract
+
+       # Append the JSON representation of `self` to the specified buffer.
+       #
+       # SEE: `to_json`
+       fun append_json(buffer: Buffer) do
+               buffer.append(to_json)
+       end
+end
+
+redef class Text
+       super Jsonable
+
+       redef fun append_json(buffer) do
+               buffer.add '\"'
+               for i in [0..self.length[ do
+                       var char = self[i]
+                       if char == '\\' then
+                               buffer.append "\\\\"
+                       else if char == '\"' then
+                               buffer.append "\\\""
+                       else if char == '\/' then
+                               buffer.append "\\/"
+                       else if char < 16.ascii then
+                               if char == '\n' then
+                                       buffer.append "\\n"
+                               else if char == '\r' then
+                                       buffer.append "\\r"
+                               else if char == '\t' then
+                                       buffer.append "\\t"
+                               else if char == 0x0C.ascii then
+                                       buffer.append "\\f"
+                               else if char == 0x08.ascii then
+                                       buffer.append "\\b"
+                               else
+                                       buffer.append "\\u000{char.ascii.to_hex}"
+                               end
+                       else if char < ' ' then
+                               buffer.append "\\u00{char.ascii.to_hex}"
+                       else
+                               buffer.add char
+                       end
+               end
+               buffer.add '\"'
+       end
+
+       # Encode `self` in JSON.
+       #
+       #     assert "\t\"http://example.com\"\r\n\0\\".to_json ==
+       #               "\"\\t\\\"http:\\/\\/example.com\\\"\\r\\n\\u0000\\\\\""
+       redef fun to_json do
+               var buffer = new FlatBuffer
+               append_json(buffer)
+               return buffer.write_to_string
+       end
+
+       # Parse `self` as JSON.
+       #
+       # If `self` is not a valid JSON document or contains an unsupported escape
+       # sequence, return a `JSONParseError`.
+       #
+       # Example with `JsonObject`:
+       #
+       #     var obj = "\{\"foo\": \{\"bar\": true, \"goo\": [1, 2, 3]\}\}".parse_json
+       #     assert obj isa JsonObject
+       #     assert obj["foo"] isa JsonObject
+       #     assert obj["foo"].as(JsonObject)["bar"] == true
+       #
+       # Example with `JsonArray`:
+       #
+       #     var arr = "[1, 2, 3]".parse_json
+       #     assert arr isa JsonArray
+       #     assert arr.length == 3
+       #     assert arr.first == 1
+       #     assert arr.last == 3
+       #
+       # Example with `String`:
+       #
+       #     var str = "\"foo, bar, baz\"".parse_json
+       #     assert str isa String
+       #     assert str == "foo, bar, baz"
+       #
+       # Example of a syntaxic error:
+       #
+       #     var bad = "\{foo: \"bar\"\}".parse_json
+       #     assert bad isa JsonParseError
+       #     assert bad.position.col_start == 2
+       fun parse_json: nullable Jsonable do
+               var lexer = new Lexer_json(to_s)
+               var parser = new Parser_json
+               var tokens = lexer.lex
+               parser.tokens.add_all(tokens)
+               var root_node = parser.parse
+               if root_node isa NStart then
+                       return root_node.n_0.to_nit_object
+               else if root_node isa NError then
+                       return new JsonParseError(root_node.message, root_node.position)
+               else abort
+       end
+end
+
+redef class Buffer
+
+       # Append the JSON representation of `jsonable` to `self`.
+       #
+       # Append `"null"` for `null`.
+       private fun append_json_of(jsonable: nullable Jsonable) do
+               if jsonable isa Jsonable then
+                       append jsonable.to_json
+               else
+                       append "null"
+               end
+       end
+end
+
+redef class Int
+       super Jsonable
+
+       # Encode `self` in JSON.
+       #
+       #     assert 0.to_json == "0"
+       #     assert (-42).to_json == "-42"
+       redef fun to_json do return self.to_s
+end
+
+redef class Float
+       super Jsonable
+
+       # Encode `self` in JSON.
+       #
+       # Note: Because this method use `to_s`, it may lose precision.
+       #
+       # ~~~
+       # # Will not work as expected.
+       # # assert (-0.0).to_json == "-0.0"
+       #
+       # assert (.5).to_json == "0.5"
+       # assert (0.0).to_json == "0.0"
+       # ~~~
+       redef fun to_json do return self.to_s
+end
+
+redef class Bool
+       super Jsonable
+
+       # Encode `self` in JSON.
+       #
+       #     assert true.to_json == "true"
+       #     assert false.to_json == "false"
+       redef fun to_json do return self.to_s
+end
+
+# A map that can be translated into a JSON object.
+interface JsonMapRead[K: String, V: nullable Jsonable]
+       super MapRead[K, V]
+       super Jsonable
+
+       redef fun append_json(buffer) do
+               buffer.append "\{"
+               var it = iterator
+               if it.is_ok then
+                       append_json_entry(it, buffer)
+                       while it.is_ok do
+                               buffer.append ","
+                               append_json_entry(it, buffer)
+                       end
+               end
+               it.finish
+               buffer.append "\}"
+       end
+
+       # Encode `self` in JSON.
+       #
+       #     var obj = new JsonObject
+       #     obj["foo"] = "bar"
+       #     assert obj.to_json == "\{\"foo\":\"bar\"\}"
+       #     obj = new JsonObject
+       #     obj["baz"] = null
+       #     assert obj.to_json == "\{\"baz\":null\}"
+       redef fun to_json do
+               var buffer = new FlatBuffer
+               append_json(buffer)
+               return buffer.write_to_string
+       end
+
+       private fun append_json_entry(iterator: MapIterator[String, nullable Jsonable],
+                       buffer: Buffer) do
+               buffer.append iterator.key.to_json
+               buffer.append ":"
+               buffer.append_json_of(iterator.item)
+               iterator.next
+       end
+end
+
+# A JSON Object.
+class JsonObject
+       super JsonMapRead[String, nullable Jsonable]
+       super HashMap[String, nullable Jsonable]
+end
+
+# A sequence that can be translated into a JSON array.
+class JsonSequenceRead[E: nullable Jsonable]
+       super Jsonable
+       super SequenceRead[E]
+
+       redef fun append_json(buffer) do
+               buffer.append "["
+               var it = iterator
+               if it.is_ok then
+                       append_json_entry(it, buffer)
+                       while it.is_ok do
+                               buffer.append ","
+                               append_json_entry(it, buffer)
+                       end
+               end
+               it.finish
+               buffer.append "]"
+       end
+
+       # Encode `self` in JSON.
+       #
+       #     var arr = new JsonArray.with_items("foo", null)
+       #     assert arr.to_json == "[\"foo\",null]"
+       #     arr.pop
+       #     assert arr.to_json =="[\"foo\"]"
+       #     arr.pop
+       #     assert arr.to_json =="[]"
+       redef fun to_json do
+               var buffer = new FlatBuffer
+               append_json(buffer)
+               return buffer.write_to_string
+       end
+
+       private fun append_json_entry(iterator: Iterator[nullable Jsonable],
+                       buffer: Buffer) do
+               buffer.append_json_of(iterator.item)
+               iterator.next
+       end
+end
+
+# A JSON array.
+class JsonArray
+       super JsonSequenceRead[nullable Jsonable]
+       super Array[nullable Jsonable]
+end
+
+redef class JsonParseError
+       super Jsonable
+
+       # Get the JSON representation of `self`.
+       #
+       #     var err = new JsonParseError("foo", new Position(1, 2, 3, 4, 5, 6))
+       #     assert err.to_json == "\{\"error\":\"JsonParseError\"," +
+       #               "\"position\":\{" +
+       #                       "\"pos_start\":1,\"pos_end\":2," +
+       #                       "\"line_start\":3,\"line_end\":4," +
+       #                       "\"col_start\":5,\"col_end\":6" +
+       #               "\},\"message\":\"foo\"\}"
+       redef fun to_json do
+               return "\{\"error\":\"JsonParseError\"," +
+                               "\"position\":{position.to_json}," +
+                               "\"message\":{message.to_json}\}"
+       end
+end
+
+redef class Position
+       super Jsonable
+
+       # Get the JSON representation of `self`.
+       #
+       #     var pos = new Position(1, 2, 3, 4, 5, 6)
+       #     assert pos.to_json == "\{" +
+       #                       "\"pos_start\":1,\"pos_end\":2," +
+       #                       "\"line_start\":3,\"line_end\":4," +
+       #                       "\"col_start\":5,\"col_end\":6" +
+       #               "\}"
+       redef fun to_json do
+               return "\{\"pos_start\":{pos_start},\"pos_end\":{pos_end}," +
+                               "\"line_start\":{line_start},\"line_end\":{line_end}," +
+                               "\"col_start\":{col_start},\"col_end\":{col_end}\}"
+       end
+end
+
+################################################################################
+# Redef parser
+
 redef class Nvalue
-       fun to_nit_object: nullable Object is abstract
+       fun to_nit_object: nullable Jsonable is abstract
 end
 
 redef class Nvalue_number
@@ -55,17 +348,46 @@ redef class Nvalue_null
 end
 
 redef class Nstring
-       # FIXME support \n, etc.
-       fun to_nit_string: String do return text.substring(1, text.length-2).
-               replace("\\\\", "\\").replace("\\\"", "\"").replace("\\b", "\b").
-               replace("\\/", "/").replace("\\n", "\n").replace("\\r", "\r").
-               replace("\\t", "\t")
+       fun to_nit_string: String do
+               var res = new FlatBuffer
+               var i = 1
+               while i < text.length - 1 do
+                       var char = text[i]
+                       if char == '\\' then
+                               i += 1
+                               char = text[i]
+                               if char == 'b' then
+                                       char = 0x08.ascii
+                               else if char == 'f' then
+                                       char = 0x0C.ascii
+                               else if char == 'n' then
+                                       char = '\n'
+                               else if char == 'r' then
+                                       char = '\r'
+                               else if char == 't' then
+                                       char = '\t'
+                               else if char == 'u' then
+                                       var code = text.substring(i + 1, 4).to_hex
+                                       # TODO UTF-16 escaping is not supported yet.
+                                       if code >= 128 then
+                                               char = '?'
+                                       else
+                                               char = code.ascii
+                                       end
+                                       i += 4
+                               end
+                               # `"`, `/` or `\` => Keep `char` as-is.
+                       end
+                       res.add char
+                       i += 1
+               end
+               return res.write_to_string
+       end
 end
 
 redef class Nvalue_object
-       redef fun to_nit_object
-       do
-               var obj = new HashMap[String, nullable Object]
+       redef fun to_nit_object do
+               var obj = new JsonObject
                var members = n_members
                if members != null then
                        var pairs = members.pairs
@@ -94,13 +416,13 @@ end
 
 redef class Npair
        fun name: String do return n_string.to_nit_string
-       fun value: nullable Object do return n_value.to_nit_object
+       fun value: nullable Jsonable do return n_value.to_nit_object
 end
 
 redef class Nvalue_array
        redef fun to_nit_object
        do
-               var arr = new Array[nullable Object]
+               var arr = new JsonArray
                var elements = n_elements
                if elements != null then
                        var items = elements.items
@@ -126,25 +448,3 @@ end
 redef class Nelements_head
        redef fun items do return [n_value]
 end
-
-redef class Text
-       fun json_to_nit_object: nullable Object
-       do
-               var lexer = new Lexer_json(to_s)
-               var parser = new Parser_json
-               var tokens = lexer.lex
-               parser.tokens.add_all(tokens)
-               var root_node = parser.parse
-               if root_node isa NStart then
-                       return root_node.n_0.to_nit_object
-               else if root_node isa NLexerError then
-                       var pos = root_node.position
-                       print "Json lexer error: {root_node.message} at {pos or else "<unknown>"} for {root_node}"
-                       return null
-               else if root_node isa NParserError then
-                       var pos = root_node.position
-                       print "Json parsing error: {root_node.message} at {pos or else "<unknown>"} for {root_node}"
-                       return null
-               else abort
-       end
-end
index 6a2d82a..f090b24 100644 (file)
@@ -71,16 +71,16 @@ end
 class JsonDeserializer
        super Deserializer
 
-       var root: nullable Object
-       var path = new Array[HashMap[String, nullable Object]]
+       var root: nullable Jsonable
+       var path = new Array[JsonObject]
        var id_to_object = new HashMap[Int, Object]
 
        var just_opened_id: nullable Int = null
 
        init(text: Text)
        do
-               var root = text.json_to_nit_object
-               if root isa HashMap[String, nullable Object] then path.add(root)
+               var root = text.parse_json
+               if root isa JsonObject then path.add(root)
                self.root = root
        end
 
@@ -107,7 +107,7 @@ class JsonDeserializer
        # Convert from simple Json object to Nit object
        private fun convert_object(object: nullable Object): nullable Object
        do
-               if object isa HashMap[String, nullable Object] then
+               if object isa JsonObject then
                        assert object.keys.has("__kind")
                        var kind = object["__kind"]
 
@@ -197,18 +197,11 @@ redef class Bool
 end
 
 redef class Char
-       redef fun serialize_to_json(v) do v.stream.write "\{\"__kind\": \"char\", \"__val\": \"{to_s.to_json_s}\"\}"
+       redef fun serialize_to_json(v) do v.stream.write "\{\"__kind\": \"char\", \"__val\": {to_s.to_json}\}"
 end
 
 redef class String
-       redef fun serialize_to_json(v) do v.stream.write("\"{to_json_s}\"")
-
-       private fun to_json_s: String do return self.replace("\\", "\\\\").
-               replace("\"", "\\\"").replace("/", "\\/").
-               replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t")
-               # FIXME add support for unicode char when supported by Nit strings
-               # FIXME add support for \f! # .replace("\f", "\\f")
-               # FIXME add support for \b .replace("\b", "\\b")
+       redef fun serialize_to_json(v) do v.stream.write(to_json)
 end
 
 redef class NativeString
@@ -259,7 +252,7 @@ redef class Array[E]
                        v.notify_of_creation self
 
                        var length = v.deserialize_attribute("__length").as(Int)
-                       var arr = v.path.last["__items"].as(Array[nullable Object])
+                       var arr = v.path.last["__items"].as(SequenceRead[nullable Object])
                        for i in length.times do
                                var obj = v.convert_object(arr[i])
                                self.add obj
index 5b73d3b..2f675d6 100644 (file)
@@ -30,8 +30,91 @@ import template
 # SEE: `String::md_to_html` for a shortcut.
 class MarkdownProcessor
 
+       # `MarkdownEmitter` used for ouput.
        var emitter: MarkdownEmitter is noinit
 
+       # Work in extended mode (default).
+       #
+       # Behavior changes when using extended mode:
+       #
+       # * Lists and code blocks end a paragraph
+       #
+       #   In normal markdown the following:
+       #
+       #               This is a paragraph
+       #               * and this is not a list
+       #
+       #   Will produce:
+       #
+       #               <p>This is a paragraph
+       #               * and this is not a list</p>
+       #
+       #       When using extended mode this changes to:
+       #
+       #               <p>This is a paragraph</p>
+       #               <ul>
+       #               <li>and this is not a list</li>
+       #               </ul>
+       #
+       # * Fences code blocks
+       #
+       #   If you don't want to indent your all your code with 4 spaces,
+       #   you can wrap your code in ``` ``` ``` or `~~~`.
+       #
+       #       Here's an example:
+       #
+       #               ```
+       #               fun test do
+       #                       print "Hello World!"
+       #               end
+       #               ```
+       #
+       # * Code blocks meta
+       #
+       #   If you want to use syntax highlighting tools, most of them need to know what kind
+       #   of language they are highlighting.
+       #   You can add an optional language identifier after the fence declaration to output
+       #   it in the HTML render.
+       #
+       #               ```nit
+       #               import markdown
+       #
+       #               print "# Hello World!".md_to_html
+       #               ```
+       #
+       #   Becomes
+       #
+       #               <pre class="nit"><code>import markdown
+       #
+       #               print "Hello World!".md_to_html
+       #               </code></pre>
+       #
+       # * Underscores (Emphasis)
+       #
+       #   Underscores in the middle of a word like:
+       #
+       #               Con_cat_this
+       #
+       #       normally produces this:
+       #
+       #               <p>Con<em>cat</em>this</p>
+       #
+       #   With extended mode they don't result in emphasis.
+       #
+       #               <p>Con_cat_this</p>
+       #
+       # * Strikethrough
+       #
+       #   Like in [GFM](https://help.github.com/articles/github-flavored-markdown),
+       #   strikethrought span is marked with `~~`.
+       #
+       #               ~~Mistaken text.~~
+       #
+       #   becomes
+       #
+       #               <del>Mistaken text.</del>
+       var ext_mode = true
+
        init do self.emitter = new MarkdownEmitter(self)
 
        # Process the mardown `input` string and return the processed output.
@@ -218,12 +301,14 @@ class MarkdownProcessor
                if value[leading] == '#' then return new LineHeadline
                if value[leading] == '>' then return new LineBlockquote
 
-               if value.length - leading - trailing > 2 then
-                       if value[leading] == '`' and md.count_chars_start('`') >= 3 then
-                               return new LineFence
-                       end
-                       if value[leading] == '~' and md.count_chars_start('~') >= 3 then
-                               return new LineFence
+               if ext_mode then
+                       if value.length - leading - trailing > 2 then
+                               if value[leading] == '`' and md.count_chars_start('`') >= 3 then
+                                       return new LineFence
+                               end
+                               if value[leading] == '~' and md.count_chars_start('~') >= 3 then
+                                       return new LineFence
+                               end
                        end
                end
 
@@ -261,6 +346,108 @@ class MarkdownProcessor
                return new LineOther
        end
 
+       # Get the token kind at `pos`.
+       fun token_at(text: Text, pos: Int): Token do
+               var c0: Char
+               var c1: Char
+               var c2: Char
+
+               if pos > 0 then
+                       c0 = text[pos - 1]
+               else
+                       c0 = ' '
+               end
+               var c = text[pos]
+
+               if pos + 1 < text.length then
+                       c1 = text[pos + 1]
+               else
+                       c1 = ' '
+               end
+               if pos + 2 < text.length then
+                       c2 = text[pos + 2]
+               else
+                       c2 = ' '
+               end
+
+               if c == '*' then
+                       if c1 == '*' then
+                               if c0 != ' ' or c2 != ' ' then
+                                       return new TokenStrongStar(pos, c)
+                               else
+                                       return new TokenEmStar(pos, c)
+                               end
+                       end
+                       if c0 != ' ' or c1 != ' ' then
+                               return new TokenEmStar(pos, c)
+                       else
+                               return new TokenNone(pos, c)
+                       end
+               else if c == '_' then
+                       if c1 == '_' then
+                               if c0 != ' ' or c2 != ' 'then
+                                       return new TokenStrongUnderscore(pos, c)
+                               else
+                                       return new TokenEmUnderscore(pos, c)
+                               end
+                       end
+                       if ext_mode then
+                               if (c0.is_letter or c0.is_digit) and c0 != '_' and
+                                  (c1.is_letter or c1.is_digit) then
+                                       return new TokenNone(pos, c)
+                               else
+                                       return new TokenEmUnderscore(pos, c)
+                               end
+                       end
+                       if c0 != ' ' or c1 != ' ' then
+                               return new TokenEmUnderscore(pos, c)
+                       else
+                               return new TokenNone(pos, c)
+                       end
+               else if c == '!' then
+                       if c1 == '[' then return new TokenImage(pos, c)
+                       return new TokenNone(pos, c)
+               else if c == '[' then
+                       return new TokenLink(pos, c)
+               else if c == ']' then
+                       return new TokenNone(pos, c)
+               else if c == '`' then
+                       if c1 == '`' then
+                               return new TokenCodeDouble(pos, c)
+                       else
+                               return new TokenCodeSingle(pos, c)
+                       end
+               else if c == '\\' then
+                       if c1 == '\\' or c1 == '[' or c1 == ']' or c1 == '(' or c1 == ')' or c1 == '{' or c1 == '}' or c1 == '#' or c1 == '"' or c1 == '\'' or c1 == '.' or c1 == '<' or c1 == '>' or c1 == '*' or c1 == '+' or c1 == '-' or c1 == '_' or c1 == '!' or c1 == '`' or c1 == '~' or c1 == '^' then
+                               return new TokenEscape(pos, c)
+                       else
+                               return new TokenNone(pos, c)
+                       end
+               else if c == '<' then
+                       return new TokenHTML(pos, c)
+               else if c == '&' then
+                       return new TokenEntity(pos, c)
+               else
+                       if ext_mode then
+                               if c == '~' and c1 == '~' then
+                                       return new TokenStrike(pos, c)
+                               end
+                       end
+                       return new TokenNone(pos, c)
+               end
+       end
+
+       # Find the position of a `token` in `self`.
+       fun find_token(text: Text, start: Int, token: Token): Int do
+               var pos = start
+               while pos < text.length do
+                       if token_at(text, pos).is_same_type(token) then
+                               return pos
+                       end
+                       pos += 1
+               end
+               return -1
+       end
 end
 
 # Emit output corresponding to blocks content.
@@ -276,11 +463,6 @@ class MarkdownEmitter
        # Default is `HTMLDecorator`
        var decorator: Decorator = new HTMLDecorator is writable
 
-       # Create a new `MardownEmitter` using the default `HTMLDecorator`
-       init(processor: MarkdownProcessor) do
-               self.processor = processor
-       end
-
        # Create a new `MarkdownEmitter` using a custom `decorator`.
        init with_decorator(processor: MarkdownProcessor, decorator: Decorator) do
                init processor
@@ -312,7 +494,7 @@ class MarkdownEmitter
                current_text = text
                current_pos = start
                while current_pos < text.length do
-                       var mt = text.token_at(current_pos)
+                       var mt = processor.token_at(text, current_pos)
                        if (token != null and not token isa TokenNone) and
                        (mt.is_same_type(token) or
                        (token isa TokenEmStar and mt isa TokenStrongStar) or
@@ -428,8 +610,10 @@ interface Decorator
        # Render a strong text.
        fun add_strong(v: MarkdownEmitter, text: Text) is abstract
 
-       # Render a super text.
-       fun add_super(v: MarkdownEmitter, text: Text) is abstract
+       # Render a strike text.
+       #
+       # Extended mode only (see `MarkdownProcessor::ext_mode`)
+       fun add_strike(v: MarkdownEmitter, text: Text) is abstract
 
        # Render a link.
        fun add_link(v: MarkdownEmitter, link: Text, name: Text, comment: nullable Text) is abstract
@@ -503,7 +687,11 @@ class HTMLDecorator
        end
 
        redef fun add_code(v, block) do
-               v.add "<pre><code>"
+               if block isa BlockFence and block.meta != null then
+                       v.add "<pre class=\"{block.meta.to_s}\"><code>"
+               else
+                       v.add "<pre><code>"
+               end
                v.emit_in block
                v.add "</code></pre>\n"
        end
@@ -544,10 +732,10 @@ class HTMLDecorator
                v.add "</strong>"
        end
 
-       redef fun add_super(v, text) do
-               v.add "<sup>"
+       redef fun add_strike(v, text) do
+               v.add "<del>"
                v.add text
-               v.add "</sup>"
+               v.add "</del>"
        end
 
        redef fun add_image(v, link, name, comment) do
@@ -941,6 +1129,9 @@ end
 class BlockFence
        super BlockCode
 
+       # Any string found after fence token.
+       var meta: nullable Text
+
        # Fence code lines start at 0 spaces.
        redef var line_start = 0
 end
@@ -1112,8 +1303,7 @@ class MDLine
        var next_empty: Bool = false is writable
 
        # Initialize a new MDLine from its string value
-       init(value: String) do
-               self.value = value
+       init do
                self.leading = process_leading
                if leading != value.length then
                        self.is_empty = false
@@ -1319,10 +1509,10 @@ class LineOther
                var was_empty = line.prev_empty
                while line != null and not line.is_empty do
                        var t = v.line_kind(line)
-                       if v.in_list and t isa LineList then
+                       if (v.in_list or v.ext_mode) and t isa LineList then
                                break
                        end
-                       if t isa LineCode or t isa LineFence then
+                       if v.ext_mode and (t isa LineCode or t isa LineFence) then
                                break
                        end
                        if t isa LineHeadline or t isa LineHeadline1 or t isa LineHeadline2 or
@@ -1461,7 +1651,8 @@ class LineFence
                else
                        block = v.current_block.split(v.current_block.last_line.as(not null))
                end
-               block.kind = new BlockFence(block)
+               var meta = block.first_line.value.meta_from_fence
+               block.kind = new BlockFence(block, meta)
                block.first_line.clear
                var last = block.last_line
                if last != null and v.line_kind(last) isa LineFence then
@@ -1675,7 +1866,7 @@ abstract class TokenCode
 
        redef fun emit(v) do
                var a = pos + next_pos + 1
-               var b = v.current_text.find_token(a, self)
+               var b = v.processor.find_token(v.current_text.as(not null), a, self)
                if b > 0 then
                        v.current_pos = b + next_pos
                        while a < b and v.current_text[a] == ' ' do a += 1
@@ -1948,17 +2139,19 @@ class TokenEscape
        end
 end
 
-# A markdown super token.
-class TokenSuper
+# A markdown strike token.
+#
+# Extended mode only (see `MarkdownProcessor::ext_mode`)
+class TokenStrike
        super Token
 
        redef fun emit(v) do
                var tmp = v.push_buffer
-               var b = v.emit_text_until(v.current_text.as(not null), pos + 1, self)
+               var b = v.emit_text_until(v.current_text.as(not null), pos + 2, self)
                v.pop_buffer
                if b > 0 then
-                       v.decorator.add_super(v, tmp)
-                       v.current_pos = b
+                       v.decorator.add_strike(v, tmp)
+                       v.current_pos = b + 1
                else
                        v.addc char
                end
@@ -1967,102 +2160,6 @@ end
 
 redef class Text
 
-       # Get the token kind at `pos`.
-       private fun token_at(pos: Int): Token do
-               var c0: Char
-               var c1: Char
-               var c2: Char
-
-               if pos > 0 then
-                       c0 = self[pos - 1]
-               else
-                       c0 = ' '
-               end
-               var c = self[pos]
-
-               if pos + 1 < length then
-                       c1 = self[pos + 1]
-               else
-                       c1 = ' '
-               end
-               if pos + 2 < length then
-                       c2 = self[pos + 2]
-               else
-                       c2 = ' '
-               end
-
-               if c == '*' then
-                       if c1 == '*' then
-                               if c0 != ' ' or c2 != ' ' then
-                                       return new TokenStrongStar(pos, c)
-                               else
-                                       return new TokenEmStar(pos, c)
-                               end
-                       end
-                       if c0 != ' ' or c1 != ' ' then
-                               return new TokenEmStar(pos, c)
-                       else
-                               return new TokenNone(pos, c)
-                       end
-               else if c == '_' then
-                       if c1 == '_' then
-                               if c0 != ' ' or c2 != ' 'then
-                                       return new TokenStrongUnderscore(pos, c)
-                               else
-                                       return new TokenEmUnderscore(pos, c)
-                               end
-                       end
-                       if c0 != ' ' or c1 != ' ' then
-                               return new TokenEmUnderscore(pos, c)
-                       else
-                               return new TokenNone(pos, c)
-                       end
-               else if c == '!' then
-                       if c1 == '[' then return new TokenImage(pos, c)
-                       return new TokenNone(pos, c)
-               else if c == '[' then
-                       return new TokenLink(pos, c)
-               else if c == ']' then
-                       return new TokenNone(pos, c)
-               else if c == '`' then
-                       if c1 == '`' then
-                               return new TokenCodeDouble(pos, c)
-                       else
-                               return new TokenCodeSingle(pos, c)
-                       end
-               else if c == '\\' then
-                       if c1 == '\\' or c1 == '[' or c1 == ']' or c1 == '(' or c1 == ')' or c1 == '{' or c1 == '}' or c1 == '#' or c1 == '"' or c1 == '\'' or c1 == '.' or c1 == '<' or c1 == '>' or c1 == '*' or c1 == '+' or c1 == '-' or c1 == '_' or c1 == '!' or c1 == '`' or c1 == '~' or c1 == '^' then
-                               return new TokenEscape(pos, c)
-                       else
-                               return new TokenNone(pos, c)
-                       end
-               else if c == '<' then
-                       return new TokenHTML(pos, c)
-               else if c == '&' then
-                       return new TokenEntity(pos, c)
-               else if c == '^' then
-                       if c0 == '^' or c1 == '^' then
-                               return new TokenNone(pos, c)
-                       else
-                               return new TokenSuper(pos, c)
-                       end
-               else
-                       return new TokenNone(pos, c)
-               end
-       end
-
-       # Find the position of a `token` in `self`.
-       private fun find_token(start: Int, token: Token): Int do
-               var pos = start
-               while pos < length do
-                       if token_at(pos).is_same_type(token) then
-                               return pos
-                       end
-                       pos += 1
-               end
-               return -1
-       end
-
        # Get the position of the next non-space character.
        private fun skip_spaces(start: Int): Int do
                var pos = start
@@ -2294,6 +2391,18 @@ redef class Text
                return pos
        end
 
+       # Extract string found at end of fence opening.
+       private fun meta_from_fence: nullable Text do
+               for i in [0..chars.length[ do
+                       var c = chars[i]
+                       print c
+                       if c != ' ' and c != '`' and c != '~' then
+                               return substring_from(i).trim
+                       end
+               end
+               return null
+       end
+
        # Is `self` an unsafe HTML element?
        private fun is_html_unsafe: Bool do return html_unsafe_tags.has(self.write_to_string)
 
index dd7fd58..a8d3b9f 100644 (file)
@@ -407,6 +407,36 @@ sit amet, consectetuer adipiscing elit.</p>
                assert res == exp
        end
 
+       fun test_process_list11 do
+               var test = """
+This is a paragraph
+* and this is not a list
+"""
+               var exp = """
+<p>This is a paragraph
+* and this is not a list</p>
+"""
+               var proc = new MarkdownProcessor
+               proc.ext_mode = false
+               var res = proc.process(test).write_to_string
+               assert res == exp
+       end
+
+       fun test_process_list_ext do
+               var test = """
+This is a paragraph
+* and this is not a list
+"""
+               var exp = """
+<p>This is a paragraph</p>
+<ul>
+<li>and this is not a list</li>
+</ul>
+"""
+               var res = test.md_to_html.write_to_string
+               assert res == exp
+       end
+
        fun test_process_code1 do
                var test = """
 This is a normal paragraph:
@@ -448,7 +478,7 @@ end tell
                assert res == exp
        end
 
-       fun test_process_code3 do
+       fun test_process_code_ext1 do
                var test = """
 Here is an example of AppleScript:
 ~~~
@@ -476,7 +506,7 @@ end tell
                assert res == exp
        end
 
-       fun test_process_code4 do
+       fun test_process_code_ext2 do
                var test = """
 Here is an example of AppleScript:
 ```
@@ -504,6 +534,49 @@ end tell
                assert res == exp
        end
 
+       fun test_process_code_ext3 do
+               var proc = new MarkdownProcessor
+               proc.ext_mode = false
+
+               var test = """
+Here is an example of AppleScript:
+    beep
+"""
+               var exp = """
+<p>Here is an example of AppleScript:
+beep</p>
+"""
+               var res = proc.process(test).write_to_string
+               assert res == exp
+       end
+
+       fun test_process_code_ext4 do
+               var test = """
+Here is an example of AppleScript:
+    beep
+"""
+               var exp = """
+<p>Here is an example of AppleScript:</p>
+<pre><code>beep
+</code></pre>
+"""
+               var res = test.md_to_html.write_to_string
+               assert res == exp
+       end
+
+       fun test_process_code_ext5 do
+               var test = """
+```nit
+print "Hello World!"
+```
+"""
+               var exp = """
+<pre class="nit"><code>print "Hello World!"
+</code></pre>
+"""
+               var res = test.md_to_html.write_to_string
+               assert res == exp
+       end
 
        fun test_process_nesting1 do
                var test = """
@@ -641,6 +714,22 @@ __double underscores__
                assert res == exp
        end
 
+       fun test_process_emph3 do
+               var proc = new MarkdownProcessor
+               proc.ext_mode = false
+               var test = "Con_cat_this"
+               var exp = "<p>Con<em>cat</em>this</p>\n"
+               var res = proc.process(test).write_to_string
+               assert res == exp
+       end
+
+       fun test_process_emph_ext do
+               var test = "Con_cat_this"
+               var exp = "<p>Con_cat_this</p>\n"
+               var res = test.md_to_html.write_to_string
+               assert res == exp
+       end
+
        fun test_process_xml1 do
                var test = """
 This is a regular paragraph.
@@ -908,6 +997,23 @@ break</a> with a line-ending space.</p>
                assert res == exp
        end
 
+       fun test_process_strike do
+               var proc = new MarkdownProcessor
+               proc.ext_mode = false
+               var test = "This is how you ~~strike text~~"
+               var exp = "<p>This is how you ~~strike text~~</p>\n"
+               var res = proc.process(test).write_to_string
+               assert exp == res
+       end
+
+       fun test_process_strike_ext do
+               var test = "This is how you ~~strike text~~"
+               var exp = "<p>This is how you <del>strike text</del></p>\n"
+               var res = test.md_to_html.write_to_string
+               assert exp == res
+       end
+
+
        fun test_daring_encoding do
                var test = """
 AT&T has an ampersand in their name.
@@ -1275,6 +1381,9 @@ Here's how you put `` `backticks` `` in a code span.
        end
 
        fun test_daring_pars do
+               var proc = new MarkdownProcessor
+               proc.ext_mode = false
+
                var test = """
 In Markdown 1.0.0 and earlier. Version
 8. This line turns into a list item.
@@ -1295,7 +1404,7 @@ list item.</p>
 <p>Here's one with a bullet.
 * criminey.</p>
 """
-               var res = test.md_to_html.write_to_string
+               var res = proc.process(test).write_to_string
                assert res == exp
        end
 
@@ -2462,10 +2571,6 @@ class TestLine
                assert v.line_kind(subject) isa LineHeadline
                subject = new MDLine("    code")
                assert v.line_kind(subject) isa LineCode
-               subject = new MDLine("  ~~~")
-               assert v.line_kind(subject) isa LineFence
-               subject = new MDLine("  ```")
-               assert v.line_kind(subject) isa LineFence
                subject = new MDLine("   Title  ")
                subject.next = new MDLine("== ")
                assert v.line_kind(subject) isa LineHeadline1
@@ -2498,6 +2603,14 @@ class TestLine
                assert v.line_kind(subject) isa LineOList
        end
 
+       fun test_line_type_ext do
+               var v = new MarkdownProcessor
+               subject = new MDLine("  ~~~")
+               assert v.line_kind(subject) isa LineFence
+               subject = new MDLine("  ```")
+               assert v.line_kind(subject) isa LineFence
+       end
+
        fun test_count_chars do
                subject = new MDLine("")
                assert subject.count_chars('*') == 0
index 725c612..87afb4a 100644 (file)
 # limitations under the License.
 
 # Impements the services of `mnit:app` using the API from the Android ndk
-module android_app
+module android_app is
+       android_manifest_activity """
+               android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
+               android:configChanges="orientation|keyboardHidden"
+               android:screenOrientation="portrait""""
+end
 
 import mnit
 import android
index dc5c8ac..e458eb1 100644 (file)
@@ -15,7 +15,7 @@
 # cURL requests compatible with the JSON REST APIs.
 module curl_json
 
-import jsonable
+import json::static
 intrude import curl
 
 # An abstract request that defines most of the standard options for Neo4j REST API
diff --git a/lib/neo4j/error.nit b/lib/neo4j/error.nit
new file mode 100644 (file)
index 0000000..f9df000
--- /dev/null
@@ -0,0 +1,34 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This file is free software, which comes along with NIT. This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE. You can modify it is you want, provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You are allowed to redistribute it and sell it, alone or is a part of
+# another product.
+
+# Errors thrown by the `neo4j` library.
+module neo4j::error
+
+import json::static
+
+# An error thrown by the `neo4j` API.
+#
+#     var error = new NeoError("ErrorMessage", "ErrorName")
+#     assert error.to_json == "\{\"error\":\"ErrorName\",\"message\":\"ErrorMessage\"\}"
+class NeoError
+       super Error
+       super Jsonable
+
+       # The name of the error.
+       #
+       # Used to programmatically distinguish this kind of error from others.
+       var name: String
+
+       redef fun to_json do
+               return "\{\"error\":{name.to_json},\"message\":{message.to_json}\}"
+       end
+
+       redef fun to_s do return "[{name}] {super}"
+end
index 49e055c..cf9f441 100644 (file)
@@ -70,7 +70,7 @@ class JsonGraph
        #     end
        #     assert 2 == graph.nodes.length
        init from_json(t: Text) do
-               from_json_object(t.to_jsonable.as(JsonObject))
+               from_json_object(t.parse_json.as(JsonObject))
        end
 
        # Retrieve the graph from the specified JSON object.
@@ -138,7 +138,7 @@ redef class NeoNode
        #     assert ["foo", "Bar"] == node.labels
        #     assert 42 == node["baz"]
        init from_json(t: Text) do
-               from_json_object(t.to_jsonable.as(JsonObject))
+               from_json_object(t.parse_json.as(JsonObject))
        end
 
        # Retrieve the node from the specified JSON value.
diff --git a/lib/neo4j/jsonable.nit b/lib/neo4j/jsonable.nit
deleted file mode 100644 (file)
index c82a240..0000000
+++ /dev/null
@@ -1,430 +0,0 @@
-# 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.
-
-# Introduce base classes and services for JSON handling.
-module jsonable
-
-import standard
-private import json::json_parser
-private import json::json_lexer
-
-# Something that can be translated to JSON
-interface Jsonable
-       # Get the JSON representation of `self`
-       fun to_json: String is abstract
-end
-
-redef class String
-       super Jsonable
-
-       redef fun to_json do
-               var res = new FlatBuffer
-               res.add '\"'
-               for i in [0..self.length[ do
-                       var char = self[i]
-                       if char == '\\' then
-                               res.append("\\\\")
-                               continue
-                       else if char == '\"' then
-                               res.append("\\\"")
-                               continue
-                       else if char == '\/' then
-                               res.append("\\/")
-                               continue
-                       else if char == '\n' then
-                               res.append("\\n")
-                               continue
-                       else if char == '\r' then
-                               res.append("\\r")
-                               continue
-                       else if char == '\t' then
-                               res.append("\\t")
-                               continue
-                       end
-                       res.add char
-               end
-               res.add '\"'
-               return res.write_to_string
-       end
-end
-
-redef class Int
-       super Jsonable
-
-       redef fun to_json do return self.to_s
-end
-
-redef class Float
-       super Jsonable
-
-       redef fun to_json do return self.to_s
-end
-
-redef class Bool
-       super Jsonable
-
-       redef fun to_json do return self.to_s
-end
-
-# A JSON Object representation that behaves like a `Map`
-class JsonObject
-       super Jsonable
-       super Map[String, nullable Jsonable]
-
-       private var map = new HashMap[String, nullable Jsonable]
-
-       # Create an empty `JsonObject`
-       #
-       #     var obj = new JsonObject
-       #     assert obj.is_empty
-       init do end
-
-       # Init the JSON Object from a Nit `Map`
-       #
-       #     var map = new HashMap[String, String]
-       #     map["foo"] = "bar"
-       #     map["goo"] = "baz"
-       #     var obj = new JsonObject.from(map)
-       #     assert obj.length == 2
-       #     assert obj["foo"] == "bar"
-       #     assert obj["goo"] == "baz"
-       init from(items: Map[String, nullable Jsonable]) do
-               for k, v in items do map[k] = v
-       end
-
-       redef fun [](key) do return map[key]
-       redef fun []=(key, value) do map[key] = value
-       redef fun clear do map.clear
-       redef fun has_key(key) do return map.has_key(key)
-       redef fun is_empty do return map.is_empty
-       redef fun iterator do return map.iterator
-       redef fun keys do return map.keys
-       redef fun values do return map.values
-       redef fun length do return map.length
-
-       # Advanced query to get a value within `self` or its children.
-       #
-       # A query is composed of the keys to each object seperated by '.'.
-       #
-       # REQUIRE `self.has_key(query)`
-       #
-       #     var obj1 = new JsonObject
-       #     obj1["baz"] = "foobarbaz"
-       #     var obj2 = new JsonObject
-       #     obj2["bar"] = obj1
-       #     var obj3 = new JsonObject
-       #     obj3["foo"] = obj2
-       #     assert obj3.get("foo.bar.baz") == "foobarbaz"
-       fun get(query: String): nullable Jsonable do
-               var keys = query.split(".").reversed
-               var key = keys.pop
-
-               assert has_key(key)
-               var node = self[key]
-
-               while not keys.is_empty do
-                       key = keys.pop
-                       assert node isa JsonObject and node.has_key(key)
-                       node = node[key]
-               end
-               return node
-       end
-
-       # Create an empty `JsonObject`
-       #
-       #     var obj = new JsonObject
-       #     obj["foo"] = "bar"
-       #     assert obj.to_json == "\{\"foo\": \"bar\"\}"
-       redef fun to_json do
-               var tpl = new Array[String]
-               tpl.add "\{"
-               var vals = new Array[String]
-               for k, v in self do
-                       if v == null then
-                               vals.add "{k.to_json}: null"
-                       else
-                               vals.add "{k.to_json}: {v.to_json}"
-                       end
-               end
-               tpl.add vals.join(",")
-               tpl.add "\}"
-               return tpl.join("")
-       end
-
-       redef fun to_s do return to_json
-end
-
-# A JSON Array representation that behaves like a `Sequence`
-class JsonArray
-       super Jsonable
-       super Sequence[nullable Jsonable]
-
-       private var array = new Array[nullable Jsonable]
-
-       init do end
-
-       # init the JSON Array from a Nit `Collection`
-       init from(items: Collection[nullable Jsonable]) do
-               array.add_all(items)
-       end
-
-       redef fun [](key) do return array[key]
-       redef fun []=(key, value) do array[key] = value
-       redef fun clear do array.clear
-       redef fun insert(item, index) do array.insert(item, index)
-       redef fun is_empty do return array.is_empty
-       redef fun iterator do return array.iterator
-       redef fun length do return array.length
-       redef fun pop do return array.pop
-       redef fun push(value) do array.push(value)
-       redef fun remove_at(index) do array.remove_at(index)
-       redef fun shift do return array.shift
-       redef fun unshift(e) do array.unshift(e)
-
-       redef fun to_json do
-               var tpl = new Array[String]
-               tpl.add "["
-               var vals = new Array[String]
-               for v in self do
-                       if v == null then
-                               vals.add "null"
-                       else
-                               vals.add v.to_json
-                       end
-               end
-               tpl.add vals.join(",")
-               tpl.add "]"
-               return tpl.join("")
-       end
-
-       redef fun to_s do return to_json
-end
-
-# An error in JSON format that can be returned by tools using JSON like parsers.
-#
-#     var error = new JsonError("ErrorCode", "ErrorMessage")
-#     assert error.to_s == "ErrorCode: ErrorMessage"
-#     assert error.to_json == "\{\"error\": \"ErrorCode\", \"message\": \"ErrorMessage\"\}"
-class JsonError
-       super Jsonable
-
-       # The error code
-       var error: String
-
-       # The error message
-       var message: String
-
-       redef fun to_json do
-               var tpl = new Array[String]
-               tpl.add "\{"
-               tpl.add "\"error\": {error.to_json}, "
-               tpl.add "\"message\": {message.to_json}"
-               tpl.add "\}"
-               return tpl.join("")
-       end
-
-       redef fun to_s do return "{error}: {message}"
-end
-
-# Redef parser
-
-redef class Nvalue
-       private fun to_nit_object: nullable Jsonable is abstract
-end
-
-redef class Nvalue_number
-       redef fun to_nit_object
-       do
-               var text = n_number.text
-               if text.chars.has('.') or text.chars.has('e') or text.chars.has('E') then return text.to_f
-               return text.to_i
-       end
-end
-
-redef class Nvalue_string
-       redef fun to_nit_object do return n_string.to_nit_string
-end
-
-redef class Nvalue_true
-       redef fun to_nit_object do return true
-end
-
-redef class Nvalue_false
-       redef fun to_nit_object do return false
-end
-
-redef class Nvalue_null
-       redef fun to_nit_object do return null
-end
-
-redef class Nstring
-       # FIXME support \n, etc.
-       fun to_nit_string: String do
-               var res = new FlatBuffer
-               var skip = false
-               for i in [1..text.length-2] do
-                       if skip then
-                               skip = false
-                               continue
-                       end
-                       var char = text[i]
-                       if char == '\\' and i < text.length - 2 then
-                               if text[i + 1] == '\\' then
-                                       res.add('\\')
-                                       skip = true
-                                       continue
-                               end
-                               if text[i + 1] == '\"' then
-                                       res.add('\"')
-                                       skip = true
-                                       continue
-                               end
-                               if text[i + 1] == '/' then
-                                       res.add('\/')
-                                       skip = true
-                                       continue
-                               end
-                               if text[i + 1] == 'n' then
-                                       res.add('\n')
-                                       skip = true
-                                       continue
-                               end
-                               if text[i + 1] == 'r' then
-                                       res.add('\r')
-                                       skip = true
-                                       continue
-                               end
-                               if text[i + 1] == 't' then
-                                       res.add('\t')
-                                       skip = true
-                                       continue
-                               end
-                       end
-                       res.add char
-               end
-               return res.write_to_string
-       end
-end
-
-redef class Nvalue_object
-       redef fun to_nit_object
-       do
-               var obj = new JsonObject
-               var members = n_members
-               if members != null then
-                       var pairs = members.pairs
-                       for pair in pairs do obj[pair.name] = pair.value
-               end
-               return obj
-       end
-end
-
-redef class Nmembers
-       fun pairs: Array[Npair] is abstract
-end
-
-redef class Nmembers_tail
-       redef fun pairs
-       do
-               var arr = n_members.pairs
-               arr.add n_pair
-               return arr
-       end
-end
-
-redef class Nmembers_head
-       redef fun pairs do return [n_pair]
-end
-
-redef class Npair
-       fun name: String do return n_string.to_nit_string
-       fun value: nullable Jsonable do return n_value.to_nit_object
-end
-
-redef class Nvalue_array
-       redef fun to_nit_object
-       do
-               var arr = new JsonArray
-               var elements = n_elements
-               if elements != null then
-                       var items = elements.items
-                       for item in items do arr.add(item.to_nit_object)
-               end
-               return arr
-       end
-end
-
-redef class Nelements
-       fun items: Array[Nvalue] is abstract
-end
-
-redef class Nelements_tail
-       redef fun items
-       do
-               var items = n_elements.items
-               items.add(n_value)
-               return items
-       end
-end
-
-redef class Nelements_head
-       redef fun items do return [n_value]
-end
-
-redef class Text
-       # Parse a JSON String as Jsonable entities
-       #
-       # Example with `JsonObject`"
-       #
-       #     var obj = "\{\"foo\": \{\"bar\": true, \"goo\": [1, 2, 3]\}\}".to_jsonable
-       #     assert obj isa JsonObject
-       #     assert obj["foo"] isa JsonObject
-       #     assert obj["foo"].as(JsonObject)["bar"] == true
-       #
-       # Example with `JsonArray`
-       #
-       #     var arr = "[1, 2, 3]".to_jsonable
-       #     assert arr isa JsonArray
-       #     assert arr.length == 3
-       #     assert arr.first == 1
-       #     assert arr.last == 3
-       #
-       # Example with `String`
-       #
-       #     var str = "\"foo, bar, baz\"".to_jsonable
-       #     assert str isa String
-       #     assert str == "foo, bar, baz"
-       #
-       # Malformed JSON input returns a `JsonError` object
-       #
-       #     var bad = "\{foo: \"bar\"\}".to_jsonable
-       #     assert bad isa JsonError
-       #     assert bad.error == "JsonLexerError"
-       fun to_jsonable: nullable Jsonable
-       do
-               var lexer = new Lexer_json(to_s)
-               var parser = new Parser_json
-               var tokens = lexer.lex
-               parser.tokens.add_all(tokens)
-               var root_node = parser.parse
-               if root_node isa NStart then
-                       return root_node.n_0.to_nit_object
-               else if root_node isa NLexerError then
-                       var pos = root_node.position
-                       var msg =  "{root_node.message} at {pos or else "<unknown>"} for {root_node}"
-                       return new JsonError("JsonLexerError", msg)
-               else if root_node isa NParserError then
-                       var pos = root_node.position
-                       var msg = "{root_node.message} at {pos or else "<unknown>"} for {root_node}"
-                       return new JsonError("JsonParsingError", msg)
-               else abort
-       end
-end
-
index b84a60b..efe4f6a 100644 (file)
@@ -64,6 +64,7 @@
 module neo4j
 
 import curl_json
+import error
 
 # Handles Neo4j server start and stop command
 #
@@ -336,29 +337,31 @@ class Neo4jClient
        # Parse the cURL `response` as a JSON string
        private fun parse_response(response: CurlResponse): Jsonable do
                if response isa CurlResponseSuccess then
-                       if response.body_str.is_empty then
+                       var str = response.body_str
+                       if str.is_empty then return new JsonObject
+                       var res = str.parse_json
+                       if res isa JsonParseError then
+                               var e = new NeoError(res.to_s, "JsonParseError")
+                               e.cause = res
+                               return e
+                       end
+                       if res == null then
+                               # empty response wrap it in empty object
                                return new JsonObject
-                       else
-                               var str = response.body_str
-                               var res = str.to_jsonable
-                               if res == null then
-                                       # empty response wrap it in empty object
-                                       return new JsonObject
-                               else if res isa JsonObject and res.has_key("exception") then
-                                       var error = "Neo4jError::{res["exception"] or else "null"}"
-                                       var msg = ""
-                                       if res.has_key("message") then
-                                               msg = res["message"].to_s
-                                       end
-                                       return new JsonError(error, msg.to_json)
-                               else
-                                       return res
+                       else if res isa JsonObject and res.has_key("exception") then
+                               var error = "Neo4jError::{res["exception"] or else "null"}"
+                               var msg = ""
+                               if res.has_key("message") then
+                                       msg = res["message"].to_s
                                end
+                               return new NeoError(msg, error)
+                       else
+                               return res
                        end
                else if response isa CurlResponseFailed then
-                       return new JsonError("Curl error", "{response.error_msg} ({response.error_code})")
+                       return new NeoError("{response.error_msg} ({response.error_code})", "CurlError")
                else
-                       return new JsonError("Curl error", "Unexpected response '{response}'")
+                       return new NeoError("Unexpected response \"{response}\".", "CurlError")
                end
        end
 end
@@ -896,7 +899,7 @@ class NeoBatch
        fun save_edges(edges: Collection[NeoEdge]) do for edge in edges do save_edge(edge)
 
        # Execute the batch and update local nodes
-       fun execute: List[JsonError] do
+       fun execute: List[NeoError] do
                var request = new JsonPOST(client.batch_url, client.curl)
                # request.headers["X-Stream"] = "true"
                var json_jobs = new JsonArray
@@ -908,16 +911,16 @@ class NeoBatch
        end
 
        # Associate data from response in original nodes and edges
-       private fun finalize_batch(response: Jsonable): List[JsonError] do
-               var errors = new List[JsonError]
+       private fun finalize_batch(response: Jsonable): List[NeoError] do
+               var errors = new List[NeoError]
                if not response isa JsonArray then
-                       errors.add(new JsonError("Neo4jError", "Unexpected batch response format"))
+                       errors.add(new NeoError("Unexpected batch response format.", "Neo4jError"))
                        return errors
                end
                # print " {res.length} jobs executed"
                for res in response do
                        if not res isa JsonObject then
-                               errors.add(new JsonError("Neo4jError", "Unexpected job format in batch response"))
+                               errors.add(new NeoError("Unexpected job format in batch response.", "Neo4jError"))
                                continue
                        end
                        var id = res["id"].as(Int)
diff --git a/lib/pthreads/extra.nit b/lib/pthreads/extra.nit
new file mode 100644 (file)
index 0000000..416b174
--- /dev/null
@@ -0,0 +1,62 @@
+# This file is part of NIT (http://www.nitlanguage.org).
+#
+# Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Offers some POSIX threads services that are not available on all platforms
+module extra is
+       c_compiler_option("-pthread")
+       c_linker_option("-pthread")
+end
+
+intrude import pthreads
+
+in "C" `{
+       // TODO protect with: #ifdef WITH_LIBGC
+       #ifndef ANDROID
+               #define GC_THREADS
+               #include <gc.h>
+       #endif
+`}
+
+redef extern class NativePthread
+       fun cancel: Bool `{
+               return pthread_cancel(*recv);
+       `}
+end
+
+redef class Thread
+       # Cancel the execution of the thread
+       fun cancel
+       do
+               if native == null then return
+               native.cancel
+               native = null
+       end
+end
+
+# Does not return if the running thread is to be cancelled
+fun test_cancel `{ pthread_testcancel(); `}
+
+private extern class NativePthreadBarrier in "C" `{ pthread_barrier_t * `}
+       new(count: Int) `{
+               pthread_barrier_t *barrier = malloc(sizeof(pthread_barrier_t));
+               int res = pthread_barrier_init(barrier, NULL, count);
+               return barrier;
+       `}
+
+       fun destroy `{ pthread_barrier_destroy(recv); `}
+
+       fun wait `{ pthread_barrier_wait(recv); `}
+end
index 1e76e28..94cf078 100644 (file)
@@ -34,9 +34,10 @@ in "C" `{
        // TODO protect with: #ifdef WITH_LIBGC
        // We might have to add the next line to gc_chooser.c too, especially
        // if we get an error like "thread not registered with GC".
+       #ifndef ANDROID
                #define GC_THREADS
                #include <gc.h>
-       //#endif
+       #endif
 `}
 
 redef class Sys
@@ -114,10 +115,6 @@ private extern class NativePthread in "C" `{ pthread_t * `}
                return (nullable_Object)thread_return;
        `}
 
-       fun cancel: Bool `{
-               return pthread_cancel(*recv);
-       `}
-
        fun attr: NativePthreadAttr `{
                pthread_attr_t *pattr = malloc(sizeof(pthread_attr_t));
                pthread_getattr_np(*recv, pattr);
@@ -192,18 +189,6 @@ private extern class NativePthreadMutexAttr in "C" `{ pthread_mutexattr_t * `}
        # pthread_mutexattr_setrobust_np
 end
 
-private extern class NativePthreadBarrier in "C" `{ pthread_barrier_t * `}
-       new(count: Int) `{
-               pthread_barrier_t *barrier = malloc(sizeof(pthread_barrier_t));
-               int res = pthread_barrier_init(barrier, NULL, count);
-               return barrier;
-       `}
-
-       fun destroy `{ pthread_barrier_destroy(recv); `}
-
-       fun wait `{ pthread_barrier_wait(recv); `}
-end
-
 private extern class NativePthreadKey in "C" `{ pthread_key_t * `}
        new `{
                pthread_key_t *key = malloc(sizeof(pthread_key_t));
@@ -222,6 +207,27 @@ private extern class NativePthreadKey in "C" `{ pthread_key_t * `}
        `}
 end
 
+private extern class NativePthreadCond in "C" `{ pthread_cond_t * `}
+       new `{
+               pthread_cond_t cond;
+               int r = pthread_cond_init(&cond, NULL);
+               if (r == 0) {
+                       pthread_cond_t *pcond = malloc(sizeof(pthread_cond_t));
+                       memmove(pcond, &cond, sizeof(pthread_cond_t));
+                       return pcond;
+               }
+               return NULL;
+       `}
+
+       fun destroy `{ pthread_cond_destroy(recv); `}
+
+       fun signal `{ pthread_cond_signal(recv); `}
+
+       fun broadcast `{ pthread_cond_broadcast(recv);  `}
+
+       fun wait(mutex: NativePthreadMutex) `{ pthread_cond_wait(recv, mutex); `}
+end
+
 #
 ## Nity part
 #
@@ -272,14 +278,6 @@ abstract class Thread
                return r
        end
 
-       # Cancel the execution of the thread
-       fun cancel
-       do
-               if native == null then return
-               native.cancel
-               native = null
-       end
-
        redef fun finalize
        do
                if native == null then return
@@ -298,9 +296,6 @@ end
 # Exit current thread and return `value` to caller of `Thread::join`
 fun exit_thread(value: nullable Object) `{ pthread_exit(value); `}
 
-# Does not return if the running thread is to be cancelled
-fun test_cancel `{ pthread_testcancel(); `}
-
 # Returns the handle to the running `Thread`
 fun thread: Thread
 do
@@ -364,24 +359,36 @@ end
 class Barrier
        super Finalizable
 
+       private var mutex = new Mutex
+       private var cond: nullable NativePthreadCond = new NativePthreadCond
+
        # Number of threads that must be waiting for `wait` to unblock
        var count: Int
 
-       private var native: nullable NativePthreadBarrier is noinit
-
-       init do native = new NativePthreadBarrier(count)
+       private var threads_waiting = 0
 
        # Wait at this barrier and block until there are a `count` threads waiting
-       fun wait do native.wait
+       fun wait
+       do
+               mutex.lock
+               threads_waiting += 1
+               if threads_waiting == count then
+                       threads_waiting = 0
+                       cond.broadcast
+               else
+                       cond.wait(mutex.native.as(not null))
+               end
+               mutex.unlock
+       end
 
        redef fun finalize
        do
-               var native = self.native
-               if native != null then
-                       native.destroy
-                       native.free
+               var cond = self.cond
+               if cond != null then
+                       cond.destroy
+                       cond.free
                end
-               self.native = null
+               self.cond = null
        end
 end
 
index 7d710bd..ca6e9fa 100644 (file)
@@ -251,7 +251,6 @@ end
 #     assert a == b
 class Array[E]
        super AbstractArray[E]
-       super ArrayCapable[E]
 
        redef fun [](index)
        do
@@ -286,7 +285,7 @@ class Array[E]
                var c = _capacity
                if cap <= c then return
                while c <= cap do c = c * 2 + 2
-               var a = calloc_array(c)
+               var a = new NativeArray[E](c)
                if _capacity > 0 then _items.copy_to(a, _length)
                _items = a
                _capacity = c
@@ -317,7 +316,7 @@ class Array[E]
        init with_capacity(cap: Int)
        do
                assert positive: cap >= 0
-               _items = calloc_array(cap)
+               _items = new NativeArray[E](cap)
                _capacity = cap
                _length = 0
        end
@@ -326,7 +325,7 @@ class Array[E]
        init filled_with(value: E, count: Int)
        do
                assert positive: count >= 0
-               _items = calloc_array(count)
+               _items = new NativeArray[E](count)
                _capacity = count
                _length = count
                var i = 0
@@ -769,12 +768,6 @@ end
 
 # Native classes ##############################################################
 
-# Subclasses of this class can create native arrays
-interface ArrayCapable[E]
-       # Get a new array of `size` elements.
-       protected fun calloc_array(size: Int): NativeArray[E] is intern
-end
-
 # Native Nit array
 # Access are unchecked and it has a fixed size
 # Not for public use: may become private.
index 6490024..92b8d5c 100644 (file)
@@ -16,8 +16,8 @@ module hash_collection
 import array
 
 # A HashCollection is an array of HashNode[K] indexed by the K hash value
-private abstract class HashCollection[K: Object, N: HashNode[Object]]
-       super ArrayCapable[nullable N]
+private abstract class HashCollection[K: Object]
+       type N: HashNode[K]
 
        private var array: nullable NativeArray[nullable N] = null # Used to store items
        private var capacity: Int = 0 # Size of _array
@@ -163,7 +163,7 @@ private abstract class HashCollection[K: Object, N: HashNode[Object]]
                _last_accessed_key = null
 
                # get a new array
-               var new_array = calloc_array(cap)
+               var new_array = new NativeArray[nullable N](cap)
                _array = new_array
 
                # clean the new array
@@ -203,7 +203,9 @@ end
 # Keys of such a map cannot be null and require a working `hash` method
 class HashMap[K: Object, V]
        super Map[K, V]
-       super HashCollection[K, HashMapNode[K, V]]
+       super HashCollection[K]
+
+       redef type N: HashMapNode[K, V] is fixed
 
        redef fun [](key)
        do
@@ -389,7 +391,9 @@ end
 # Keys of such a map cannot be null and require a working `hash` method
 class HashSet[E: Object]
        super Set[E]
-       super HashCollection[E, HashSetNode[E]]
+       super HashCollection[E]
+
+       redef type N: HashSetNode[E] is fixed
 
        redef fun length do return _the_length
 
index 2e9c9ae..a179e9a 100644 (file)
@@ -30,6 +30,28 @@ import end # Mark this module is a top level one. (must be only one)
 #
 # Currently, Object is also used to collect all top-level methods.
 interface Object
+       # Type of this instance, automatically specialized in every class
+       #
+       # A common use case of the virtual type `SELF` is to type an attribute and
+       # store another instance of the same type as `self`. It can also be used as as
+       # return type to a method producing a copy of `self` or returning an instance
+       # expected to be the exact same type as self.
+       #
+       # This virtual type must be used with caution as it can hinder specialization.
+       # In fact, it imposes strict restrictions on all sub-classes and their usage.
+       # For example, using `SELF` as a return type of a method `foo`
+       # forces all subclasses to ensure that `foo` returns the correct and updated
+       # type.
+       # A dangerous usage take the form of a method typed by `SELF` which creates
+       # and returns a new instance.
+       # If not correctly specialized, this method would break when invoked on a
+       # sub-class.
+       #
+       # A general rule for safe usage of `SELF` is to ensure that inputs typed
+       # `SELF` are stored in attributes typed `SELF` and returned by methods typed
+       # `SELF`, pretty much the same things as you would do with parameter types.
+       type SELF: Object
+
        # The unique object identifier in the class.
        # Unless specific code, you should not use this method.
        # The identifier is used internally to provide a hash value.
@@ -331,6 +353,21 @@ universal Float
                end
        end
 
+       # Compare float numbers with a given precision.
+       #
+       # Because of the loss of precision in floating numbers,
+       # the `==` method is often not the best way to compare them.
+       #
+       # ~~~
+       # assert 0.01.is_approx(0.02, 0.1)   == true
+       # assert 0.01.is_approx(0.02, 0.001) == false
+       # ~~~
+       fun is_approx(other, precision: Float): Bool
+       do
+               assert precision >= 0.0
+               return self <= other + precision and self >= other - precision
+       end
+
        redef fun max(other)
        do
                if self < other then
index 2bbb6aa..6214a65 100644 (file)
@@ -138,6 +138,14 @@ redef class Float
        #     assert 2.0.floor == 2.0
        #     assert (-1.5).floor == -2.0
        fun floor: Float `{ return floor(recv); `}
+
+       # Rounds the value of a float to its nearest integer value
+       #
+       #     assert 1.67.round == 2.0
+       #     assert 1.34.round == 1.0
+       #     assert -1.34.round == -1.0
+       #     assert -1.67.round == -2.0
+       fun round: Float is extern "round"
        
        # Returns a random `Float` in `[0.0 .. self[`.
        fun rand: Float is extern "kernel_Float_Float_rand_0"
index 0ffb303..48cb238 100644 (file)
@@ -30,7 +30,6 @@ intrude import collection::array
 # High-level abstraction for all text representations
 abstract class Text
        super Comparable
-       super StringCapable
 
        redef type OTHER: Text
 
@@ -992,7 +991,7 @@ class FlatString
 
        redef fun reversed
        do
-               var native = calloc_string(self.length + 1)
+               var native = new NativeString(self.length + 1)
                var length = self.length
                var items = self.items
                var pos = 0
@@ -1030,7 +1029,7 @@ class FlatString
 
        redef fun to_upper
        do
-               var outstr = calloc_string(self.length + 1)
+               var outstr = new NativeString(self.length + 1)
                var out_index = 0
 
                var myitems = self.items
@@ -1050,7 +1049,7 @@ class FlatString
 
        redef fun to_lower
        do
-               var outstr = calloc_string(self.length + 1)
+               var outstr = new NativeString(self.length + 1)
                var out_index = 0
 
                var myitems = self.items
@@ -1095,7 +1094,7 @@ class FlatString
                if real_items != null then
                        return real_items.as(not null)
                else
-                       var newItems = calloc_string(length + 1)
+                       var newItems = new NativeString(length + 1)
                        self.items.copy_to(newItems, length, index_from, 0)
                        newItems[length] = '\0'
                        self.real_items = newItems
@@ -1173,7 +1172,7 @@ class FlatString
 
                var total_length = my_length + its_length
 
-               var target_string = calloc_string(my_length + its_length + 1)
+               var target_string = new NativeString(my_length + its_length + 1)
 
                self.items.copy_to(target_string, my_length, index_from, 0)
                if s isa FlatString then
@@ -1204,7 +1203,7 @@ class FlatString
 
                var my_items = self.items
 
-               var target_string = calloc_string((final_length) + 1)
+               var target_string = new NativeString(final_length + 1)
 
                target_string[final_length] = '\0'
 
@@ -1502,7 +1501,7 @@ class FlatBuffer
                # The COW flag can be set at false here, since
                # it does a copy of the current `Buffer`
                written = false
-               var a = calloc_string(c+1)
+               var a = new NativeString(c+1)
                if length > 0 then items.copy_to(a, length, 0, 0)
                items = a
                capacity = c
@@ -1518,7 +1517,7 @@ class FlatBuffer
        redef fun to_cstring
        do
                if is_dirty then
-                       var new_native = calloc_string(length + 1)
+                       var new_native = new NativeString(length + 1)
                        new_native[length] = '\0'
                        if length > 0 then items.copy_to(new_native, length, 0, 0)
                        real_items = new_native
@@ -1534,7 +1533,7 @@ class FlatBuffer
        do
                capacity = s.length + 1
                length = s.length
-               items = calloc_string(capacity)
+               items = new NativeString(capacity)
                if s isa FlatString then
                        s.items.copy_to(items, length, s.index_from, 0)
                else if s isa FlatBuffer then
@@ -1554,7 +1553,7 @@ class FlatBuffer
        do
                assert cap >= 0
                # _items = new NativeString.calloc(cap)
-               items = calloc_string(cap+1)
+               items = new NativeString(cap+1)
                capacity = cap
                length = 0
        end
@@ -1611,7 +1610,7 @@ class FlatBuffer
        redef fun reverse
        do
                written = false
-               var ns = calloc_string(capacity)
+               var ns = new NativeString(capacity)
                var si = length - 1
                var ni = 0
                var it = items
@@ -1682,7 +1681,6 @@ end
 
 private class FlatBufferCharView
        super BufferCharView
-       super StringCapable
 
        redef type SELFTYPE: FlatBuffer
 
@@ -2118,7 +2116,6 @@ end
 
 # Native strings are simple C char *
 extern class NativeString `{ char* `}
-       super StringCapable
        # Creates a new NativeString with a capacity of `length`
        new(length: Int) is intern
        fun [](index: Int): Char is intern
@@ -2150,7 +2147,7 @@ extern class NativeString `{ char* `}
        fun to_s_with_copy: FlatString
        do
                var length = cstring_length
-               var new_self = calloc_string(length + 1)
+               var new_self = new NativeString(length + 1)
                copy_to(new_self, length, 0, 0)
                var str = new FlatString.with_infos(new_self, length, 0, length - 1)
                new_self[length] = '\0'
@@ -2159,11 +2156,6 @@ extern class NativeString `{ char* `}
        end
 end
 
-# StringCapable objects can create native strings
-interface StringCapable
-       protected fun calloc_string(size: Int): NativeString is intern
-end
-
 redef class Sys
        private var args_cache: nullable Sequence[String]
 
index 5feef35..b7a74ce 100644 (file)
@@ -214,7 +214,7 @@ redef class FlatString
        redef fun to_cstring
        do
                if real_items != null then return real_items.as(not null)
-               var new_items = calloc_string(bytelen + 1)
+               var new_items = new NativeString(bytelen + 1)
                self.items.copy_to(new_items, bytelen, index[index_from].pos, 0)
                new_items[bytelen] = '\0'
                self.real_items = new_items
@@ -245,7 +245,7 @@ redef class FlatString
 
        redef fun reversed
        do
-               var native = calloc_string(self.bytelen + 1)
+               var native = new NativeString(self.bytelen + 1)
                var length = self.length
                var index = self.index
                var pos = 0
@@ -278,7 +278,7 @@ redef class FlatString
                var my_real_len = length
                var my_real_fin_len = my_real_len * i
 
-               var target_string = calloc_string((finlen) + 1)
+               var target_string = new NativeString((finlen) + 1)
 
                var my_index = index
                var new_index = new StringIndex(my_real_fin_len)
@@ -300,7 +300,7 @@ redef class FlatString
 
        redef fun to_upper
        do
-               var outstr = calloc_string(self.bytelen + 1)
+               var outstr = new NativeString(self.bytelen + 1)
 
                var out_index = 0
                var index = self.index
@@ -322,7 +322,7 @@ redef class FlatString
 
        redef fun to_lower
        do
-               var outstr = calloc_string(self.bytelen + 1)
+               var outstr = new NativeString(self.bytelen + 1)
 
                var out_index = 0
                var index = self.index
@@ -406,7 +406,7 @@ redef class NativeString
                var real_len = new Container[Int](0)
                var length = cstring_length
                var x = make_index(length, real_len)
-               var new_self = calloc_string(length + 1)
+               var new_self = new NativeString(length + 1)
                copy_to(new_self, length, 0, 0)
                return new FlatString.with_infos_index(new_self, real_len.item, 0, real_len.item - 1, x, length)
        end
index c473a3c..db51f0a 100644 (file)
@@ -401,7 +401,7 @@ redef class FlatString
        end
 
        redef fun reversed do
-               var new_str = calloc_string(bytelen)
+               var new_str = new NativeString(bytelen)
                var s_pos = bytelen
                var my_pos = index_from
                var its = items
@@ -415,7 +415,7 @@ redef class FlatString
        end
 
        redef fun to_upper do
-               var ns = calloc_string(bytelen)
+               var ns = new NativeString(bytelen)
                var offset = 0
                for i in [0 .. length[
                do
@@ -427,7 +427,7 @@ redef class FlatString
        end
 
        redef fun to_lower do
-               var ns = calloc_string(bytelen)
+               var ns = new NativeString(bytelen)
                var offset = 0
                for i in [0 .. length[
                do
@@ -441,7 +441,7 @@ redef class FlatString
        redef fun +(o) do
                if o isa Buffer then o = o.to_s
                if o isa FlatString then
-                       var new_str = calloc_string(bytelen + o.bytelen + 1)
+                       var new_str = new NativeString(bytelen + o.bytelen + 1)
                        var new_bytelen = bytelen + o.bytelen
                        new_str[new_bytelen] = '\0'
                        var newlen = length + o.length
@@ -461,7 +461,7 @@ redef class FlatString
                var new_bytelen = mybtlen * i
                var mylen = length
                var newlen = mylen * i
-               var ns = calloc_string(new_bytelen + 1)
+               var ns = new NativeString(new_bytelen + 1)
                ns[new_bytelen] = '\0'
                var offset = 0
                while i > 0 do
@@ -499,7 +499,7 @@ redef class FlatString
 
        redef fun to_cstring do
                if real_items != null then return real_items.as(not null)
-               var new_items = calloc_string(bytelen + 1)
+               var new_items = new NativeString(bytelen + 1)
                self.items.copy_to(new_items, bytelen, index_from, 0)
                new_items[bytelen] = '\0'
                self.real_items = new_items
@@ -523,7 +523,7 @@ redef class FlatBuffer
                        with_capacity(50)
                        for i in s.substrings do self.append(i)
                end
-               items = calloc_string(s.bytelen)
+               items = new NativeString(s.bytelen)
                if s isa FlatString then
                        s.items.copy_to(items, s.bytelen, s.index_from, 0)
                else
@@ -611,7 +611,7 @@ redef class FlatBuffer
                var c = capacity
                if cap <= c then return
                while c <= cap do c = c * 2 + 2
-               var a = calloc_string(c+1)
+               var a = new NativeString(c+1)
                if bytelen > 0 then items.copy_to(a, bytelen, 0, 0)
                items = a
                capacity = c
@@ -635,7 +635,7 @@ redef class FlatBuffer
 
        redef fun reverse
        do
-               var nns = calloc_string(bytelen)
+               var nns = new NativeString(bytelen)
                var ns = items
                var btlen = bytelen
                var myp = 0
@@ -701,7 +701,7 @@ redef class FlatBuffer
        end
 
        redef fun to_cstring do
-               var ns = calloc_string(bytelen)
+               var ns = new NativeString(bytelen)
                items.copy_to(ns, bytelen, 0, 0)
                return ns
        end
@@ -723,7 +723,7 @@ redef class NativeString
        redef fun to_s_with_copy
        do
                var length = cstring_length
-               var new_self = calloc_string(length + 1)
+               var new_self = new NativeString(length + 1)
                copy_to(new_self, length, 0, 0)
                return new FlatString.with_bytelen(new_self, 0, length - 1, length)
        end
index d05663e..6626036 100644 (file)
@@ -42,7 +42,7 @@ end
 class TreeNode[K: Comparable, E]
 
        # TreeNode type
-       type SELF: TreeNode[K, E]
+       type N: TreeNode[K, E]
 
        # `key` for this node
        var key: K
@@ -51,7 +51,7 @@ class TreeNode[K: Comparable, E]
        var value: E
 
        # Direct parent of this node (null if the node is root)
-       var parent: nullable SELF = null is writable
+       var parent: nullable N = null is writable
 
        redef fun to_s do return "\{{value or else ""}\}"
 
index b689700..4c647b3 100644 (file)
@@ -370,38 +370,38 @@ class BinTreeNode[K: Comparable, E]
        private var prev: nullable BinTreeNode[K, E]
        private var next: nullable BinTreeNode[K, E]
 
-       redef type SELF: BinTreeNode[K, E]
+       redef type N: BinTreeNode[K, E]
 
        init(key: K, item: E) do
                super(key, item)
        end
 
-       private var left_node: nullable SELF = null
+       private var left_node: nullable N = null
 
        # `left` tree node child (null if node has no left child)
-       fun left: nullable SELF do return left_node
+       fun left: nullable N do return left_node
 
        # set `left` child for this node (or null if left no child)
        # ENSURE: node.key < key (only if node != null)
-       fun left=(node: nullable SELF) do
+       fun left=(node: nullable N) do
                #assert node != null implies node.key < key
                left_node = node
        end
 
-       private var right_node: nullable SELF = null
+       private var right_node: nullable N = null
 
        # `right` tree node child (null if node has no right child)
-       fun right: nullable SELF do return right_node
+       fun right: nullable N do return right_node
 
        # set `right` child for this node (or null if right no child)
        # ENSURE: node.key < key (only if node != null)
-       fun right=(node: nullable SELF) do
+       fun right=(node: nullable N) do
                #assert node != null implies node.key > key
                right_node = node
        end
 
        # `parent` of the `parent` of this node (null if root)
-       fun grandparent: nullable SELF do
+       fun grandparent: nullable N do
                if parent == null then
                        return null
                else
@@ -411,7 +411,7 @@ class BinTreeNode[K: Comparable, E]
 
        # Other child of the `grandparent`
        # `left` or `right` depends on the position of the current node against its parent
-       fun uncle: nullable SELF do
+       fun uncle: nullable N do
                var g = grandparent
                if g == null then
                        return null
@@ -426,7 +426,7 @@ class BinTreeNode[K: Comparable, E]
 
        # Other child of the parent
        # `left` or `right` depends on the position of the current node against its parent
-       fun sibling: nullable SELF do
+       fun sibling: nullable N do
                if parent == null then
                        return null
                else if self == parent.left then
index ce17feb..e1fe413 100644 (file)
@@ -130,7 +130,7 @@ end
 class RBTreeNode[K: Comparable, E]
        super BinTreeNode[K, E]
 
-       redef type SELF: RBTreeNode[K, E]
+       redef type N: RBTreeNode[K, E]
 
        # Is the node red?
        private var is_red = true
index cc24d6e..ad34b9a 100644 (file)
@@ -44,6 +44,9 @@ class AndroidProject
        # Custom lines to add to the AndroidManifest.xml in the <application> node
        var manifest_application_lines = new Array[String]
 
+       # Custom lines to add to AndroidManifest.xml as attributes inside the <activity> node
+       var manifest_activity_attributes = new Array[String]
+
        # Minimum API level required for the application to run
        var min_api: nullable Int = null
 
@@ -107,6 +110,9 @@ redef class ModelBuilder
                annots = collect_annotations_on_modules("android_manifest_application", mmodule)
                for an in annots do project.manifest_application_lines.add an.arg_as_string(self) or else ""
 
+               annots = collect_annotations_on_modules("android_manifest_activity", mmodule)
+               for an in annots do project.manifest_activity_attributes.add an.arg_as_string(self) or else ""
+
                # Get the date and time (down to the minute) as string
                var local_time = new Tm.localtime
                var local_time_s = local_time.strftime("%y%m%d%H%M")
index 55468fd..1cf6ba8 100644 (file)
@@ -124,10 +124,12 @@ class AndroidToolchain
 
                # Also copy over the java files
                dir = "{android_project_root}/src/"
-               var extra_java_files = compiler.mainmodule.extra_java_files
-               if extra_java_files != null then for file in extra_java_files do
-                       var path = file.filename
-                       path.file_copy_to("{dir}/{path.basename("")}")
+               for mmodule in compiler.mainmodule.in_importation.greaters do
+                       var extra_java_files = mmodule.extra_java_files
+                       if extra_java_files != null then for file in extra_java_files do
+                               var path = file.filename
+                               path.file_copy_to(dir/path.basename(""))
+                       end
                end
 
                ## Generate delagating makefile
@@ -178,13 +180,11 @@ $(call import-module,android/native_app_glue)
              This will take care of integrating with our NDK code. -->
         <activity android:name="android.app.NativeActivity"
                 android:label="@string/app_name"
-                android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
-                android:configChanges="orientation|keyboardHidden"
-                android:screenOrientation="portrait"
+                {{{project.manifest_activity_attributes.join("\n")}}}
                 {{{icon_declaration}}}>
-            <!-- Tell NativeActivity the name of or .so -->
-            <meta-data android:name=\"{{{app_package}}}\"
-                    android:value=\"{{{app_name}}}\" />
+            <!-- Tell NativeActivity the name of our .so -->
+            <meta-data android:name=\"android.app.lib_name\"
+                    android:value=\"main\" />
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
index 510b1ac..94dda37 100644 (file)
@@ -396,6 +396,7 @@ class GlobalCompilerVisitor
        redef fun native_array_instance(elttype: MType, length: RuntimeVariable): RuntimeVariable
        do
                var ret_type = self.get_class("NativeArray").get_mtype([elttype])
+               ret_type = anchor(ret_type).as(MClassType)
                return self.new_expr("NEW_{ret_type.c_name}({length})", ret_type)
        end
 
index 5d71b02..78ccd7f 100644 (file)
@@ -17,6 +17,7 @@ module doc_pages
 
 import toolcontext
 import doc_model
+private import json::static
 
 redef class ToolContext
        private var opt_dir = new OptionString("output directory", "-d", "--dir")
@@ -182,9 +183,7 @@ end
 # All entities are grouped by name to make the research easier.
 class QuickSearch
 
-       private var mmodules = new HashSet[MModule]
-       private var mclasses = new HashSet[MClass]
-       private var mpropdefs = new HashMap[String, Set[MPropDef]]
+       private var table = new QuickSearchTable
 
        var ctx: ToolContext
        var model: Model
@@ -192,51 +191,72 @@ class QuickSearch
        init do
                for mmodule in model.mmodules do
                        if mmodule.is_fictive then continue
-                       mmodules.add mmodule
+                       add_result_for(mmodule.name, mmodule.full_name, mmodule.nitdoc_url)
                end
                for mclass in model.mclasses do
                        if mclass.visibility < ctx.min_visibility then continue
-                       mclasses.add mclass
+                       add_result_for(mclass.name, mclass.full_name, mclass.nitdoc_url)
                end
                for mproperty in model.mproperties do
                        if mproperty.visibility < ctx.min_visibility then continue
                        if mproperty isa MAttribute then continue
-                       if not mpropdefs.has_key(mproperty.name) then
-                               mpropdefs[mproperty.name] = new HashSet[MPropDef]
+                       for mpropdef in mproperty.mpropdefs do
+                               var full_name = mpropdef.mclassdef.mclass.full_name
+                               var cls_url = mpropdef.mclassdef.mclass.nitdoc_url
+                               var def_url = "{cls_url}#{mpropdef.mproperty.nitdoc_id}"
+                               add_result_for(mproperty.name, full_name, def_url)
                        end
-                       mpropdefs[mproperty.name].add_all(mproperty.mpropdefs)
                end
        end
 
+       private fun add_result_for(query: String, txt: String, url: String) do
+               table[query].add new QuickSearchResult(txt, url)
+       end
+
        fun render: Template do
                var tpl = new Template
-               tpl.add "var nitdocQuickSearchRawList=\{ "
-               for mmodule in mmodules do
-                       tpl.add "\"{mmodule.name}\":["
-                       tpl.add "\{txt:\"{mmodule.full_name}\",url:\"{mmodule.nitdoc_url}\"\},"
-                       tpl.add "],"
-               end
-               for mclass in mclasses do
-                       var full_name = mclass.intro.mmodule.full_name
-                       tpl.add "\"{mclass.name}\":["
-                       tpl.add "\{txt:\"{full_name}\",url:\"{mclass.nitdoc_url}\"\},"
-                       tpl.add "],"
-               end
-               for mproperty, mprops in mpropdefs do
-                       tpl.add "\"{mproperty}\":["
-                       for mpropdef in mprops do
-                               var full_name = mpropdef.mclassdef.mclass.full_name
-                               var cls_url = mpropdef.mclassdef.mclass.nitdoc_url
-                               var def_url = "{cls_url}#{mpropdef.mproperty.nitdoc_id}"
-                               tpl.add "\{txt:\"{full_name}\",url:\"{def_url}\"\},"
-                       end
-                       tpl.add "],"
-               end
-               tpl.add " \};"
+               var buffer = new RopeBuffer
+               tpl.add buffer
+               buffer.append "var nitdocQuickSearchRawList="
+               table.append_json buffer
+               buffer.append ";"
                return tpl
        end
 end
 
+# The result map for QuickSearch.
+private class QuickSearchTable
+       super JsonMapRead[String, QuickSearchResultList]
+       super HashMap[String, QuickSearchResultList]
+
+       redef fun provide_default_value(key) do
+               var v = new QuickSearchResultList
+               self[key] = v
+               return v
+       end
+end
+
+# A QuickSearch result list.
+private class QuickSearchResultList
+       super JsonSequenceRead[QuickSearchResult]
+       super Array[QuickSearchResult]
+end
+
+# A QuickSearch result.
+private class QuickSearchResult
+       super Jsonable
+
+       # The text of the link.
+       var txt: String
+
+       # The destination of the link.
+       var url: String
+
+       redef fun to_json do
+               return "\{\"txt\":{txt.to_json},\"url\":{url.to_json}\}"
+       end
+end
+
 # Nitdoc base page
 # Define page structure and properties
 abstract class NitdocPage
index 2879ac9..476608c 100644 (file)
@@ -901,6 +901,8 @@ redef class AMethPropdef
                                return v.bool_instance(args[0].to_f.is_nan)
                        else if pname == "is_inf_extern" then
                                return v.bool_instance(args[0].to_f.is_inf != 0)
+                       else if pname == "round" then
+                               return v.float_instance(args[0].to_f.round)
                        end
                else if cname == "NativeString" then
                        if pname == "new" then
index cb56d38..81b3245 100644 (file)
@@ -54,6 +54,7 @@ redef class ModelBuilder
                        build_properties(mclassdef2nclassdef[superclassdef])
                end
 
+               mclassdef.build_self_type(self, nclassdef)
                for nclassdef2 in nclassdef.all_defs do
                        for npropdef in nclassdef2.n_propdefs do
                                npropdef.build_property(self, mclassdef)
@@ -310,6 +311,50 @@ redef class MClassDef
        # What is the `APropdef` associated to a `MProperty`?
        # Used to check multiple definition of a property.
        var mprop2npropdef: Map[MProperty, APropdef] = new HashMap[MProperty, APropdef]
+
+       # Build the virtual type `SELF` only for introduction `MClassDef`
+       fun build_self_type(modelbuilder: ModelBuilder, nclassdef: AClassdef)
+       do
+               if not is_intro then return
+
+               var name = "SELF"
+               var mprop = modelbuilder.try_get_mproperty_by_name(nclassdef, self, name)
+
+               # If SELF type is declared nowherer?
+               if mprop == null then return
+
+               # SELF is not a virtual type? it is weird but we ignore it
+               if not mprop isa MVirtualTypeProp then return
+
+               # Is this the intro of SELF in the library?
+               var intro = mprop.intro
+               var intro_mclassdef = intro.mclassdef
+               if intro_mclassdef == self then
+                       var nintro = modelbuilder.mpropdef2npropdef[intro]
+
+                       # SELF must be declared in Object, otherwise this will create conflicts
+                       if intro_mclassdef.mclass.name != "Object" then
+                               modelbuilder.error(nintro, "Error: the virtual type SELF must be declared in Object.")
+                       end
+
+                       # SELF must be public
+                       if mprop.visibility != public_visibility then
+                               modelbuilder.error(nintro, "Error: the virtual type SELF must be public.")
+                       end
+
+                       # SELF must not be fixed
+                       if intro.is_fixed then
+                               modelbuilder.error(nintro, "Error: the virtual type SELF cannot be fixed.")
+                       end
+
+                       return
+               end
+
+               # This class introduction inherits a SELF
+               # We insert an artificial property to update it
+               var mpropdef = new MVirtualTypeDef(self, mprop, self.location)
+               mpropdef.bound = mclass.mclass_type
+       end
 end
 
 redef class APropdef
diff --git a/tests/base_self_type.nit b/tests/base_self_type.nit
new file mode 100644 (file)
index 0000000..7c5d204
--- /dev/null
@@ -0,0 +1,58 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import kernel
+
+class X
+       fun foo: SELF do return self
+       fun bar(o: SELF) do o.output_class_name
+end
+
+class Y
+       super X
+
+#alt1# redef fun foo do return new X
+#alt2# redef fun foo do return new Y
+end
+
+class A[E]
+       fun foo: Object do return new G[SELF]
+end
+
+class B[F]
+       super A[F]
+end
+
+class G[E:A[nullable Object]]
+end
+
+var x = new X
+x.output_class_name
+x.foo.output_class_name
+x.bar x
+
+var y = new Y
+y.output_class_name
+y.foo.output_class_name
+x.bar y
+y.bar y
+#alt3# y.bar x
+
+var a = new A[Int]
+a.output_class_name
+a.foo.output_class_name
+
+var b = new B[Bool]
+b.output_class_name
+b.foo.output_class_name
index a9dbe22..a5dab0a 100644 (file)
@@ -1 +1 @@
-Runtime error: Assert 'index' failed (../lib/standard/collection/array.nit:258)
+Expected two numbers
diff --git a/tests/sav/base_self_type.res b/tests/sav/base_self_type.res
new file mode 100644 (file)
index 0000000..4c75853
--- /dev/null
@@ -0,0 +1,11 @@
+X
+X
+X
+Y
+Y
+Y
+Y
+A[Int]
+G[A[Int]]
+B[Bool]
+G[B[Bool]]
diff --git a/tests/sav/base_self_type_alt1.res b/tests/sav/base_self_type_alt1.res
new file mode 100644 (file)
index 0000000..467d315
--- /dev/null
@@ -0,0 +1 @@
+alt/base_self_type_alt1.nit:25,25--29: Type error: expected SELF, got X
diff --git a/tests/sav/base_self_type_alt2.res b/tests/sav/base_self_type_alt2.res
new file mode 100644 (file)
index 0000000..4c75853
--- /dev/null
@@ -0,0 +1,11 @@
+X
+X
+X
+Y
+Y
+Y
+Y
+A[Int]
+G[A[Int]]
+B[Bool]
+G[B[Bool]]
diff --git a/tests/sav/base_self_type_alt3.res b/tests/sav/base_self_type_alt3.res
new file mode 100644 (file)
index 0000000..78888be
--- /dev/null
@@ -0,0 +1 @@
+alt/base_self_type_alt3.nit:50,7: Type error: expected Y, got X
index 30a32a5..695948f 100644 (file)
@@ -1 +1 @@
-../lib/standard/kernel.nit:79,1--95,3: Fatal error: kernel#Sys does not specialize module_0#Object. Possible duplication of the root class `Object`?
+../lib/standard/kernel.nit:101,1--117,3: Fatal error: kernel#Sys does not specialize module_0#Object. Possible duplication of the root class `Object`?
diff --git a/tests/sav/nitg-e/base_self_type.res b/tests/sav/nitg-e/base_self_type.res
new file mode 100644 (file)
index 0000000..3c20b42
--- /dev/null
@@ -0,0 +1,11 @@
+X
+X
+X
+Y
+Y
+Y
+Y
+A
+G
+B
+G
diff --git a/tests/sav/nitg-e/base_self_type_alt2.res b/tests/sav/nitg-e/base_self_type_alt2.res
new file mode 100644 (file)
index 0000000..3c20b42
--- /dev/null
@@ -0,0 +1,11 @@
+X
+X
+X
+Y
+Y
+Y
+Y
+A
+G
+B
+G
index 56621b6..980ce17 100644 (file)
@@ -1,4 +1,4 @@
-Runtime error: Cast failed. Expected `OTHER`, got `Float` (../lib/standard/kernel.nit:374)
+Runtime error: Cast failed. Expected `OTHER`, got `Float` (../lib/standard/kernel.nit:411)
 11
 21
 31
index 56621b6..980ce17 100644 (file)
@@ -1,4 +1,4 @@
-Runtime error: Cast failed. Expected `OTHER`, got `Float` (../lib/standard/kernel.nit:374)
+Runtime error: Cast failed. Expected `OTHER`, got `Float` (../lib/standard/kernel.nit:411)
 11
 21
 31
index 56621b6..980ce17 100644 (file)
@@ -1,4 +1,4 @@
-Runtime error: Cast failed. Expected `OTHER`, got `Float` (../lib/standard/kernel.nit:374)
+Runtime error: Cast failed. Expected `OTHER`, got `Float` (../lib/standard/kernel.nit:411)
 11
 21
 31
index 2ebbe38..e95605c 100644 (file)
@@ -12,8 +12,6 @@ redef class Deserializer
                if name == "Array[Serializable]" then return new Array[Serializable].from_deserializer(self)
                if name == "Array[String]" then return new Array[String].from_deserializer(self)
                if name == "Array[Object]" then return new Array[Object].from_deserializer(self)
-               if name == "Array[Match]" then return new Array[Match].from_deserializer(self)
-               if name == "Array[FlatBuffer]" then return new Array[FlatBuffer].from_deserializer(self)
                return super
        end
 end
index af28f14..e248df6 100644 (file)
@@ -41,7 +41,7 @@ Bool [
 Object -> Bool [dir=back arrowtail=open style=dashed];
 
 Float [
- label = "{Float||}"
+ label = "{Float||+ is_approx(other: Float, precision: Float): Bool\l}"
 ]
 Numeric -> Float [dir=back arrowtail=open style=dashed];
 
index 6189897..9a7b30f 100644 (file)
@@ -41,7 +41,7 @@ Bool [
 Object -> Bool [dir=back arrowtail=open style=dashed];
 
 Float [
- label = "{Float||}"
+ label = "{Float||+ is_approx(other: Float, precision: Float): Bool\l}"
 ]
 Numeric -> Float [dir=back arrowtail=open style=dashed];
 
index aa38be4..9533b69 100644 (file)
@@ -1 +1 @@
-Runtime error: Assert failed (../lib/c.nit:64)
+Runtime error: Assert failed (../lib/c.nit:63)
index 95d7a74..17a3dc5 100644 (file)
@@ -1,2 +1,2 @@
-Runtime error: Assert failed (../lib/c.nit:57)
+Runtime error: Assert failed (../lib/c.nit:56)
 0
index 95d7a74..17a3dc5 100644 (file)
@@ -1,2 +1,2 @@
-Runtime error: Assert failed (../lib/c.nit:57)
+Runtime error: Assert failed (../lib/c.nit:56)
 0
index 3bb984c..fdc9a45 100644 (file)
@@ -1,4 +1,4 @@
-Runtime error: Assert failed (../lib/c.nit:56)
+Runtime error: Assert failed (../lib/c.nit:55)
 0
 0
 1
index 0a122d4..22ab94a 100644 (file)
Binary files a/tests/sav/test_json_static.res and b/tests/sav/test_json_static.res differ
index bf79e4b..ba6c5fe 100644 (file)
@@ -1,4 +1,4 @@
-Runtime error: Cast failed. Expected `E`, got `Bool` (../lib/standard/collection/array.nit:789)
+Runtime error: Cast failed. Expected `E`, got `Bool` (../lib/standard/collection/array.nit:782)
 NativeString
 N
 Nit
index 0d15f98..019f202 100644 (file)
@@ -28,7 +28,7 @@ class Toto[E]
 end
 
 class TestNative
-       super ArrayCapable[Int]
+
 
 init
 do
@@ -39,7 +39,7 @@ do
        a[1] = 2
        print(a[0])
        print(a[1])
-       b = calloc_array(5)
+       b = new NativeArray[Int](5)
        b[0]=200
        b[1]=300
        print(b[0])
index b510725..db6b1cf 100644 (file)
@@ -1,6 +1,7 @@
 # This file is part of NIT ( http://www.nitlanguage.org ).
 #
 # Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
+# Copyright 2014 Jean-Christophe Beaupré <jcbrinfo@users.noreply.github.com>
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -22,9 +23,11 @@ end
 
 var a = "\{\"__kind\": \"obj\", \"__id\": 0, \"__class\": \"C\", \"a\": \{\"__kind\": \"obj\", \"__id\": 1, \"__class\": \"A\", \"b\": true, \"c\": \"a\", \"f\": 0.123, \"i\": 1234, \"s\": \"asdf\", \"n\": null, \"array\": [88, \"hello\", null]\}, \"b\": \{\"__kind\": \"obj\", \"__id\": 2, \"__class\": \"B\", \"b\": false, \"c\": \"b\", \"f\": 123.123, \"i\": 2345, \"s\": \"hjkl\", \"n\": null, \"array\": [88, \"hello\", null], \"ii\": 1111, \"ss\": \"qwer\"\}, \"aa\": \{\"__kind\": \"ref\", \"__id\": 1\}\}"
 var b = "\{\"__kind\": \"obj\", \"__id\": 0, \"__class\": \"A\", \"b\": true, \"c\": \"a\", \"f\": 0.123, \"i\": 1234, \"s\": \"asdf\", \"n\": null, \"array\": [88, \"hello\", null]\}"
+var c = "\{\"foo\":\"bar\\\"\\\\\\/\\b\\f\\n\\r\\t\\u0020\\u0000\"\}"
+var d = "\{ \"face with tears of joy\" : \"\\uD83D\\uDE02\" \}"
 
-for s in [a, b] do
-       var obj = s.json_to_nit_object
+for s in [a, b, c, d] do
+       var obj = s.parse_json
        print "# Json: {s}"
        print "# Nit: {obj or else "<null>"}"
 end
index aac7a83..467fa78 100644 (file)
 # limitations under the License.
 
 class Toto
-       super ArrayCapable[Int]
+
 
        fun toto
        do
-               var a = calloc_array(3)
+               var a = new NativeArray[Int](3)
                a[0] = 10
                a[1] = 20
                a[2] = 30
index f8cbd4b..7ee824c 100644 (file)
@@ -43,7 +43,7 @@ assert res1.is_linked
 print res1["name"].to_s
 print res1["age"].to_s
 print res1["status"].to_s
-print res1["groups"].to_s
+print res1["groups"].to_json
 print res1.labels.join(" ")
 assert res1.out_edges.is_empty
 
@@ -92,7 +92,7 @@ assert res4.is_linked
 print res4["name"].to_s
 print res4["age"].to_s
 print res4["status"].to_s
-print res4["groups"].to_s
+print res4["groups"].to_json
 print res4.labels.join(" ")
 assert res4.in_edges.is_empty
 assert not res4.out_edges.is_empty
index e6ac890..7455557 100644 (file)
@@ -65,7 +65,7 @@ assert res4.is_linked
 print res4["name"].to_s
 print res4["age"].to_s
 print res4["status"].to_s
-print res4["groups"].to_s
+print res4["groups"].to_json
 print res4.labels.join(" ")
 assert res4.in_edges.is_empty
 assert not res4.out_edges.is_empty