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>
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
# 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
# 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
#
# * 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.
#
#
# * 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.
#
#
# * 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.
#
# * 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.
#
#
# * 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
#
# * 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.
#
#
# * 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
#
# * 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
#
# * 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
#
# * 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
#
# * 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
#
# * 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
#
# * 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.
#
# 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
--- /dev/null
+# 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
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`.
# 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.
# 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
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
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"
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
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
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
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"
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"
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> "
+ 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> "
+ 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
</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
# Base HTML rendering templates for `nitpg`.
module templates_base
-import statistics
+import achievements
redef class GameEntity
# 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
# Templates to display `GameEvent` kinds.
module templates_events
-import events
+import achievements
import templates_base
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
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">
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
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
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 ""
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
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(); `}
# 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" `{
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
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
# 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
# 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
# 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)
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.
# 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`.
return c.second
end
end
+
+ redef fun has_key(key) do return couple_at(key) != null
end
# Iterator on CoupleMap
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
# assert a == b
class Array[E]
super AbstractArray[E]
+ super Cloneable
redef fun [](index)
do
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.
redef var index = 0
var array: AbstractArrayRead[E]
+
+ redef fun finish do _array._free_iterator = self
end
private class ArrayReverseIterator[E]
# A set implemented with an Array.
class ArraySet[E]
super Set[E]
+ super Cloneable
# The stored elements.
private var array: Array[E] is noinit
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.
# 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)
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
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]
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
_file = new NativeFile.native_stdout
path = "/dev/stdout"
_is_writable = true
+ set_buffering_mode(256, sys.buffer_mode_line)
end
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"
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
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.
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`
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'
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
# 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`.
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
#
# 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
#
# 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
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"
# 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
# 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
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
# 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
init with_capacity(cap: Int)
do
assert cap >= 0
- # _items = new NativeString.calloc(cap)
items = new NativeString(cap+1)
capacity = cap
length = 0
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
* 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
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>
+~~~
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'
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, '')
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
" 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
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
`--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`
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]
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
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
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
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)
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
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]};")
# 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
+++ /dev/null
-# This file is part of NIT ( http://www.nitlanguage.org ).
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# 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
writable
autoinit
noautoinit
-cached
nosuper
old_style_init
abstract
import modelize
import semantize
import div_by_zero
-import cached
import serialization_phase
import check_annotation
import glsl_validation
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]
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
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
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
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
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)
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
# 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
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
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 """
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
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
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
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)
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
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
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
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)
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.")
# 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
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
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.
return 20
end
#alt1#var a3: Object is lazy
+ #alt2#fun a4: Object is lazy
end
var f = new Foo
# 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)
--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
+++ /dev/null
-10
-30
-70
-110
-130
-170
-
-10
-11
-51
-110
-11
-51
-
-10
-30
-32
-110
-130
-32
-
+++ /dev/null
-alt/base_at_cached_alt1.nit:21,6--9: Syntax error: only a function can be cached.
+++ /dev/null
-alt/base_at_cached_alt2.nit:22,6--9: Syntax error: only a function without arguments can be cached.
+++ /dev/null
-alt/base_at_cached_alt3.nit:37,5--8: Error: only abstract and concrete classes can have cached functions.
--- /dev/null
+alt/base_attr_lazy_alt2.nit:29,20--23: Syntax error: `lazy` must be used on attributes.
--- /dev/null
+Aa
+AaBb
+Aac
--- /dev/null
+info.txt
+test_prog.rta_methods.txt
+test_prog.rta_types.csv
-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
-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
-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
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)
]
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}"
]
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];
]
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}"
]
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];
-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