Merge: Introduces JoinTask, joinabale tasks
authorJean Privat <jean@pryen.org>
Fri, 4 Dec 2015 20:40:14 +0000 (15:40 -0500)
committerJean Privat <jean@pryen.org>
Fri, 4 Dec 2015 20:40:14 +0000 (15:40 -0500)
JoinTask prototype

Pull-Request: #1853
Reviewed-by: Jean Privat <jean@pryen.org>
Reviewed-by: Alexis Laferrière <alexis.laf@xymus.net>

67 files changed:
contrib/benitlux/src/benitlux_daily.nit
contrib/jwrapper/Makefile
contrib/jwrapper/src/model.nit
contrib/nitiwiki/examples/default/config.ini
contrib/nitiwiki/examples/default/templates/header.html
contrib/nitiwiki/examples/nitiwiki/pages/section/other_section/first.md [new file with mode: 0644]
contrib/nitiwiki/examples/nitiwiki/pages/section/other_section/index.md [new file with mode: 0644]
contrib/nitiwiki/examples/nitiwiki/pages/section/other_section/second.md [new file with mode: 0644]
contrib/nitiwiki/examples/nitiwiki/templates/menu.html
contrib/nitiwiki/src/wiki_html.nit
contrib/nitiwiki/src/wiki_links.nit
contrib/nitrpg/src/achievements.nit
contrib/nitrpg/src/events.nit
contrib/nitrpg/src/game.nit
contrib/nitrpg/src/reactors.nit
contrib/nitrpg/src/statistics.nit
contrib/nitrpg/src/templates/templates_base.nit
lib/android/audio.nit
lib/console.nit
lib/core/bytes.nit
lib/core/collection/array.nit
lib/core/collection/circular_array.nit [new file with mode: 0644]
lib/core/collection/collection.nit
lib/core/collection/hash_collection.nit
lib/core/collection/list.nit
lib/core/text/abstract_text.nit
lib/core/text/flat.nit
lib/gen_nit.nit
lib/github/api.nit
lib/github/events.nit
lib/libevent.nit
lib/mongodb/mongodb.nit
lib/mongodb/native_mongodb.nit
lib/nitcorn/examples/.gitignore [new file with mode: 0644]
lib/nitcorn/examples/Makefile
lib/nitcorn/examples/src/restful_annot.nit [new file with mode: 0644]
lib/nitcorn/log.nit [new file with mode: 0644]
lib/nitcorn/restful.nit [new file with mode: 0644]
share/man/nitrestful.md [new file with mode: 0644]
src/Makefile
src/astvalidation.nit
src/ffi/cpp.nit
src/frontend/serialization_phase.nit
src/modelize/modelize_property.nit
src/nitrestful.nit [new file with mode: 0644]
src/nitserial.nit
src/rapid_type_analysis.nit
tests/base_prot_sig.nit
tests/bench_seq.nit [new file with mode: 0644]
tests/niti.skip
tests/nitrestful.args [new file with mode: 0644]
tests/nitvm.skip
tests/sav/base_prot_sig_alt1.res
tests/sav/base_prot_sig_alt2.res
tests/sav/base_prot_sig_alt3.res
tests/sav/base_prot_sig_alt4.res
tests/sav/base_prot_sig_alt5.res
tests/sav/base_prot_sig_alt6.res
tests/sav/base_prot_sig_alt7.res
tests/sav/bench_seq.res [new file with mode: 0644]
tests/sav/nitrestful.res [new file with mode: 0644]
tests/sav/nitrestful_args1.res [new file with mode: 0644]
tests/sav/nituml_args3.res
tests/sav/nituml_args4.res
tests/sav/test_new_native_alt1.res
tests/sav/test_seq.res
tests/test_seq.nit

index d242751..f9e8400 100644 (file)
@@ -89,9 +89,19 @@ class Benitlux
                # Get the web page
                var body = download_html_page
 
+               if opts.verbose.value > 1 then
+                       print " # Body"
+                       print body
+               end
+
                # Parse the Web page and get the available beers
                var beers = parse_beers_from_html(body)
 
+               if opts.verbose.value > 0 then
+                       print " # Beers"
+                       print beers
+               end
+
                var db = new DB.open(db_path)
 
                # Update the database with the beers of the day
@@ -112,6 +122,10 @@ class Benitlux
                # Set the email if desired
                if send_emails then
                        var subs = db.subscribers
+                       if opts.verbose.value > 0 then
+                               print " # Subscribers"
+                               print subs
+                       end
                        send_emails_to subs
                end
 
@@ -152,9 +166,14 @@ class Benitlux
                var of_interest = body.substring(start, finish-start)
                var lines = of_interest.strip_tags.to_clean_lines
 
+               if opts.verbose.value > 0 then
+                       print " # Lines"
+                       print lines
+               end
+
                var beers = new HashSet[Beer]
                for line in lines do
-                       var parts = line.split(" - ")
+                       var parts = line.split("- ")
                        if parts.length >= 2 then
                                beers.add new Beer(parts[0].trim, parts[1].trim)
                        end
@@ -202,16 +221,23 @@ redef class OptionContext
        # Shall we mail the mailing list?
        var send_emails = new OptionBool("Send emails to subscribers", "-e", "--email")
 
+       # Display more debug messages
+       var verbose = new OptionCount("Display extra debug messages", "-v")
+
        # Print the usage message
        var help = new OptionBool("Print this help message", "-h", "--help")
 
-       redef init do add_option(send_emails, help)
+       redef init do add_option(send_emails, verbose, help)
+end
+
+redef class Sys
+       # Command line options
+       var opts = new OptionContext
 end
 
 # Avoid executing when running tests
 if "NIT_TESTING".environ == "true" then exit 0
 
-var opts = new OptionContext
 opts.parse args
 if not opts.errors.is_empty or opts.help.value == true then
        print opts.errors.join("\n")
index 3173333..c1901bf 100644 (file)
@@ -12,7 +12,7 @@ src/javap_test_parser.nit: ../nitcc/src/nitcc grammar/javap.sablecc
        mv javap* gen/
 
 src/serial.nit: $(shell ../../bin/nitls -M src/jwrapper.nit)
-       ../../bin/nitserial -o src/serial.nit -d package src/jwrapper.nit
+       ../../bin/nitserial -o src/serial.nit src/jwrapper.nit
 
 bin/jwrapper: src/javap_test_parser.nit src/serial.nit $(shell ../../bin/nitls -M src/jwrapper.nit) ../../bin/nitc
        mkdir -p bin
index 9013bff..e642da4 100644 (file)
@@ -157,7 +157,7 @@ class NitType
        var identifier: String
 
        # If this NitType was found in `lib/android`, contains the module name to import
-       var mod: nullable NitModule
+       var mod: nullable NitModuleRef
 
        # Is this type known, wrapped and available in Nit?
        var is_known: Bool = true
@@ -183,7 +183,7 @@ class JavaClass
        var constructors = new Array[JavaConstructor]
 
        # Importations from this class
-       var imports = new HashSet[NitModule]
+       var imports = new HashSet[NitModuleRef]
 
        # Interfaces implemented by this class
        var implements = new HashSet[JavaType]
@@ -482,7 +482,7 @@ class JavaConstructor
 end
 
 # A Nit module, use to import the referenced extern classes
-class NitModule
+class NitModuleRef
        # Relative path to the module
        var path: String
 
@@ -490,7 +490,7 @@ class NitModule
        var name: String is lazy do return path.basename(".nit")
 
        redef fun to_s do return self.name
-       redef fun ==(other) do return other isa NitModule and self.path == other.path
+       redef fun ==(other) do return other isa NitModuleRef and self.path == other.path
        redef fun hash do return self.path.hash
 end
 
@@ -501,7 +501,7 @@ redef class Sys
        # * The value is the corresponding `NitType`.
        var find_extern_class: DefaultMap[String, nullable NitType] is lazy do
                var map = new DefaultMap[String, nullable NitType](null)
-               var modules = new HashMap[String, NitModule]
+               var modules = new HashMap[String, NitModuleRef]
 
                var lib_paths = opt_libs.value
                if lib_paths == null then lib_paths = new Array[String]
@@ -543,7 +543,7 @@ redef class Sys
 
                                var mod = modules.get_or_null(path)
                                if mod == null then
-                                       mod = new NitModule(path)
+                                       mod = new NitModuleRef(path)
                                        modules[path] = mod
                                end
 
index 7917052..9ac9824 100644 (file)
@@ -1,4 +1,3 @@
 wiki.name=MyWiki
 wiki.desc=proudly powered by nit
 wiki.logo=assets/logo.png
-wiki.root_dir=/full/path/to/your/wiki/root/dir
index d86aac9..298f8ab 100644 (file)
@@ -1,5 +1,5 @@
 <header>
-    <a href="#"><img src="%ROOT_URL%/%LOGO%" alt="logo"/></a>
+    <a href="%ROOT_URL%/index.html"><img src="%ROOT_URL%/%LOGO%" alt="logo"/></a>
     <h2>%SUBTITLE%</h2>
     <h1>%TITLE%</h1>
 </header>
diff --git a/contrib/nitiwiki/examples/nitiwiki/pages/section/other_section/first.md b/contrib/nitiwiki/examples/nitiwiki/pages/section/other_section/first.md
new file mode 100644 (file)
index 0000000..c61423f
--- /dev/null
@@ -0,0 +1 @@
+first page
diff --git a/contrib/nitiwiki/examples/nitiwiki/pages/section/other_section/index.md b/contrib/nitiwiki/examples/nitiwiki/pages/section/other_section/index.md
new file mode 100644 (file)
index 0000000..0a918d5
--- /dev/null
@@ -0,0 +1,3 @@
+# A independant trail
+
+a [[trail: first]] and a [[trail: second]] page.
diff --git a/contrib/nitiwiki/examples/nitiwiki/pages/section/other_section/second.md b/contrib/nitiwiki/examples/nitiwiki/pages/section/other_section/second.md
new file mode 100644 (file)
index 0000000..ce04f0f
--- /dev/null
@@ -0,0 +1 @@
+a second page
index 1ff832e..ec20a0e 100644 (file)
@@ -8,7 +8,7 @@
                                <span class="icon-bar"></span>
                                <span class="icon-bar"></span>
                        </button>
-                       <a class="navbar-brand" href="%ROOT_URL%index.html">%TITLE%</a>
+                       <a class="navbar-brand" href="%ROOT_URL%/index.html">%TITLE%</a>
                </div>
                <!-- Collect the nav links, forms, and other content for toggling -->
                <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
index 05c06f9..ab79f09 100644 (file)
@@ -189,10 +189,7 @@ redef class WikiArticle
        fun load_template(template_file: String): TemplateString do
                var tpl = wiki.load_template(template_file)
                if tpl.has_macro("ROOT_URL") then
-                       var root_dir = href.dirname.relpath("")
-                       # Avoid issues if the macro is just followed by a `/` (as with url prefix)
-                       if root_dir == "" then root_dir = "."
-                       tpl.replace("ROOT_URL", root_dir)
+                       tpl.replace("ROOT_URL", root_href)
                end
                return tpl
        end
@@ -313,17 +310,21 @@ redef class WikiArticle
 
                var res = new Template
                res.add "<ul class=\"trail\">"
-               if pos > 0 then
-                       var target = flat[pos-1]
-                       res.add "<li>{target.a_from(self, "prev")}</li>"
-               end
                var parent = wiki.trails.parent(self)
+               # Up and prev are disabled on a root
                if parent != null then
+                       if pos > 0 then
+                               var target = flat[pos-1]
+                               res.add "<li>{target.a_from(self, "prev")}</li>"
+                       end
                        res.add "<li>{parent.a_from(self, "up")}</li>"
                end
                if pos < flat.length - 1 then
                        var target = flat[pos+1]
-                       res.add "<li>{target.a_from(self, "next")}</li>"
+                       # Only print the next if it is not a root
+                       if target.parent != null then
+                               res.add "<li>{target.a_from(self, "next")}</li>"
+                       end
                end
                res.add "</ul>"
 
index 03f21ca..c1785b2 100644 (file)
@@ -100,12 +100,23 @@ redef class WikiEntry
        # Relative path to `self` from the target root_url
        fun href: String do return breadcrumbs.join("/")
 
