Merge: Abstract attribute
authorJean Privat <jean@pryen.org>
Tue, 10 Mar 2015 13:51:12 +0000 (20:51 +0700)
committerJean Privat <jean@pryen.org>
Tue, 10 Mar 2015 13:51:12 +0000 (20:51 +0700)
Add the annotation `abstract` on attributes.

It is just a syntactic sugar to define a couple of abstract getter-setters with a shared documentation without an associated slot in the instance so it can be used in interfaces.

~~~nit
interface Foo
var a: Object is abstract
end

class Bar
super Foo
         # A concrete attribute that redefine the abstract one
redef var a
end

class Baz
super Foo
var real_a: Object
         # A pair of concrete methods that redefine the abstract attribute
redef fun a do return real_a
redef fun a=(x) do real_a = x
end
~~~

The visibility rules are unchanged with regard to concrete attributes, so by default the writer is private.

Needed cleaning (and a bugfix) are included in the PR

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

66 files changed:
VERSION
contrib/jwrapper/src/model.nit
contrib/nitiwiki/src/wiki_base.nit
contrib/nitiwiki/src/wiki_html.nit
contrib/nitrpg/src/achievements.nit [new file with mode: 0644]
contrib/nitrpg/src/game.nit
contrib/nitrpg/src/listener.nit
contrib/nitrpg/src/statistics.nit
contrib/nitrpg/src/templates/panels.nit
contrib/nitrpg/src/templates/templates.nit
contrib/nitrpg/src/templates/templates_base.nit
contrib/nitrpg/src/templates/templates_events.nit
contrib/nitrpg/src/web.nit
lib/android/assets_and_resources.nit
lib/android/audio.nit
lib/android/shared_preferences/shared_preferences_api10.nit
lib/android/vibration.nit
lib/glesv2/glesv2.nit
lib/poset.nit
lib/sqlite3/sqlite3.nit
lib/standard/collection/abstract_collection.nit
lib/standard/collection/array.nit
lib/standard/collection/hash_collection.nit
lib/standard/file.nit
lib/standard/kernel.nit
lib/standard/ropes.nit
lib/standard/stream.nit
lib/standard/string.nit
misc/README.md
misc/vim/plugin/nit.vim
share/man/nitc.md
src/astvalidation.nit
src/compiler/abstract_compiler.nit
src/compiler/separate_compiler.nit
src/doc/vim_autocomplete.nit
src/frontend/cached.nit [deleted file]
src/frontend/check_annotation.nit
src/frontend/frontend.nit
src/frontend/serialization_phase.nit
src/model_utils.nit
src/modelize/modelize_property.nit
src/nitlight.nit
src/nitserial.nit
src/parser/parser_nodes.nit
src/phase.nit
src/rapid_type_analysis.nit
src/semantize/auto_super_init.nit
src/toolcontext.nit
tests/base_attr_lazy.nit
tests/base_init_raf2.nit [moved from tests/base_at_cached.nit with 52% similarity]
tests/nitc.args
tests/sav/base_at_cached.res [deleted file]
tests/sav/base_at_cached_alt1.res [deleted file]
tests/sav/base_at_cached_alt2.res [deleted file]
tests/sav/base_at_cached_alt3.res [deleted file]
tests/sav/base_attr_lazy_alt2.res [new file with mode: 0644]
tests/sav/base_init_raf2.res [new file with mode: 0644]
tests/sav/nitc_args9.res [new file with mode: 0644]
tests/sav/nitg-e/fixme/base_gen_reassign_alt4.res
tests/sav/nitg-e/fixme/base_gen_reassign_alt5.res
tests/sav/nitg-e/fixme/base_gen_reassign_alt6.res
tests/sav/nitg-sg/fixme/test_gen.res [deleted file]
tests/sav/nitserial_args1.res
tests/sav/nituml_args3.res
tests/sav/nituml_args4.res
tests/sav/test_new_native_alt1.res

diff --git a/VERSION b/VERSION
index 63f2359..2c0a9c7 100644 (file)
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-v0.7.1
+v0.7.2
index 34792c9..d38a7a1 100644 (file)
@@ -230,9 +230,9 @@ class JavaType
                return id
        end
 
-       fun collections_list: Array[String] is cached do return ["List", "ArrayList", "LinkedList", "Vector", "Set", "SortedSet", "HashSet", "TreeSet", "LinkedHashSet", "Map", "SortedMap", "HashMap", "TreeMap", "Hashtable", "LinkedHashMap"]
-       fun iterable: Array[String] is cached do return ["ArrayList", "Set", "HashSet", "LinkedHashSet", "LinkedList", "Stack", "TreeSet", "Vector"]
-       fun maps: Array[String] is cached do return ["Map", "SortedMap", "HashMap", "TreeMap", "Hashtable", "LinkedHashMap"]
+       var collections_list: Array[String] is lazy do return ["List", "ArrayList", "LinkedList", "Vector", "Set", "SortedSet", "HashSet", "TreeSet", "LinkedHashSet", "Map", "SortedMap", "HashMap", "TreeMap", "Hashtable", "LinkedHashMap"]
+       var iterable: Array[String] is lazy do return ["ArrayList", "Set", "HashSet", "LinkedHashSet", "LinkedList", "Stack", "TreeSet", "Vector"]
+       var maps: Array[String] is lazy do return ["Map", "SortedMap", "HashMap", "TreeMap", "Hashtable", "LinkedHashMap"]
 end
 
 class NitType
index fa78818..1527d4c 100644 (file)
@@ -308,7 +308,7 @@ abstract class WikiEntry
        # Result is returned as an array containg ordered entries:
        # `breadcrumbs.first` is the root entry and
        # `breadcrumbs.last == self`
-       fun breadcrumbs: Array[WikiEntry] is cached do
+       var breadcrumbs: Array[WikiEntry] is lazy do
                var path = new Array[WikiEntry]
                var entry: nullable WikiEntry = self
                while entry != null and not entry.is_root do
@@ -538,7 +538,7 @@ class WikiArticle
        # Extract the markdown text from `source_file`.
        #
        # REQUIRE: `has_source`.
-       fun md: String is cached do
+       var md: String is lazy do
                assert has_source
                var file = new FileReader.open(src_full_path.to_s)
                var md = file.read_all
@@ -577,7 +577,7 @@ class WikiConfig
        #
        # * key: `wiki.name`
        # * default: `MyWiki`
-       fun wiki_name: String is cached do return value_or_default("wiki.name", "MyWiki")
+       var wiki_name: String is lazy do return value_or_default("wiki.name", "MyWiki")
 
        # Site description.
        #
@@ -585,7 +585,7 @@ class WikiConfig
        #
        # * key: `wiki.desc`
        # * default: ``
-       fun wiki_desc: String is cached do return value_or_default("wiki.desc", "")
+       var wiki_desc: String is lazy do return value_or_default("wiki.desc", "")
 
        # Site logo url.
        #
@@ -593,13 +593,13 @@ class WikiConfig
        #
        # * key: `wiki.logo`
        # * default: ``
-       fun wiki_logo: String is cached do return value_or_default("wiki.logo", "")
+       var wiki_logo: String is lazy do return value_or_default("wiki.logo", "")
 
        # Root url of the wiki.
        #
        # * key: `wiki.root_url`
        # * default: `http://localhost/`
-       fun root_url: String is cached do return value_or_default("wiki.root_url", "http://localhost/")
+       var root_url: String is lazy do return value_or_default("wiki.root_url", "http://localhost/")
 
 
        # Root directory of the wiki.
@@ -608,7 +608,7 @@ class WikiConfig
        #
        # * key: `wiki.root_dir`
        # * default: `./`
-       fun root_dir: String is cached do return value_or_default("wiki.root_dir", "./").simplify_path
+       var root_dir: String is lazy do return value_or_default("wiki.root_dir", "./").simplify_path
 
        # Pages directory.
        #
@@ -616,7 +616,7 @@ class WikiConfig
        #
        # * key: `wiki.source_dir
        # * default: `pages/`
-       fun source_dir: String is cached do
+       var source_dir: String is lazy do
                return value_or_default("wiki.source_dir", "pages/").simplify_path
        end
 
@@ -627,7 +627,7 @@ class WikiConfig
        #
        # * key: `wiki.out_dir`
        # * default: `out/`
-       fun out_dir: String is cached do return value_or_default("wiki.out_dir", "out/").simplify_path
+       var out_dir: String is lazy do return value_or_default("wiki.out_dir", "out/").simplify_path
 
        # Asset files directory.
        #
@@ -636,7 +636,7 @@ class WikiConfig
        #
        # * key: `wiki.assets_dir`
        # * default: `assets/`
-       fun assets_dir: String is cached do
+       var assets_dir: String is lazy do
                return value_or_default("wiki.assets_dir", "assets/").simplify_path
        end
 
@@ -647,7 +647,7 @@ class WikiConfig
        #
        # * key: `wiki.templates_dir`
        # * default: `templates/`
-       fun templates_dir: String is cached do
+       var templates_dir: String is lazy do
                return value_or_default("wiki.templates_dir", "templates/").simplify_path
        end
 
@@ -657,7 +657,7 @@ class WikiConfig
        #
        # * key: `wiki.template`
        # * default: `template.html`
-       fun template_file: String is cached do
+       var template_file: String is lazy do
                return value_or_default("wiki.template", "template.html")
        end
 
@@ -668,7 +668,7 @@ class WikiConfig
        #
        # * key: `wiki.header`
        # * default: `header.html`
-       fun header_file: String is cached do
+       var header_file: String is lazy do
                return value_or_default("wiki.header", "header.html")
        end
 
@@ -678,7 +678,7 @@ class WikiConfig
        #
        # * key: `wiki.menu`
        # * default: `menu.html`
-       fun menu_file: String is cached do
+       var menu_file: String is lazy do
                return value_or_default("wiki.menu", "menu.html")
        end
 
@@ -689,7 +689,7 @@ class WikiConfig
        #
        # * key: `wiki.footer`
        # * default: `footer.html`
-       fun footer_file: String is cached do
+       var footer_file: String is lazy do
                return value_or_default("wiki.footer", "footer.html")
        end
 
@@ -699,19 +699,19 @@ class WikiConfig
        #
        # * key: `wiki.rsync_dir`
        # * default: ``
-       fun rsync_dir: String is cached do return value_or_default("wiki.rsync_dir", "")
+       var rsync_dir: String is lazy do return value_or_default("wiki.rsync_dir", "")
 
        # Remote repository used to pull modifications on sources.
        #
        # * key: `wiki.git_origin`
        # * default: `origin`
-       fun git_origin: String is cached do return value_or_default("wiki.git_origin", "origin")
+       var git_origin: String is lazy do return value_or_default("wiki.git_origin", "origin")
 
        # Remote branch used to pull modifications on sources.
        #
        # * key: `wiki.git_branch`
        # * default: `master`
-       fun git_branch: String is cached do return value_or_default("wiki.git_branch", "master")
+       var git_branch: String is lazy do return value_or_default("wiki.git_branch", "master")
 end
 
 # WikiSection custom configuration.
index 10ab09b..3427016 100644 (file)
@@ -107,7 +107,7 @@ redef class WikiSection
        #
        # If no file `index.md` exists for this section,
        # a summary is generated using contained articles.
-       fun index: WikiArticle is cached do
+       var index: WikiArticle is lazy do
                for child in children.values do
                        if child isa WikiArticle and child.is_index then return child
                end
diff --git a/contrib/nitrpg/src/achievements.nit b/contrib/nitrpg/src/achievements.nit
new file mode 100644 (file)
index 0000000..625bfc2
--- /dev/null
@@ -0,0 +1,402 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2014-2015 Alexandre Terrasa <alexandre@moz-code.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.
+
+# `nitrpg` achievements.
+#
+# Players can unlock achievements by performing remarkable actions on the repo.
+# Achievements are rewarded by nitcoins.
+module achievements
+
+import events
+import statistics
+
+redef class GameEntity
+
+       # Register a new achievement for this game entity.
+       #
+       # Saves the achievement in game data.
+       # Do nothing is the achievement is already registered.
+       #
+       # TODO should update the achievement?
+       fun add_achievement(achievement: Achievement) do
+               var key = self.key / achievement.key
+               if game.store.has_key(key) then return
+               stats.inc("achievements")
+               achievement.save_in(self)
+               save
+       end
+
+       # Load the event from its `id`.
+       #
+       # Looks for the event save file in game data.
+       # Returns `null` if the event cannot be found.
+       fun load_achievement(id: String): nullable Achievement do
+               var key = self.key / "achievements" / id
+               if not game.store.has_key(key) then return null
+               var json = game.store.load_object(key)
+               return new Achievement.from_json(game, json)
+       end
+
+       # List all events registered in this entity.
+       #
+       # This list is reloaded from game data each time its called.
+       #
+       # To add events see `add_event`.
+       fun load_achievements: MapRead[String, Achievement] do
+               var res = new HashMap[String, Achievement]
+               var key = self.key / "achievements"
+               if not game.store.has_collection(key) then return res
+               var coll = game.store.list_collection(key)
+               for id in coll do
+                       res[id.to_s] = load_achievement(id.to_s).as(not null)
+               end
+               return res
+       end
+end
+
+# Achievements are rewarded by `nitcoins`.
+#
+# An achievement represents a notable action performed by a `Player`.
+# Player that `unlock` achievements are rewarded by nitcoins.
+class Achievement
+       super GameEntity
+
+       redef var key is lazy do return "achievements" / id
+
+       redef var game
+
+       # Uniq ID for this achievement.
+       var id: String
+
+       # Name of this achievement.
+       var name: String
+
+       # Description of the achievement.
+       var desc: String
+
+       # Reward that this achievement give in nitcoins.
+       var reward: Int
+
+       # Is this achievement unlocked by somebody?
+       var is_unlocked: Bool is lazy do return not load_events.is_empty
+
+       # Init `self` from a `json` object.
+       #
+       # Used to load achievements from storage.
+       init from_json(game: Game, json: JsonObject) do
+               self.game = game
+               id = json["id"].to_s
+               name = json["name"].to_s
+               desc = json["desc"].to_s
+               reward = json["reward"].as(Int)
+       end
+
+       redef fun to_json do
+               var json = super
+               json["id"] = id
+               json["name"] = name
+               json["desc"] = desc
+               json["reward"] = reward
+               return json
+       end
+end
+
+redef class Player
+
+       # Is `a` unlocked for this `Player`?
+       fun has_achievement(a: Achievement): Bool do
+               return load_achievement(a.id) != null
+       end
+
+       # Unlocks an achievement for this Player based on a GithubEvent.
+       #
+       # Register the achievement and adds the achievement reward to the player
+       # nitcoins.
+       #
+       # Do nothing is this player has already unlocked the achievement.
+       #
+       # TODO: add abstraction so achievements do not depend on GithubEvent.
+       fun unlock_achievement(a: Achievement, event: GithubEvent) do
+               if has_achievement(a) then return
+               nitcoins += a.reward
+               add_achievement(a)
+               trigger_unlock_event(a, event)
+       end
+
+       # Create a new event that marks the achievement unlocking.
+       fun trigger_unlock_event(achievement: Achievement, event: GithubEvent) do
+               var obj = new JsonObject
+               obj["player"] = name
+               obj["reward"] = achievement.reward
+               obj["achievement"] = achievement.id
+               obj["github_event"] = event.json
+               var ge = new GameEvent(game, "achievement_unlocked", obj)
+               add_event(ge)
+               game.add_event(ge)
+               achievement.add_event(ge)
+       end
+end
+
+# `GameReactor` dedicated to achievements unlocking.
+interface AchievementReactor
+       super GameReactor
+
+       # Unic ID of the achievement this reactor unlocks.
+       fun id: String is abstract
+
+       # Name of the achievement this reactor unlocks.
+       fun name: String is abstract
+
+       # Description of the achievement this reactor unlocks.
+       fun desc: String is abstract
+
+       # Amount of nitcoins rewarded for unlocking the achievement.
+       fun reward: Int is abstract
+
+       # Return a new instance of the achievement to unlock.
+       fun new_achievement(game: Game): Achievement do
+               var achievement = new Achievement(game, id, name, desc, reward)
+               game.add_achievement(achievement)
+               return achievement
+       end
+end
+
+#####################
+### Issues
+#####################
+
+# Unlock achievement after X issues.
+#
+# Used to factorize behavior.
+abstract class PlayerXIssues
+       super AchievementReactor
+
+       # Number of PR required to unlock the achievement.
+       var threshold: Int is noinit
+
+       redef fun react_event(game, event) do
+               if not event isa IssuesEvent then return
+               if not event.action == "opened" then return
+               var player = event.issue.user.player(game)
+               if player.stats["issues"] == threshold then
+                       var a = new_achievement(game)
+                       player.unlock_achievement(a, event)
+               end
+       end
+end
+
+# Player open his first issue.
+class Player1Issue
+       super PlayerXIssues
+
+       redef var id = "player_1_issue"
+       redef var name = "First complaint"
+       redef var desc = "Open your first issue."
+       redef var reward = 10
+       redef var threshold = 1
+end
+
+# Player open 100 issues.
+class Player100Issues
+       super PlayerXIssues
+
+       redef var id = "player_100_issues"
+       redef var name = "Mature whiner"
+       redef var desc = "Open 100 issues in the game."
+       redef var reward = 100
+       redef var threshold = 100
+end
+
+# Player open 1 000 issues.
+class Player1KIssues
+       super PlayerXIssues
+
+       redef var id = "player_1000_issues"
+       redef var name = "You, sir, complain a lot"
+       redef var desc = "Open 1000 issues in the game."
+       redef var reward = 1000
+       redef var threshold = 1000
+end
+
+# Player open an issue about nitdoc.
+class IssueAboutNitdoc
+       super AchievementReactor
+
+       redef var id = "issue_about_nitdoc"
+       redef var name = "Say nitdoc again, I double dare you!"
+       redef var desc = "Open an issue with \"nitdoc\" in the title."
+       redef var reward = 10
+
+       redef fun react_event(game, event) do
+               if not event isa IssuesEvent then return
+               if not event.action == "opened" then return
+               var player = event.issue.user.player(game)
+               var re = "nitdoc".to_re
+               re.ignore_case = true
+               if event.issue.title.has(re) then
+                       var a = new_achievement(game)
+                       player.unlock_achievement(a, event)
+               end
+       end
+end
+
+# Player open an issue about FFI.
+class IssueAboutFFI
+       super PlayerXIssues
+
+       redef var id = "issue_about_ffi"
+       redef var name = "Polyglot what?"
+       redef var desc = "Open an issue with `ffi` in the title."
+       redef var reward = 10
+
+       redef fun react_event(game, event) do
+               if not event isa IssuesEvent then return
+               if not event.action == "opened" then return
+               var player = event.issue.user.player(game)
+               var re = "\\bffi\\b".to_re
+               re.ignore_case = true
+               if event.issue.title.has(re) then
+                       var a = new_achievement(game)
+                       player.unlock_achievement(a, event)
+               end
+       end
+end
+
+#####################
+### Pull requests
+#####################
+
+# Unlock achievement after X pull requests.
+#
+# Used to factorize behavior.
+abstract class PlayerXPulls
+       super AchievementReactor
+
+       # Number of PR required to unlock the achievement.
+       var threshold: Int is noinit
+
+       redef fun react_event(game, event) do
+               if not event isa PullRequestEvent then return
+               if not event.action == "opened" then return
+               var player = event.pull.user.player(game)
+               if player.stats["pulls"] == threshold then
+                       var a = new_achievement(game)
+                       player.unlock_achievement(a, event)
+               end
+       end
+end
+
+# Open your first pull request.
+class Player1Pull
+       super PlayerXPulls
+
+       redef var id = "player_1_pull"
+       redef var name = "First PR"
+       redef var desc = "Open your first pull request."
+       redef var reward = 10
+       redef var threshold = 1
+end
+
+# Author 100 pull requests.
+class Player100Pulls
+       super PlayerXPulls
+
+       redef var id = "player_100_pulls"
+       redef var name = "100 pull requests!!!"
+       redef var desc = "Open 100 pull requests in the game."
+       redef var reward = 100
+       redef var threshold = 100
+end
+
+# Author 1000 pull requests.
+class Player1KPulls
+       super PlayerXPulls
+
+       redef var id = "player_1000_pulls"
+       redef var name = "1000 PULL REQUESTS!!!"
+       redef var desc = "Open 1000 pull requests in the game."
+       redef var reward = 1000
+       redef var threshold = 1000
+end
+
+#####################
+### Commits
+#####################
+
+# Unlock achievement after X merged commits.
+#
+# Used to factorize behavior.
+abstract class PlayerXCommits
+       super AchievementReactor
+
+       # Number of PR required to unlock the achievement.
+       var threshold: Int is noinit
+
+       redef fun react_event(game, event) do
+               if not event isa PullRequestEvent then return
+               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
+                       var a = new_achievement(game)
+                       player.unlock_achievement(a, event)
+               end
+       end
+end
+
+# Author your first commit in the game.
+class Player1Commit
+       super PlayerXCommits
+
+       redef var id = "player_1_commit"
+       redef var name = "First blood"
+       redef var desc = "Author your first commit in the game."
+       redef var reward = 10
+       redef var threshold = 1
+end
+
+# Author 100 commits.
+class Player100Commits
+       super PlayerXCommits
+
+       redef var id = "player_100_commits"
+       redef var name = "100 commits"
+       redef var desc = "Author 100 commits in the game."
+       redef var reward = 100
+       redef var threshold = 100
+end
+
+# Author 1 000 commits.
+class Player1KCommits
+       super PlayerXCommits
+
+       redef var id = "player_1000_commits"
+       redef var name = "1000 commits!!!"
+       redef var desc = "Author 1000 commits in the game."
+       redef var reward = 1000
+       redef var threshold = 1000
+end
+
+# Author 10 000 commits.
+class Player10KCommits
+       super PlayerXCommits
+
+       redef var id = "player_10000_commits"
+       redef var name = "10000 COMMITS!!!"
+       redef var desc = "Author 10000 commits in the game."
+       redef var reward = 10000
+       redef var threshold = 10000
+end
index 9c71566..4d62283 100644 (file)
@@ -235,11 +235,16 @@ end
 
 redef class User
        # The player linked to `self`.
-       fun player(game: Game): Player is lazy do
-               var player = game.load_player(login)
+       fun player(game: Game): Player do
+               var player = player_cache.get_or_null(game)
+               if player != null then return player
+               player = game.load_player(login)
                if player == null then player = game.add_player(self)
+               player_cache[game] = player
                return player
        end
+
+       private var player_cache = new HashMap[Game, Player]
 end
 
 # A GameReactor reacts to event sent by a `Github::HookListener`.
index d35b169..0e4ba00 100644 (file)
@@ -17,8 +17,8 @@
 # This tool is runned to listen to `Github::Event` and update the game.
 module listener
 
-import statistics
 import reactors
+import achievements
 import github::hooks
 
 # `HookListener` that redirects events to a `Game` instance.
@@ -34,8 +34,14 @@ class RpgHookListener
                # TODO handle verbosity with opts
                game.verbose_lvl = 1
                game.message(1, "Received event {event} for {game.repo.full_name}")
-               for reactor in reactors do reactor.react_event(game, event)
+               for reactor in reactors do
+                       game.message(2, "Apply reactor {reactor} on {event}")
+                       reactor.react_event(game, event)
+               end
        end
+
+       # Register a reactor for this listener.
+       fun add_reactor(reactors: GameReactor...) do self.reactors.add_all reactors
 end
 
 if args.length != 2 then
@@ -51,9 +57,12 @@ var port = args[1].to_i
 
 var api = new GithubAPI(get_github_oauth)
 
-var listener = new RpgHookListener(api, host, port)
-listener.reactors.add new StatisticsReactor
-listener.reactors.add new PlayerReactor
+var l = new RpgHookListener(api, host, port)
+l.add_reactor(new StatisticsReactor, new PlayerReactor)
+l.add_reactor(new Player1Issue, new Player100Issues, new Player1KIssues)
+l.add_reactor(new Player1Pull, new Player100Pulls, new Player1KPulls)
+l.add_reactor(new Player1Commit, new Player100Commits, new Player1KCommits)
+l.add_reactor(new IssueAboutNitdoc, new IssueAboutFFI)
 
 print "Listening events on {host}:{port}"
-listener.listen
+l.listen
index 39c05af..dfdb3ba 100644 (file)
@@ -26,6 +26,9 @@ import counter
 
 redef class GameEntity
 
+       # Statistics for this entity.
+       fun stats: GameStats is abstract
+
        # Load statistics for this `MEntity` if any.
        fun load_statistics: nullable GameStats do
                var key = self.key / "statistics"
@@ -33,23 +36,17 @@ redef class GameEntity
                var json = game.store.load_object(key)
                return new GameStats.from_json(game, json)
        end
-
-       # Save statistics under this `MEntity`.
-       fun save_statistics(stats: GameStats) do
-               game.store.store_object(key / stats.key, stats.to_json)
-       end
 end
 
 redef class Game
 
-       # Statistics for this game instance.
-       var stats: GameStats is lazy do
+       redef var stats is lazy do
                return load_statistics or else new GameStats(game)
        end
 
        redef fun save do
                super