+       # Relative path to the directory `self` from the target root_url
+       fun dir_href: String do return href.dirname
+
+       # Relative path to the root url from `self`
+       fun root_href: String do
+               var root_dir = dir_href.relpath("")
+               # Avoid issues if used as a macro just followed by a `/` (as with url prefix)
+               if root_dir == "" then root_dir = "."
+               return root_dir
+       end
+
        # A relative `href` to `self` from the page `context`.
        #
        # Should be used to navigate between documents.
        fun href_from(context: WikiEntry): String
        do
-               var res = context.href.dirname.relpath(href)
+               var res = context.dir_href.relpath(href)
                return res
        end
 
@@ -174,6 +185,8 @@ redef class WikiSection
                end
                return new WikiSectionIndex(wiki, "index", self)
        end
+
+       redef fun dir_href do return href
 end
 
 redef class WikiArticle
@@ -212,6 +225,8 @@ class WikiSectionIndex
        redef fun title do return section.title
 
        redef fun href do return section.href
+
+       redef fun dir_href do return section.dir_href
 end
 
 # A MarkdownProcessor able to parse wiki links.
index 9b18a6b..5ce317a 100644 (file)
@@ -74,10 +74,15 @@ end
 class Achievement
        super GameEntity
 
-       redef var key is lazy do return "achievements" / id
 
        redef var game
 
+       redef var key is lazy do
+               var owner = self.owner
+               if owner == null then return id
+               return "{owner.key}-{id}"
+       end
+
        # Uniq ID for this achievement.
        var id: String
 
@@ -93,6 +98,9 @@ class Achievement
        # Is this achievement unlocked by somebody?
        var is_unlocked: Bool is lazy do return not load_events.is_empty
 
+       # Game entity this achievement is about.
+       var owner: nullable GameEntity = null
+
        # Init `self` from a `json` object.
        #
        # Used to load achievements from storage.
@@ -106,6 +114,8 @@ class Achievement
                json["name"] = name
                json["desc"] = desc
                json["reward"] = reward
+               json["game"] = game.key
+               if owner != null then json["owner"] = owner.key
                return json
        end
 end
@@ -346,7 +356,7 @@ abstract class PlayerXCommits
                if not event.action == "closed" then return
                if not event.pull.merged then return
                var player = event.pull.user.player(game)
-               if player.stats["commits"] == threshold then
+               if player.stats["commits"] >= threshold then
                        var a = new_achievement(game)
                        player.unlock_achievement(a, event)
                end
@@ -447,7 +457,7 @@ end
 class Player1KComments
        super PlayerXComments
 
-       redef var id = "player_1000__comments"
+       redef var id = "player_1000_comments"
        redef var name = "You sir, talk a lot!"
        redef var desc = "Comment 1000 times on issues."
        redef var reward = 1000
index d1ba25a..bd1ee58 100644 (file)
@@ -24,8 +24,10 @@ import game
 
 redef class GameEntity
 
-       # Saves `event` in `self`.
-       fun add_event(event: GameEvent) do event.save_in(self.key)
+       fun add_event(event: GameEvent) do
+               event.owner = self
+               event.save
+       end
 
        # List all events registered in this entity.
        #
@@ -61,10 +63,12 @@ end
 class GameEvent
        super GameEntity
 
-       redef var key is lazy do return "events" / internal_id
 
        redef var game
 
+       # Entity this event belongs to.
+       var owner: nullable GameEntity = null
+
        # String used to dissociate events in the display.
        var kind: String
 
@@ -76,6 +80,8 @@ class GameEvent
        # GameEvent uniq id used for storage.
        var internal_id: String is noinit
 
+       redef var key = internal_id is lazy
+
        # Date and time of the event.
        var time: ISODate is noinit, writable
 
@@ -100,6 +106,8 @@ class GameEvent
                json["kind"] = kind
                json["time"] = time.to_s
                json["data"] = data
+               json["game"] = game.key
+               if owner != null then json["owner"] = owner.key
                return json
        end
 end
index a109949..2924e46 100644 (file)
@@ -65,20 +65,20 @@ class Game
 
        redef fun game do return self
 
-       # Returns the repo `full_name`.
-       #
-       # Example: `"nitlang/nit"`
-       redef fun key do return repo.full_name
-
        # We need a `GithubAPI` client to load Github data.
        var api: GithubAPI
 
        # A game takes place in a `github::Repo`.
        var repo: Repo
 
+       # Game name
+       var name: String = repo.full_name is lazy
+
        # Directory where game data are stored.
        var game_dir: String is lazy do return "nitrpg_data" / repo.full_name
 
+       redef var key = name is lazy
+
        # Used for data storage.
        #
        # File are stored in `game_dir`.
@@ -92,6 +92,12 @@ class Game
        # Used to load entities from saved data.
        fun from_json(json: JsonObject) do end
 
+       redef fun to_json do
+               var json = super
+               json["name"] = name
+               return json
+       end
+
        # Create a player from a Github `User`.
        #
        # Or return the existing one from game data.
@@ -180,9 +186,6 @@ end
 class Player
        super GameEntity
 
-       # Key is based on player `name`.
-       redef var key is lazy do return "players" / name
-
        redef var game
 
        # FIXME contructor should be private
@@ -195,6 +198,8 @@ class Player
        # The name is also used to load the user data lazilly from Github API.
        var name: String
 
+       redef var key = name is lazy
+
        # Player amount of nitcoins.
        #
        # Nitcoins is the currency used in nitrpg.
@@ -218,6 +223,7 @@ class Player
 
        redef fun to_json do
                var json = super
+               json["game"] = game.key
                json["name"] = name
                json["nitcoins"] = nitcoins
                return json
index 7073c98..e2717f4 100644 (file)
@@ -58,7 +58,7 @@ redef class PullRequestEvent
 
        # Rewards player for opened pull requests.
        redef fun react_player_event(r, game) do
-               if action == "opened" then
+               if action == "opened" or action == "reopened" then
                        react_pull_open(r, game)
                else if action == "closed" then
                        react_pull_close(r, game)
@@ -95,14 +95,18 @@ redef class IssueCommentEvent
        # Rewards player for review comments.
        #
        # TODO only give nitcoins if reviewers < 2
+       # TODO give more points to first reviewer
        redef fun react_player_event(r, game) do
                if comment.is_ack then
                        react_player_review(r, game)
                end
        end
 
+       # TODO same player should not be authorized to review multiple times? How to handle rerols?
        private fun react_player_review(r: PlayerReactor, game: Game) do
+               if issue.state == "closed" then return
                var player = comment.user.player(game)
+               if issue.user == player.user then return
                player.nitcoins += r.nc_pull_review
                player.save
                var event = player_reward_event("pull_review", player, r.nc_pull_review)
index b7d3530..153b5cd 100644 (file)
@@ -79,49 +79,32 @@ class GameStatsManager
        # The GameEntity monitored by these statistics.
        var owner: GameEntity
 
-       redef var key = "stats"
+       # Current date to extract stats
+       private var date = new Tm.gmtime
 
        # Returns the `GameStats` instance for the overall statistics.
-       var overall: GameStats is lazy do
-               return load_stats_for("all")
-       end
+       var overall: GameStats = load_stats_for("all") is lazy
 
        # Returns the `GameStats` instance for the current year statistics.
-       var yearly: GameStats is lazy do
-               var date = new Tm.gmtime
-               var key = date.strftime("%Y")
-               return load_stats_for(key)
-       end
+       var yearly: GameStats = load_stats_for(date.strftime("%Y")) is lazy
 
        # Returns the `GameStats` instance for the current month statistics.
-       var monthly: GameStats is lazy do
-               var date = new Tm.gmtime
-               var key = date.strftime("%Y-%m")
-               return load_stats_for(key)
-       end
+       var monthly: GameStats = load_stats_for(date.strftime("%Y-%m")) is lazy
 
        # Returns the `GameStats` instance for the current day statistics.
-       var daily: GameStats is lazy do
-               var date = new Tm.gmtime
-               var key = date.strftime("%Y-%m-%d")
-               return load_stats_for(key)
-       end
+       var daily: GameStats = load_stats_for(date.strftime("%Y-%m-%d")) is lazy
 
        # Returns the `GameStats` instance for the current week statistics.
-       var weekly: GameStats is lazy do
-               var date = new Tm.gmtime
-               var key = date.strftime("%Y-W%U")
-               return load_stats_for(key)
-       end
+       var weekly: GameStats = load_stats_for(date.strftime("%Y-W%U")) is lazy
 
        # Load statistics for a `period` key.
        fun load_stats_for(period: String): GameStats do
                var key = owner.key / self.key / period
                if not game.store.has_key(key) then
-                       return new GameStats(game, period)
+                       return new GameStats(game, period, owner)
                end
                var json = game.store.load_object(key)
-               return new GameStats.from_json(game, period, json)
+               return new GameStats.from_json(game, period, owner, json)
        end
 
        redef fun [](key) do return overall[key]
@@ -168,19 +151,28 @@ class GameStats
 
        redef var game
 
-       # The pedriod these stats are about.
+       # The period these stats are about.
        var period: String
 
-       redef fun key do return period
+       # The game entity these stats are about.
+       var owner: GameEntity
+
+       redef var key = "{owner.key}-{period}" is lazy
 
        # Load `self` from saved data.
-       init from_json(game: Game, period: String, json: JsonObject) do
-               for k, v in json do self[k] = v.as(Int)
+       init from_json(game: Game, period: String, owner: GameEntity, json: JsonObject) do
+               var values = json.get_or_null("values")
+               if not values isa JsonObject then return
+               for k, v in values do self[k] = v.as(Int)
        end
 
        redef fun to_json do
-               var obj = new JsonObject
-               for k, v in self do obj[k] = v
+               var obj = super
+               obj["period"] = period
+               obj["owner"] = owner.key
+               var values = new JsonObject
+               values.recover_with(self)
+               obj["values"] = values
                return obj
        end
 
index 8b85a4d..a4f40d9 100644 (file)
@@ -34,9 +34,6 @@ redef class Game
 
        redef fun url do return "{root_url}/games" / key
 
-       # Displayed name.
-       fun name: String do return repo.full_name
-
        # Return a HTML link to this Game.
        fun link: String do return "<a href=\"{url}\">{name}</a>"
 end
index 9cc54d7..649a95d 100644 (file)
@@ -646,7 +646,7 @@ redef class App
        var default_soundpool: SoundPool is lazy do return new SoundPool
 
        # Get the native audio manager