-               save_statistics(stats)
+               stats.save_in(self)
        end
 
        redef fun pretty do
@@ -63,14 +60,13 @@ end
 
 redef class Player
 
-       # Statistics for this player.
-       var stats: GameStats is lazy do
+       redef var stats is lazy do
                return load_statistics or else new GameStats(game)
        end
 
        redef fun save do
                super
-               save_statistics(stats)
+               stats.save_in(self)
        end
 
        redef fun pretty do
@@ -125,9 +121,7 @@ end
 class StatisticsReactor
        super GameReactor
 
-       redef fun react_event(game, e) do
-               e.react_stats_event(game)
-       end
+       redef fun react_event(game, e) do e.react_stats_event(game)
 end
 
 redef class GithubEvent
index 89d6431..14538c5 100644 (file)
@@ -112,7 +112,9 @@ class GameStatusPanel
 
        redef fun render_body do
                add "<strong class=\"text-success\">{game.load_players.length}</strong>"
-               add " <a href=\"{game.url}/players\">players</a><br><br>"
+               add " <a href=\"{game.url}/players\">players</a><br>"
+               add "<strong class=\"text-success\">{game.stats["achievements"]}</strong>"
+               add " <a href=\"{game.url}/achievements\">achievements</a><br><br>"
                add "<strong class=\"text-success\">{game.stats["pulls"]}</strong> pull requests"
                add " (<strong>{game.stats["pulls_open"]}</strong> open)<br>"
                add "<strong class=\"text-success\">{game.stats["issues"]}</strong> issues"
@@ -144,6 +146,7 @@ class PlayerStatusPanel
                add "<p class=\"lead\">ranked "
                add " <span class=\"text-success\"># {ranking[player.name]}</span></p>"
                add "<strong class=\"text-success\">{player.nitcoins}</strong> nitcoins<br><br>"
+               add "<strong class=\"text-success\">{player.stats["achievements"]}</strong> achievements<br><br>"
                add "<strong>{player.stats["pulls"]}</strong> pull requests<br>"
                add "<strong>{player.stats["issues"]}</strong> issues<br>"
                add "<strong>{player.stats["commits"]}</strong> commits"
@@ -360,3 +363,75 @@ class EventListPanel
                add "</div>"
        end
 end
+
+# Achievement unlocked list panel.
+class AchievementsListPanel
+       super Panel
+
+       # Entity to load the events from.
+       var entity: GameEntity
+
+       redef fun render_title do
+               add "<span class=\"glyphicon glyphicon-list\"></span>&nbsp;&nbsp;"
+               add "Achievements unlocked"
+       end
+
+       redef fun render_body do
+               var achs = entity.load_achievements.values.to_a
+               if achs.is_empty then
+                       add "<em>No achievement yet...</em>"
+                       return
+               end
+               for ach in achs do add ach.list_item
+       end
+end
+
+# Achievement detail panel.
+class AchievementPanel
+       super Panel
+
+       # Achievement to display.
+       var achievement: Achievement
+
+       redef fun render_title do
+               add "<span class=\"glyphicon glyphicon-check\"></span>&nbsp;&nbsp;"
+               add "Achievement details"
+       end
+
+       redef fun render_body do
+               add """<p class=\"lead\">
+                               <span class="badge progress-bar-success"
+                                style="vertical-align: middle">+{{{achievement.reward}}}</span>
+                       {{{achievement.name}}}
+                      </p>
+                      <p><strong>{{{achievement.desc}}}</strong></p>"""
+
+               var events = achievement.load_events
+
+               if events.is_empty then
+                       add "<em>Never unlocked...</em>"
+                       return
+               end
+
+               var event = events.last
+               var tpl = event.tpl_event
+               var player = tpl.player
+               add "<hr>"
+               add """<div class="media">
+                       <a class="media-left" href="{{{player.url}}}">
+                                <span class="badge progress-bar-warning" style="position: absolute">#1</span>
+                        <img class=\"img-circle\" style="width:50px"
+                         src="{{{player.user.avatar_url}}}" alt="{{{player.name}}}">
+                       </a>
+                               <div class="media-body">
+                                <h4 class="media-heading">Unlocked first by {{{player.link}}}</h4>
+                                <span class="text-muted">at {{{event.time}}} </span>
+                               </div>
+                          </div>"""
+
+               if events.length > 1 then
+                       add """<p><br>Also unlocked by <strong class="text-success">
+                       {{{events.length}}} players</strong>.</p>"""
+               end
+       end
+end
index f1affb9..92a4915 100644 (file)
@@ -54,7 +54,7 @@ class NitRpgPage
        </head>
        <body>
                <nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
-                       <a class="navbar-brand" href="/">Github RPG</a>"""
+                       <a class="navbar-brand" href="{{{root_url}}}/">Github RPG</a>"""
                if not breadcrumbs == null then
                        add breadcrumbs.as(not null)
                end
index 9b28d57..8b85a4d 100644 (file)
@@ -17,7 +17,7 @@
 # Base HTML rendering templates for `nitpg`.
 module templates_base
 
-import statistics
+import achievements
 
 redef class GameEntity
 
@@ -50,3 +50,24 @@ redef class Issue
        # Return a HTML link to this Issue.
        fun link: String do return "<a href=\"{html_url}\">#{number}</a>"
 end
+
+redef class Achievement
+       # Return a HTML link to this Issue.
+       fun link: String do return "<a href=\"{url}\">{name}</a>"
+
+       fun list_item: String do
+               return """<div class="media">
+                              <div class="media-left" style="width: 50px">
+                               <span class="glyphicon glyphicon-check"></span>
+                               <span class="badge progress-bar-success"
+                                style="position: absolute; margin-top: 10px;
+                                        margin-left: -5px;">+{{{reward}}}</span>
+                              </div>
+                              <div class="media-body">
+                               <h4 class="media-heading">{{{link}}}</h4>
+                               <span class="text-muted">{{{desc}}}</span>
+                              </div>
+                             </div>"""
+
+       end
+end
index 20f7b58..234c9e5 100644 (file)
@@ -17,7 +17,7 @@
 # Templates to display `GameEvent` kinds.
 module templates_events
 
-import events
+import achievements
 import templates_base
 
 redef class GameEvent
@@ -29,6 +29,8 @@ redef class GameEvent
                        return new TplPullMerged(self)
                else if kind == "pull_review" then
                        return new TplPullReview(self)
+               else if kind == "achievement_unlocked" then
+                       return new TplAchievementUnlocked(self)
                end
                abort
        end
@@ -63,6 +65,11 @@ class TplEvent
                return new IssueCommentEvent.from_json(event.game.api, obj)
        end
 
+       # Load `achievement` data key as an Achievement.
+       var achievement: Achievement is lazy do
+               return player.load_achievement(event.data["achievement"].to_s).as(not null)
+       end
+
        # Display a media item for a reward event.
        fun media_item: String do
                return """<div class="media">
@@ -109,3 +116,12 @@ class TplPullReview
                return "{player.link} reviewed {issue.link}"
        end
 end
+
+# Event: achievement_unlocked
+class TplAchievementUnlocked
+       super TplEvent
+
+       redef var title is lazy do
+               return "{player.link} unlocked {achievement.link}"
+       end
+end
index ac7f91d..b27c457 100644 (file)
@@ -114,6 +114,7 @@ class RepoHome
                page.side_panels.add new ShortListPlayersPanel(game)
                page.flow_panels.add new PodiumPanel(game)
                page.flow_panels.add new EventListPanel(game, list_limit, list_from)
+               page.flow_panels.add new AchievementsListPanel(game)
                rsp.body = page.write_to_string
                return rsp
        end
@@ -152,12 +153,50 @@ class PlayerHome
                page.side_panels.clear
                page.side_panels.add new PlayerStatusPanel(game, player)
                page.flow_panels.add new PlayerReviewsPanel(game, player)
+               page.flow_panels.add new AchievementsListPanel(player)
                page.flow_panels.add new EventListPanel(player, list_limit, list_from)
                rsp.body = page.write_to_string
                return rsp
        end
 end
 
+# Display the list of achievements unlocked for this game.
+class ListAchievements
+       super GameAction
+
+       redef fun answer(request, url) do
+               var rsp = prepare_response(request, url)
+               page.breadcrumbs.add_link(game.url / "achievements", "achievements")
+               page.flow_panels.add new AchievementsListPanel(game)
+               rsp.body = page.write_to_string
+               return rsp
+       end
+end
+
+# Player details page.
+class AchievementHome
+       super GameAction
+
+       redef fun answer(request, url) do
+               var rsp = prepare_response(request, url)
+               var name = request.param("achievement")
+               if name == null then
+                       var msg = "Bad request: should look like /:owner/:repo/achievements/:achievement."
+                       return bad_request(msg)
+               end
+               var achievement = game.load_achievement(name)
+               if achievement == null then
+                       return bad_request("Request Error: unknown achievement {name}.")
+               end
+               page.breadcrumbs.add_link(game.url / "achievements", "achievements")
+               page.breadcrumbs.add_link(achievement.url, achievement.name)
+               page.flow_panels.add new AchievementPanel(achievement)
+               page.flow_panels.add new EventListPanel(achievement, list_limit, list_from)
+               rsp.body = page.write_to_string
+               return rsp
+       end
+end
+
 if args.length != 3 then
        print "Error: missing argument"
        print ""
@@ -175,6 +214,8 @@ var vh = new VirtualHost(iface)
 vh.routes.add new Route("/styles/", new FileServer("www/styles"))
 vh.routes.add new Route("/games/:owner/:repo/players/:player", new PlayerHome(root))
 vh.routes.add new Route("/games/:owner/:repo/players", new ListPlayers(root))
+vh.routes.add new Route("/games/:owner/:repo/achievements/:achievement", new AchievementHome(root))
+vh.routes.add new Route("/games/:owner/:repo/achievements", new ListAchievements(root))
 vh.routes.add new Route("/games/:owner/:repo", new RepoHome(root))
 
 var fac = new HttpFactory.and_libevent
index 8195e2c..ae124ba 100644 (file)
@@ -372,10 +372,10 @@ end
 
 redef class App
        # Resource Manager used to manage resources placed in the `res` folder of the app
-       fun resource_manager: ResourcesManager is cached do return new ResourcesManager(self.resources, self.package_name.to_s)
+       var resource_manager: ResourcesManager is lazy  do return new ResourcesManager(self.resources, self.package_name.to_s)
 
        # Assets Manager used to manage resources placed in the `assets` folder of the app
-       fun asset_manager: AssetManager is cached do return new AssetManager(self)
+       var asset_manager: AssetManager is lazy do return new AssetManager(self)
 
        # Get the native AssetsManager of the application, used to initialize the nit's AssetManager
        private fun assets: NativeAssetManager import native_activity in "Java" `{ return App_native_activity(recv).getAssets(); `}
index 2301bd0..74452e0 100644 (file)
@@ -415,12 +415,12 @@ redef class App
        # Returns the default MediaPlayer of the application.
        # When you load a music, it goes in this MediaPlayer.
        # Use it for advanced sound management
-       fun default_mediaplayer: MediaPlayer is cached do return new MediaPlayer
+       var default_mediaplayer: MediaPlayer is lazy do return new MediaPlayer
 
        # Returns the default MediaPlayer of the application.
        # When you load a short sound (not a music), it's added to this soundpool.
        # Use it for advanced sound management.
-       fun default_soundpool: SoundPool is cached do return new SoundPool
+       var default_soundpool: SoundPool is lazy do return new SoundPool
 
        # Get the native audio manager
        fun audio_manager: NativeAudioManager import native_activity in "Java" `{
index 05dbf82..6b6edde 100644 (file)
@@ -404,7 +404,7 @@ class SharedPreferences
 end
 
 redef class App
-       fun shared_preferences: SharedPreferences is cached do 
+       var shared_preferences: SharedPreferences is lazy do
                return new SharedPreferences.privately(self, "")
        end
 end
index f56dbef..79941dc 100644 (file)
@@ -48,7 +48,7 @@ end
 
 redef class App
        # Get the handle to this device vibrator as a global ref
-       fun vibrator: Vibrator is cached do
+       var vibrator: Vibrator is lazy do
                var v = vibrator_native(native_activity)
                return v.new_global_ref
        end
index 183d62b..de2babc 100644 (file)
@@ -692,47 +692,47 @@ class GLCapabilities
        # GL capability: blend the computed fragment color values
        #
        # Foreign: GL_BLEND
-       fun blend: GLCap is lazy do return new GLCap(0x0BE2)
+       var blend: GLCap is lazy do return new GLCap(0x0BE2)
 
        # GL capability: cull polygons based of their winding in window coordinates
        #
        # Foreign: GL_CULL_FACE
-       fun cull_face: GLCap is lazy do return new GLCap(0x0B44)
+       var cull_face: GLCap is lazy do return new GLCap(0x0B44)
 
        # GL capability: do depth comparisons and update the depth buffer
        #
        # Foreign: GL_DEPTH_TEST
-       fun depth_test: GLCap is lazy do return new GLCap(0x0B71)
+       var depth_test: GLCap is lazy do return new GLCap(0x0B71)
 
        # GL capability: dither color components or indices before they are written to the color buffer
        #
        # Foreign: GL_DITHER
-       fun dither: GLCap is lazy do return new GLCap(0x0BE2)
+       var dither: GLCap is lazy do return new GLCap(0x0BE2)
 
        # GL capability: add an offset to depth values of a polygon fragment before depth test
        #
        # Foreign: GL_POLYGON_OFFSET_FILL
-       fun polygon_offset_fill: GLCap is lazy do return new GLCap(0x8037)
+       var polygon_offset_fill: GLCap is lazy do return new GLCap(0x8037)
 
        # GL capability: compute a temporary coverage value where each bit is determined by the alpha value at the corresponding location
        #
        # Foreign: GL_SAMPLE_ALPHA_TO_COVERAGE
-       fun sample_alpha_to_coverage: GLCap is lazy do return new GLCap(0x809E)
+       var sample_alpha_to_coverage: GLCap is lazy do return new GLCap(0x809E)
 
        # GL capability: AND the fragment coverage with the temporary coverage value
        #
        # Foreign: GL_SAMPLE_COVERAGE
-       fun sample_coverage: GLCap is lazy do return new GLCap(0x80A0)
+       var sample_coverage: GLCap is lazy do return new GLCap(0x80A0)
 
        # GL capability: discard fragments that are outside the scissor rectangle
        #
        # Foreign: GL_SCISSOR_TEST
-       fun scissor_test: GLCap is lazy do return new GLCap(0x0C11)
+       var scissor_test: GLCap is lazy do return new GLCap(0x0C11)
 
        # GL capability: do stencil testing and update the stencil buffer
        #
        # Foreign: GL_STENCIL_TEST
-       fun stencil_test: GLCap is lazy do return new GLCap(0x0B90)
+       var stencil_test: GLCap is lazy do return new GLCap(0x0B90)
 end
 
 # Float related data types of OpenGL ES 2.0 shaders
index 93afcf1..3288e5c 100644 (file)
@@ -149,20 +149,37 @@ class POSet[E]
                # Update the transitive reduction
                if te.tos.has(f) then return # Skip the reduction if there is a loop
 
-               for x in te.dfroms.to_a do
+               # Remove transitive edges.
+               # Because the sets of direct is iterated, the list of edges to remove
+               # is stored and is applied after the iteration.
+               # The usual case is that no direct edges need to be removed,
+               # so start with a `null` list of edges.
+               var to_remove: nullable Array[E] = null
+               for x in te.dfroms do
                        var xe = self.elements[x]
                        if xe.tos.has(f) then
-                               te.dfroms.remove(x)
+                               if to_remove == null then to_remove = new Array[E]
+                               to_remove.add x
                                xe.dtos.remove(t)
                        end
                end
-               for x in fe.dtos.to_a do
+               if to_remove != null then
+                       for x in to_remove do te.dfroms.remove(x)
+                       to_remove.clear
+               end
+
+               for x in fe.dtos do
                        var xe = self.elements[x]
                        if xe.froms.has(t) then
                                xe.dfroms.remove(f)
-                               fe.dtos.remove(x)
+                               if to_remove == null then to_remove = new Array[E]
+                               to_remove.add x
                        end
                end
+               if to_remove != null then
+                       for x in to_remove do fe.dtos.remove(x)
+               end
+
                fe.dtos.add t
                te.dfroms.add f
        end
index 23b1ef7..0fe8050 100644 (file)
@@ -161,7 +161,7 @@ class StatementEntry
        # Name of the column
        #
        # require: `self.statement.is_open`
-       fun name: String is cached do
+       var name: String is lazy do
                assert statement_closed: statement.is_open
 
                return statement.native_statement.column_name(index)
index 858d569..d38e001 100644 (file)
@@ -416,7 +416,14 @@ interface MapRead[K, V]
                return default
        end
 
-       # Alias for `keys.has`
+       # Is there an item associated with `key`?
+       #
+       #     var x = new HashMap[String, Int]
+       #     x["four"] = 4
+       #     assert x.has_key("four") == true
+       #     assert x.has_key("five") == false
+       #
+       # By default it is a synonymous to `keys.has` but could be redefined with a direct implementation.
        fun has_key(key: K): Bool do return self.keys.has(key)
 
        # Get a new iterator on the map.
@@ -466,6 +473,30 @@ interface MapRead[K, V]
        # Note: the value is returned *as is*, implementations may want to store the value in the map before returning it
        # @toimplement
        protected fun provide_default_value(key: K): V do abort
+
+       # Does `self` and `other` have the same keys associated with the same values?
+       #
+       # ~~~
+       # var a = new HashMap[String, Int]
+       # var b = new ArrayMap[Object, Numeric]
+       # assert a == b
+       # a["one"] = 1
+       # assert a != b
+       # b["one"] = 1
+       # assert a == b
+       # b["one"] = 2
+       # assert a != b
+       # ~~~
+       redef fun ==(other)
+       do
+               if not other isa MapRead[nullable Object, nullable Object] then return false
+               if other.length != self.length then return false
+               for k, v in self do
+                       if not other.has_key(k) then return false
+                       if other[k] != v then return false
+               end
+               return true
+       end
 end
 
 # Maps are associative collections: `key` -> `item`.
@@ -963,6 +994,8 @@ interface CoupleMap[K, V]
                        return c.second
                end
        end
+
+       redef fun has_key(key) do return couple_at(key) != null
 end
 
 # Iterator on CoupleMap
index ae351cf..233621e 100644 (file)
@@ -130,7 +130,20 @@ abstract class AbstractArrayRead[E]
                end
        end
 
-       redef fun iterator: ArrayIterator[E] do return new ArrayIterator[E](self)
+       redef fun iterator: ArrayIterator[E] do
+               var res = _free_iterator
+               if res == null then return new ArrayIterator[E](self)
+               res._index = 0
+               _free_iterator = null
+               return res
+       end
+
+       # An old iterator, free to reuse.
+       # Once an iterator is `finish`, it become reusable.
+       # Since some arrays are iterated a lot, this avoid most of the
+       # continuous allocation/garbage-collection of the needed iterators.
+       private var free_iterator: nullable ArrayIterator[E] = null
+
        redef fun reverse_iterator do return new ArrayReverseIterator[E](self)
 end
 
@@ -252,6 +265,7 @@ end
 #     assert a == b
 class Array[E]
        super AbstractArray[E]
+       super Cloneable
 
        redef fun [](index)
        do
@@ -393,6 +407,29 @@ class Array[E]
                return true
        end
 
+       # Shallow clone of `self`
+       #
+       # ~~~
+       # var a = [1,2,3]
+       # var b = a.clone
+       # assert a == b
+       # a.add 4
+       # assert a != b
+       # b.add 4
+       # assert a == b
+       # ~~~
+       #
+       # Note that the clone is shallow and elements are shared between `self` and the result.
+       #
+       # ~~~
+       # var aa = [a]
+       # var bb = aa.clone
+       # assert aa == bb
+       # aa.first.add 5
+       # assert aa == bb
+       # ~~~
+       redef fun clone do return to_a
+
        # Concatenation of arrays.
        #
        # Returns a new array built by concatenating `self` and `other` together.
@@ -453,6 +490,8 @@ private class ArrayIterator[E]
        redef var index = 0
 
        var array: AbstractArrayRead[E]
+
+       redef fun finish do _array._free_iterator = self
 end
 
 private class ArrayReverseIterator[E]
@@ -473,6 +512,7 @@ end
 # A set implemented with an Array.
 class ArraySet[E]
        super Set[E]
+       super Cloneable
 
        # The stored elements.
        private var array: Array[E] is noinit
@@ -519,6 +559,37 @@ class ArraySet[E]
        init with_capacity(i: Int) do _array = new Array[E].with_capacity(i)
 
        redef fun new_set do return new ArraySet[E]
+
+       # Shallow clone of `self`
+       #
+       # ~~~
+       # var a = new ArraySet[Int]
+       # a.add 1
+       # a.add 2
+       # var b = a.clone
+       # assert a == b
+       # a.add 3
+       # assert a != b
+       # b.add 3
+       # assert a == b
+       # ~~~
+       #
+       # Note that the clone is shallow and keys and values are shared between `self` and the result.
+       #
+       # ~~~
+       # var aa = new ArraySet[Array[Int]]
+       # aa.add([1,2])
+       # var bb = aa.clone
+       # assert aa == bb
+       # aa.first.add 5
+       # assert aa == bb
+       # ~~~
+       redef fun clone
+       do
+               var res = new ArraySet[E]
+               res.add_all self
+               return res
+       end
 end
 
 # Iterators on sets implemented with arrays.
@@ -538,6 +609,7 @@ end
 # Associative arrays implemented with an array of (key, value) pairs.
 class ArrayMap[K, E]
        super CoupleMap[K, E]
+       super Cloneable
 
        # O(n)
        redef fun [](key)
@@ -561,8 +633,8 @@ class ArrayMap[K, E]
                end
        end
 
-       redef var keys: RemovableCollection[K] = new ArrayMapKeys[K, E](self)
-       redef var values: RemovableCollection[E] = new ArrayMapValues[K, E](self)
+       redef var keys: RemovableCollection[K] = new ArrayMapKeys[K, E](self) is lazy
+       redef var values: RemovableCollection[E] = new ArrayMapValues[K, E](self) is lazy
 
        # O(1)
        redef fun length do return _items.length
@@ -616,6 +688,35 @@ class ArrayMap[K, E]
                end
                return -1
        end
+
+       # Shallow clone of `self`
+       #
+       # ~~~
+       # var a = new ArrayMap[String,Int]
+       # a["one"] = 1
+       # a["two"] = 2
+       # var b = a.clone
+       # assert a == b
+       # a["zero"] = 0
+       # assert a != b
+       # ~~~
+       #
+       # Note that the clone is shallow and keys and values are shared between `self` and the result.
+       #
+       # ~~~
+       # var aa = new ArrayMap[String, Array[Int]]
+       # aa["two"] = [1,2]
+       # var bb = aa.clone
+       # assert aa == bb
+       # aa["two"].add 5
+       # assert aa == bb
+       # ~~~
+       redef fun clone
+       do
+               var res = new ArrayMap[K,E]
+               res.recover_with self
+               return res
+       end
 end
 
 private class ArrayMapKeys[K, E]
index b62d876..e5ff507 100644 (file)
@@ -272,8 +272,9 @@ class HashMap[K, V]
                enlarge(0)
        end
 
-       redef var keys: RemovableCollection[K] = new HashMapKeys[K, V](self)
-       redef var values: RemovableCollection[V] = new HashMapValues[K, V](self)
+       redef var keys: RemovableCollection[K] = new HashMapKeys[K, V](self) is lazy
+       redef var values: RemovableCollection[V] = new HashMapValues[K, V](self) is lazy
+       redef fun has_key(k) do return node_at(k) != null
 end
 
 # View of the keys of a HashMap
index 2009a50..4d77955 100644 (file)
@@ -254,6 +254,7 @@ class Stdout
                _file = new NativeFile.native_stdout
                path = "/dev/stdout"
                _is_writable = true
+               set_buffering_mode(256, sys.buffer_mode_line)
        end
 end
 
@@ -998,18 +999,14 @@ end
 
 redef class Sys
 
-       init do
-               if stdout isa FileStream then stdout.as(FileStream).set_buffering_mode(256, buffer_mode_line)
-       end
-
        # Standard input
-       var stdin: PollableReader = new Stdin is protected writable
+       var stdin: PollableReader = new Stdin is protected writable, lazy
 
        # Standard output
-       var stdout: Writer = new Stdout is protected writable
+       var stdout: Writer = new Stdout is protected writable, lazy
 
        # Standard output for errors
-       var stderr: Writer = new Stderr is protected writable
+       var stderr: Writer = new Stderr is protected writable, lazy
 
        # Enumeration for buffer mode full (flushes when buffer is full)
        fun buffer_mode_full: Int is extern "file_Sys_Sys_buffer_mode_full_0"
index 5cc0547..01e72e3 100644 (file)
@@ -226,6 +226,25 @@ interface Discrete
        end
 end
 
+# Something that can be cloned
+#
+# This interface introduces the `clone` method used to duplicate an instance
+# Its specific semantic is let to the subclasses.
+interface Cloneable
+       # Duplicate `self`
+       #
+       # The specific semantic of this method is let to the subclasses;
+       # Especially, if (and how) attributes are cloned (depth vs. shallow).
+       #
+       # As a rule of thumb, the principle of least astonishment should
+       # be used to guide the semantic.
+       #
+       # Note that as the returned clone depends on the semantic,
+       # the `==` method, if redefined, should ensure the equality
+       # between an object and its clone.
+       fun clone: SELF is abstract
+end
+
 # A numeric value supporting mathematical operations
 interface Numeric
        super Comparable
@@ -699,6 +718,22 @@ universal Char
        do
                return is_lower or is_upper
        end
+
+       # Is self a whitespace character?
+       #
+       # These correspond to the "Other" and "Separator" groups of the Unicode.
+       #
+       # In the ASCII encoding, this is those <= to space (0x20) plus delete (0x7F).
+       #
+       #     assert 'A'.is_whitespace  == false
+       #     assert ','.is_whitespace  == false
+       #     assert ' '.is_whitespace  == true
+       #     assert '\t'.is_whitespace == true
+       fun is_whitespace: Bool
+       do
+               var i = ascii
+               return i <= 0x20 or i == 0x7F
+       end
 end
 
 # Pointer classes are used to manipulate extern C structures.
index 243f98f..644e3dc 100644 (file)
@@ -67,7 +67,7 @@ private abstract class RopeString
        super Rope
        super String
 
-       redef fun chars is cached do return new RopeChars(self)
+       redef var chars is lazy do return new RopeChars(self)
 end
 
 # Node that represents a concatenation between two `String`
@@ -80,7 +80,7 @@ private class Concat
 
        redef fun empty do return ""
 
-       redef fun to_cstring is cached do
+       redef var to_cstring is lazy do
                var len = length
                var ns = new NativeString(len + 1)
                ns[len] = '\0'
@@ -177,7 +177,7 @@ class RopeBuffer
        super Rope
        super Buffer
 
-       redef fun chars: Sequence[Char] is cached do return new RopeBufferChars(self)
+       redef var chars: Sequence[Char] is lazy do return new RopeBufferChars(self)
 
        # The final string being built on the fly
        private var str: String is noinit
index 476c684..7df1bd1 100644 (file)
@@ -220,6 +220,66 @@ abstract class Reader
        # Is there something to read.
        # This function returns 'false' if there is something to read.
        fun eof: Bool is abstract
+
+       # Read the next sequence of non whitespace characters.
+       #
+       # Leading whitespace characters are skipped.
+       # The first whitespace character that follows the result is consumed.
+       #
+       # An empty string is returned if the end of the file or an error is encounter.
+       #
+       # ~~~
+       # var w = new StringReader(" Hello, \n\t World!")
+       # assert w.read_word == "Hello,"
+       # assert w.read_char == '\n'.ascii
+       # assert w.read_word == "World!"
+       # assert w.read_word == ""
+       # ~~~
+       #
+       # `Char::is_whitespace` determines what is a whitespace.
+       fun read_word: String
+       do
+               var buf = new FlatBuffer
+               var c = read_nonwhitespace
+               if c > 0 then
+                       buf.add(c.ascii)
+                       while not eof do
+                               c = read_char
+                               if c < 0 then break
+                               var a = c.ascii
+                               if a.is_whitespace then break
+                               buf.add(a)
+                       end
+               end
+               var res = buf.to_s
+               return res
+       end
+
+       # Skip whitespace characters (if any) then return the following non-whitespace character.
+       #
+       # Returns the code point of the character.
+       # Return -1 on end of file or error.
+       #
+       # In fact, this method works like `read_char` except it skips whitespace.
+       #
+       # ~~~
+       # var w = new StringReader(" \nab\tc")
+       # assert w.read_nonwhitespace == 'a'.ascii
+       # assert w.read_nonwhitespace == 'b'.ascii
+       # assert w.read_nonwhitespace == 'c'.ascii
+       # assert w.read_nonwhitespace == -1
+       # ~~~
+       #
+       # `Char::is_whitespace` determines what is a whitespace.
+       fun read_nonwhitespace: Int
+       do
+               var c = -1
+               while not eof do
+                       c = read_char
+                       if c < 0 or not c.ascii.is_whitespace then break
+               end
+               return c
+       end
 end
 
 # Iterator returned by `Reader::each_line`.
@@ -344,6 +404,7 @@ abstract class BufferedReader
                if _buffer_pos + i >= _buffer.length then
                        var from = _buffer_pos
                        _buffer_pos = _buffer.length
+                       if from == 0 then return _buffer.to_s
                        return _buffer.substring_from(from).to_s
                end
                _buffer_pos += i
index 88c4074..e78f655 100644 (file)
@@ -350,12 +350,12 @@ abstract class Text
        #
        #     assert " \n\thello \n\t".l_trim == "hello \n\t"
        #
-       # A whitespace is defined as any character which ascii value is less than or equal to 32
+       # `Char::is_whitespace` determines what is a whitespace.
        fun l_trim: SELFTYPE
        do
                var iter = self.chars.iterator
                while iter.is_ok do
-                       if iter.item.ascii > 32 then break
+                       if not iter.item.is_whitespace then break
                        iter.next
                end
                if iter.index == length then return self.empty
@@ -366,12 +366,12 @@ abstract class Text
        #
        #     assert " \n\thello \n\t".r_trim == " \n\thello"
        #
-       # A whitespace is defined as any character which ascii value is less than or equal to 32
+       # `Char::is_whitespace` determines what is a whitespace.
        fun r_trim: SELFTYPE
        do
                var iter = self.chars.reverse_iterator
                while iter.is_ok do
-                       if iter.item.ascii > 32 then break
+                       if not iter.item.is_whitespace then break
                        iter.next
                end
                if iter.index < 0 then return self.empty
@@ -379,12 +379,29 @@ abstract class Text
        end
 
        # Trims trailing and preceding white spaces
-       # A whitespace is defined as any character which ascii value is less than or equal to 32
        #
        #     assert "  Hello  World !  ".trim   == "Hello  World !"
        #     assert "\na\nb\tc\t".trim          == "a\nb\tc"
+       #
+       # `Char::is_whitespace` determines what is a whitespace.
        fun trim: SELFTYPE do return (self.l_trim).r_trim
 
+       # Is the string non-empty but only made of whitespaces?
+       #
+       #    assert " \n\t ".is_whitespace    == true
+       #    assert "  hello  ".is_whitespace == false
+       #    assert "".is_whitespace          == false
+       #
+       # `Char::is_whitespace` determines what is a whitespace.
+       fun is_whitespace: Bool
+       do
+               if is_empty then return false
+               for c in self.chars do
+                       if not c.is_whitespace then return false
+               end
+               return true
+       end
+
        # Returns `self` removed from its last line terminator (if any).
        #
        #    assert "Hello\n".chomp == "Hello"
@@ -1052,7 +1069,7 @@ class FlatString
        # Indes in _items of the last item of the string
        private var index_to: Int is noinit
 
-       redef var chars: SequenceRead[Char] = new FlatStringCharView(self)
+       redef var chars: SequenceRead[Char] = new FlatStringCharView(self) is lazy
 
        redef fun [](index)
        do
@@ -1163,10 +1180,14 @@ class FlatString
        #              String Specific Methods           #
        ##################################################
 
-       private init with_infos(items: NativeString, len: Int, from: Int, to: Int)
+       # Low-level creation of a new string with given data.
+       #
+       # `items` will be used as is, without copy, to retrieve the characters of the string.
+       # Aliasing issues is the responsibility of the caller.
+       private init with_infos(items: NativeString, length: Int, from: Int, to: Int)
        do
                self.items = items
-               length = len
+               self.length = length
                index_from = from
                index_to = to
        end
@@ -1522,7 +1543,7 @@ class FlatBuffer
        super FlatText
        super Buffer
 
-       redef var chars: Sequence[Char] = new FlatBufferCharView(self)
+       redef var chars: Sequence[Char] = new FlatBufferCharView(self) is lazy
 
        private var capacity: Int = 0
 
@@ -1610,6 +1631,20 @@ class FlatBuffer
        # Create a new empty string.
        init do end
 
+       # Low-level creation a new buffer with given data.
+       #
+       # `items` will be used as is, without copy, to store the characters of the buffer.
+       # Aliasing issues is the responsibility of the caller.
+       #
+       # If `items` is shared, `written` should be set to true after the creation
+       # so that a modification will do a copy-on-write.
+       private init with_infos(items: NativeString, capacity, length: Int)
+       do
+               self.items = items
+               self.length = length
+               self.capacity = capacity
+       end
+
        # Create a new string copied from `s`.
        init from(s: Text)
        do
@@ -1634,7 +1669,6 @@ class FlatBuffer
        init with_capacity(cap: Int)
        do
                assert cap >= 0
-               # _items = new NativeString.calloc(cap)
                items = new NativeString(cap+1)
                capacity = cap
                length = 0
@@ -1678,11 +1712,10 @@ class FlatBuffer
                if from < 0 then from = 0
                if count > length then count = length
                if from < count then
-                       var r = new FlatBuffer.with_capacity(count - from)
-                       while from < count do
-                               r.chars.push(items[from])
-                               from += 1
-                       end
+                       var len = count - from
+                       var r_items = new NativeString(len)
+                       items.copy_to(r_items, len, from, 0)
+                       var r = new FlatBuffer.with_infos(r_items, len, len)
                        return r
                else
                        return new FlatBuffer
index 181c047..1661f43 100644 (file)
@@ -37,6 +37,8 @@ Ensure that `~/.vimrc` contains
  * Automatic indentation
  * Syntax checker (require [Syntastic][2]).
  * Autocomplete for whole projects using module importations
+ * Show documentation in preview window
+ * Search declarations and usages of the word under the cursor
 
   [2]: https://github.com/scrooloose/syntastic
 
@@ -93,3 +95,25 @@ will use general metadata in the plugin directory.
 
 The metadata files from nitpick are stored in `~/.vim/nit/`. This location can be customized with
 the environment variable `NIT_VIM_DIR`.
+
+## Documentation in preview window
+
+You can display the documentation for the entity under the cursor with `:call Nitdoc()`.
+It will use the same metadata files as the omnifunc and the preview window.
+You may want to map the function to a shortcut by adding the following code to `~/.vimrc`.
+
+~~~
+" Map displaying Nitdoc to Ctrl-D
+map <C-d> :call Nitdoc()<enter>
+~~~
+
+## Search declarations and usages of the word under the cursor
+
+The function `NitGitGrep` calls `git grep` to find declarations and usages of the word under the cursor.
+It displays the results in the preview window.
+You may want to map the function to a shortcut by adding the following code to `~/.vimrc`.
+
+~~~
+" Map the NitGitGrep function to Ctrl-G
+map <C-g> :call NitGitGrep()<enter>
+~~~
index 23e557a..046ab9f 100644 (file)
@@ -81,17 +81,10 @@ function ForceNitComplete()
        call NitComplete()
 endfunction
 
-" Internal function to search for lines in `path` corresponding to the partial
-" word `base`. Adds found and formated match to `matches`.
+" Get path to the best metadata file named `name`
 "
-" Will order the results in 3 levels:
-" 1. Exact matches
-" 2. Common prefix matches
-" 3. Substring matches
-fun NitOmnifuncAddFromFile(base, matches, path)
-       let prefix_matches = []
-       let substring_matches = []
-
+" Returns an empty string if not found.
+fun NitMetadataFile(name)
        " Where are the generated metadata files?
        if empty($NIT_VIM_DIR)
                let metadata_dir = $HOME . '/.vim/nit'
@@ -99,17 +92,41 @@ fun NitOmnifuncAddFromFile(base, matches, path)
                let metadata_dir = $NIT_VIM_DIR
        end
 
-       let path = metadata_dir . '/' . a:path
+       let path = metadata_dir . '/' . a:name
+
        " Is there generated custom metadata files?
        if ! filereadable(path)
-               let path = s:script_dir . '/' . a:path
+               let path = s:script_dir . '/' . a:name
 
                " Is there standard metadata files?
                if ! filereadable(path)
-                       return
+                       return ''
                endif
        endif
 
+       return path
+endfun
+
+" Internal function to search for lines in `path` corresponding to the partial
+" word `base`. Adds found and formated match to `matches`.
+"
+" Will order the results in 5 levels:
+" 1. Exact matches
+" 2. Common prefix matches
+" 3. Substring matches
+" 4. Synopsis matches
+" 5. Doc matches
+fun NitOmnifuncAddFromFile(base, matches, path)
+       let prefix_matches = []
+       let substring_matches = []
+       let synopsis_matches = []
+       let doc_matches = []
+
+       let path = NitMetadataFile(a:path)
+       if empty(path)
+               return
+       endif
+
        for line in readfile(path)
                let words = split(line, '#====#', 1)
                let name = get(words, 0, '')
@@ -118,18 +135,26 @@ fun NitOmnifuncAddFromFile(base, matches, path)
                if name == a:base
                        " Exact match
                        call NitOmnifuncAddAMatch(a:matches, words, name)
-               elseif name =~ '^'.a:base
+               elseif name =~? '^'.a:base
                        " Common-prefix match
                        call NitOmnifuncAddAMatch(prefix_matches, words, name)
-               elseif name =~ a:base
+               elseif name =~? a:base
                        " Substring match
                        call NitOmnifuncAddAMatch(substring_matches, words, name)
+               elseif get(words, 2, '') =~? a:base
+                       " Match in the synopsis
+                       call NitOmnifuncAddAMatch(synopsis_matches, words, name)
+               elseif get(words, 3, '') =~? a:base
+                       " Match in the longer doc
+                       call NitOmnifuncAddAMatch(synopsis_matches, words, name)
                endif
        endfor
 
        " Assemble the final match list
        call extend(a:matches, sort(prefix_matches))
        call extend(a:matches, sort(substring_matches))
+       call extend(a:matches, sort(synopsis_matches))
+       call extend(a:matches, sort(doc_matches))
 endfun
 
 " Internal function to search parse the information from a metadata line
@@ -159,10 +184,10 @@ fun NitOmnifunc(findstart, base)
                " find keyword matching with "a:base"
                let matches = []
 
-               " Advanced suggestions
+               " advanced suggestions
                let cursor_line = getline('.')
 
-               " Content of the line before the partial word
+               " content of the line before the partial word
                let line_prev_cursor = cursor_line[:col('.')-1]
 
                let prev_char_at = strlen(line_prev_cursor) - 1
@@ -240,6 +265,89 @@ fun NitOmnifunc(findstart, base)
        endif
 endfun
 
+" Show doc for the entity under the cursor in the preview window
+fun Nitdoc()
+       " Word under cursor
+       let word = expand("<cword>")
+
+       " All possible docs (there may be more than one entity with the same name)
+       let docs = []
+
+       " Search in all metadata files
+       for file in ['modules', 'classes', 'properties']
+               let path = NitMetadataFile(file.'.txt')
+               if empty(path)
+                       continue
+               endif
+
+               for line in readfile(path)
+                       let words = split(line, '#====#', 1)
+                       let name = get(words, 0, '')
+                       if name =~ '^' . word
+                               " It fits our word, get long doc
+                               let desc = get(words,3,'')
+                               let desc = join(split(desc, '#nnnn#', 1), "\n")
+                               call add(docs, desc)
+                       endif
+               endfor
+       endfor
+
+       " Found no doc, give up
+       if empty(docs) || !(join(docs, '') =~ '\w')
+               return
+       endif
+
+       " Open the preview window on a temp file
+       execute "silent pedit " . tempname()
+
+       " Change to preview window
+       wincmd P
+
+       " Show all found doc one after another
+       for doc in docs
+               if doc =~ '\w'
+                       silent put = doc
+                       silent put = ''
+               endif
+       endfor
+
+       " Set options
+       setlocal buftype=nofile
+       setlocal noswapfile
+       setlocal syntax=none
+       setlocal bufhidden=delete
+
+       " Change back to the source buffer
+       wincmd p
+       redraw!
+endfun
+
+" Call `git grep` on the word under the cursor
+"
+" Shows declarations first, then all matches, in the preview window.
+fun NitGitGrep()
+       let word = expand("<cword>")
+       let out = tempname()
+       execute 'silent !(git grep "\\(module\\|class\\|universal\\|interface\\|var\\|fun\\) '.word.'";'.
+               \'echo; git grep '.word.') > '.out
+
+       " Open the preview window on a temp file
+       execute "silent pedit " . out
+
+       " Change to preview window
+       wincmd P
+
+       " Set options
+       setlocal buftype=nofile
+       setlocal noswapfile
+       setlocal syntax=none
+       setlocal bufhidden=delete
+
+       " Change back to the source buffer
+       wincmd p
+       redraw!
+endfun
+
 " Activate the omnifunc on Nit files
 autocmd FileType nit set omnifunc=NitOmnifunc
 
index 75da7c8..5daafeb 100644 (file)
@@ -115,11 +115,14 @@ See the documentation of these specific modules for details.
 
 `--log`
 :   Generate various log files.
-    Currently unused.
+
+    The tool will generate some files in the logging directory (see `--log-dir`).
+    These files are intended to the advanced user and the developers of the tools.
 
 `--log-dir`
 :   Directory where to generate log files.
-    Currently unused.
+
+    By default the directory is called `logs` in the working directory.
 
 
 `-h`, `-?`, `--help`
index b568366..b34d42f 100644 (file)
@@ -22,9 +22,7 @@ class ASTValidationVisitor
        super Visitor
        redef fun visit(node)
        do
-               path.unshift(node)
                node.accept_ast_validation(self)
-               path.shift
        end
        private var path = new List[ANode]
        private var seen = new HashSet[ANode]
@@ -34,29 +32,33 @@ redef class ANode
        private fun accept_ast_validation(v: ASTValidationVisitor)
        do
                var parent = self.parent
+               var path = v.path
 
-               if v.path.length > 1 then
-                       var path_parent = v.path[1]
+               if path.length > 0 then
+                       var path_parent = v.path.first
                        if parent == null then
                                self.parent = path_parent
                                #debug "PARENT: expected parent: {path_parent}"
+                               v.seen.add(self)
                        else if parent != path_parent then
                                self.parent = path_parent
-                               debug "PARENT: expected parent: {path_parent}, got {parent}"
+                               if v.seen.has(self) then
+                                       debug "DUPLICATE (NOTATREE): already seen node with parent {parent} now with {path_parent}."
+                               else
+                                       v.seen.add(self)
+                                       debug "PARENT: expected parent: {path_parent}, got {parent}"
+                               end
                        end
                end
 
-               if v.seen.has(self) then
-                       debug "DUPLICATE: already seen node. NOTATREE"
-               end
-               v.seen.add(self)
-
                if not isset _location then
                        #debug "LOCATION: unlocated node {v.path.join(", ")}"
                        _location = self.parent.location
                end
 
+               path.unshift(self)
                visit_all(v)
+               path.shift
        end
 end
 
index 58b4b38..aa57850 100644 (file)
@@ -1443,7 +1443,7 @@ abstract class AbstractCompilerVisitor
                var name = self.get_name("varonce")
                self.add_decl("static {mtype.ctype} {name};")
                var res = self.new_var(mtype)
-               self.add("if ({name}) \{")
+               self.add("if (likely({name}!=NULL)) \{")
                self.add("{res} = {name};")
                self.add("\} else \{")
                var native_mtype = self.get_class("NativeString").mclass_type
@@ -2306,7 +2306,7 @@ redef class AAttrPropdef
 
        fun init_expr(v: AbstractCompilerVisitor, recv: RuntimeVariable)
        do
-               if has_value and not is_lazy then evaluate_expr(v, recv)
+               if has_value and not is_lazy and not n_expr isa ANullExpr then evaluate_expr(v, recv)
        end
 
        # Evaluate, store and return the default value of the attribute
@@ -2825,7 +2825,7 @@ redef class AOnceExpr
                v.add_decl("static {mtype.ctype} {name};")
                v.add_decl("static int {guard};")
                var res = v.new_var(mtype)
-               v.add("if ({guard}) \{")
+               v.add("if (likely({guard})) \{")
                v.add("{res} = {name};")
                v.add("\} else \{")
                var i = v.expr(self.n_expr, mtype)
index da2ed1e..a33c098 100644 (file)
@@ -1156,25 +1156,37 @@ class SeparateCompilerVisitor
        redef fun compile_callsite(callsite, args)
        do
                var rta = compiler.runtime_type_analysis
-               var mmethod = callsite.mproperty
                # TODO: Inlining of new-style constructors with initializers
                if compiler.modelbuilder.toolcontext.opt_direct_call_monomorph.value and rta != null and callsite.mpropdef.initializers.is_empty then
                        var tgs = rta.live_targets(callsite)
                        if tgs.length == 1 then
-                               # DIRECT CALL
-                               var res0 = before_send(mmethod, args)
-                               var res = call(tgs.first, tgs.first.mclassdef.bound_mtype, args)
-                               if res0 != null then
-                                       assert res != null
-                                       self.assign(res0, res)
-                                       res = res0
-                               end
-                               add("\}") # close the before_send
-                               return res
+                               return direct_call(tgs.first, args)
                        end
                end
+               # Shortcut intern methods as they are not usually redefinable
+               if callsite.mpropdef.is_intern and callsite.mproperty.name != "object_id" then
+                       # `object_id` is the only redefined intern method, so it can not be directly called.
+                       # TODO find a less ugly approach?
+                       return direct_call(callsite.mpropdef, args)
+               end
                return super
        end
+
+       # Fully and directly call a mpropdef
+       #
+       # This method is used by `compile_callsite`
+       private fun direct_call(mpropdef: MMethodDef, args: Array[RuntimeVariable]): nullable RuntimeVariable
+       do
+               var res0 = before_send(mpropdef.mproperty, args)
+               var res = call(mpropdef, mpropdef.mclassdef.bound_mtype, args)
+               if res0 != null then
+                       assert res != null
+                       self.assign(res0, res)
+                       res = res0
+               end
+               add("\}") # close the before_send
+               return res
+       end
        redef fun send(mmethod, arguments)
        do
                if arguments.first.mcasttype.ctype != "val*" then
@@ -1852,7 +1864,10 @@ class SeparateCompilerVisitor
                var nclass = self.get_class("NativeArray")
                var recv = "((struct instance_{nclass.c_name}*){arguments[0]})->values"
                if pname == "[]" then
-                       self.ret(self.new_expr("{recv}[{arguments[1]}]", ret_type.as(not null)))
+                       # Because the objects are boxed, return the box to avoid unnecessary (or broken) unboxing/reboxing
+                       var res = self.new_expr("{recv}[{arguments[1]}]", compiler.mainmodule.object_type)
+                       res.mcasttype = ret_type.as(not null)
+                       self.ret(res)
                        return
                else if pname == "[]=" then
                        self.add("{recv}[{arguments[1]}]={arguments[2]};")
index 10c6bc0..b7edca0 100644 (file)
@@ -71,10 +71,10 @@ redef class MEntity
 
                # 4. Full doc with extra
                stream.write field_separator
+               stream.write "# "
+               stream.write full_name
+               write_signature_to_stream(stream)
                if mdoc != null then
-                       stream.write "# "
-                       stream.write full_name
-                       write_signature_to_stream(stream)
                        for i in 2.times do stream.write line_separator
                        stream.write mdoc.content.join(line_separator)
                end
diff --git a/src/frontend/cached.nit b/src/frontend/cached.nit
deleted file mode 100644 (file)
index f331b1d..0000000
+++ /dev/null
@@ -1,154 +0,0 @@
-# This file is part of NIT ( http://www.nitlanguage.org ).
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Implementation of the method-related annotation `cached`
-#
-# The cached annotation is deprecated, use the `lazy` annotation instead.
-module cached
-
-import modelize
-private import parser_util
-import simple_misc_analysis
-private import annotation
-intrude import modelize::modelize_property
-
-redef class ToolContext
-       # Process the `cached` annotation on methods
-       var cached_phase: Phase = new CachedPhase(self, [modelize_property_phase])
-end
-
-private class CachedPhase
-       super Phase
-
-       init
-       do
-               # FIXME The phase has to be executed just after `modelize_property_phase`
-               # But there is no simple way to express this
-               # So, for the moment, I just looked at the linearization and see what phase is after `modelize_property_phase`
-               # And inserted before it
-               toolcontext.phases.add_edge(toolcontext.simple_misc_analysis_phase, self)
-       end
-
-       redef fun process_annotated_node(npropdef, nat)
-       do
-               # Skip if we are not interested
-               if nat.name != "cached" then return
-
-               # Do some validity checks and print errors if the annotation is used incorrectly
-               var modelbuilder = toolcontext.modelbuilder
-
-               if not npropdef isa AMethPropdef then
-                       modelbuilder.error(npropdef, "Syntax error: only a function can be cached.")
-                       return
-               end
-
-               var mpropdef = npropdef.mpropdef.as(not null)
-
-               var mtype = mpropdef.msignature.return_mtype
-               if mtype == null then
-                       modelbuilder.error(npropdef, "Syntax error: only a function can be cached.")
-                       return
-               end
-
-               if not npropdef.n_signature.n_params.is_empty then
-                       modelbuilder.error(npropdef, "Syntax error: only a function without arguments can be cached.")
-                       return
-               end
-
-               # OK, let we do some meta-programming...
-
-               var location = npropdef.location
-               var name = mpropdef.mproperty.name
-               var nclassdef = npropdef.parent.as(AClassdef)
-               var mclassdef = nclassdef.mclassdef.as(not null)
-
-               if not mclassdef.mclass.kind.need_init then
-                       modelbuilder.error(npropdef, "Error: only abstract and concrete classes can have cached functions.")
-                       return
-               end
-
-               # Create a new private attribute to store the cache
-               var cache_mpropdef = new MAttributeDef(mclassdef, new MAttribute(mclassdef, "@{name}<cache>", private_visibility), location)
-               cache_mpropdef.static_mtype = mtype.as_nullable
-
-               # Create another new private attribute to store the boolean «is the function cached?»
-               # The point is to manage the case where `null` is a genuine return value of the method
-               var is_cached_mpropdef = new MAttributeDef(mclassdef, new MAttribute(mclassdef, "@{name}<is_cached>", private_visibility), location)
-               is_cached_mpropdef.static_mtype = mclassdef.mmodule.get_primitive_class("Bool").mclass_type
-               # FIXME? Because there is a default value ("false") a real propdef is required
-               var is_cached_npropdef = toolcontext.parse_propdef("var is_cached = false").as(AAttrPropdef)
-               associate_propdef(is_cached_mpropdef, is_cached_npropdef)
-
-               # Create a new private method to do the real work
-               var real_mpropdef = new MMethodDef(mclassdef, new MMethod(mclassdef, "{name}<real>", private_visibility), location)
-               real_mpropdef.msignature = mpropdef.msignature
-               # FIXME: Again, if the engine require a real propdef even if it is empty
-               var real_npropdef = toolcontext.parse_propdef("fun real do end").as(AMethPropdef)
-               associate_propdef(real_mpropdef, real_npropdef)
-               # Note: the body is set at the last line of this function
-
-               # Save the original body
-               var real_body = npropdef.n_block.as(not null)
-
-               # Replace the original body with a new body that do the proxy'n'cache work
-               var proxy_body = toolcontext.parse_stmts("if self._is_cached then return self._cache.as(not null)\nvar res = call_real\nself._cache_write = res\nself._is_cached_write = true\nreturn res")
-               real_body.replace_with(proxy_body)
-
-               # Do some transformation on the identifiers used on the proxy body so that correct entities are designated
-               # FIXME: we just trick the following phases into associating by name some tokens with some model-entities
-               # But this is bad at at least two levels
-               # - we already know the real model-entities, so why doing latter the association and not now?
-               # - associating by names may cause a useless fragility (name-conflicts, etc.)
-               proxy_body.collect_tokens_by_text("_is_cached").first.text = is_cached_mpropdef.mproperty.name
-               proxy_body.collect_tokens_by_text("_is_cached_write").first.text = is_cached_mpropdef.mproperty.name
-               proxy_body.collect_tokens_by_text("_cache").first.text = cache_mpropdef.mproperty.name
-               proxy_body.collect_tokens_by_text("_cache_write").first.text = cache_mpropdef.mproperty.name
-               proxy_body.collect_tokens_by_text("call_real").first.text = real_mpropdef.mproperty.name
-
-               # FIXME a last transformation cannot be done yet. So, the call to `super` (`ASuperExpr`) is broken in cached methods.
-
-               # Give the original body to the private real methoddef
-               real_npropdef.n_block.replace_with(real_body)
-       end
-
-       # Detach `n` from its original AST and attach it to `m` (and its related AST)
-       # `n` must not be already attached to an existing model entity
-       # `m` must not be already attached to an existing AST node
-       fun associate_propdef(m: MPropDef, n: APropdef)
-       do
-               # FIXME: the model-AST relations **must** be rationalized:
-               # * 1- fragility: the risk of inconsistencies is too hight
-               # * 2- complexity: there is too much paths to access the same things
-
-               # Easy attach
-               assert n.mpropdef == null
-               n.mpropdef = m
-
-               # Required to so that look-for implementation works
-               assert not toolcontext.modelbuilder.mpropdef2npropdef.has_key(m)
-               toolcontext.modelbuilder.mpropdef2npropdef[m] = n
-
-               var mclassdef = m.mclassdef
-               var nclassdef = toolcontext.modelbuilder.mclassdef2nclassdef[mclassdef]
-               # Sanity checks
-               assert nclassdef.mclassdef == mclassdef
-
-               if n isa AAttrPropdef then
-                       n.has_value = n.n_expr != null or n.n_block != null
-               end
-
-               # Required so that propdef are visited in visitors
-               if not nclassdef.n_propdefs.has(n) then nclassdef.n_propdefs.add(n)
-       end
-end
index 9408e95..419c5ae 100644 (file)
@@ -84,7 +84,6 @@ readonly
 writable
 autoinit
 noautoinit
-cached
 nosuper
 old_style_init
 abstract
index 5b04e01..8c5079f 100644 (file)
@@ -21,7 +21,6 @@ import literal
 import modelize
 import semantize
 import div_by_zero
-import cached
 import serialization_phase
 import check_annotation
 import glsl_validation
index 75863d9..2b68eea 100644 (file)
@@ -97,8 +97,11 @@ private class SerializationPhasePreModel
        end
 
        # Add a constructor to the automated nclassdef
-       fun generate_deserialization_init(nclassdef: AClassdef)
+       fun generate_deserialization_init(nclassdef: AStdClassdef)
        do
+               # Do not generate constructors for abstract classes
+               if nclassdef.n_classkind isa AAbstractClasskind then return
+
                var npropdefs = nclassdef.n_propdefs
 
                var code = new Array[String]
@@ -156,7 +159,9 @@ private class SerializationPhasePreModel
 
                for nclassdef in nclassdefs do
                        var name = nclassdef.n_id.text
-                       if nclassdef.n_formaldefs.is_empty then
+                       if nclassdef.n_formaldefs.is_empty and
+                               not nclassdef.n_classkind isa AAbstractClasskind then
+
                                code.add "              if name == \"{name}\" then return new {name}.from_deserializer(self)"
                        end
                end
index 7302619..24105e6 100644 (file)
@@ -34,7 +34,7 @@ redef class MConcern
 end
 
 redef class MProject
-       redef fun concern_rank is cached do
+       redef var concern_rank is lazy do
                var max = 0
                for mgroup in mgroups do
                        var mmax = mgroup.concern_rank
@@ -87,7 +87,7 @@ redef class MGroup
                return res
        end
 
-       redef fun concern_rank is cached do
+       redef var concern_rank is lazy do
                var max = 0
                for mmodule in collect_mmodules do
                        var mmax = mmodule.concern_rank
@@ -163,7 +163,7 @@ redef class MModule
                return mclasses
        end
 
-       redef fun concern_rank is cached do
+       redef var concern_rank is lazy do
                var max = 0
                for p in in_importation.direct_greaters do
                        var pmax = p.concern_rank
index 7c6ea5d..a4d1141 100644 (file)
@@ -863,6 +863,10 @@ redef class AMethPropdef
                mpropdef.is_abstract = self.get_single_annotation("abstract", modelbuilder) != null
                mpropdef.is_intern = self.get_single_annotation("intern", modelbuilder) != null
                mpropdef.is_extern = self.n_extern_code_block != null or self.get_single_annotation("extern", modelbuilder) != null
+
+               # Check annotations
+               var at = self.get_single_annotation("lazy", modelbuilder)
+               if at != null then modelbuilder.error(at, "Syntax error: `lazy` must be used on attributes.")
        end
 
        redef fun check_signature(modelbuilder)
index 5b64e3d..9a7b050 100644 (file)
@@ -19,9 +19,6 @@ import highlight
 
 var toolcontext = new ToolContext
 
-# Disable `cached` because it causes issues when printing transformed AST. FIXME
-toolcontext.cached_phase.disabled = true
-
 # Try to colorize, even if programs are non valid
 toolcontext.keep_going = true
 
index 98b1923..db70d4c 100644 (file)
@@ -69,16 +69,20 @@ redef class ToolContext
        # Where do we put the result?
        var opt_dir: OptionString = new OptionString("Output directory", "--dir")
 
+       # Depth of the visit and generation
+       var opt_depth = new OptionEnum(["module", "group", "project"],
+               "Depth of the visit and generation", 0, "-d", "--depth")
+
        redef init
        do
-               option_context.add_option(opt_output, opt_dir)
+               option_context.add_option(opt_output, opt_dir, opt_depth)
                super
        end
 end
 
 redef class MModule
        # Get the type of the class `Serializable`
-       fun serializable_type: MClassType is cached do
+       var serializable_type: MClassType is lazy do
                return self.get_primitive_class("Serializable").mclass_type
        end
 end
@@ -130,10 +134,8 @@ var modelbuilder = new ModelBuilder(model, toolcontext)
 var mmodules = modelbuilder.parse_full(arguments)
 modelbuilder.run_phases
 
-# Create a distinct support module per targetted modules
+# Create a distinct support module per target modules
 for mmodule in mmodules do
-       var rta = modelbuilder.do_rapid_type_analysis(mmodule)
-
        # Name of the support module
        var module_name
 
@@ -155,13 +157,47 @@ for mmodule in mmodules do
                module_path += ".nit"
        end
 
+       var target_modules = null
+       var importations = null
+       var mgroup = mmodule.mgroup
+       if toolcontext.opt_depth.value == 1 and mgroup != null then
+               modelbuilder.visit_group mgroup
+               target_modules = mgroup.mmodules
+       else if toolcontext.opt_depth.value == 2 then
+               # project
+               target_modules = new Array[MModule]
+               importations = new Array[MModule]
+               if mgroup != null then
+                       for g in mgroup.mproject.mgroups do
+                               target_modules.add_all g.mmodules
+                       end
+
+                       for g in mgroup.in_nesting.direct_smallers do
+                               var dm = g.default_mmodule
+                               if dm != null then
+                                       importations.add dm
+                               end
+                       end
+
+                       for m in mgroup.mmodules do
+                               importations.add m
+                       end
+               end
+       end
+
+       if target_modules == null then target_modules = [mmodule]
+       if importations == null then importations = target_modules
+
        var nit_module = new NitModule(module_name)
        nit_module.header = """
 # This file is generated by nitserial
 # Do not modify, but you can redef
 """
 
-       nit_module.imports.add mmodule.name
+       for importation in importations do
+               nit_module.imports.add importation.name
+       end
+
        nit_module.imports.add "serialization"
 
        nit_module.content.add """