-       fun audio_manager: NativeAudioManager import native_activity in "Java" `{
+       private fun audio_manager: NativeAudioManager import native_activity in "Java" `{
                return (AudioManager)App_native_activity(self).getSystemService(Context.AUDIO_SERVICE);
        `}
 
index 0020b7a..b8fe277 100644 (file)
@@ -337,3 +337,75 @@ redef class String
        # WARNING: SEE: `TermCharFormat`
        fun underline: String do return apply_format(normal.underline)
 end
+
+# A dynamic progressbar displayable in console.
+#
+# Example:
+# ~~~nitish
+# var max = 10
+# var current = 0
+# var pb = new TermProgress(max, current)
+#
+# pb.display
+# for i in [current + 1 .. max] do
+#      nanosleep(1, 0)
+#      pb.update(i)
+# end
+#
+# print "\ndone"
+# ~~~
+#
+# Progressbar can accept metadata to display a small amount of data.
+#
+# Example with metadata:
+# ~~~nitish
+# var pb = new TermProgress(10, 0)
+# for i in [0..10] do
+#      pb.update(i, "Step {i}")
+# end
+# ~~~
+class TermProgress
+
+       # Max value of the progress bar (business value).
+       var max_value: Int
+
+       # Current value of the progress bar (business value).
+       var current_value: Int
+
+       # Number of columns used to display the progress bar.
+       var max_columns = 70 is writable
+
+       # Get the current percent value.
+       fun current_percentage: Int do
+               return current_value * 100 / max_value
+       end
+
+       # Display the progress bar.
+       #
+       # `metadata`  can be used to pass a small amount of data to display after
+       # the progressbar.
+       fun display(metadata: nullable String) do
+               var percent = current_percentage
+               var p = current_value * max_columns / max_value
+               printn "\r{percent}% ["
+               for i in [1..max_columns] do
+                       if i < p then
+                               printn "="
+                       else if i == p then
+                               printn ">"
+                       else
+                               printn " "
+                       end
+               end
+               printn "]"
+               if metadata != null then printn " ({metadata})"
+       end
+
+       # Update and display the progresssbar.
+       #
+       # See `display`.
+       fun update(new_current: Int, metadata: nullable String) do
+               current_value = new_current
+               display(metadata)
+       end
+end
index a1dbfbe..af5b61b 100644 (file)
@@ -19,7 +19,39 @@ import kernel
 import collection::array
 intrude import text::flat
 
+# Any kind of entity which can be searched for in a Sequence of Byte
+interface BytePattern
+       # Return the first occurence of `self` in `b`, or -1 if not found
+       fun first_index_in(b: SequenceRead[Byte]): Int do return first_index_in_from(b, 0)
+
+       # Return the first occurence of `self` in `b` starting at `from`, or -1 if not found
+       fun first_index_in_from(b: SequenceRead[Byte], from: Int): Int is abstract
+
+       # Return the last occurence of `self` in `b`, or -1 if not found
+       fun last_index_in(b: SequenceRead[Byte]): Int do return last_index_in_from(b, b.length - 1)
+
+       # Return the last occurence of `self` in `b`, or -1 if not found
+       fun last_index_in_from(b: SequenceRead[Byte], from: Int): Int is abstract
+
+       # Returns the indexes of all the occurences of `self` in `b`
+       fun search_all_in(b: SequenceRead[Byte]): SequenceRead[Int] is abstract
+
+       # Length of the pattern
+       fun pattern_length: Int is abstract
+
+       # Appends `self` to `b`
+       fun append_to(b: Sequence[Byte]) is abstract
+
+       # Is `self` a prefix for `b` ?
+       fun is_prefix(b: SequenceRead[Byte]): Bool is abstract
+
+       # Is `self` a suffix for `b` ?
+       fun is_suffix(b: SequenceRead[Byte]): Bool is abstract
+end
+
 redef class Byte
+       super BytePattern
+
        # Write self as a string into `ns` at position `pos`
        private fun add_digest_at(ns: NativeString, pos: Int) do
                var tmp = (0xF0u8 & self) >> 4
@@ -72,6 +104,39 @@ redef class Byte
                # i.e. this abort is here to please the compiler
                abort
        end
+
+       redef fun first_index_in_from(b, from) do
+               for i in [from .. b.length[ do if b[i] == self then return i
+               return -1
+       end
+
+       redef fun last_index_in_from(b, from) do
+               for i in [0 .. from].step(-1) do if b[i] == self then return i
+               return -1
+       end
+
+       redef fun search_all_in(b) do
+               var ret = new Array[Int]
+               var pos = 0
+               loop
+                       pos = first_index_in_from(b, pos)
+                       if pos == -1 then return ret
+                       ret.add pos
+                       pos += 1
+               end
+       end
+
+       redef fun pattern_length do return 1
+
+       redef fun append_to(b) do b.push self
+
+       #     assert 'b'.ascii.is_suffix("baqsdb".to_bytes)
+       #     assert not 'b'.ascii.is_suffix("baqsd".to_bytes)
+       redef fun is_suffix(b) do return b.length != 0 and b.last == self
+
+       #     assert 'b'.ascii.is_prefix("baqsdb".to_bytes)
+       #     assert not 'b'.ascii.is_prefix("aqsdb".to_bytes)
+       redef fun is_prefix(b) do return b.length != 0 and b.first == self
 end
 
 # A buffer containing Byte-manipulation facilities
@@ -79,6 +144,7 @@ end
 # Uses Copy-On-Write when persisted
 class Bytes
        super AbstractArray[Byte]
+       super BytePattern
 
        # A NativeString being a char*, it can be used as underlying representation here.
        var items: NativeString
@@ -107,7 +173,9 @@ class Bytes
                init(ns, 0, cap)
        end
 
-       redef fun is_empty do return length != 0
+       redef fun pattern_length do return length
+
+       redef fun is_empty do return length == 0
 
        #     var b = new Bytes.empty
        #     b.add 101u8
@@ -118,6 +186,71 @@ class Bytes
                return items[i]
        end
 
+       # Returns a copy of `self`
+       fun clone: Bytes do
+               var b = new Bytes.with_capacity(length)
+               b.append(self)
+               return b
+       end
+
+       # Trims off the whitespaces at the beginning and the end of `self`
+       #
+       #     var b = "102041426E6F1020" .hexdigest_to_bytes
+       #     assert b.trim.hexdigest == "41426E6F"
+       #
+       # NOTE: A whitespace is defined here as a byte whose value is <= 0x20
+       fun trim: Bytes do
+               var st = 0
+               while st < length do
+                       if self[st] > 0x20u8 then break
+                       st += 1
+               end
+               if st >= length then return new Bytes.empty
+               var ed = length - 1
+               while ed > 0 do
+                       if self[ed] > 0x20u8 then break
+                       ed -= 1
+               end
+               return slice(st, ed - st + 1)
+       end
+
+       # Returns a subset of the content of `self` starting at `from` and of length `count`
+       #
+       #     var b = "abcd".to_bytes
+       #     assert b.slice(1, 2).hexdigest == "6263"
+       #     assert b.slice(-1, 2).hexdigest == "61"
+       #     assert b.slice(1, 0).hexdigest == ""
+       #     assert b.slice(2, 5).hexdigest == "6364"
+       fun slice(from, count: Int): Bytes do
+               if count <= 0 then return new Bytes.empty
+
+               if from < 0 then
+                       count += from
+                       if count < 0 then count = 0
+                       from = 0
+               end
+
+               if (count + from) > length then count = length - from
+               if count <= 0 then return new Bytes.empty
+
+               var ret = new Bytes.with_capacity(count)
+
+               ret.append_ns(items.fast_cstring(from), count)
+               return ret
+       end
+
+       # Returns a copy of `self` starting at `from`
+       #
+       #     var b = "abcd".to_bytes
+       #     assert b.slice_from(1).hexdigest  == "626364"
+       #     assert b.slice_from(-1).hexdigest == "61626364"
+       #     assert b.slice_from(2).hexdigest  == "6364"
+       fun slice_from(from: Int): Bytes do
+               if from >= length then return new Bytes.empty
+               if from < 0 then from = 0
+               return slice(from, length)
+       end
+
        # Returns self as a hexadecimal digest
        fun hexdigest: String do
                var elen = length * 2
@@ -218,6 +351,15 @@ class Bytes
                length += ln
        end
 
+       # Appends the bytes of `s` to `selftextextt`
+       fun append_text(s: Text) do
+               for i in s.substrings do
+                       append_ns(i.fast_cstring, i.bytelen)
+               end
+       end
+
+       redef fun append_to(b) do b.append self
+
        redef fun enlarge(sz) do
                if capacity >= sz then return
                persisted = false
@@ -237,6 +379,157 @@ class Bytes
 
        redef fun iterator do return new BytesIterator.with_buffer(self)
 
+       redef fun first_index_in_from(b, from) do
+               if is_empty then return -1
+               var fst = self[0]
+               var bpos = fst.first_index_in_from(self, from)
+               for i in [0 .. length[ do
+                       if self[i] != b[bpos] then return first_index_in_from(b, bpos + 1)
+                       bpos += 1
+               end
+               return bpos
+       end
+
+       redef fun last_index_in_from(b, from) do
+               if is_empty then return -1
+               var lst = self[length - 1]
+               var bpos = lst.last_index_in_from(b, from)
+               for i in [0 .. length[.step(-1) do
+                       if self[i] != b[bpos] then return last_index_in_from(b, bpos - 1)
+                       bpos -= 1
+               end
+               return bpos
+       end
+
+       redef fun search_all_in(b) do
+               var ret = new Array[Int]
+               var pos = first_index_in_from(b, 0)
+               if pos == -1 then return ret
+               pos = pos + 1
+               ret.add pos
+               loop
+                       pos = first_index_in_from(b, pos)
+                       if pos == -1 then return ret
+                       ret.add pos
+                       pos += length
+               end
+       end
+
+       # Splits the content on self when encountering `b`
+       #
+       #     var a = "String is string".to_bytes.split_with('s'.ascii)
+       #     assert a.length == 3
+       #     assert a[0].hexdigest == "537472696E672069"
+       #     assert a[1].hexdigest == "20"
+       #     assert a[2].hexdigest == "7472696E67"
+       fun split_with(b: BytePattern): Array[Bytes] do
+               var fst = b.search_all_in(self)
+               if fst.is_empty then return [clone]
+               var retarr = new Array[Bytes]
+               var prev = 0
+               for i in fst do
+                       retarr.add(slice(prev, i - prev))
+                       prev = i + b.pattern_length
+               end
+               retarr.add slice_from(prev)
+               return retarr
+       end
+
+       # Splits `self` in two parts at the first occurence of `b`
+       #
+       #     var a = "String is string".to_bytes.split_once_on('s'.ascii)
+       #     assert a[0].hexdigest == "537472696E672069"
+       #     assert a[1].hexdigest == "20737472696E67"
+       fun split_once_on(b: BytePattern): Array[Bytes] do
+               var spl = b.first_index_in(self)
+               if spl == -1 then return [clone]
+               var ret = new Array[Bytes].with_capacity(2)
+               ret.add(slice(0, spl))
+               ret.add(slice_from(spl + b.pattern_length))
+               return ret
+       end
+
+       # Replaces all the occurences of `this` in `self` by `by`
+       #
+       #     var b = "String is string".to_bytes.replace(0x20u8, 0x41u8)
+       #     assert b.hexdigest == "537472696E6741697341737472696E67"
+       fun replace(pattern: BytePattern, bytes: BytePattern): Bytes do
+               if is_empty then return new Bytes.empty
+               var pos = pattern.search_all_in(self)
+               if pos.is_empty then return clone
+               var ret = new Bytes.with_capacity(length)
+               var prev = 0
+               for i in pos do
+                       ret.append_ns(items.fast_cstring(prev), i - prev)
+                       bytes.append_to ret
+                       prev = i + pattern.pattern_length
+               end
+               ret.append(slice_from(pos.last + pattern.pattern_length))
+               return ret
+       end
+
+       # Decode `self` from percent (or URL) encoding to a clear string
+       #
+       # Replace invalid use of '%' with '?'.
+       #
+       #     assert "aBc09-._~".to_bytes.from_percent_encoding == "aBc09-._~".to_bytes
+       #     assert "%25%28%29%3c%20%3e".to_bytes.from_percent_encoding == "%()< >".to_bytes
+       #     assert ".com%2fpost%3fe%3dasdf%26f%3d123".to_bytes.from_percent_encoding == ".com/post?e=asdf&f=123".to_bytes
+       #     assert "%25%28%29%3C%20%3E".to_bytes.from_percent_encoding == "%()< >".to_bytes
+       #     assert "incomplete %".to_bytes.from_percent_encoding == "incomplete ?".to_bytes
+       #     assert "invalid % usage".to_bytes.from_percent_encoding == "invalid ? usage".to_bytes
+       #     assert "%c3%a9%e3%81%82%e3%81%84%e3%81%86".to_bytes.from_percent_encoding == "éあいう".to_bytes
+       fun from_percent_encoding: Bytes do
+               var tmp = new Bytes.with_capacity(length)
+               var pos = 0
+               while pos < length do
+                       var b = self[pos]
+                       if b != '%'.ascii then
+                               tmp.add b
+                               pos += 1
+                               continue
+                       end
+                       if length - pos < 2 then
+                               tmp.add '?'.ascii
+                               pos += 1
+                               continue
+                       end
+                       var bn = self[pos + 1]
+                       var bnn = self[pos + 2]
+                       if not bn.is_valid_hexdigit or not bnn.is_valid_hexdigit then
+                               tmp.add '?'.ascii
+                               pos += 1
+                               continue
+                       end
+                       tmp.add((bn.hexdigit_to_byteval << 4) + bnn.hexdigit_to_byteval)
+                       pos += 3
+               end
+               return tmp
+       end
+
+       # Is `b` a prefix of `self` ?
+       fun has_prefix(b: BytePattern): Bool do return b.is_prefix(self)
+
+       # Is `b` a suffix of `self` ?
+       fun has_suffix(b: BytePattern): Bool do return b.is_suffix(self)
+
+       redef fun is_suffix(b) do
+               if length > b.length then return false
+               var j = b.length - 1
+               var i = length - 1
+               while i > 0 do
+                       if self[i] != b[j] then return false
+                       i -= 1
+                       j -= 1
+               end
+               return true
+       end
+
+       redef fun is_prefix(b) do
+               if length > b.length then return false
+               for i in [0 .. length[ do if self[i] != b[i] then return false
+               return true
+       end
 end
 
 private class BytesIterator
@@ -397,3 +690,20 @@ redef class NativeString
                return new Bytes(nns, len, len)
        end
 end
+
+# Joins an array of bytes `arr` separated by `sep`
+#
+#     assert join_bytes(["String".to_bytes, "is".to_bytes, "string".to_bytes], ' '.ascii).hexdigest == "537472696E6720697320737472696E67"
+fun join_bytes(arr: Array[Bytes], sep: nullable BytePattern): Bytes do
+       if arr.is_empty then return new Bytes.empty
+       sep = sep or else new Bytes.empty
+       var endln = sep.pattern_length * (arr.length - 1)
+       for i in arr do endln += i.length
+       var ret = new Bytes.with_capacity(endln)
+       ret.append(arr.first)
+       for i in  [1 .. arr.length[ do
+               sep.append_to(ret)
+               ret.append arr[i]
+       end
+       return ret
+end
index ce89435..d196333 100644 (file)
@@ -111,11 +111,18 @@ abstract class AbstractArrayRead[E]
        #     assert b      ==  [10, 20, 2, 3, 50]
        fun copy_to(start: Int, len: Int, dest: AbstractArray[E], new_start: Int)
        do
-               # TODO native one
-               var i = len
-               while i > 0 do
-                       i -= 1
-                       dest[new_start+i] = self[start+i]
+               if start < new_start then
+                       var i = len
+                       while i > 0 do
+                               i -= 1
+                               dest[new_start+i] = self[start+i]
+                       end
+               else
+                       var i = 0
+                       while i < len do
+                               dest[new_start+i] = self[start+i]
+                               i += 1
+                       end
                end
        end
 
@@ -130,7 +137,7 @@ abstract class AbstractArrayRead[E]
                end
        end
 
-       redef fun iterator: ArrayIterator[E] do
+       redef fun iterator: IndexedIterator[E] do
                var res = _free_iterator
                if res == null then return new ArrayIterator[E](self)
                res._index = 0
@@ -220,22 +227,18 @@ abstract class AbstractArray[E]
        do
                assert not_empty: not is_empty
                var r = first
-               var i = 1
-               var l = length
-               while i < l do
-                       self[i-1] = self[i]
-                       i += 1
-               end
-               _length = l - 1
+               var l = length-1
+               copy_to(1, l, self, 0)
+               _length = l
                return r
        end
 
        redef fun unshift(item)
        do
-               var i = length - 1
-               while i >= 0 do
-                       self[i+1] = self[i]
-                       i -= 1
+               var l = length
+               if l > 0 then
+                       enlarge(l + 1)
+                       copy_to(0, l, self, 1)
                end
                self[0] = item
        end
@@ -369,6 +372,32 @@ class Array[E]
                _length = nl
        end
 
+       redef fun copy_to(start, len, dest, new_start)
+       do
+               # Fast code when source and destination are two arrays
+
+               if not dest isa Array[E] then
+                       super
+                       return
+               end
+
+               # Enlarge dest if required
+               var dest_len = new_start + len
+               if dest_len > dest.length then
+                       dest.enlarge(dest_len)
+                       dest.length = dest_len
+               end
+
+               # Get underlying native arrays
+               var items = self.items
+               if items == null then return
+               var dest_items = dest.items
+               assert dest_items != null
+
+               # Native copy
+               items.memmove(start, len, dest_items, new_start)
+       end
+
        redef fun enlarge(cap)
        do
                var c = _capacity
@@ -653,7 +682,7 @@ private class ArraySetIterator[E]
 
        redef fun item: E do return _iter.item
 
-       var iter: ArrayIterator[E]
+       var iter: Iterator[E]
 end
 
 
@@ -962,6 +991,25 @@ universal NativeArray[E]
 
        # Copy `length` items to `dest`.
        fun copy_to(dest: NativeArray[E], length: Int) is intern
+
+       # Copy `length` items to `dest` starting from `dest`.
+       fun memmove(start: Int, length: Int, dest: NativeArray[E], dest_start: Int) do
+               # TODO native one
+               if start < dest_start then
+                       var i = length
+                       while i > 0 do
+                               i -= 1
+                               dest[dest_start+i] = self[start+i]
+                       end
+               else
+                       var i = 0
+                       while i < length do
+                               dest[dest_start+i] = self[start+i]
+                               i += 1
+                       end
+               end
+       end
+
        #fun =(o: NativeArray[E]): Bool is intern
        #fun !=(o: NativeArray[E]): Bool is intern
 end
diff --git a/lib/core/collection/circular_array.nit b/lib/core/collection/circular_array.nit
new file mode 100644 (file)
index 0000000..9419853
--- /dev/null
@@ -0,0 +1,258 @@
+# 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.
+
+# Efficient data structure to access both end of the sequence.
+module circular_array
+
+import array
+
+# Efficient data structure to access both end of the sequence.
+#
+# A circular array offers efficient random access,
+# efficient manipulation for both ends of the structure (push, pop, ) and
+# automatic amortized growth.
+#
+# Therefore it can be used as is or as and efficient queue (FIFO/LIFO)
+class CircularArray[E]
+       super Sequence[E]
+
+       # The low-level storage of the items.
+       #
+       # Internally, there is two main configurations
+       #
+       # One part: from `head` to `tail` (inclusive)
+       #
+       # ~~~raw
+       # ...0123...
+       #    h  t
+       # ~~~
+       #
+       # Two parts: from `head` to `capacity-1`,
+       # then from `0` to `tail` (both inclusive)
+       # Test with `head > tail`
+       #
+       # ~~~raw
+       # 345....012
+       #   t    h
+       # ~~~
+       #
+       # For consistency, *length* and *index* are used in the context of the sequence (self) and
+       # *capacity* and *offset* are used in the context of the native array.
+       #
+       # Initially the native is not allocated, the first one is done with `enlarge`
+       private var native: NativeArray[E] is noautoinit
+
+       # The offset of the `first` item in `native`
+       private var head = 0
+
+       # The offset of the `last` item in `native`
+       private var tail: Int = -1
+
+       redef var length = 0
+
+       # Transform an index into an offset.
+       #
+       # The method takes care of the initial gap and the possible wrapping.
+       #
+       # REQUIRE: `0 <= index and index < length`
+       private fun offset(index: Int): Int
+       do
+               assert index >= 0
+               var head = self._head
+               var tail = self._tail
+               var offset = head + index
+
+               if head > tail then
+                       # Two parts
+                       var capacity = native.length
+                       if offset < capacity then
+                               return offset
+                       end
+                       offset -= capacity
+               end
+
+               assert offset <= tail
+               return offset
+       end
+
+       redef fun [](index) do return native[offset(index)]
+
+       redef fun []=(index, item) do
+               var l = length
+               if index == l then
+                       push(item)
+                       return
+               end
+               native[offset(index)] = item
+       end
+
+       redef fun push(item) do
+               var l = length + 1
+               enlarge(l)
+               length = l
+
+               var native = _native
+               var cap = native.length
+               var t = tail + 1
+               if t >= cap then t -= cap
+
+               native[t] = item
+               tail = t
+       end
+
+       redef fun add_all(items) do
+               enlarge length + items.length
+               super
+       end
+
+       redef fun pop do
+               var l = length - 1
+               assert l >= 0
+               length = l
+
+               var native = _native
+               var t = tail
+               var res = native[t]
+
+               t -= 1
+               if t < 0 then t += native.length
+               tail = t
+
+               return res
+       end
+
+       redef fun unshift(item) do
+               var l = length + 1
+               enlarge(l)
+               length = l
+
+               var native = _native
+               var h = head - 1
+               if h < 0 then h += native.length
+
+               native[h] = item
+               head = h
+       end
+
+       redef fun shift do
+               var l = length - 1
+               assert l >= 0
+               length = l
+
+               var native = _native
+               var h = head
+               var res = native[h]
+
+               h += 1
+               var cap = native.length
+               if h >= cap then h -= cap
+               head = h
+
+               return res
+       end
+
+       # Ensure at least a given capacity
+       #
+       # If the current capacity is enough, then no-op.
+       fun enlarge(capacity: Int)
+       do
+               # First allocation
+               if not isset _native then
+                       var new_c = 8
+                       while new_c < capacity do new_c *= 2
+                       native = new NativeArray[E](new_c)
+                       return
+               end
+
+               # Compute more capacity
+               var c = native.length
+               if capacity <= c then return
+               var new_c = c
+               while new_c < capacity do new_c *= 2
+
+               var new_native = new NativeArray[E](new_c)
+
+               # Reallocation: just realign the parts on 0
+               if head > tail then
+                       # Two parts
+                       native.memmove(head, c-head, new_native, 0)
+                       native.memmove(0, tail+1, new_native, c-head)
+               else
+                       # One part
+                       native.memmove(head, length, new_native, 0)
+               end
+               head = 0
+               tail = length - 1
+               native = new_native
+       end
+
+       redef fun insert(item, index)
+       do
+               # Special insertion at the end (is push)
+               if index >= length then
+                       assert index == length
+                       push(item)
+                       return
+               end
+               assert index >= 0
+
+               var new_len = length + 1
+
+               # TODO be more efficient:
+               # Here, we just allocate a new native and copy everything.
+
+               # Allocate a new native array
+               var c = native.length
+               while c < new_len do c *= 2
+               var new_native = new NativeArray[E](c)
+
+               # Copy everything
+               var i = 0
+               while i < index do
+                       new_native[i] = self[i]
+                       i += 1
+               end
+               new_native[index] = item
+               var l = length
+               while i < l do
+                       new_native[i+1] = self[i]
+                       i += 1
+               end
+
+               # Use the new native array
+               length = new_len
+               head = 0
+               tail = new_len - 1
+               native = new_native
+       end
+
+       redef fun clear do
+               length = 0
+               head = 0
+               tail = -1
+       end
+
+       redef fun iterator do return new CircularArrayIterator[E](self)
+end
+
+private class CircularArrayIterator[E]
+       super IndexedIterator[E]
+
+       var array: CircularArray[E]
+
+       redef var index = 0
+       redef fun is_ok do return index < array.length
+       redef fun item do return array[index]
+       redef fun next do index += 1
+end
index 0701cc7..2475b68 100644 (file)
@@ -16,6 +16,7 @@ module collection
 import range
 import list
 import array
+import circular_array
 import sorter
 import hash_collection
 import union_find
index 890ecd9..ac4a8a1 100644 (file)
@@ -238,7 +238,7 @@ class HashMap[K, V]
                end
        end
 
-       redef fun iterator: HashMapIterator[K, V] do return new HashMapIterator[K,V](self)
+       redef fun iterator do return new HashMapIterator[K,V](self)
 
        redef fun length do return _the_length
 
index 4aa3242..4516703 100644 (file)
@@ -42,17 +42,8 @@ class List[E]
        # O(1)
        redef fun is_empty do return _head == null
 
-       # O(n)
-       redef fun length
-       do
-               var l = 0
-               var t = _head
-               while t != null do
-                       l += 1
-                       t = t.next
-               end
-               return l
-       end
+       # O(1)
+       redef var length = 0
 
        # O(n)
        redef fun has(e) do return search_node_after(e, _head) != null
@@ -103,6 +94,7 @@ class List[E]
                        node.prev = _tail
                end
                _tail = node
+               length += 1
        end
 
        # O(1)
@@ -116,6 +108,7 @@ class List[E]
                        _head.prev = node
                end
                _head = node
+               length += 1
        end
 
        # O(n)
@@ -141,6 +134,7 @@ class List[E]
                        _tail.next.prev = _tail
                end
                _tail = l._tail
+               length += l.length
                l.clear
        end
 
@@ -157,6 +151,7 @@ class List[E]
                else
                        _tail.next = null
                end
+               length -= 1
                return node.item
        end
 
@@ -171,6 +166,7 @@ class List[E]
                else
                        _head.prev = null
                end
+               length -= 1
                return node.item
        end
 
@@ -233,6 +229,7 @@ class List[E]
        # Remove the node (ie. atach prev and next)
        private fun remove_node(node: ListNode[E])
        do
+               length -= 1
                if node.prev == null then
                        _head = node.next
                        if node.next == null then
@@ -251,6 +248,7 @@ class List[E]
 
        private fun insert_before(element: E, node: ListNode[E])
        do
+               length += 1
                var nnode = new ListNode[E](element)
                var prev = node.prev
                if prev == null then
index 64c22c0..76823e2 100644 (file)
@@ -68,7 +68,7 @@ abstract class Text
        fun substring(from: Int, count: Int): SELFTYPE is abstract
 
        # Iterates on the substrings of self if any
-       fun substrings: Iterator[FlatText] is abstract
+       private fun substrings: Iterator[FlatText] is abstract
 
        # Is the current Text empty (== "")
        #
@@ -998,7 +998,7 @@ abstract class Text
 end
 
 # All kinds of array-based text representations.
-private abstract class FlatText
+abstract class FlatText
        super Text
 
        # Underlying C-String (`char*`)
index 4d35126..c3332d0 100644 (file)
@@ -36,9 +36,11 @@ end
 
 redef class FlatText
 
-       fun first_byte: Int do return 0
+       # First byte of the NativeString
+       protected fun first_byte: Int do return 0
 
-       fun last_byte: Int do return _bytelen - 1
+       # Last byte of the NativeString
+       protected fun last_byte: Int do return _bytelen - 1
 
        # Cache of the latest position (char) explored in the string
        var position: Int = 0
index ea410a2..a9dc0ed 100644 (file)
@@ -15,6 +15,8 @@
 # Support to generate and otherwise manipulate Nit code
 module gen_nit
 
+import template
+
 redef class Sys
        # Reserved keywords in the Nit language
        var keywords: Set[String] is lazy do return new HashSet[String].from([
@@ -39,3 +41,37 @@ redef class Sys
        var methods_in_pointer: Array[String] is lazy do return methods_in_object + [
                "free"]
 end
+
+# Template of a Nit module to generate Nit code
+class NitModule
+       super Template
+
+       # Header on top of the module, usually the documentation
+       var header: nullable Writable = null is writable
+
+       # The module's name
+       var name: Writable is writable
+
+       # Imports from this module
+       var imports = new Array[Writable]
+
+       # Main content of this module
+       var content = new Array[Writable]
+
+       redef fun rendering
+       do
+               var header = header
+               if header != null then add header
+
+               var name = name
+               add "module {name}\n\n"
+
+               for i in imports do add "import {i}\n"
+               add "\n"
+
+               for l in content do
+                       add l
+                       add "\n"
+               end
+       end
+end
index 7e7400c..18880ec 100644 (file)
@@ -732,6 +732,12 @@ class Issue
                super
        end
 
+       # Issue id.
+       fun id: Int do return json["id"].as(Int)
+
+       # Set issue id.
+       fun id=(id: Int) do json["id"] = id
+
        # Issue title.
        fun title: String do return json["title"].as(String)
 
@@ -814,10 +820,16 @@ class Issue
                        for obj in array do
                                if not obj isa JsonObject then continue
                                var id = obj["id"].as(Int)
-                               res.add(api.load_issue_comment(repo, id).as(not null))
+                               var comment = api.load_issue_comment(repo, id)
+                               if comment == null then continue
+                               res.add(comment)
                        end
                        page += 1
-                       array = api.get("{key}/comments?page={page}").as(JsonArray)
+                       var json = api.get("{key}/comments?page={page}")
+                       if not json isa JsonArray then
+                               return res
+                       end
+                       array = json
                end
                return res
        end
index df7c8c3..543a786 100644 (file)
@@ -38,6 +38,12 @@ class GithubEvent
                self.json = json
        end
 
+       # Event ID from Github.
+       fun id: String do return json["id"].as(String)
+
+       # Set id.
+       fun id=(id: String) do json["id"] = id
+
        # Action performed by the event.
        fun action: String do return json["action"].as(String)
 
index 9cbe38e..c01d417 100644 (file)
@@ -346,6 +346,10 @@ extern class ConnectionListener `{ struct evconnlistener * `}
 
                struct hostent *hostent = gethostbyname(address);
 
+               if (!hostent) {
+                       return NULL;
+               }
+
                memset(&sin, 0, sizeof(sin));
                sin.sin_family = hostent->h_addrtype;
                sin.sin_port = htons(port);
index 624dc8a..78cd75d 100644 (file)
@@ -55,16 +55,11 @@ in "C header" `{
 # * [Binary JSON spec](http://bsonspec.org/)
 # * [Libbson](http://api.mongodb.org/libbson/1.1.4/)#
 private class BSON
-       super Finalizable
+       super FinalizableOnce
 
        # Native instance pointer.
        var native: NativeBSON
 
-       # Is the native instance valid?
-       #
-       # This is set to false if the `native` is destroyed.
-       var is_alive = true
-
        # Returns a new BSON object initialized from the content of `json`.
        #
        # ~~~
@@ -95,7 +90,6 @@ private class BSON
        end
 
        redef fun to_s do
-               assert is_alive
                var ns = native.to_native_string
                var res = ns.to_s_with_copy
                ns.free # manual free of gc allocated NativeString
@@ -114,16 +108,15 @@ private class BSON
        # assert json["ELS"].as(JsonArray).is_empty
        # ~~~
        fun to_json: JsonObject do
-               assert is_alive
-               return to_s.parse_json.as(JsonObject)
-       end
-
-       redef fun finalize do
-               if is_alive then
-                       native.destroy
-                       is_alive = false
+               var json = to_s.parse_json
+               if json isa JsonParseError then
+                       print json.message
+                       sys.exit 1
                end
+               return json.as(JsonObject)
        end
+
+       redef fun finalize_once do native.destroy
 end
 
 redef class JsonObject
@@ -144,26 +137,14 @@ class MongoError
 
        private var native: BSONError
 
-       # Is the native instance valid?
-       #
-       # This is set to false if the `native` is destroyed.
-       private var is_alive = true
-
        # Logical domain within a library that created the error.
-       fun domain: Int do
-               assert is_alive
-               return native.domain
-       end
+       fun domain: Int do return native.domain
 
        # Domain specific error code.
-       fun code: Int do
-               assert is_alive
-               return native.code
-       end
+       fun code: Int do return native.code
 
        # Human readable error message.
        fun message: String do
-               assert is_alive
                var ns = native.message
                var res = ns.to_s_with_copy
                ns.free
@@ -211,21 +192,14 @@ end
 # assert client.server_uri == uri
 # ~~~
 class MongoClient
-       super Finalizable
+       super FinalizableOnce
 
        # Server URI.
        var server_uri: String
 
        private var native: NativeMongoClient is noinit
 
-       # Is the native instance valid?
-       #
-       # This is set to false if the `native` is destroyed.
-       private var is_alive = true
-
-       init do
-               native = new NativeMongoClient(server_uri.to_cstring)
-       end
+       init do native = new NativeMongoClient(server_uri.to_cstring)
 
        # Gets server data.
        #
@@ -236,7 +210,6 @@ class MongoClient
        # assert client.server_status["process"] == "mongod"
        # ~~~
        fun server_status: nullable JsonObject do
-               assert is_alive
                var nbson = native.server_status
                if nbson == null then return null
                var bson = new BSON(nbson)
@@ -253,7 +226,6 @@ class MongoClient
        # assert client.database_names.has("test")
        # ~~~
        fun database_names: Array[String] do
-               assert is_alive
                var res = new Array[String]
                var nas = native.database_names
                if nas == null then return res
@@ -278,25 +250,14 @@ class MongoClient
        # var client = new MongoClient("mongodb://localhost:27017/")
        # assert client.database("test").name == "test"
        # ~~~
-       fun database(name: String): MongoDb do
-               assert is_alive
-               return new MongoDb(self, name)
-       end
+       fun database(name: String): MongoDb do return new MongoDb(self, name)
 
        # Close the connexion and destroy the instance.
        #
        # The reference should not be used beyond this point!
-       fun close do
-               assert is_alive
-               finalize
-       end
+       fun close do finalize_once
 
-       redef fun finalize do
-               if is_alive then
-                       native.destroy
-                       is_alive = false
-               end
-       end
+       redef fun finalize_once do native.destroy
 
        # Last error raised by mongoc.
        fun last_error: nullable MongoError do
@@ -328,7 +289,7 @@ end
 # first document into a collection.
 # There is no need to create a database manually.
 class MongoDb
-       super Finalizable
+       super FinalizableOnce
 
        # `MongoClient` used to load this database.
        var client: MongoClient
@@ -338,14 +299,7 @@ class MongoDb
 
        private var native: NativeMongoDb is noinit
 
-       # Is the native instance valid?
-       #
-       # This is set to false if the `native` is destroyed.
-       private var is_alive = true
-
-       init do
-               native = new NativeMongoDb(client.native, name.to_cstring)
-       end
+       init do native = new NativeMongoDb(client.native, name.to_cstring)
 
        # Lists available collection names.
        #
@@ -358,7 +312,6 @@ class MongoDb
        # assert db.collection_names.has("test")
        # ~~~
        fun collection_names: Array[String] do
-               assert is_alive
                var res = new Array[String]
                var nas = native.collection_names
                if nas == null then return res
@@ -382,7 +335,6 @@ class MongoDb
        # assert col.name == "test"
        # ~~~
        fun collection(name: String): MongoCollection do
-               assert is_alive
                return new MongoCollection(self, name)
        end
 
@@ -394,23 +346,14 @@ class MongoDb
        # assert not db.has_collection("qwerty")
        # ~~~
        fun has_collection(name: String): Bool do
-               assert is_alive
                # TODO handle error
                return native.has_collection(name.to_cstring)
        end
 
        # Drop `self`, returns false if an error occured.
-       fun drop: Bool do
-               assert is_alive
-               return native.drop
-       end
+       fun drop: Bool do return native.drop
 
-       redef fun finalize do
-               if is_alive then
-                       native.destroy
-                       is_alive = false
-               end
-       end
+       redef fun finalize_once do native.destroy
 end
 
 # A Mongo collection.
@@ -419,7 +362,7 @@ end
 # the first document.
 # There is no need to create a database manually.
 class MongoCollection
-       super Finalizable
+       super FinalizableOnce
 
        # Database that collection belongs to.
        var database: MongoDb
@@ -429,11 +372,6 @@ class MongoCollection
 
        private var native: NativeMongoCollection is noinit
 
-       # Is the native instance valid?
-       #
-       # This is set to false if the `native` is destroyed.
-       private var is_alive = true
-
        # Loads a collection.
        #
        # Call `MongoDb::collection` instead.
@@ -471,7 +409,6 @@ class MongoCollection
        # assert doc.has_key("_id")
        # ~~~
        fun insert(doc: JsonObject): Bool do
-               assert is_alive
                var res = native.insert(doc.to_bson.native)
                if res then set_id(doc)
                return res
@@ -481,7 +418,6 @@ class MongoCollection
        #
        # See `insert`.
        fun insert_all(docs: Collection[JsonObject]): Bool do
-               assert is_alive
                var res = true
                for doc in docs do res = insert(doc) and res
                return res
@@ -511,9 +447,12 @@ class MongoCollection
        # assert doc["_id"] == id
        # ~~~
        fun save(doc: JsonObject): Bool do
-               assert is_alive
-               var res = native.save(doc.to_bson.native)
+               var bson = doc.to_bson
+               var nat = bson.native
+               var res = native.save(nat)
                if res then set_id(doc)
+               assert nat != self #FIXME used to avoid GC crashes
+               assert bson != self #FIXME used to avoid GC crashes
                return res
        end
 
@@ -529,7 +468,6 @@ class MongoCollection
        # assert col.remove(sel)
        # ~~~
        fun remove(selector: JsonObject): Bool do
-               assert is_alive
                return native.remove(selector.to_bson.native)
        end
 
@@ -537,7 +475,6 @@ class MongoCollection
        #
        # See `remove`.
        fun remove_all(selector: JsonObject): Bool do
-               assert is_alive
                return native.remove_all(selector.to_bson.native)
        end
 
@@ -555,7 +492,6 @@ class MongoCollection
        # assert col.update(sel, upd)
        # ~~~
        fun update(selector: JsonObject, update: JsonObject): Bool do
-               assert is_alive
                return native.update(
                        selector.to_bson.native,
                        update.to_bson.native)
@@ -582,12 +518,15 @@ class MongoCollection
        # assert col.count(query) > 0
        # ~~~
        fun count(query: JsonObject): Int do
-               assert is_alive
                return native.count(query.to_bson.native)
        end
 
        # Finds the first document that matches `query`.
        #
+       # Params:
+       # * `skip` number of documents to skip
+       # * `limit` number of documents to return
+       #
        # Returns `null` if an error occured. See `Sys::last_mongoc_error`.
        #
        # ~~~
@@ -598,19 +537,28 @@ class MongoCollection
        # var doc = col.find(query)
        # assert doc["foo"] == 10
        # ~~~
-       fun find(query: JsonObject): nullable JsonObject do
-               assert is_alive
+       fun find(query: JsonObject, skip, limit: nullable Int): nullable JsonObject do
                var q = new NativeBSON.from_json_string(query.to_json.to_cstring)
-               var c = native.find(q)
+               var s = skip or else 0
+               var l = limit or else 0
+               var c = native.find(q, s, l)
                q.destroy
                if c == null then return null
                var cursor = new MongoCursor(c)
-               if cursor.is_ok then return cursor.item
-               return null
+               if not cursor.is_ok then
+                       return null
+               end
+               var item = cursor.item
+               assert cursor != self
+               return item
        end
 
        # Finds all the documents matching the `query`.
        #
+       # Params:
+       # * `skip` number of documents to skip
+       # * `limit` number of documents to return
+       #
        # ~~~
        # var client = new MongoClient("mongodb://localhost:27017/")
        # var col = client.database("test").collection("test")
@@ -618,13 +566,17 @@ class MongoCollection
        # query["foo"] = 10
        # assert col.find_all(query).length > 0
        # ~~~
-       fun find_all(query: JsonObject): Array[JsonObject] do
-               assert is_alive
+       fun find_all(query: JsonObject, skip, limit: nullable Int): Array[JsonObject] do
+               var s = skip or else 0
+               var l = limit or else 0
                var res = new Array[JsonObject]
-               var c = native.find(query.to_bson.native)
+               var c = native.find(query.to_bson.native, s, l)
                if c == null then return res
                var cursor = new MongoCursor(c)
-               for item in cursor do res.add item
+               while cursor.is_ok do
+                       res.add cursor.item
+                       cursor.next
+               end
                return res
        end
 
@@ -638,17 +590,13 @@ class MongoCollection
        # assert col.stats["ns"] == "test.test"
        # ~~~
        fun stats: nullable JsonObject do
-               assert is_alive
                var bson = native.stats
                if bson == null then return null
                return new JsonObject.from_bson(new BSON(bson))
        end
 
        # Drops `self`, returns false if an error occured.
-       fun drop: Bool do
-               assert is_alive
-               return native.drop
-       end
+       fun drop: Bool do return native.drop
 
        # Moves `self` to another `database`.
        #
@@ -656,7 +604,6 @@ class MongoCollection
        # this collection after the move.
        # Additional operations will occur on moved collection.
        fun move(database: MongoDb): Bool do
-               assert is_alive
                self.database = database
                return native.rename(database.name.to_cstring, name.to_cstring)
        end
@@ -667,17 +614,11 @@ class MongoCollection
        # to continue using this collection after the rename.
        # Additional operations will occur on renamed collection.
        fun rename(name: String): Bool do
-               assert is_alive
                self.name = name
                return native.rename(database.name.to_cstring, name.to_cstring)
        end
 
-       redef fun finalize do
-               if is_alive then
-                       native.destroy
-                       is_alive = false
-               end
-       end
+       redef fun finalize_once do native.destroy
 end
 
 # A MongoDB query cursor.
@@ -685,37 +626,20 @@ end
 # It wraps up the wire protocol negotation required to initiate a query and
 # retreive an unknown number of documents.
 class MongoCursor
-       super Finalizable
+       super FinalizableOnce
        super Iterator[JsonObject]
 
        private var native: NativeMongoCursor
 
-       # Is the native instance valid?
-       #
-       # This is set to false if the `native` is destroyed.
-       private var is_alive = true
-
        init do next
 
-       redef fun is_ok do
-               assert is_alive
-               return native.more
-       end
+       redef var is_ok = true
 
-       redef fun next do
-               assert is_alive
-               native.next
-       end
+       redef fun next do is_ok = native.next
 
        redef fun item do
-               assert is_alive
                return new JsonObject.from_bson(new BSON(native.current))
        end
 
-       redef fun finalize do
-               if is_alive then
-                       native.destroy
-                       is_alive = false
-               end
-       end
+       redef fun finalize_once do native.destroy
 end
index d25a9ac..fb8b305 100644 (file)
@@ -421,11 +421,11 @@ extern class NativeMongoCollection `{ mongoc_collection_t * `}
        #
        # If you would like to specify options such as a sort order,
        # the query must be placed inside of `{"$query": {}}`.
-       fun find(query: NativeBSON): nullable NativeMongoCursor import
+       fun find(query: NativeBSON, skip, limit: Int): nullable NativeMongoCursor import
                NativeMongoCursor.as nullable, set_mongoc_error `{
                bson_error_t error;
                mongoc_cursor_t *cursor;
-               cursor = mongoc_collection_find(self, MONGOC_QUERY_NONE, 0, 0, 0, query, NULL, NULL);
+               cursor = mongoc_collection_find(self, MONGOC_QUERY_NONE, skip, limit, 0, query, NULL, NULL);
                if (mongoc_cursor_error(cursor, &error)) {
                        NativeMongoCollection_set_mongoc_error(self, &error);
                        return null_NativeMongoCursor();
@@ -506,7 +506,12 @@ extern class NativeMongoCursor `{ mongoc_cursor_t* `}
        # Wrapper for `mongoc_cursor_current()`.
        #
        # Fetches the cursors current document or NULL if there has been an error.
-       fun current: NativeBSON `{ return (bson_t*) mongoc_cursor_current(self); `}
+       fun current: NativeBSON `{
+               // As said in documentation, BSON objects should not be freed manually.
+               bson_t* bson = (bson_t*) mongoc_cursor_current(self);
+               // Copy BSON so we can let the GC free it automatically.
+               return bson_copy(bson);
+       `}
 
        # Wrapper for `mongoc_cursor_next()`.
        #
@@ -519,11 +524,6 @@ extern class NativeMongoCursor `{ mongoc_cursor_t* `}
                return mongoc_cursor_next(self, &doc);
        `}
 
-       # Wrapper for `mongoc_cursor_more()`.
-       #
-       # This function shall indicate if there is more data to be read from the cursor.
-       fun more: Bool `{ return mongoc_cursor_more(self); `}
-
        # Wrapper for `mongoc_cursor_destroy()`.
        #
        # This instance should not be used beyond this point!
diff --git a/lib/nitcorn/examples/.gitignore b/lib/nitcorn/examples/.gitignore
new file mode 100644 (file)
index 0000000..c704121
--- /dev/null
@@ -0,0 +1 @@
+src/restful_annot_gen.nit
index 9597941..d1bdeae 100644 (file)
@@ -1,7 +1,15 @@
-all:
+all: bin/restful_annot
        mkdir -p bin/
        ../../../bin/nitc --dir bin src/nitcorn_hello_world.nit src/simple_file_server.nit
 
 xymus.net:
        mkdir -p bin/
        ../../../bin/nitc --dir bin/ src/xymus_net.nit
+
+pre-build: src/restful_annot_gen.nit
+src/restful_annot_gen.nit:
+       ../../../bin/nitrestful -o $@ src/restful_annot.nit
+
+bin/restful_annot: src/restful_annot_gen.nit
+       mkdir -p bin/
+       ../../../bin/nitc -o $@ src/restful_annot_gen.nit
diff --git a/lib/nitcorn/examples/src/restful_annot.nit b/lib/nitcorn/examples/src/restful_annot.nit
new file mode 100644 (file)
index 0000000..3657031
--- /dev/null
@@ -0,0 +1,47 @@
+# 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 nitcorn::restful
+
+class MyAction
+       super RestfulAction
+
+       # Method answering requests like `foo?s=some_string&i=42&b=true`
+       fun foo(s: String, i: Int, b: Bool): HttpResponse
+       is restful do
+               var resp = new HttpResponse(200)
+               resp.body = "foo {s} {i} {b}"
+               return resp
+       end
+
+       # Method answering requests like `bar?s=these_arguments_are_optional`
+       fun bar(s: nullable String, i: nullable Int, b: nullable Bool): HttpResponse
+       is restful do
+               var resp = new HttpResponse(200)
+               resp.body = "bar {s or else "null"} {i or else "null"} {b or else "null"}"
+               return resp
+       end
+end
+
+var vh = new VirtualHost("localhost:8080")
+
+# Serve everything with our restful action
+vh.routes.add new Route(null, new MyAction)
+
+# Avoid executing when running tests
+if "NIT_TESTING".environ == "true" then exit 0
+
+var factory = new HttpFactory.and_libevent
+factory.config.virtual_hosts.add vh
+factory.run
diff --git a/lib/nitcorn/log.nit b/lib/nitcorn/log.nit
new file mode 100644 (file)
index 0000000..c0d6d52
--- /dev/null
@@ -0,0 +1,59 @@
+# 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.
+
+# Services inserting a timestamp in all prints and to log each requests
+# Also keep track of the performances of the requests
+module log
+
+import reactor
+import realtime
+import performance_analysis
+
+redef class Action
+
+       redef fun prepare_respond_and_close(request, truncated_uri, http_server) do
+               if not log_nitcorn_actions then
+                       super
+                       return
+               end
+               print """{{{class_name}}} enter:
+uri="{{{truncated_uri}}}"
+query="{{{request.query_string}}}"
+body:{{{request.body.length}}} bytes"""
+               var clock = new Clock
+               super
+               var perf = sys.perfs[class_name]
+               perf.add(clock.lapse)
+               if perf.count % perfs_print_period == 0 then print "{class_name} perfs: {perf}:"
+               print "{class_name} return: uri={truncated_uri}"
+       end
+end
+
+redef fun print(object) do
+       var timestamp = new Tm.gmtime
+       super "{timestamp.year}/{timestamp.mon}/{timestamp.mday} "+
+       "{timestamp.hour}:{timestamp.min}:{timestamp.sec}: {object}"
+end
+
+redef fun print_error(object) do
+       var timestamp = new Tm.gmtime
+       super "{timestamp.year}/{timestamp.mon}/{timestamp.mday} "+
+       "{timestamp.hour}:{timestamp.min}:{timestamp.sec}: {object}"
+end
+
+# Should the actions be logged ?
+fun log_nitcorn_actions: Bool do return false
+
+# Number of actions executed before printing the perfs
+fun perfs_print_period: Int do return 20
diff --git a/lib/nitcorn/restful.nit b/lib/nitcorn/restful.nit
new file mode 100644 (file)
index 0000000..bccfd2c
--- /dev/null
@@ -0,0 +1,45 @@
+# 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.
+
+# Support module for the `nitrestful` tool and the `restful` annotation
+module restful is new_annotation(restful)
+
+import nitcorn
+import json::serialization
+
+# Action with `restful` methods
+class RestfulAction
+       super Action
+
+       redef fun answer(request, truncated_uri) do return new HttpResponse(400)
+
+       # Service to deserialize arguments from JSON
+       #
+       # Accepts `nullable String` for convenience, but returns `null` when `val == null`.
+       #
+       # This method is called by the code generated by `nitrestful`.
+       # It can be specialized to customize its behavior.
+       protected fun deserialize_arg(val: nullable String): nullable Object
+       do
+               if val == null then return null
+
+               var deserializer = new JsonDeserializer(val)
+               if deserializer.errors.not_empty then
+                       print_error deserializer.errors.join("\n")
+                       return null
+               end
+
+               return deserializer.deserialize
+       end
+end
diff --git a/share/man/nitrestful.md b/share/man/nitrestful.md
new file mode 100644 (file)
index 0000000..0968830
--- /dev/null
@@ -0,0 +1,19 @@
+# NAME
+
+nitrestful - generates boilerplate code to relay RESTful request to static Nit methods
+
+# SYNOPSIS
+
+nitrestful [*options*]... FILE
+
+# OPTIONS
+
+### `-o`, `--output`
+Output file (can also be 'stdout').
+
+### `--dir`
+Output directory.
+
+# SEE ALSO
+
+The Nit language documentation and the source code of its tools and libraries may be downloaded from <http://nitlanguage.org>
index 6b9866a..0a5b909 100644 (file)
@@ -16,7 +16,7 @@
 
 NITCOPT=--semi-global
 OLDNITCOPT=--semi-global
-OBJS=nitc nitpick nit nitdoc nitls nitunit nitpretty nitmetrics nitx nitlight nitdbg_client nitserial
+OBJS=nitc nitpick nit nitdoc nitls nitunit nitpretty nitmetrics nitx nitlight nitdbg_client nitserial nitrestful
 SRCS=$(patsubst %,%.nit,$(OBJS))
 BINS=$(patsubst %,../bin/%,$(OBJS))
 
index 183b4b2..cf3da76 100644 (file)
@@ -24,7 +24,7 @@ class ASTValidationVisitor
        do
                node.accept_ast_validation(self)
        end
-       private var path = new List[ANode]
+       private var path = new CircularArray[ANode]
        private var seen = new HashSet[ANode]
 end
 
index 8bcd56c..13a6e92 100644 (file)
@@ -195,9 +195,9 @@ redef class NitniCallback
        fun compile_callback_to_cpp(mmodule: MModule, mainmodule: MModule) do end
 end
 
-fun cpp_call_context: CppCallContext do return once new CppCallContext
-fun to_cpp_call_context: ToCppCallContext do return once new ToCppCallContext
-fun from_cpp_call_context: FromCppCallContext do return once new FromCppCallContext
+private fun cpp_call_context: CppCallContext do return once new CppCallContext
+private fun to_cpp_call_context: ToCppCallContext do return once new ToCppCallContext
+private fun from_cpp_call_context: FromCppCallContext do return once new FromCppCallContext
 
 redef class MExplicitCall
        redef fun compile_callback_to_cpp(mmodule, mainmodule)
index 3a70e66..358f211 100644 (file)
@@ -56,7 +56,6 @@ redef class ADefinition
        end
 end
 
-# TODO add annotations on attributes (volatile, sensitive or do_not_serialize?)
 private class SerializationPhasePreModel
        super Phase
 
index cd50d4a..afd299f 100644 (file)
@@ -1060,7 +1060,7 @@ redef class AMethPropdef
                        end
                end
 
-               if mysignature.arity > 0 then
+               if nsig != null then
                        # Check parameters visibility
                        for i in [0..mysignature.arity[ do
                                var nt = nsig.n_params[i].n_type
diff --git a/src/nitrestful.nit b/src/nitrestful.nit
new file mode 100644 (file)
index 0000000..22ffd1d
--- /dev/null
@@ -0,0 +1,252 @@
+# 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.
+
+# Tool generating boilerplate code linking RESTful actions to Nit methods
+module nitrestful
+
+import gen_nit
+
+import frontend
+
+private class RestfulPhase
+       super Phase
+
+       # Classes with methods marked with the `restful` annotation
+       var restful_classes = new HashSet[MClass]
+
+       redef fun process_annotated_node(node, nat)
+       do
+               # Skip if we are not interested
+               var text = nat.n_atid.n_id.text
+               if text != "restful" then return
+
+               if not node isa AMethPropdef then
+                       toolcontext.error(nat.location,
+                               "Syntax Error: `restful` can only be applied on method definitions")
+                       return
+               end
+
+               var mpropdef = node.mpropdef
+               if mpropdef == null then return
+
+               var mproperty = mpropdef.mproperty
+               var mclassdef = mpropdef.mclassdef
+               var mmodule = mclassdef.mmodule
+
+               # Test subclass of `RestfulAction`
+               var sup_class_name = "RestfulAction"
+               var sup_class = toolcontext.modelbuilder.try_get_mclass_by_name(
+                       nat, mmodule, sup_class_name)
+               var in_hierarchy = mclassdef.in_hierarchy
+               if in_hierarchy == null or sup_class == null then return
+               var sup_classes = in_hierarchy.greaters
+               if not sup_classes.has(sup_class.intro) then
+                       toolcontext.error(nat.location,
+                               "Syntax Error: `restful` is only valid within subclasses of `{sup_class_name}`")
+                       return
+               end
+
+               # Register the property
+               var mclass = mclassdef.mclass
+               mclass.restful_methods.add mproperty
+               restful_classes.add mclass
+       end
+end
+
+redef class MClass
+
+       # Methods with the `restful` annotation in this class
+       private var restful_methods = new Array[MMethod]
+end
+
+redef class ToolContext
+       # Generate serialization and deserialization methods on `auto_serializable` annotated classes.
+       var restful_phase: Phase = new RestfulPhase(self, [modelize_class_phase])
+
+       # Where do we put a single result?
+       var opt_output: OptionString = new OptionString("Output file (can also be 'stdout')", "-o", "--output")
+
+       # Where do we put the result?
+       var opt_dir: OptionString = new OptionString("Output directory", "--dir")
+
+       redef init
+       do
+               option_context.add_option(opt_output, opt_dir)
+               super
+       end
+end
+
+redef class MType
+       # Write code in `template` to parse the argument `arg_name` to this parameter type
+       private fun gen_arg_convert(template: Template, arg_name: String)
+       do
+               if self.name == "String" or self.name == "nullable String" then
+                       # String are used as is
+                       template.add """
+                       var out_{{{arg_name}}} = in_{{{arg_name}}}
+"""
+               else
+                       # Deserialize everything else
+                       template.add """
+                       var out_{{{arg_name}}} = deserialize_arg(in_{{{arg_name}}})
+"""
+               end
+       end
+
+       # Does this parameter type needs to be checked before calling the method?
+       #
+       # Some nullable types do not need to be check as `null` values are acceptable.
+       private fun needs_type_check: Bool do return true
+end
+
+redef class MNullableType
+       redef fun needs_type_check do return name != "nullable String" and name != "nullable Object"
+end
+
+var toolcontext = new ToolContext
+toolcontext.tooldescription = """
+Usage: nitrestful [OPTION] module.nit [other_module.nit [...]]
+Generates the boilerplate code to link RESTful request to static Nit methods."""
+
+toolcontext.process_options args
+var arguments = toolcontext.option_context.rest
+
+# Check options
+if toolcontext.opt_output.value != null and toolcontext.opt_dir.value != null then
+       print "Error: cannot use both --dir and --output"
+       exit 1
+end
+if arguments.length > 1 and toolcontext.opt_output.value != null then
+       print "Error: --output needs a single source file. Do you prefer --dir?"
+       exit 1
+end
+
+var model = new Model
+var modelbuilder = new ModelBuilder(model, toolcontext)
+
+var mmodules = modelbuilder.parse(arguments)
+modelbuilder.run_phases
+var first_mmodule = mmodules.first
+
+# Name of the support module
+var module_name
+
+# Path to the support module
+var module_path = toolcontext.opt_output.value
+if module_path == null then
+       module_name = "{first_mmodule.name}_rest"
+       module_path = "{module_name}.nit"
+
+       var dir = toolcontext.opt_dir.value
+       if dir != null then module_path = dir.join_path(module_path)
+else if module_path == "stdout" then
+       module_name = "{first_mmodule.name}_rest"
+       module_path = null
+else if module_path.has_suffix(".nit") then
+       module_name = module_path.basename(".nit")
+else
+       module_name = module_path.basename
+       module_path += ".nit"
+end
+
+var nit_module = new NitModule(module_name)
+nit_module.header = """
+# This file is generated by nitrestful
+# Do not modify, instead refine the generated services.
+"""
+
+for mmod in mmodules do
+       nit_module.imports.add mmod.name
+end
+
+var phase = toolcontext.restful_phase
+assert phase isa RestfulPhase
+
+for mclass in phase.restful_classes do
+
+       var t = new Template
+       nit_module.content.add t
+
+       t.add """
+redef class {{{mclass}}}
+       redef fun answer(request, truncated_uri)
+       do
+               var verbs = truncated_uri.split("/")
+               if verbs.not_empty and verbs.first.is_empty then verbs.shift
+
+               if verbs.length != 1 then return super
+               var verb = verbs.first
+
+"""
+       var methods = mclass.restful_methods
+       for i in methods.length.times, method in methods do
+               var msig = method.intro.msignature
+               if msig == null then continue
+
+               t.add "         "
+               if i != 0 then t.add "else "
+
+               t.add """if verb == "{{{method.name}}}" then
+"""
+
+               var args = new Array[String]
+               var isas = new Array[String]
+               for param in msig.mparameters do
+
+                       t.add """
+                       var in_{{{param.name}}} = request.string_arg("{{{param.name}}}")
+"""
+
+                       var mtype = param.mtype
+                       mtype.gen_arg_convert(t, param.name)
+
+                       var arg = "out_{param.name}"
+                       args.add arg
+
+                       if mtype.needs_type_check then
+                               isas.add "{arg} isa {mtype.name}"
+                       end
+
+                       t.add "\n"
+               end
+
+               if isas.not_empty then t.add """
+                       if not {{{isas.join(" or not ")}}} then
+                               return super
+                       end
+"""
+
+               var sig = ""
+               if args.not_empty then sig = "({args.join(", ")})"
+
+               t.add """
+                       return {{{method.name}}}{{{sig}}}
+"""
+       end
+
+       t.add """
+               end
+               return super
+       end
+end"""
+end
+
+# Write support module
+if module_path != null then
+       # To file
+       nit_module.write_to_file module_path
+else
+       # To stdout
+       nit_module.write_to stdout
+end
index 1c24d69..9c8a07b 100644 (file)
 # generate and include its own serialization support module.
 module nitserial
 
-import frontend
-import rapid_type_analysis
 import template
+import gen_nit
 
-# A Nit module
-#
-# TODO add more features and move to lib
-class NitModule
-       super Template
-
-       var header: nullable Writable = null
-
-       # The module's name
-       var name: Writable
-
-       # Imports from this module
-       var imports = new Array[Writable]
-
-       # Main content of this module
-       var content = new Array[Writable]
-
-       redef fun rendering
-       do
-               var header = header
-               if header != null then add header
-
-               var name = name
-               add "module {name}\n\n"
-
-               for i in imports do add "import {i}\n"
-               add "\n"
-
-               for l in content do add "{l}\n"
-       end
-end
+import frontend
+import rapid_type_analysis
 
 redef class ToolContext
        # Where do we put a single result?
index a87a48f..9a6b117 100644 (file)
@@ -25,7 +25,7 @@ module rapid_type_analysis
 
 import semantize
 
-private import csv # for live_types_to_csv
+import csv # for live_types_to_csv
 private import ordered_tree # for live_methods_to_tree
 
 private import more_collections
index 22d361e..5c399c6 100644 (file)
@@ -18,6 +18,10 @@ class A
        protected fun proA(a: A) do end
        private fun priA(a: A) do end
 
+       fun pubA2: A do abort
+       protected fun proA2: A do abort
+       private fun priA2: A do abort
+
        var vpubA: nullable A is writable, noinit
        protected var vproA: nullable A is protected writable, noinit
        private var vpriA: nullable A is noinit
@@ -30,6 +34,10 @@ class A
        #alt2#protected fun proB(a: B) do end
        private fun priB(a: B) do end
 
+       #alt1#fun pubB2: B do abort
+       #alt2#protected fun proB2: B do abort
+       private fun priB2: B do abort
+
        #alt3#var vpubB: nullable B is writable, noinit
        #alt4#protected var vproB: nullable B is protected writable, noinit
        private var vpriB: nullable B is noinit
diff --git a/tests/bench_seq.nit b/tests/bench_seq.nit
new file mode 100644 (file)
index 0000000..8e83868
--- /dev/null
@@ -0,0 +1,42 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2005-2008 Jean Privat <jean@pryen.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 more_collections
+
+fun test_list(l: Sequence[Int], n: Int)
+do
+       for i in [0..n[ do l.push i
+       print "{l.length} {l.first} {l.last}"
+       for i in [0..n[ do l.unshift -i-1
+       print "{l.length} {l.first} {l.last}"
+       for i in [0..n[ do l.unshift l.pop
+       print "{l.length} {l.first} {l.last}"
+       for i in [0..n[ do l.push l.shift
+       print "{l.length} {l.first} {l.last}"
+       for i in [0..n[ do l[i] = l[n+i]
+       print "{l.length} {l.first} {l.last}"
+       for i in [0..n] do l.insert(i*10, i*2)
+       print "{l.length} {l.first} {l.last}"
+       print ""
+end
+
+var nb = 100
+if args.not_empty then nb = args.first.to_i
+
+test_list(new Array[Int], nb)
+test_list(new List[Int], nb)
+test_list(new UnrolledList[Int], nb)
+test_list(new CircularArray[Int], nb)
index 2fc5a83..8c7c00e 100644 (file)
@@ -17,6 +17,7 @@ test_docdown_args
 pep8analysis
 emscripten
 nitserial_args
+nitrestful_args
 nitunit_args
 nitpretty_args
 hamming_number
diff --git a/tests/nitrestful.args b/tests/nitrestful.args
new file mode 100644 (file)
index 0000000..054e93a
--- /dev/null
@@ -0,0 +1 @@
+-o stdout ../lib/nitcorn/examples/src/restful_annot.nit
index 2fc5a83..8c7c00e 100644 (file)
@@ -17,6 +17,7 @@ test_docdown_args
 pep8analysis
 emscripten
 nitserial_args
+nitrestful_args
 nitunit_args
 nitpretty_args
 hamming_number
index 6d747f4..30dc40c 100644 (file)
@@ -1 +1,2 @@
-alt/base_prot_sig_alt1.nit:29,14: Error: the public property `pubB` cannot contain the private type `B`.
+alt/base_prot_sig_alt1.nit:33,14: Error: the public property `pubB` cannot contain the private type `B`.
+alt/base_prot_sig_alt1.nit:37,13: Error: the public property `pubB2` cannot contain the private type `B`.
index e28956b..f31d97a 100644 (file)
@@ -1 +1,2 @@
-alt/base_prot_sig_alt2.nit:30,24: Error: the protected property `proB` cannot contain the private type `B`.
+alt/base_prot_sig_alt2.nit:34,24: Error: the protected property `proB` cannot contain the private type `B`.
+alt/base_prot_sig_alt2.nit:38,23: Error: the protected property `proB2` cannot contain the private type `B`.
index 6c32a28..aa019c0 100644 (file)
@@ -1,2 +1,2 @@
-alt/base_prot_sig_alt3.nit:33,13--22: Error: the public property `vpubB` cannot contain the private type `B`.
-alt/base_prot_sig_alt3.nit:33,13--22: Error: the public property `vpubB=` cannot contain the private type `B`.
+alt/base_prot_sig_alt3.nit:41,13--22: Error: the public property `vpubB` cannot contain the private type `B`.
+alt/base_prot_sig_alt3.nit:41,13--22: Error: the public property `vpubB=` cannot contain the private type `B`.
index 7a646cb..bb4255d 100644 (file)
@@ -1,2 +1,2 @@
-alt/base_prot_sig_alt4.nit:34,23--32: Error: the protected property `vproB` cannot contain the private type `B`.
-alt/base_prot_sig_alt4.nit:34,23--32: Error: the protected property `vproB=` cannot contain the private type `B`.
+alt/base_prot_sig_alt4.nit:42,23--32: Error: the protected property `vproB` cannot contain the private type `B`.
+alt/base_prot_sig_alt4.nit:42,23--32: Error: the protected property `vproB=` cannot contain the private type `B`.
index 01696e4..071db82 100644 (file)
@@ -1,2 +1,2 @@
-alt/base_prot_sig_alt5.nit:37,6--11: Error: the public property `vpubB2` cannot contain the private type `B`.
-alt/base_prot_sig_alt5.nit:37,6--11: Error: the public property `vpubB2=` cannot contain the private type `B`.
+alt/base_prot_sig_alt5.nit:45,6--11: Error: the public property `vpubB2` cannot contain the private type `B`.
+alt/base_prot_sig_alt5.nit:45,6--11: Error: the public property `vpubB2=` cannot contain the private type `B`.
index f129fb5..154d877 100644 (file)
@@ -1,2 +1,2 @@
-alt/base_prot_sig_alt6.nit:38,16--21: Error: the protected property `vproB2` cannot contain the private type `B`.
-alt/base_prot_sig_alt6.nit:38,16--21: Error: the protected property `vproB2=` cannot contain the private type `B`.
+alt/base_prot_sig_alt6.nit:46,16--21: Error: the protected property `vproB2` cannot contain the private type `B`.
+alt/base_prot_sig_alt6.nit:46,16--21: Error: the protected property `vproB2=` cannot contain the private type `B`.
index 1451e5f..786426c 100644 (file)
@@ -1,10 +1,10 @@
-alt/base_prot_sig_alt7.nit:46,2--10: Error: `private` is the only legal visibility for properties in a private class.
-alt/base_prot_sig_alt7.nit:50,2--10: Error: `private` is the only legal visibility for properties in a private class.
-alt/base_prot_sig_alt7.nit:50,37--45: Error: `private` is the only legal visibility for properties in a private class.
 alt/base_prot_sig_alt7.nit:54,2--10: Error: `private` is the only legal visibility for properties in a private class.
-alt/base_prot_sig_alt7.nit:54,34--42: Error: `private` is the only legal visibility for properties in a private class.
 alt/base_prot_sig_alt7.nit:58,2--10: Error: `private` is the only legal visibility for properties in a private class.
+alt/base_prot_sig_alt7.nit:58,37--45: Error: `private` is the only legal visibility for properties in a private class.
 alt/base_prot_sig_alt7.nit:62,2--10: Error: `private` is the only legal visibility for properties in a private class.
-alt/base_prot_sig_alt7.nit:62,37--45: Error: `private` is the only legal visibility for properties in a private class.
+alt/base_prot_sig_alt7.nit:62,34--42: Error: `private` is the only legal visibility for properties in a private class.
 alt/base_prot_sig_alt7.nit:66,2--10: Error: `private` is the only legal visibility for properties in a private class.
-alt/base_prot_sig_alt7.nit:66,34--42: Error: `private` is the only legal visibility for properties in a private class.
+alt/base_prot_sig_alt7.nit:70,2--10: Error: `private` is the only legal visibility for properties in a private class.
+alt/base_prot_sig_alt7.nit:70,37--45: Error: `private` is the only legal visibility for properties in a private class.
+alt/base_prot_sig_alt7.nit:74,2--10: Error: `private` is the only legal visibility for properties in a private class.
+alt/base_prot_sig_alt7.nit:74,34--42: Error: `private` is the only legal visibility for properties in a private class.
diff --git a/tests/sav/bench_seq.res b/tests/sav/bench_seq.res
new file mode 100644 (file)
index 0000000..d9c755c
--- /dev/null
@@ -0,0 +1,28 @@
+100 0 99
+200 -100 99
+200 0 -1
+200 -100 99
+200 0 99
+301 0 99
+
+100 0 99
+200 -100 99
+200 0 -1
+200 -100 99
+200 0 99
+301 0 99
+
+100 0 99
+200 -100 99
+200 0 -1
+200 -100 99
+200 0 99
+301 0 99
+
+100 0 99
+200 -100 99
+200 0 -1
+200 -100 99
+200 0 99
+301 0 99
+
diff --git a/tests/sav/nitrestful.res b/tests/sav/nitrestful.res
new file mode 100644 (file)
index 0000000..c1bc443
--- /dev/null
@@ -0,0 +1,3 @@
+Usage: nitrestful [OPTION] module.nit [other_module.nit [...]]
+Generates the boilerplate code to link RESTful request to static Nit methods.
+Use --help for help
diff --git a/tests/sav/nitrestful_args1.res b/tests/sav/nitrestful_args1.res
new file mode 100644 (file)
index 0000000..e2947d2
--- /dev/null
@@ -0,0 +1,47 @@
+# This file is generated by nitrestful
+# Do not modify, instead refine the generated services.
+module restful_annot_rest
+
+import restful_annot
+
+redef class MyAction
+       redef fun answer(request, truncated_uri)
+       do
+               var verbs = truncated_uri.split("/")
+               if verbs.not_empty and verbs.first.is_empty then verbs.shift
+
+               if verbs.length != 1 then return super
+               var verb = verbs.first
+
+               if verb == "foo" then
+                       var in_s = request.string_arg("s")
+                       var out_s = in_s
+
+                       var in_i = request.string_arg("i")
+                       var out_i = deserialize_arg(in_i)
+
+                       var in_b = request.string_arg("b")
+                       var out_b = deserialize_arg(in_b)
+
+                       if not out_s isa String or not out_i isa Int or not out_b isa Bool then
+                               return super
+                       end
+                       return foo(out_s, out_i, out_b)
+               else if verb == "bar" then
+                       var in_s = request.string_arg("s")
+                       var out_s = in_s
+
+                       var in_i = request.string_arg("i")
+                       var out_i = deserialize_arg(in_i)
+
+                       var in_b = request.string_arg("b")
+                       var out_b = deserialize_arg(in_b)
+
+                       if not out_i isa nullable Int or not out_b isa nullable Bool then
+                               return super
+                       end
+                       return bar(out_s, out_i, out_b)
+               end
+               return super
+       end
+end
index ec356da..9f86235 100644 (file)
@@ -78,7 +78,7 @@ Task [
 Object -> Task [dir=back arrowtail=open style=dashed];
 
 A [
- label = "{A|- _vpubA: nullable A\l- _vproA: nullable A\l- _vpriA: nullable A\l- _vpubA2: A\l- _vproA2: A\l- _vpriA2: A\l- _vpriB: nullable B\l- _vpriB2: B\l|+ pubA(a: A)\l# proA(a: A)\l- priA(a: A)\l+ vpubA(): nullable A\l+ vpubA=(vpubA: nullable A)\l# vproA(): nullable A\l# vproA=(vproA: nullable A)\l- vpriA(): nullable A\l- vpriA=(vpriA: nullable A)\l+ vpubA2(): A\l+ vpubA2=(vpubA2: A)\l# vproA2(): A\l# vproA2=(vproA2: A)\l- vpriA2(): A\l- vpriA2=(vpriA2: A)\l- priB(a: B)\l- vpriB(): nullable B\l- vpriB=(vpriB: nullable B)\l- vpriB2(): B\l- vpriB2=(vpriB2: B)\l}"
+ label = "{A|- _vpubA: nullable A\l- _vproA: nullable A\l- _vpriA: nullable A\l- _vpubA2: A\l- _vproA2: A\l- _vpriA2: A\l- _vpriB: nullable B\l- _vpriB2: B\l|+ pubA(a: A)\l# proA(a: A)\l- priA(a: A)\l+ pubA2(): A\l# proA2(): A\l- priA2(): A\l+ vpubA(): nullable A\l+ vpubA=(vpubA: nullable A)\l# vproA(): nullable A\l# vproA=(vproA: nullable A)\l- vpriA(): nullable A\l- vpriA=(vpriA: nullable A)\l+ vpubA2(): A\l+ vpubA2=(vpubA2: A)\l# vproA2(): A\l# vproA2=(vproA2: A)\l- vpriA2(): A\l- vpriA2=(vpriA2: A)\l- priB(a: B)\l- priB2(): B\l- vpriB(): nullable B\l- vpriB=(vpriB: nullable B)\l- vpriB2(): B\l- vpriB2=(vpriB2: B)\l}"
 ]
 Object -> A [dir=back arrowtail=open style=dashed];
 
index a87d928..c846cb7 100644 (file)
@@ -78,7 +78,7 @@ Task [
 Object -> Task [dir=back arrowtail=open style=dashed];
 
 A [
- label = "{A||+ pubA(a: A)\l+ vpubA(): nullable A\l+ vpubA=(vpubA: nullable A)\l+ vpubA2(): A\l+ vpubA2=(vpubA2: A)\l}"
+ label = "{A||+ pubA(a: A)\l+ pubA2(): A\l+ vpubA(): nullable A\l+ vpubA=(vpubA: nullable A)\l+ vpubA2(): A\l+ vpubA2=(vpubA2: A)\l}"
 ]
 Object -> A [dir=back arrowtail=open style=dashed];
 
index 458de3c..9e5ceab 100644 (file)
@@ -1,4 +1,4 @@
-Runtime error: Cast failed. Expected `E`, got `Bool` (../lib/core/collection/array.nit:960)
+Runtime error: Cast failed. Expected `E`, got `Bool` (../lib/core/collection/array.nit:989)
 NativeString
 0x4e
 Nit
index 1de5fa0..c55a08b 100644 (file)
@@ -14,3 +14,7 @@ true
 true
 true
 
+true
+true
+true
+
index ff77897..40cfa68 100644 (file)
@@ -29,3 +29,4 @@ test_seq(new List[Int], new List[Int])
 test_seq(new Array[Int], new Array[Int])
 test_seq(new List[Int], new Array[Int])
 test_seq(new Array[Int], new List[Int])
+test_seq(new Array[Int], new CircularArray[Int])