@@ -170,15 +206,25 @@ redef class Deserializer
        do"""
 
        var serializable_type = mmodule.serializable_type
-       for mtype in rta.live_types do
-               # We are only interested in instanciated generics, subtypes of Serializable
-               # and which are visibles.
-               if mtype isa MGenericType and
-                  mtype.is_subtype(mmodule, null, serializable_type) and
-                  mtype.is_visible_from(mmodule) then
-
-                       nit_module.content.add """
+       var compiled_types = new Array[MType]
+       for m in target_modules do
+               nit_module.content.add """
+               # Module: {{{m.to_s}}}"""
+
+               var rta = modelbuilder.do_rapid_type_analysis(m)
+
+               for mtype in rta.live_types do
+                       # We are only interested in instanciated generics, subtypes of Serializable
+                       # and which are visibles.
+                       if mtype isa MGenericType and
+                          mtype.is_subtype(m, null, serializable_type) and
+                          mtype.is_visible_from(mmodule) and
+                          not compiled_types.has(mtype) then
+
+                               compiled_types.add mtype
+                               nit_module.content.add """
                if name == \"{{{mtype}}}\" then return new {{{mtype}}}.from_deserializer(self)"""
+                       end
                end
        end
 
index 270c185..1cadb05 100644 (file)
@@ -1598,7 +1598,7 @@ class ALabel
        var n_kwlabel: TKwlabel is writable, noinit
 
        # The name of the label, if any
-       var n_id: nullable TId is writable
+       var n_id: nullable TId is writable, noinit
 end
 
 # Expression and statements
@@ -2222,7 +2222,7 @@ class ASelfExpr
        super AExpr
 
        # The `self` keyword
-       var n_kwself: nullable TKwself is writable
+       var n_kwself: nullable TKwself = null is writable
 end
 
 # When there is no explicit receiver, `self` is implicit
index 0a32484..bdec532 100644 (file)
@@ -110,7 +110,6 @@ redef class ToolContext
 
                        for phase in phases do
                                if phase.disabled then continue
-                               self.info(" phase: {phase}", 3)
                                assert phase.toolcontext == self
                                var errcount = self.error_count
                                phase.process_nmodule(nmodule)
index 434c0e0..a6cd32e 100644 (file)
@@ -36,6 +36,13 @@ redef class ModelBuilder
        do
                var analysis = new RapidTypeAnalysis(self, mainmodule)
                analysis.run_analysis
+
+               if toolcontext.opt_log.value then
+                       var basename = toolcontext.log_directory / mainmodule.name
+                       analysis.live_methods_to_tree.write_to_file(basename + ".rta_methods.txt")
+                       analysis.live_types_to_csv.write_to_file(basename + ".rta_types.csv")
+               end
+
                return analysis
        end
 end
index b16eaf5..d60e2a3 100644 (file)
@@ -71,9 +71,6 @@ redef class AMethPropdef
                        return
                end
 
-               # FIXME: THIS IS STUPID (be here to keep the old code working)
-               if not mpropdef.mclassdef.is_intro then return
-
                # Do we inherit for a constructor?
                var skip = true
                for cd in mclassdef.in_hierarchy.direct_greaters do
@@ -102,6 +99,7 @@ redef class AMethPropdef
                if not mpropdef.is_intro then
                        auto_super_call = true
                        mpropdef.has_supercall = true
+                       modelbuilder.toolcontext.info("Auto-super call for {mpropdef}", 4)
                        return
                end
 
@@ -136,6 +134,7 @@ redef class AMethPropdef
 
                        var callsite = new CallSite(self, recvtype, mmodule, anchor, true, candidate, candidatedef, msignature, false)
                        auto_super_inits.add(callsite)
+                       modelbuilder.toolcontext.info("Old-style auto-super init for {mpropdef} to {candidate.full_name}", 4)
                end
 
                # No old style? The look for new-style super constructors (called from a old style constructor)
@@ -170,6 +169,7 @@ redef class AMethPropdef
 
                        var callsite = new CallSite(self, recvtype, mmodule, anchor, true, the_root_init_mmethod, candidatedef, msignature, false)
                        auto_super_inits.add(callsite)
+                       modelbuilder.toolcontext.info("Auto-super init for {mpropdef} to {the_root_init_mmethod.full_name}", 4)
                end
                if auto_super_inits.is_empty then
                        modelbuilder.error(self, "Error: No constructors to call implicitely in {mpropdef}. Call one explicitely.")
index a5b7ceb..62c184a 100644 (file)
@@ -102,6 +102,9 @@ class ToolContext
        # Directory where to generate log files
        var log_directory: String = "logs"
 
+       # Stream in `log_directory` where all info messages are written
+       var log_info: nullable Writer = null
+
        # Messages
        private var messages = new Array[Message]
        private var message_sorter: Comparator = default_comparator
@@ -236,6 +239,10 @@ class ToolContext
                if level <= verbose_level then
                        print "{s}"
                end
+               if log_info != null then
+                       log_info.write s
+                       log_info.write "\n"
+               end
        end
 
        # Executes a program while checking if it's available and if the execution ended correctly
@@ -429,8 +436,10 @@ The Nit language documentation and the source code of its tools and libraries ma
                if opt_log.value then
                        # Make sure the output directory exists
                        log_directory.mkdir
-               end
 
+                       # Redirect the verbose messages
+                       log_info = (log_directory/"info.txt").to_path.open_wo
+               end
        end
 
        # Get the current `nit_version` or "DUMMY_VERSION" if `--set-dummy-tool` is set.
index d271411..6449ce9 100644 (file)
@@ -26,6 +26,7 @@ class Foo
                return 20
        end
        #alt1#var a3: Object is lazy
+       #alt2#fun a4: Object is lazy
 end
 
 var f = new Foo
similarity index 52%
rename from tests/base_at_cached.nit
rename to tests/base_init_raf2.nit
index b6159a6..fd4ee5b 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import kernel
+import base_init
 
-class Base
-       var foo: Int = 10
-       fun -: Int do return foo + 20
-       fun bar: Int do return -self + 40
-       #alt1#fun fail is cached do end
-       #alt2#fun fail(i: Int): Int is cached do return i
+redef class A
+       init
+       do
+               'a'.output
+       end
 end
 
-class CMinus
-       super Base
-
-       redef fun - is cached do return foo + 1
-end
-
-class CBar
-       super Base
-
-       redef fun bar is cached do return -self + 2
+redef class B
+       init
+       do
+               'b'.output
+       end
 end
 
-#alt3#fun fail: Int is cached do return 0
-
-fun test(b: Base)
-do
-       b.foo.output
-       (-b).output
-       b.bar.output
-       b.foo = 110
-       b.foo.output
-       (-b).output
-       b.bar.output
-       '\n'.output
+redef class C
+       init
+       do
+               'c'.output
+       end
 end
-
-test(new Base)
-test(new CMinus)
-test(new CBar)
index 6a47146..0d83b02 100644 (file)
@@ -6,3 +6,4 @@
 --separate ../examples/hello_world.nit -m test_mixin.nit -o out/nitgs-hello_world_mixed ; out/nitgs-hello_world_mixed
 base_simple_import.nit base_simple.nit --dir out/ ; out/base_simple ; out/base_simple_import
 test_define.nit -D text=hello -D num=42 -D flag --dir out/ ; out/test_define
+--log --log-dir $WRITE test_prog -o out/test_prog.bin
diff --git a/tests/sav/base_at_cached.res b/tests/sav/base_at_cached.res
deleted file mode 100644 (file)
index 9fc72b5..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-10
-30
-70
-110
-130
-170
-
-10
-11
-51
-110
-11
-51
-
-10
-30
-32
-110
-130
-32
-
diff --git a/tests/sav/base_at_cached_alt1.res b/tests/sav/base_at_cached_alt1.res
deleted file mode 100644 (file)
index 29c505e..0000000
+++ /dev/null
@@ -1 +0,0 @@
-alt/base_at_cached_alt1.nit:21,6--9: Syntax error: only a function can be cached.
diff --git a/tests/sav/base_at_cached_alt2.res b/tests/sav/base_at_cached_alt2.res
deleted file mode 100644 (file)
index 993e94e..0000000
+++ /dev/null
@@ -1 +0,0 @@
-alt/base_at_cached_alt2.nit:22,6--9: Syntax error: only a function without arguments can be cached.
diff --git a/tests/sav/base_at_cached_alt3.res b/tests/sav/base_at_cached_alt3.res
deleted file mode 100644 (file)
index d1eb131..0000000
+++ /dev/null
@@ -1 +0,0 @@
-alt/base_at_cached_alt3.nit:37,5--8: Error: only abstract and concrete classes can have cached functions.
diff --git a/tests/sav/base_attr_lazy_alt2.res b/tests/sav/base_attr_lazy_alt2.res
new file mode 100644 (file)
index 0000000..7c398a6
--- /dev/null
@@ -0,0 +1 @@
+alt/base_attr_lazy_alt2.nit:29,20--23: Syntax error: `lazy` must be used on attributes.
diff --git a/tests/sav/base_init_raf2.res b/tests/sav/base_init_raf2.res
new file mode 100644 (file)
index 0000000..b64a053
--- /dev/null
@@ -0,0 +1,3 @@
+Aa
+AaBb
+Aac
diff --git a/tests/sav/nitc_args9.res b/tests/sav/nitc_args9.res
new file mode 100644 (file)
index 0000000..f18fded
--- /dev/null
@@ -0,0 +1,3 @@
+info.txt
+test_prog.rta_methods.txt
+test_prog.rta_types.csv
index 0856809..2ddb582 100644 (file)
@@ -1,4 +1,4 @@
-Runtime error: Cast failed. Expected `OTHER`, got `Float` (../lib/standard/kernel.nit:413)
+Runtime error: Cast failed. Expected `OTHER`, got `Float` (../lib/standard/kernel.nit:432)
 11
 21
 31
index 0856809..2ddb582 100644 (file)
@@ -1,4 +1,4 @@
-Runtime error: Cast failed. Expected `OTHER`, got `Float` (../lib/standard/kernel.nit:413)
+Runtime error: Cast failed. Expected `OTHER`, got `Float` (../lib/standard/kernel.nit:432)
 11
 21
 31
index 0856809..2ddb582 100644 (file)
@@ -1,4 +1,4 @@
-Runtime error: Cast failed. Expected `OTHER`, got `Float` (../lib/standard/kernel.nit:413)
+Runtime error: Cast failed. Expected `OTHER`, got `Float` (../lib/standard/kernel.nit:432)
 11
 21
 31
diff --git a/tests/sav/nitg-sg/fixme/test_gen.res b/tests/sav/nitg-sg/fixme/test_gen.res
deleted file mode 100644 (file)
index 4ad3dc3..0000000
+++ /dev/null
@@ -1 +0,0 @@
-UNDEFINED
index 62be912..b69dc33 100644 (file)
@@ -8,6 +8,7 @@ import serialization
 redef class Deserializer
        redef fun deserialize_class(name)
        do
+               # Module: test_serialization
                if name == "Array[Object]" then return new Array[Object].from_deserializer(self)
                if name == "Array[nullable Object]" then return new Array[nullable Object].from_deserializer(self)
                if name == "Array[Serializable]" then return new Array[Serializable].from_deserializer(self)
index e248df6..94b3764 100644 (file)
@@ -30,6 +30,11 @@ Discrete [
 ]
 Comparable -> Discrete [dir=back arrowtail=open style=dashed];
 
+Cloneable [
+ label = "{interface\nCloneable||+ clone(): SELF\l}"
+]
+Object -> Cloneable [dir=back arrowtail=open style=dashed];
+
 Numeric [
  label = "{interface\nNumeric||+ +(i: OTHER): OTHER\l+ -(i: OTHER): OTHER\l+ unary -(): OTHER\l+ *(i: OTHER): OTHER\l+ /(i: OTHER): OTHER\l+ to_i(): Int\l+ to_f(): Float\l+ is_zero(): Bool\l+ zero(): OTHER\l+ value_of(val: Numeric): OTHER\l}"
 ]
@@ -52,7 +57,7 @@ Discrete -> Int [dir=back arrowtail=open style=dashed];
 Numeric -> Int [dir=back arrowtail=open style=dashed];
 
 Char [
- label = "{Char||+ to_i(): Int\l+ ascii(): Int\l+ to_lower(): Char\l+ to_upper(): Char\l+ is_digit(): Bool\l+ is_lower(): Bool\l+ is_upper(): Bool\l+ is_letter(): Bool\l}"
+ label = "{Char||+ to_i(): Int\l+ ascii(): Int\l+ to_lower(): Char\l+ to_upper(): Char\l+ is_digit(): Bool\l+ is_lower(): Bool\l+ is_upper(): Bool\l+ is_letter(): Bool\l+ is_whitespace(): Bool\l}"
 ]
 Discrete -> Char [dir=back arrowtail=open style=dashed];
 
index 9a7b30f..1664fce 100644 (file)
@@ -30,6 +30,11 @@ Discrete [
 ]
 Comparable -> Discrete [dir=back arrowtail=open style=dashed];
 
+Cloneable [
+ label = "{interface\nCloneable||+ clone(): SELF\l}"
+]
+Object -> Cloneable [dir=back arrowtail=open style=dashed];
+
 Numeric [
  label = "{interface\nNumeric||+ +(i: OTHER): OTHER\l+ -(i: OTHER): OTHER\l+ unary -(): OTHER\l+ *(i: OTHER): OTHER\l+ /(i: OTHER): OTHER\l+ to_i(): Int\l+ to_f(): Float\l+ is_zero(): Bool\l+ zero(): OTHER\l+ value_of(val: Numeric): OTHER\l}"
 ]
@@ -52,7 +57,7 @@ Discrete -> Int [dir=back arrowtail=open style=dashed];
 Numeric -> Int [dir=back arrowtail=open style=dashed];
 
 Char [
- label = "{Char||+ to_i(): Int\l+ ascii(): Int\l+ to_lower(): Char\l+ to_upper(): Char\l+ is_digit(): Bool\l+ is_lower(): Bool\l+ is_upper(): Bool\l+ is_letter(): Bool\l}"
+ label = "{Char||+ to_i(): Int\l+ ascii(): Int\l+ to_lower(): Char\l+ to_upper(): Char\l+ is_digit(): Bool\l+ is_lower(): Bool\l+ is_upper(): Bool\l+ is_letter(): Bool\l+ is_whitespace(): Bool\l}"
 ]
 Discrete -> Char [dir=back arrowtail=open style=dashed];
 
index d21eb0d..1fb7aeb 100644 (file)
@@ -1,4 +1,4 @@
-Runtime error: Cast failed. Expected `E`, got `Bool` (../lib/standard/collection/array.nit:808)
+Runtime error: Cast failed. Expected `E`, got `Bool` (../lib/standard/collection/array.nit:909)
 NativeString
 N
 Nit