_gamnit_ update to use SDL2 instead of SDL1.2. This change brings better code portability and fixes a few known SDL1.2 limitations. This PR does not significantly change the API, asides from removing the workarounds for old limitations.
As support, this PR also:
* Adds more SDL2 events and surface services.
* Updates the `opengles1_hello_triangle` example to use SDL2 and make it compatible with ANGLE and Windows.
* Drops the _gamnit_ workarounds for the previously slow mouse move events and mouse wrap for FPS like controls.
* Fixes shader compatibility with some graphics card and driver.
* General cleanup for the `egl` module.
Pull-Request: #2376
Reviewed-by: Jean Privat <jean@pryen.org>
var opt_out_src = new OptionString("Path to output source file (folder or file)", "--src", "-s")
var opt_assets = new OptionString("Path to assert dir where to put PNG files", "--assets", "-a")
-var opt_scale = new OptionFloat("Apply scaling to exported images (default at 1.0 of 90dpi)", 1.0, "--scale", "-x")
+var opt_scale = new OptionFloat("Apply scaling to exported images (default at 1.0 of 96dpi)", 1.0, "--scale", "-x")
var opt_gamnit = new OptionBool("Target the Gamnit framework (by default it targets Mnit)", "--gamnit", "-g")
var opt_pow2 = new OptionBool("Round the image size to the next power of 2", "--pow2")
var opt_help = new OptionBool("Print this help message", "--help", "-h")
# Output png file to assets
var png_path = "{assets_path}/images/{drawing_name}.png"
var proc2 = new Process.from_a(prog, [drawing, "--without-gui",
- "--export-dpi={(90.0*scale).to_i}",
+ "--export-dpi={(96.0*scale).to_i}",
"--export-png={png_path}",
"--export-area={min_x}:{y0}:{max_x}:{y1}",
"--export-background=#000000", "--export-background-opacity=0.0"])
if t.symbol == null then continue
# Check overlaps
- var tf = t.symbol.first
- var tl = t.symbol.last
+ var tf = t.symbol.as(not null).first
+ var tl = t.symbol.as(not null).last
if l != null and tf > l then continue
if tl != null and f > tl then continue
end
end
- # Produce a graphvis file for the automaton
- fun to_dot(filepath: String)
+ # Produce a graphviz string from the automatom
+ #
+ # Set `merge_transitions = false` to generate one edge by transition (default true).
+ fun to_dot(merge_transitions: nullable Bool): Writable
do
var names = new HashMap[State, String]
var ni = 0
ni += 1
end
- var f = new FileWriter.open(filepath)
- f.write("digraph g \{\n")
+ var f = new Buffer
+ f.append("digraph g \{\n")
+ f.append("rankdir=LR;")
+ var state_nb = 0
for s in states do
- f.write("s{names[s]}[shape=oval")
+ f.append("s{names[s]}[shape=circle")
#f.write("label=\"\",")
if accept.has(s) then
- f.write(",color=blue")
+ f.append(",shape=doublecircle")
end
if tags.has_key(s) then
- f.write(",label=\"")
+ f.append(",label=\"")
for token in tags[s] do
- f.write("{token.name.escape_to_c}\\n")
+ f.append("{token.name.escape_to_dot}\\n")
end
- f.write("\"")
+ f.append("\"")
else
- f.write(",label=\"\"")
+ f.append(",label=\"{state_nb}\"")
end
- f.write("];\n")
+ f.append("];\n")
var outs = new HashMap[State, Array[nullable TSymbol]]
for t in s.outs do
var a
for s2, a in outs do
var labe = ""
for c in a do
+ if merge_transitions == false then labe = ""
if not labe.is_empty then labe += "\n"
if c == null then
- labe += "''"
+ labe += "ε"
else
labe += c.to_s
end
+ if merge_transitions == false then
+ f.append("s{names[s]}->s{names[s2]} [label=\"{labe.escape_to_dot}\"];\n")
+ end
+ end
+ if merge_transitions == null or merge_transitions == true then
+ f.append("s{names[s]}->s{names[s2]} [label=\"{labe.escape_to_c}\"];\n")
end
- f.write("s{names[s]}->s{names[s2]} [label=\"{labe.escape_to_c}\"];\n")
end
+ state_nb += 1
end
- f.write("empty->s{names[start]}; empty[label=\"\",shape=none];\n")
-
- f.write("\}\n")
- f.close
+ f.append("empty->s{names[start]}; empty[label=\"\",shape=none];\n")
+ f.append("\}\n")
+ return f
end
# Transform a NFA to a DFA.
var dfa = new Automaton.empty
var n2d = new ArrayMap[Set[State], State]
- var seen = new ArraySet[Set[State]]
+ var seen = new ArraySet[Set[State]]
var alphabet = new HashSet[Int]
var st = eclosure([start])
var todo = [st]
seen.add(nfa_dest)
end
if lastst != null and lastst.to == dfa_dest then
- lastst.symbol.last = sym.last
+ lastst.symbol.as(not null).last = sym.last
else
lastst = dfa_state.add_trans(dfa_dest, sym)
end
for t in s.outs do
if t.symbol != null then continue
var to = t.to
- if res.has(to) then continue
+ if res.has(to) then continue
res.add(to)
todo.add(to)
end
var l = sym.last
if l != null and l < symbol then continue
var to = t.to
- if res.has(to) then continue
+ if res.has(to) then continue
res.add(to)
end
end
if not node isa NProd then
assert node isa NError
- print "{node.position.to_s} Syntax Error: {node.message}"
+ print "{node.position.as(not null)} Syntax Error: {node.message}"
exit 1
abort
end
var nfa = v2.nfa
print "NFA automaton: {nfa.states.length} states (see {name}.nfa.dot)"
-nfa.to_dot("{name}.nfa.dot")
+nfa.to_dot.write_to_file("{name}.nfa.dot")
var dfa = nfa.to_dfa.to_minimal_dfa
dfa.solve_token_inclusion
print "DFA automaton: {dfa.states.length} states (see {name}.dfa.dot)"
-dfa.to_dot("{name}.dfa.dot")
+dfa.to_dot.write_to_file("{name}.dfa.dot")
if dfa.tags.has_key(dfa.start) then
print "Error: Empty tokens {dfa.tags[dfa.start].join(" ")}"
end
is_building = false
var nre = self.children[2]
- res = nre.make_rfa
+ res = nre.make_nfa
nfa = res
return res
end
redef class Nre_id
# The named expression
var nexpr: nullable Nexpr
-
+
redef fun accept_check_name_visitor(v) do
var id = children.first.as(Nid)
var name = id.text
v.nexpr.precs.add(node)
end
- redef fun make_rfa
+ redef fun make_nfa
do
return nexpr.nfa.dup
end
redef class Node
# Build the NFA of the regular expression
- fun make_rfa: Automaton do
+ fun make_nfa: Automaton do
print inspect
abort
end
end
redef class Nstr
- redef fun value: String do return text.substring(1, text.length-2).unescape_nit
- redef fun make_rfa: Automaton
+ redef fun value do return text.substring(1, text.length-2).unescape_nit
+ redef fun make_nfa
do
var a = new Automaton.epsilon
for c in self.value.chars do
end
redef class Nch_dec
- redef fun value: String do return text.substring_from(1).to_i.code_point.to_s
- redef fun make_rfa: Automaton
+ redef fun value do return text.substring_from(1).to_i.code_point.to_s
+ redef fun make_nfa
do
var a = new Automaton.atom(self.value.chars.first.code_point)
return a
end
redef class Nch_hex
- redef fun value: String do return text.substring_from(2).to_hex.code_point.to_s
- redef fun make_rfa: Automaton
+ redef fun value do return text.substring_from(2).to_hex.code_point.to_s
+ redef fun make_nfa
do
var a = new Automaton.atom(self.value.chars.first.code_point)
return a
end
redef class NProd
- redef fun make_rfa: Automaton
+ redef fun make_nfa
do
- assert children.length == 1 else print "no make_rfa for {self}"
- return children.first.make_rfa
+ assert children.length == 1 else print "no make_nfa for {self}"
+ return children.first.make_nfa
end
end
redef class Nre_alter
- redef fun make_rfa
+ redef fun make_nfa
do
- var a = children[0].make_rfa
- var b = children[2].make_rfa
+ var a = children[0].make_nfa
+ var b = children[2].make_nfa
a.alternate(b)
return a
end
end
redef class Nre_minus
- redef fun make_rfa
+ redef fun make_nfa
do
- var a = children[0].make_rfa
- var b = children[2].make_rfa.to_dfa
+ var a = children[0].make_nfa
+ var b = children[2].make_nfa.to_dfa
for t in b.start.outs do
if not t.to.outs.is_empty then
# `b` is not a single char, so just use except
end
redef class Nre_end
- redef fun make_rfa
+ redef fun make_nfa
do
print "{children.first.position.to_s}: NOT YET IMPLEMENTED: token `End`; replaced with an empty string"
return new Automaton.epsilon
end
redef class Nre_and
- redef fun make_rfa
+ redef fun make_nfa
do
- var a = children[0].make_rfa
+ var a = children[0].make_nfa
var ta = new Token("1")
a.tag_accept(ta)
- var b = children[2].make_rfa
+ var b = children[2].make_nfa
var tb = new Token("2")
b.tag_accept(tb)
end
redef class Nre_except
- redef fun make_rfa
+ redef fun make_nfa
do
- var a = children[0].make_rfa
- var b = children[2].make_rfa
+ var a = children[0].make_nfa
+ var b = children[2].make_nfa
return a.except(b)
end
end
redef class Nre_shortest
- redef fun make_rfa
+ redef fun make_nfa
do
- var a = children[2].make_rfa
+ var a = children[2].make_nfa
a = a.to_dfa
for s in a.accept do
for t in s.outs.to_a do t.delete
end
redef class Nre_longest
- redef fun make_rfa
+ redef fun make_nfa
do
- var a = children[2].make_rfa
+ var a = children[2].make_nfa
a = a.to_dfa
for s in a.accept.to_a do
if not s.outs.is_empty then a.accept.remove(s)
end
redef class Nre_prefixes
- redef fun make_rfa
+ redef fun make_nfa
do
- var a = children[2].make_rfa
+ var a = children[2].make_nfa
a.trim
a.accept.add_all a.states
return a
end
redef class Nre_conc
- redef fun make_rfa
+ redef fun make_nfa
do
- var a = children[0].make_rfa
- var b = children[1].make_rfa
+ var a = children[0].make_nfa
+ var b = children[1].make_nfa
a.concat(b)
return a
end
end
redef class Nre_star
- redef fun make_rfa
+ redef fun make_nfa
do
- var a = children[0].make_rfa
+ var a = children[0].make_nfa
a.close
return a
end
end
redef class Nre_ques
- redef fun make_rfa
+ redef fun make_nfa
do
- var a = children[0].make_rfa
+ var a = children[0].make_nfa
a.optionnal
return a
end
end
redef class Nre_plus
- redef fun make_rfa
+ redef fun make_nfa
do
- var a = children[0].make_rfa
+ var a = children[0].make_nfa
a.plus
return a
end
end
redef class Nre_par
- redef fun make_rfa
+ redef fun make_nfa
do
- return children[1].make_rfa
+ return children[1].make_nfa
end
end
redef class Nre_class
- redef fun make_rfa: Automaton
+ redef fun make_nfa
do
var c1 = children[0].children[0].value
var c2 = children[3].children[0].value
end
redef class Nre_openclass
- redef fun make_rfa: Automaton
+ redef fun make_nfa
do
var c1 = children[0].children[0].value
if c1.length != 1 then
end
redef class Nre_any
- redef fun make_rfa: Automaton
+ redef fun make_nfa
do
var a = new Automaton.cla(0, null)
return a
--- /dev/null
+*.dot
+*parser_parser.nit
+*parser_lexer.nit
+*_test_parser*
+*.out
+
+re_parser
+re_app
--- /dev/null
+NITC=../../bin/nitc
+NITCC=../../nitcc/src/nitcc
+NITUNIT=../../bin/nitunit
+
+all: re_parser re_app
+
+nitcc:
+ cd ../nitcc && make nitcc
+
+grammar: nitcc
+ cd src/ && ${NITCC} re_parser.sablecc
+
+re_parser: grammar
+ ${NITC} src/re_parser.nit
+
+re_app: grammar
+ ${NITC} src/re_app.nit
+
+check:
+ ${NITUNIT} .
+
+clean:
+ rm re_parser re_app 2>/dev/null || true
+ cd src/ && rm -r \
+ *.dot *.out \
+ re_parser_lexer.nit re_parser_parser.nit re_parser_test_parser.nit re_parser_parser_gen \
+ 2>/dev/null || true
--- /dev/null
+# RE Parser
+
+RE parser provides a simple API to regular expression parsing.
+It is also able to convert a regular expression into a NFA or a DFA and produce dot files from it.
+
+## Building RE parser
+
+From the `re_parser` directory:
+
+~~~raw
+make all
+~~~
+
+## RE parser in command line
+
+RE parser can be used as a command line tool to generate NFA and DFA dot files from a regular expression:
+
+~~~raw
+./re_parser "a(b|c)+d*"
+~~~
+
+Will produce the two files `nfa.dot` and `dfa.dot`.
+
+These can be directly viwed with `xdot`:
+
+~~~raw
+xdot nfa.dot
+xdot dfa.dot
+~~~
+
+Or translated to png images with `dot`:
+
+~~~raw
+dot -Tpng -onfa.png nfa.dot
+dot -Tpng -odfa.png dfa.dot
+~~~
+
+See `man dot` for available formats.
+
+## RE parser as a web app
+
+RE parser comes with a web app that allow users to submit regular expression and see the resulting NFA and DFA.
+
+To run the web app server:
+
+~~~raw
+./re_app --host localhost --port 3000
+~~~
+
+The server will be available at <a href='http://localhost:3000'>http://localhost:3000</a>.
+
+## RE parser as a library
+
+You can also use RE parser as a library by importing `re_parser`.
+
+ import re_parser
+
+ var re = "a(b|c)+d*"
+
+ # Parse re
+ var re_parser = new REParser
+ var node = re_parser.parse_re(re)
+
+ if node == null then
+ print re_parser.last_error.as(not null)
+ exit 1
+ abort
+ end
+
+ # Build NFA and DFA
+ var nfa = re_parser.make_nfa(node)
+ print nfa.to_dot(false)
+ print nfa.to_dfa.to_dot(false)
+
+Use `to_dot(true)` to merge transitions on characters.
--- /dev/null
+[package]
+name=re_parser
+tags=re
+maintainer=Alexandre Terrasa <alexandre@moz-code.org>
+license=Apache-2.0
+[upstream]
+browse=https://github.com/nitlang/nit/tree/master/contrib/re_parser/
+git=https://github.com/nitlang/nit.git
+git.directory=contrib/re_parser/
+homepage=http://nitlanguage.org
+issues=https://github.com/nitlang/nit/issues
--- /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.
+
+module re_app
+
+import re_parser
+import popcorn
+import popcorn::pop_config
+
+class REHandler
+ super Handler
+
+ redef fun get(req, res) do
+ res.html new RETemplate
+ end
+
+ redef fun post(req, res) do
+ # TODO Retrieve re from post body
+ var re = req.string_arg("re")
+
+ var tpl = new RETemplate
+
+ if re == null or re.is_empty then
+ tpl.error = "Error: empty regexp"
+ res.html tpl
+ return
+ end
+
+ tpl.re = re
+
+ # Parse re
+ var re_parser = new REParser
+ var node = re_parser.parse_re(re)
+
+ if node == null then
+ tpl.error = re_parser.last_error.as(not null)
+ res.html tpl
+ return
+ end
+
+ # Build nfa and dfa
+ var nfa = re_parser.make_nfa(node)
+ tpl.nfa = automaton_to_svg(nfa)
+ tpl.dfa = automaton_to_svg(nfa.to_dfa)
+
+ res.html tpl
+ end
+
+ private fun automaton_to_svg(automaton: Automaton): String do
+ automaton.to_dot(false).write_to_file "dot.tmp"
+ sys.system "dot -Tsvg -osvg.tmp dot.tmp"
+ var svg = "svg.tmp".to_path.read_all
+ sys.system "rm -f dot.tmp svg.tmp"
+ return svg
+ end
+end
+
+class RETemplate
+ super Template
+
+ var re = "a*(b|cd?)+"
+ var nfa: nullable String = null
+ var dfa: nullable String = null
+ var error: nullable String = null
+
+ redef fun rendering do
+ add """
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8" />
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <title>REParser</title>
+ <link rel="stylesheet"
+ href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" />
+ </head>
+ <body>
+ <div class="container">
+ <h1>Regular Expressions to NFA and DFA</h1>
+ <br><br>
+ <label for="re">Enter a regular expression:</label>
+ <form action="/" method="POST" class="input-group">
+ <input id="re" name="re" type="text" class="form-control" value="{{{re}}}">
+ <span class="input-group-btn">
+ <input type="submit" class="btn btn-default" value="Convert" />
+ </span>
+ </form>
+ <br><br>"""
+
+ var error = self.error
+ if error != null then
+ add """<p class="text-danger">{{{error}}}</p>"""
+ end
+
+ var nfa = self.nfa
+ if nfa != null then
+ add """
+ <h2>NFA</h2>
+ <div>{{{nfa}}}</div><br><br>"""
+ end
+
+ var dfa = self.dfa
+ if dfa != null then
+ add """
+ <h2>DFA</h2>
+ <div>{{{dfa}}}</div><br><br>"""
+ end
+
+ add """</div>
+ <div class="text-center text-muted">
+ <p>Powered by <a href="http://nitlanguage.org">nit</a>!</p>
+ </div>
+ </body>
+</html>"""
+ end
+end
+
+var config = new AppConfig
+config.parse_options(args)
+
+var app = new App
+app.use("/", new REHandler)
+app.listen(config.app_host, config.app_port)
--- /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.
+
+module re_parser
+
+import nitcc::autom
+import re_parser_lexer
+import re_parser_parser
+
+# Parse regular expression into NFA
+#
+# ~~~
+# # Parse the regular expression
+# var re = "(a|b)*"
+# var re_parser = new REParser
+# var node = re_parser.parse_re(re)
+#
+# # Check syntax errors
+# assert node != null else
+# print re_parser.last_error.as(not null)
+# end
+#
+# # Build nfa and dfa
+# var nfa = re_parser.make_nfa(node)
+# print nfa.to_dot
+# print nfa.to_dfa.to_dot
+# ~~~
+class REParser
+
+ # Parse the regular expression `re` and return the root production node.
+ #
+ # Returns `null` in case of syntax error. See `last_error`.
+ #
+ # ~~~
+ # var re_parser = new REParser
+ #
+ # # Valid regular expression
+ # assert re_parser.parse_re("(a|b)*") != null
+ # assert re_parser.last_error == null
+ #
+ # # Invalid regular expression
+ # assert re_parser.parse_re("a|b)*") == null
+ # assert re_parser.last_error != null
+ # ~~~
+ fun parse_re(re: String): nullable NProd do
+ var l = new Lexer_re_parser(re)
+ var ts = l.lex
+
+ var p = new Parser_re_parser
+ p.tokens.add_all ts
+
+ var node = p.parse
+
+ if not node isa NProd then
+ if node isa NError then
+ last_error = "{node.position.as(not null).to_s} Syntax Error: {node.message}"
+ else
+ last_error = "Parsing Error: expected `NProd` got `{node.class_name}`"
+ end
+ return null
+ end
+ last_error = null
+ return node
+ end
+
+ # Contains the error from the last call to `parse_re` or null if no error
+ #
+ # ~~~
+ # var re_parser = new REParser
+ #
+ # # Invalid regular expression
+ # assert re_parser.parse_re("a|b)*") == null
+ # assert re_parser.last_error != null
+ # ~~~
+ var last_error: nullable String
+
+ # Build the NFA for `node`
+ #
+ # Use `parse_re` to transform a re string into a NProd.
+ fun make_nfa(node: NProd): Automaton do
+ var v = new REVisitor
+ v.start(node)
+ return v.nfa
+ end
+end
+
+class REVisitor
+ super Visitor
+
+ var nfa = new Automaton
+
+ fun start(n: Node) do
+ enter_visit(n)
+ end
+
+ redef fun visit(n) do n.accept_revisitor(self)
+end
+
+redef class Node
+ fun accept_revisitor(v: REVisitor) do visit_children(v)
+
+ # Build the NFA of the regular expression
+ fun make_nfa: Automaton do
+ print inspect
+ abort
+ end
+end
+
+redef class Nre
+ redef fun accept_revisitor(v) do
+ v.nfa = make_nfa
+ end
+
+ redef fun make_nfa do
+ var a = new Automaton
+ for child in children do
+ if child == null then continue
+ a.concat(child.make_nfa)
+ end
+ return a
+ end
+end
+
+redef class Nre_char
+ redef fun make_nfa do
+ return new Automaton.atom(n_char.text.chars.first.code_point)
+ end
+end
+
+redef class Nre_alter
+ redef fun make_nfa
+ do
+ var a = n_re.make_nfa
+ var b = n_re2.make_nfa
+ a.alternate(b)
+ return a
+ end
+end
+
+redef class Nre_conc
+ redef fun make_nfa
+ do
+ var a = n_re.make_nfa
+ var b = n_re2.make_nfa
+ a.concat(b)
+ return a
+ end
+end
+
+redef class Nre_star
+ redef fun make_nfa
+ do
+ var a = n_re.make_nfa
+ a.close
+ return a
+ end
+end
+
+redef class Nre_ques
+ redef fun make_nfa
+ do
+ var a = n_re.make_nfa
+ a.optionnal
+ return a
+ end
+end
+
+redef class Nre_plus
+ redef fun make_nfa
+ do
+ var a = n_re.make_nfa
+ a.plus
+ return a
+ end
+end
+
+redef class Nre_par
+ redef fun make_nfa
+ do
+ return n_re.make_nfa
+ end
+end
+
+# Parse arguments
+if args.is_empty then
+ print "usage: re_parser <re>"
+ exit 1
+end
+var re = args.first
+
+# Parse re
+var re_parser = new REParser
+var node = re_parser.parse_re(re)
+
+if node == null then
+ print re_parser.last_error.as(not null)
+ exit 1
+ abort
+end
+
+# Build nfa and dfa
+var nfa = re_parser.make_nfa(node)
+nfa.to_dot(false).write_to_file("nfa.dot")
+nfa.to_dfa.to_dot(false).write_to_file("dfa.dot")
+print "Produced files `nfa.dot` and `dfa.dot`"
--- /dev/null
+Grammar re_parser;
+
+Lexer
+
+// A printable character (inside strings)
+char = ' ' ...;
+
+// Igndored stufs
+blank = ' ';
+
+Parser
+
+Ignored blank;
+
+re =
+ {char:} char |
+ {par:} '(' re ')'
+Unary
+ {ques:} re '?' |
+ {star:} re '*' |
+ {plus:} re '+'
+Left
+ {conc:} re re
+Left
+ {alter:} re '|' re;
--- /dev/null
+# Nit Actor Model
+
+This group introduces the `actors` module which contains the abstraction of a Nit Actor Model,
+based on Celluloid (https://github.com/celluloid/celluloid).
+
+## What is an actor ?
+
+An actor is an entity which receives messages and does some kind of computation based on it.
+An actor has a mailbox in which it receives its messages, and process them one at a time.
+
+
+## `actor` annotation
+
+The `actors` module introduces the annotation `actor` which is to be used on classes.
+This annotation transform a normal Nit class into an actor.
+
+In practice, it adds a new property `async` to the annotated class.
+When using `async` on your annotated class, this means that you want your calls to be asynchronous,
+executed by the actor.
+
+For instance, if you call `a.async.foo` and `foo` doesn't have a return value, it will send
+a message to the mailbox of the actor attached to `a` which will process it asynchronously.
+
+On the other hand, if you call `a.async.bar` and `bar` returns an`Int`, it will still send
+a message to the actor, but you'll get a `Future[Int]` to be able to retrieve the value.
+When using `join` on the future, the calling thread will wait until the value of the future is set.
+
+## Managing actors
+
+When you annotate a class with `actor` and create an instance of it with `new`, the actor is not
+automatically created (which means you can completely skip the use of the actors if you
+don't need them for a specific program).
+
+The `async` added property is actually created lazily when you use it.
+
+Actors are not automatically garbage collected, but you have solutions to terminate them
+if you need to. For this, you need to use the `async` property of your annotated class :
+
+* `async.terminate` sends a shutdown message to the actor telling him to stop, so he'll finish
+processing every other messages in his mailbox before terminating properly. Every other messages sent
+to this actor after he received the shutdown message won't be processed.
+* `async.terminate_now` sends a shutdown message too, but this time it places it first, so
+if the actor is processing one message now, the next one will be the shutdown message, discarding
+every messages in its mailbox.
+* `async.wait_termination` wait for the actor to terminate properly. This call is synchronous.
+* `async.kill`. If you really need this actor to stop, without any regards of what he was doing
+or in which state he'll leave the memory, you can with this call. it's synchronous but not really
+blocking, since it's direcly canceling the native pthread associated to the actor.
+
+For now, there isn't any mecanism to recreate and actor after it was terminated.
+Sending messages after terminating it results in unspecified behaviour.
+
+## Waiting for all actors to finish processing
+
+Let's imagine you create a whole bunch of actors and make them do things asynchronously from the main thread.
+You don't want your program to exit right after giving work to your actors.
+To prevent that, we added a mecanism that waits before all your actors finished all their messages
+before quitting.
+
+It's materialized by the `active_actors` property added to `Sys` which is a `ReverseBlockingQueue`.
+In short, the `is_empty` method on this list is blocking until the list is effectively empty.
+When every actors finished working, and we're sure they won't even send another message to another
+actor, `active_actors` is empty.
+
+You can use this property as a mean of synchronisation in some specific cases (for example if you're
+using actors for fork/join parallelism instead of concurrency).
+
+
+## Examples
+
+You can find example of differents small programs implemented with Nit actors in the `examples`
+directory. For a really simple example, you can check `examples/simple`.
--- /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.
+
+# Abstraction of the actors concepts
+module actors is
+ new_annotation actor
+end
+
+import pthreads::concurrent_collections
+intrude import pthreads
+intrude import pthreads::extra
+
+# Abstraction of an actor
+# It has a mailbox, can receive and process messages asynchronously
+abstract class Actor
+ super Thread
+
+ # Type of the proxied class (or working class)
+ type V: Object
+
+ # The instance used to do the real work
+ # i.e. the real working object
+ var instance: V
+
+ # Mailbox used to receive and process messages
+ var mailbox = new BlockingQueue[Message].with_actor(self)
+
+ # Is `self` working ?
+ # i.e. does it have messages to process or is it processing one now ?
+ var working = false
+
+ redef fun main do
+ loop
+ var m = mailbox.shift
+ if m isa ShutDownMessage then
+ sys.active_actors.remove(self)
+ return null
+ end
+ m.invoke(instance)
+ if mailbox.is_empty then
+ working = false
+ sys.active_actors.remove(self)
+ end
+ end
+ end
+
+ # Ends `self`, cancel ongoing work abrutly
+ # Pretty dangerous to use
+ fun kill do
+ var n = self.native
+ if n != null then n.cancel
+ end
+end
+
+# A Message received by a Mailbox
+# In fact, this is the reification of a call
+# Each Message class represent a call to make on `instance` via `invoke`
+abstract class Message
+
+ # Type of the class on which `self` make the call
+ type E: Object
+
+ # Redef this method so it calls the right one on `instance` (double dispatch)
+ fun invoke(instance: E) is abstract
+end
+
+# Abstraction of proxies for threaded actors
+class Proxy
+
+ # Type of the actor `self` is proxiing
+ type E: Actor
+
+ # The proxied actor
+ var actor: E is noinit
+
+ # Kill `actor` without mercy
+ fun kill do actor.kill
+
+ # Tell `actor` to terminate properly
+ # Queueing a ShutDownMessage to the end of its mailbox
+ fun terminate do
+ var msg = new ShutDownMessage
+ actor.mailbox.push(msg)
+ end
+
+ # Tell `actor` to terminate now
+ # Queueing a ShutDownMessage before every other ones
+ fun terminate_now do
+ var msg = new ShutDownMessage
+ actor.mailbox.unshift(msg)
+ end
+
+ # Wait for `actor` to terminate
+ fun wait_termination do actor.join
+end
+
+# A Message to Rule them all... properly shutdown an Actor
+# It's behaviour is implemented in the actor itself
+class ShutDownMessage
+ super Message
+end
+
+# The promise of a value which will be set asynchronously
+class Future[E]
+ # Value promised by `self`
+ var value: nullable E = null
+
+ # Mutex for synchronisation
+ protected var mutex = new Mutex
+
+ # Condition variable for synchronisation
+ protected var cond: nullable PthreadCond = null
+
+ # Can be used to check if the value is available without waiting
+ protected var is_done = false
+
+ # Set the value and signal so that, someone waiting for `value` can retrieve it
+ fun set_value(value: E) do
+ mutex.lock
+ is_done = true
+ self.value = value
+ var cond = self.cond
+ if cond != null then cond.signal
+ mutex.unlock
+ end
+
+ # Return immediatly if `value` is set, or block waiting for `value` to be set
+ fun join: E do
+ mutex.lock
+ if not is_done then
+ var cond = self.cond
+ if cond == null then
+ cond = new PthreadCond
+ self.cond = cond
+ end
+ cond.wait(mutex)
+ end
+ mutex.unlock
+ return value
+ end
+end
+
+# A Blocking queue implemented from a `ConcurrentList`
+# `shift` is blocking if there isn't any element in `self`
+# `push` or `unshift` releases every blocking threads
+# Corresponds to the mailbox of an actor
+class BlockingQueue[E]
+ super ConcurrentList[E]
+
+ # The associated actor
+ var actor: Actor is noautoinit
+
+ # Used to block or signal on waiting threads
+ private var cond = new PthreadCond
+
+ # init self with an associated actor
+ init with_actor(actor: Actor) do self.actor = actor
+
+ # Adding the signal to release eventual waiting thread(s)
+ redef fun push(e) do
+ mutex.lock
+ if real_collection.is_empty and not actor.working then
+ actor.working = true
+ sys.active_actors.push(actor)
+ end
+ real_collection.push(e)
+ self.cond.signal
+ mutex.unlock
+ end
+
+ redef fun unshift(e) do
+ mutex.lock
+ real_collection.unshift(e)
+ self.cond.signal
+ mutex.unlock
+ end
+
+ # If empty, blocks until an item is inserted with `push` or `unshift`
+ redef fun shift do
+ mutex.lock
+ while real_collection.is_empty do self.cond.wait(mutex)
+ var r = real_collection.shift
+ mutex.unlock
+ return r
+ end
+end
+
+# A collection which `is_empty` method blocks until it's empty
+class ReverseBlockingQueue[E]
+ super ConcurrentList[E]
+
+ # Used to block or signal on waiting threads
+ private var cond = new PthreadCond
+
+ # Adding the signal to release eventual waiting thread(s)
+ redef fun push(e) do
+ mutex.lock
+ real_collection.push(e)
+ mutex.unlock
+ end
+
+ # When the Queue is empty, signal any
+ # possible waiting thread
+ redef fun remove(e) do
+ mutex.lock
+ real_collection.remove(e)
+ if real_collection.is_empty then cond.signal
+ mutex.unlock
+ end
+
+ # Wait until the Queue is empty
+ redef fun is_empty do
+ mutex.lock
+ while not real_collection.is_empty do self.cond.wait(mutex)
+ mutex.unlock
+ return true
+ end
+end
+
+redef class Sys
+
+ # List of running actors
+ var active_actors = new ReverseBlockingQueue[Actor] is lazy
+
+ redef fun run do
+ super
+ # The program won't end until every actor is done
+ active_actors.is_empty
+ end
+end
--- /dev/null
+actors_*.nit
--- /dev/null
+# This file is part of NIT (http://www.nitlanguage.org).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Example implemented from "The computer Language Benchmarks Game" - Chameneos-Redux
+# http://benchmarksgame.alioth.debian.org/
+#
+# Complete description of the chameneos-redux :
+# https://benchmarksgame.alioth.debian.org/u64q/chameneosredux-description.html#chameneosredux
+module chameneosredux is no_warning("missing-doc")
+
+import actors
+
+class Creature
+ actor
+ var place: MeetingPlace
+ var color: Int
+ var id: Int
+ var count = 0
+ var samecount = 0
+
+ fun run do
+ loop
+ var p = place.meet(id, color)
+ if p == null then break
+ color = p.color
+ if p.sameid then samecount += 1
+ count += 1
+ end
+ end
+
+ fun to_string: String do return count.to_s + " " + numbers[samecount]
+end
+
+class Pair
+ var sameid: Bool
+ var color: Int
+end
+
+class MeetingPlace
+ var meetings_left: Int
+ var firstcolor: nullable Int
+ var firstid: Int = 0
+ var current: Future[Pair] is noinit
+
+ private var mutex = new Mutex
+
+ fun meet(id, c: Int): nullable Pair do
+ var new_pair = new Future[Pair]
+ mutex.lock
+ if meetings_left == 0 then
+ mutex.unlock
+ return null
+ else
+ if firstcolor == null then
+ firstcolor = c
+ firstid = id
+ current = new Future[Pair]
+ else
+ var color = complement(c, firstcolor.as(not null))
+ current.set_value(new Pair(id == firstid, color))
+ firstcolor = null
+ meetings_left -= 1
+ end
+ new_pair = current
+ end
+ mutex.unlock
+ return new_pair.join
+ end
+end
+
+redef class Sys
+ fun blue: Int do return 0
+ fun red: Int do return 1
+ fun yellow: Int do return 2
+ var numbers: Array[String] = ["zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"]
+ var colors: Array[String] = ["blue", "red", "yellow"]
+end
+
+fun complement(c, other: Int): Int do
+ if c == blue then
+ if other == blue then
+ return blue
+ else if other == red then
+ return yellow
+ else if other == yellow then
+ return red
+ end
+ else if c == red then
+ if other == blue then
+ return yellow
+ else if other == red then
+ return red
+ else if other == yellow then
+ return blue
+ end
+ else if c == yellow then
+ if other == blue then
+ return red
+ else if other == red then
+ return blue
+ else if other == yellow then
+ return yellow
+ end
+ end
+ abort
+end
+
+fun print_all_colors do
+ print_colors(blue, blue)
+ print_colors(blue, red)
+ print_colors(blue, yellow)
+ print_colors(red, blue)
+ print_colors(red, red)
+ print_colors(red, yellow)
+ print_colors(yellow, blue)
+ print_colors(yellow, red)
+ print_colors(yellow, yellow)
+end
+
+fun print_colors(c1, c2: Int) do
+ print colors[c1] + " + " + colors[c2] + " -> " + colors[complement(c1, c2)]
+end
+
+fun get_number(n: Int): String do
+ var str = ""
+ var nstr = n.to_s
+ for c in nstr do
+ str += " " + numbers[c.to_i]
+ end
+ return str
+end
+
+fun work(n, nb_colors : Int ) do
+ var place = new MeetingPlace(n)
+ var creatures = new Array[Creature]
+ for i in [0..nb_colors[ do
+ printn " " + colors[i % 3]
+ creatures[i] = new Creature(place, i % 3, i)
+ end
+ print ""
+
+ for c in creatures do c.async.run
+
+ active_actors.is_empty
+
+ var total = 0
+ for c in creatures do
+ print c.to_string
+ total += c.count
+ end
+
+ print get_number(total)
+ print ""
+
+ for c in creatures do
+ c.async.terminate
+ c.async.wait_termination
+ end
+end
+
+var n = 0
+if args.is_empty then
+ n = 600
+else
+ n = args[0].to_i
+end
+
+print_all_colors
+print ""
+
+work(n, 3)
+work(n, 10)
--- /dev/null
+file= chameneosredux
+
+default: threaded
+
+threaded:
+ nitc $(file).nit
+
+test:
+ ./$(file) 1000
+
+bm:
+ time ./$(file) 50000000
+
+clean:
+ rm $(file)
+ rm actors_$(file).nit
--- /dev/null
+# This file is part of NIT (http://www.nitlanguage.org).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Example implemented from "The computer Language Benchmarks Game" - Fannkuch-Redux
+# http://benchmarksgame.alioth.debian.org/
+#
+# Complete description of the fannkuch-redux :
+# https://benchmarksgame.alioth.debian.org/u64q/fannkuchredux-description.html#fannkuchredux
+module fannkuchredux is no_warning("missing-doc")
+
+import actors
+
+class FannkuchRedux
+ actor
+
+ var p: Array[Int] is noautoinit
+ var pp: Array[Int] is noautoinit
+ var count: Array[Int] is noautoinit
+
+ fun print_p do
+ for i in [0..p.length[ do printn p[i] + 1
+ print ""
+ end
+
+ fun first_permutation(idx: Int) do
+ for i in [0..p.length[ do p[i] = i
+
+ var i = count.length - 1
+ while i > 0 do
+ var d = idx / fact[i]
+ count[i] = d
+ idx = idx % fact[i]
+
+ p.copy_to(0, i+1, pp, 0)
+
+ for j in [0..i] do
+ if j + d <= i then
+ p[j] = pp[j+d]
+ else
+ p[j] = pp[j+d-i-1]
+ end
+ end
+ i -= 1
+ end
+ end
+
+ fun next_permutation do
+ var first = p[1]
+ p[1] = p[0]
+ p[0] = first
+
+ var i = 1
+ count[i] += 1
+ while count[i] > i do
+ count[i] = 0
+ i += 1
+
+ p[0] = p[1]
+ var next = p[0]
+
+ for j in [1..i[ do p[j] = p[j+1]
+ p[i] = first
+ first = next
+
+ count[i] += 1
+ end
+ end
+
+ fun count_flips: Int do
+ var flips = 1
+ var first = p[0]
+ if p[first] != 0 then
+ p.copy_to(0, pp.length, pp, 0)
+ loop
+
+ flips += 1
+ var lo = 1
+ var hi = first - 1
+ while lo < hi do
+ var t = pp[lo]
+ pp[lo] = pp[hi]
+ pp[hi] = t
+ lo += 1
+ hi -= 1
+ end
+ var t = pp[first]
+ pp[first] = first
+ first = t
+
+ if pp[first] == 0 then break
+ end
+ end
+ return flips
+ end
+
+ fun run_task(task: Int) do
+ var idx_min = task * chunk_sz
+ var idx_max = fact[n].min(idx_min + chunk_sz)
+
+ first_permutation(idx_min)
+
+ var maxflips = 1
+ var chk_sum = 0
+
+ var i = idx_min
+ loop
+ if p[0] != 0 then
+ var flips = count_flips
+ maxflips = maxflips.max(flips)
+ if i % 2 == 0 then
+ chk_sum += flips
+ else
+ chk_sum += -flips
+ end
+ end
+
+ i += 1
+ if i == idx_max then break
+ next_permutation
+ end
+
+ max_flips[task] = maxflips
+ chk_sums[task] = chk_sum
+ end
+
+ fun run do
+ p = new Array[Int].with_capacity(n)
+ for i in [0..n[ do p.add(0)
+ pp = new Array[Int].with_capacity(n)
+ for i in [0..n[ do pp.add(0)
+ count = new Array[Int].with_capacity(n)
+ for i in [0..n[ do count.add(0)
+
+ var task = 0
+ loop
+ task = task_id.get_and_increment
+ if task < n_tasks then
+ run_task(task)
+ else
+ break
+ end
+ end
+ end
+end
+
+redef class Sys
+ var n_chunks = 150
+ var chunk_sz: Int is noautoinit
+ var n_tasks: Int is noautoinit
+ var n: Int is noautoinit
+ var fact: Array[Int] is noautoinit
+ var max_flips: Array[Int] is noautoinit
+ var chk_sums: Array[Int] is noautoinit
+ var task_id = new AtomicInt(0)
+ var nb_actors = 8
+end
+
+fun print_result(n, res, chk: Int) do
+ print chk.to_s + "\nPfannfuchen(" + n.to_s + ") = " + res.to_s
+
+end
+
+if args.is_empty then
+ n = 7
+else
+ n = args[0].to_i
+end
+
+fact = new Array[Int].with_capacity(n+1)
+fact[0] = 1
+for i in [1..n] do fact.add(fact[i-1] * i)
+
+chunk_sz = (fact[n] + n_chunks - 1) / n_chunks
+n_tasks = (fact[n] + chunk_sz - 1) / chunk_sz
+max_flips = new Array[Int].with_capacity(n_tasks)
+for i in [0..n_tasks[ do max_flips.add(0)
+chk_sums = new Array[Int].with_capacity(n_tasks)
+for i in [0..n_tasks[ do chk_sums.add(0)
+
+var actors = new Array[FannkuchRedux].with_capacity(8)
+for i in [0..nb_actors[ do
+ var a = new FannkuchRedux
+ actors.add(a)
+ a.async.run
+end
+
+for i in [0..nb_actors[ do
+ actors[i].async.terminate
+ actors[i].async.wait_termination
+end
+
+var res = 0
+for i in max_flips do res = res.max(i)
+
+var chk = 0
+for i in chk_sums do
+ chk += i
+end
+
+print_result(n, res, chk)
--- /dev/null
+file= fannkuchredux
+
+default: threaded
+
+threaded:
+ nitc $(file).nit
+
+test:
+ ./$(file) 7
+bm:
+ time ./$(file) 12
+
+clean:
+ rm $(file)
+ rm actors_$(file).nit
--- /dev/null
+file= mandelbrot
+
+default: threaded
+
+threaded:
+ nitc $(file).nit
+
+test:
+ ./$(file) 200
+
+bm:
+ time ./$(file) 16000
+
+clean:
+ rm $(file)
+ rm actors_$(file).nit
--- /dev/null
+# This file is part of NIT (http://www.nitlanguage.org).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Example implemented from "The computer Language Benchmarks Game" - Mandelbrot
+# http://benchmarksgame.alioth.debian.org/
+#
+# Complete description of mandelbrot :
+# https://benchmarksgame.alioth.debian.org/u64q/mandelbrot-description.html#mandelbrot
+module mandelbrot is no_warning("missing-doc")
+
+import actors
+
+class Worker
+ actor
+
+ fun get_byte(x, y: Int): Int do
+ var res = 0
+ for i in [0..8[.step(2) do
+ var zr1 = crb[x + i]
+ var zi1 = cib[y]
+
+ var zr2 = crb [x + i + 1]
+ var zi2 = cib[y]
+
+ var b = 0
+ for j in [0..49[ do
+ var nzr1 = zr1 * zr1 - zi1 * zi1 + crb[x+i]
+ var nzi1 = zr1 * zi1 + zr1 * zi1 + cib[y]
+ zr1 = nzr1
+ zi1 = nzi1
+
+ var nzr2 = zr2 * zr2 - zi2 * zi2 + crb[x + i + 1]
+ var nzi2 = zr2 * zi2 + zr2 * zi2 + cib[y]
+ zr2 = nzr2
+ zi2 = nzi2
+
+ if zr1 * zr1 + zi1 * zi1 > 4.0 then b = b | 2
+ if zr2 * zr2 + zi2 * zi2 > 4.0 then b = b | 1
+ if b == 3 then break
+ end
+ res = (res << 2) + b
+ end
+ return res ^-1
+ end
+
+ fun put_line(y: Int, line: Array[Byte]) do
+ for i in [0..line.length[ do line[i] = get_byte(i * 8, y).to_b
+ end
+
+ fun work do
+ var line = 0
+ loop
+ line = atomic.get_and_increment
+ if line < n then
+ put_line(line, data[line])
+ else
+ break
+ end
+ end
+ end
+end
+
+redef class Sys
+ var n = 0
+ var inv_n: Float is noautoinit
+ var data: Array[Array[Byte]] is noautoinit
+ var crb: Array[Float] is noautoinit
+ var cib: Array[Float] is noautoinit
+ var atomic = new AtomicInt(0)
+ var nb_threads = 8
+end
+
+if args.is_empty then
+ n = 200
+else
+ n = args[0].to_i
+end
+
+sys.crb = new Array[Float].with_capacity(n + 7)
+sys.cib = new Array[Float].with_capacity(n + 7)
+sys.inv_n = 2.0 / n.to_f
+for i in [0..n[ do
+ sys.cib[i] = i.to_f * inv_n - 1.0
+ sys.crb[i] = i.to_f * inv_n - 1.5
+end
+sys.data = new Array[Array[Byte]].with_capacity(n)
+for i in [0..n[ do sys.data[i] = new Array[Byte].filled_with(0.to_b, (n + 7) / 8)
+
+# Parallel Approach
+var actors = new Array[Worker]
+for i in [0..nb_threads[ do
+ var a = new Worker
+ actors.add(a)
+ a.async.work
+end
+
+for a in actors do
+ a.async.terminate
+ a.async.wait_termination
+end
+
+var filename = "WRITE".environ
+if filename == "" then filename = "out"
+var output = new FileWriter.open(filename)
+output.write_bytes("P4\n{n} {n}\n".to_bytes)
+for i in [0..n[ do
+ var length = data[i].length
+ for j in [0..length[ do output.write_byte(data[i][j])
+end
+output.close
--- /dev/null
+default: compile
+
+compile:
+ nitc simple.nit
+
+run:
+ ./simple
+
+clean:
+ rm actors_simple.nit
+ rm simple
--- /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.
+
+# A very simple example of the actor model
+module simple
+
+import actors
+
+
+# A class anotated with `actor`
+# It automatically gets the `async` property to make asynchronous calls on it
+class A
+ actor
+
+ # Prints "foo"
+ fun foo do print "foo"
+
+ # Returns i^2
+ fun bar(i : Int): Int do return i * i
+end
+
+# Create a new instance of A
+var a = new A
+
+# Make an asynchronous call
+a.async.foo
+
+# Make a synchronous call
+a.foo
+
+# Make an asynchronous call
+# Which return a `Future[Int]` instead of `Int`
+var r = a.async.bar(5)
+
+# Retrieve the value of the future
+print r.join
+
+# Make a Synchronous call
+print a.bar(5)
--- /dev/null
+file= thread_ring
+
+default: threaded
+
+threaded:
+ nitc $(file).nit
+
+test:
+ ./$(file) 1000
+bm:
+ time ./$(file) 50000000
+
+clean:
+ rm $(file)
+ rm actors_$(file).nit
--- /dev/null
+# This file is part of NIT (http://www.nitlanguage.org).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Example implemented from "The computer Language Benchmarks Game" - Thread-Ring
+# http://benchmarksgame.alioth.debian.org/
+#
+# Complete description of the thread-ring :
+# http://benchmarksgame.alioth.debian.org/u64q/threadring-description.html#threadring
+module thread_ring
+
+import actors
+
+# One Actor, who will receive the token
+class ThreadRing
+ actor
+
+ # Identification of `self`
+ var id: Int
+
+ # The next Actor to which `self` send the token
+ var next: ThreadRing is noinit
+
+ # Receive a token, then send it to `next` unless its value is `0`
+ fun send_token(message: Int) do
+ if message == 0 then
+ print id
+ end
+ if message >= 1 then
+ next.async.send_token(message - 1)
+ end
+ end
+end
+
+var first = new ThreadRing(1)
+var current = new ThreadRing(2)
+first.next = current
+for i in [3..503] do
+ var t = new ThreadRing(i)
+ current.next = t
+ current = t
+end
+current.next = first
+if args.is_empty then
+ first.send_token(1000)
+else
+ first.send_token(args[0].to_i)
+end
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
-#ifndef _WIN32
+ #include <sys/types.h>
+
+#ifdef _WIN32
+ #include <windows.h>
+ #include <fcntl.h>
+#else
#include <sys/wait.h>
#endif
-`}
-
-in "C Header" `{
- #include <sys/types.h>
- // FIXME this should be in the "C" block when bug on module blocks is fixed
- // or, even better, replace the C structure by a Nit object.
typedef struct se_exec_data se_exec_data_t;
struct se_exec_data {
+#ifdef _WIN32
+ HANDLE h_process;
+ HANDLE h_thread;
+#endif
pid_t id;
int running;
int status;
# Internal code to handle execution
protected fun execute
do
- # Pass the arguments as a big C string where elements are separated with '\0'
- var args = new FlatBuffer
- var l = 1 # Number of elements in args
- args.append(command)
var arguments = self.arguments
- if arguments != null then
- for a in arguments do
- args.add('\0')
- args.append(a)
+
+ var args = new FlatBuffer
+ var argc = 1
+
+ if not is_windows then
+ # Pass the arguments as a big C string where elements are separated with '\0'
+ args.append command
+ if arguments != null then
+ for a in arguments do
+ args.add '\0'
+ args.append a
+ end
+ argc += arguments.length
+ end
+ else
+ # Combine the program and args in a single string
+ assert not command.chars.has('"')
+ args = new FlatBuffer
+
+ args.add '"'
+ args.append command
+ args.add '"'
+
+ if arguments != null then
+ for a in arguments do
+ args.append " \""
+ args.append a.replace('"', "\\\"")
+ args.add '"'
+ end
end
- l += arguments.length
end
- data = basic_exec_execute(command.to_cstring, args.to_s.to_cstring, l, pipeflags)
+
+ data = basic_exec_execute(command.to_cstring, args.to_s.to_cstring, argc, pipeflags)
+ assert not data.address_is_null else print_error "Internal error executing: {command}"
end
private var data: NativeProcess
+
private fun basic_exec_execute(prog, args: CString, argc: Int, pipeflag: Int): NativeProcess `{
#ifdef _WIN32
- // FIXME use a higher level abstraction to support WIN32
- return -1;
+ SECURITY_ATTRIBUTES sec_attr;
+ sec_attr.nLength = sizeof(SECURITY_ATTRIBUTES);
+ sec_attr.bInheritHandle = TRUE;
+ sec_attr.lpSecurityDescriptor = NULL;
+
+ STARTUPINFO start_info;
+ ZeroMemory(&start_info, sizeof(STARTUPINFO));
+ start_info.cb = sizeof(STARTUPINFO);
+ start_info.dwFlags = STARTF_USESTDHANDLES;
+
+ HANDLE in_fd[2];
+ HANDLE out_fd[2];
+ HANDLE err_fd[2];
+
+ se_exec_data_t *result = (se_exec_data_t*)malloc(sizeof(se_exec_data_t));
+
+ // Redirect stdin?
+ if (pipeflag & 1) {
+ if (!CreatePipe(&in_fd[0], &in_fd[1], &sec_attr, 0)) {
+ return NULL;
+ }
+ start_info.hStdInput = in_fd[0];
+ result->in_fd = _open_osfhandle((intptr_t)in_fd[1], _O_APPEND);
+ if ( !SetHandleInformation(in_fd[1], HANDLE_FLAG_INHERIT, 0) )
+ return NULL;
+ } else {
+ start_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
+ result->in_fd = -1;
+ }
+
+ // Redirect stdout?
+ if (pipeflag & 2) {
+ if (!CreatePipe(&out_fd[0], &out_fd[1], &sec_attr, 0)) {
+ return NULL;
+ }
+ start_info.hStdOutput = out_fd[1];
+ result->out_fd = _open_osfhandle((intptr_t)out_fd[0], _O_RDONLY);
+ if ( !SetHandleInformation(out_fd[0], HANDLE_FLAG_INHERIT, 0) )
+ return NULL;
+ } else {
+ start_info.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
+ result->out_fd = -1;
+ }
+
+ // Redirect stderr?
+ if (pipeflag & 4) {
+ if (!CreatePipe(&err_fd[0], &err_fd[1], &sec_attr, 0)) {
+ return NULL;
+ }
+ start_info.hStdError = err_fd[1];
+ result->err_fd = _open_osfhandle((intptr_t)err_fd[0], _O_RDONLY);
+ if ( !SetHandleInformation(err_fd[0], HANDLE_FLAG_INHERIT, 0) )
+ return NULL;
+ } else {
+ start_info.hStdError = GetStdHandle(STD_ERROR_HANDLE);
+ result->err_fd = -1;
+ }
+
+ PROCESS_INFORMATION proc_info;
+ ZeroMemory(&proc_info, sizeof(PROCESS_INFORMATION));
+
+ BOOL created = CreateProcess(NULL,
+ args, // command line
+ NULL, // process security attributes
+ NULL, // primary thread security attributes
+ TRUE, // inherit handles
+ 0, // creation flags
+ NULL, // use parent's environment
+ NULL, // use parent's current directory
+ &start_info,
+ &proc_info);
+
+ // Error?
+ if (!created) {
+ result->running = 0;
+ result->status = 127;
+
+ // Close subprocess pipes
+ if (pipeflag & 1) CloseHandle(in_fd[1]);
+ if (pipeflag & 2) CloseHandle(out_fd[0]);
+ if (pipeflag & 3) CloseHandle(err_fd[0]);
+ } else {
+ result->h_process = proc_info.hProcess;
+ result->h_thread = proc_info.hThread;
+ result->id = GetProcessId(proc_info.hProcess);
+ result->running = 1;
+ }
+
+ return result;
#else
se_exec_data_t* result = NULL;
int id;
private extern class NativeProcess `{ se_exec_data_t* `}
- fun id: Int `{ return self->id; `}
+ fun id: Int `{ return (long)self->id; `}
fun status: Int `{ return self->status; `}
fun in_fd: Int `{ return self->in_fd; `}
fun out_fd: Int `{ return self->out_fd; `}
fun err_fd: Int `{ return self->err_fd; `}
fun is_finished: Bool `{
-#ifdef _WIN32
- // FIXME use a higher level abstraction to support WIN32
- return 0;
-#else
int result = (int)0;
- int status;
if (self->running) {
+#ifdef _WIN32
+ if (WaitForSingleObject(self->h_process, 0) == 0) {
+ /* child is finished */
+ result = 1;
+
+ long unsigned int status;
+ GetExitCodeProcess(self->h_process, &status);
+ self->running = 0;
+ self->status = (int)status;
+
+ CloseHandle(self->h_process);
+ CloseHandle(self->h_thread);
+ }
+#else
+ int status;
int id = waitpid(self->id, &status, WNOHANG);
if (id != 0) {
/* child is finished */
self->status = WEXITSTATUS(status);
self->running = 0;
}
+#endif
}
else{
result = (int)1;
}
return result;
-#endif
`}
fun wait `{
-#ifndef _WIN32
- // FIXME use a higher level abstraction to support WIN32
+#ifdef _WIN32
+ long unsigned int status;
+ if (self->running) {
+ WaitForSingleObject(self->h_process, INFINITE);
+ GetExitCodeProcess(self->h_process, &status);
+
+ CloseHandle(self->h_process);
+ CloseHandle(self->h_thread);
+
+ self->status = (int)status;
+ self->running = 0;
+ }
+#else
int status;
if (self->running) {
waitpid(self->id, &status, 0);
`}
fun io_write(buf: CString, from, len: Int): Int `{
- return fwrite(buf+from, 1, len, self);
+ size_t res = fwrite(buf+from, 1, len, self);
+#ifdef _WIN32
+ // Force flushing buffer because end of line does not trigger a flush
+ fflush(self);
+#endif
+ return (long)res;
`}
fun write_byte(value: Byte): Int `{
var array = get("/repos/{repo.full_name}/branches")
var res = new Array[Branch]
if not array isa JsonArray then return res
- return deserialize(array.to_json).as(Array[Branch])
+ var deser = deserialize(array.to_json)
+ if deser isa Array[Object] then return res # empty array
+ return deser.as(Array[Branch])
end
# List of issues associated with their ids.
# Provides access to [Github user data](https://developer.github.com/v3/users/).
# Should be accessed from `GithubAPI::load_user`.
class User
- super GithubEntity
+ super GitUser
serialize
# Github login.
var parents: nullable Array[Commit] = null is writable
# Author of the commit.
- var author: nullable User is writable
+ var author: nullable GitUser is writable
# Committer of the commit.
- var committer: nullable User is writable
+ var committer: nullable GitUser is writable
# Authoring date as String.
var author_date: nullable String is writable
# Commit message.
var message: nullable String is writable
+
+ # Git commit representation linked to this commit.
+ var commit: nullable GitCommit
+end
+
+# A Git Commit representation
+class GitCommit
+ super GithubEntity
+ serialize
+
+ # Commit SHA.
+ var sha: nullable String is writable
+
+ # Parent commits of `self`.
+ var parents: nullable Array[GitCommit] = null is writable
+
+ # Author of the commit.
+ var author: nullable GitUser is writable
+
+ # Committer of the commit.
+ var committer: nullable GitUser is writable
+
+ # Commit message.
+ var message: nullable String is writable
+end
+
+# Git user authoring data
+class GitUser
+ super GithubEntity
+ serialize
+
+ # Authoring date.
+ var date: nullable String = null is writable
+
+ # Authoring date as ISODate.
+ fun iso_date: nullable ISODate do
+ var date = self.date
+ if date == null then return null
+ return new ISODate.from_string(date)
+ end
end
# A Github issue.
var closed_by: nullable User is writable
# Is this issue linked to a pull request?
- var is_pull_request: Bool = false is writable, noserialize
+ var is_pull_request: Bool = false is writable
end
# A Github pull request.
serialize
# The milestone id on Github.
- var number: Int is writable
+ var number: nullable Int = null is writable
# Milestone title.
var title: String is writable
# Milestone long description.
- var description: String is writable
+ var description: nullable String is writable
# Count of opened issues linked to this milestone.
- var open_issues: Int is writable
+ var open_issues: nullable Int = null is writable
# Count of closed issues linked to this milestone.
- var closed_issues: Int is writable
+ var closed_issues: nullable Int = null is writable
# Milestone state.
- var state: String is writable
+ var state: nullable String is writable
# Creation time as String.
- var created_at: String is writable
+ var created_at: nullable String is writable
# Creation time as ISODate.
- fun iso_created_at: nullable ISODate do return new ISODate.from_string(created_at)
+ fun iso_created_at: nullable ISODate do
+ var created_at = self.created_at
+ if created_at == null then return null
+ return new ISODate.from_string(created_at)
+ end
# User that created this milestone.
- var creator: User is writable
+ var creator: nullable User is writable
# Due time as String (if any).
var due_on: nullable String is writable
super JsonDeserializer
redef fun class_name_heuristic(json_object) do
- if json_object.has_key("login") or json_object.has_key("email") then
+ if json_object.has_key("login") then
return "User"
else if json_object.has_key("full_name") then
return "Repo"
return "Branch"
else if json_object.has_key("sha") and json_object.has_key("ref") then
return "PullRef"
- else if json_object.has_key("sha") or (json_object.has_key("id") and json_object.has_key("tree_id")) then
+ else if (json_object.has_key("sha") and json_object.has_key("commit")) or (json_object.has_key("id") and json_object.has_key("tree_id")) then
return "Commit"
+ else if json_object.has_key("sha") and json_object.has_key("tree") then
+ return "GitCommit"
+ else if json_object.has_key("name") and json_object.has_key("date") then
+ return "GitUser"
else if json_object.has_key("number") and json_object.has_key("patch_url") then
return "PullRequest"
else if json_object.has_key("open_issues") and json_object.has_key("closed_issues") then
issue.is_pull_request = true
end
return issue
+ else if name == "Commit" then
+ var commit = super.as(Commit)
+ var git_commit = commit.commit
+ if git_commit != null then commit.message = git_commit.message
+ return commit
end
return super
end
if response isa CurlResponseSuccess then
var obj = response.body_str.parse_json
if obj isa JsonObject then
- if obj.keys.has("message") then
+ if obj.keys.has("message") and obj.keys.has("documentation_url") then
print "Message from Github API: {obj["message"] or else ""}"
print "Requested URI: {uri}"
abort
if response isa CurlResponseSuccess then
var obj = response.body_str.parse_json
if obj isa JsonObject then
- if obj.keys.has("message") then
+ if obj.keys.has("message") and obj.keys.has("documentation_url") then
var title = "GithubAPIError"
var msg = obj["message"].as(not null).to_s
var err = new GithubError(msg, title)
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2016 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.
+
+module loader
+
+import config
+import github::wallet
+import github::events
+import popcorn::pop_repos
+import popcorn::pop_logging
+
+# Loader configuration file
+class LoaderConfig
+ super IniConfig
+
+ redef var default_config_file = "loader.ini"
+
+ # Default database host string for MongoDb
+ var default_db_host = "mongodb://localhost:27017/"
+
+ # Default database hostname
+ var default_db_name = "github_loader"
+
+ # MongoDb host name
+ var opt_db_host = new OptionString("MongoDb host", "--db-host")
+
+ # MongoDb database name
+ var opt_db_name = new OptionString("MongoDb database name", "--db-name")
+
+ # --verbose
+ var opt_verbose = new OptionCount("Verbosity level", "-v", "--verbose")
+
+ # --no-colors
+ var opt_no_colors = new OptionBool("Do not use colors in output", "--no-colors")
+
+ # --tokens
+ var opt_tokens = new OptionArray("Token list", "--tokens")
+
+ # --show-wallet
+ var opt_show_wallet = new OptionBool("Show wallet status", "--show-wallet")
+
+ # --show-jobs
+ var opt_show_jobs = new OptionBool("Show jobs status", "--show-jobs")
+
+ # --no-branches
+ var opt_no_branches = new OptionBool("Do not load branches", "--no-branches")
+
+ # --no-commits
+ var opt_no_commits = new OptionBool("Do not load commits from default branch", "--no-commits")
+
+ # --no-issues
+ var opt_no_issues = new OptionBool("Do not load issues", "--no-issues")
+
+ # --no-comments
+ var opt_no_comments = new OptionBool("Do not load issue comments", "--no-comments")
+
+ # --no-events
+ var opt_no_events = new OptionBool("Do not load issues events", "--no-events")
+
+ # --from
+ var opt_start = new OptionInt("Start loading issues from a number", 0, "--from")
+
+ # --clear
+ var opt_clear = new OptionBool("Clear job for given repo name", "--clear")
+
+ init do
+ super
+ tool_description = "Usage: loader <repo_name>"
+ add_option(opt_db_host, opt_db_name)
+ add_option(opt_tokens, opt_show_wallet)
+ add_option(opt_verbose, opt_no_colors)
+ add_option(opt_show_jobs, opt_no_commits, opt_no_issues, opt_no_comments, opt_no_events)
+ add_option(opt_start, opt_clear)
+ end
+
+ # MongoDB server used for data persistence
+ fun db_host: String do
+ return opt_db_host.value or else ini["db.host"] or else default_db_host
+ end
+
+ # MongoDB DB used for data persistence
+ fun db_name: String do
+ return opt_db_name.value or else ini["db.name"] or else default_db_name
+ end
+
+ # Mongo db client
+ var client = new MongoClient(db_host) is lazy
+
+ # Mongo db instance
+ var db: MongoDb = client.database(db_name) is lazy
+
+ # Github tokens used to access data.
+ var tokens: Array[String] is lazy do
+ var arr = opt_tokens.value
+ if arr.is_empty then
+ var iarr = ini.at("tokens")
+ if iarr != null then arr = iarr.values.to_a
+ end
+ return arr or else new Array[String]
+ end
+
+ # Github tokens wallet\13
+ var wallet: GithubWallet is lazy do
+ var wallet = new GithubWallet.from_tokens(tokens)
+ wallet.no_colors = no_colors
+ return wallet
+ end
+
+ # Use colors in console display
+ fun no_colors: Bool do
+ if opt_no_colors.value then return true
+ return ini["loader.no_colors"] == "true"
+ end
+
+ # Verbosity level (the higher the more verbose)
+ fun verbose_level: Int do
+ var opt = opt_start.value
+ if opt > 0 then return opt
+ var v = ini["loader.verbose"]
+ if v != null then return v.to_i
+ return 4
+ end
+
+ # Logger used to print things
+ var logger: ConsoleLog is lazy do
+ var logger = new ConsoleLog
+ logger.level = verbose_level
+ return logger
+ end
+
+ # Should we avoid loading branches?
+ fun no_branches: Bool do
+ if opt_no_branches.value then return true
+ return ini["loader.no_branches"] == "true"
+ end
+
+ # Should we avoid loading commits?
+ fun no_commits: Bool do
+ if opt_no_commits.value then return true
+ return ini["loader.no_commits"] == "true"
+ end
+
+ # Should we avoid loading issues?
+ fun no_issues: Bool do
+ if opt_no_issues.value then return true
+ return ini["loader.no_issues"] == "true"
+ end
+
+ # Should we avoid loading issue comments?
+ fun no_comments: Bool do
+ if opt_no_comments.value then return true
+ return ini["loader.no_comments"] == "true"
+ end
+
+ # Should we avoid loading events?
+ fun no_events: Bool do
+ if opt_no_events.value then return true
+ return ini["loader.no_events"] == "true"
+ end
+
+ # At which issue number should we start?
+ fun start_from_issue: Int do
+ var opt = opt_start.value
+ if opt > 0 then return opt
+ var v = ini["loader.start"]
+ if v != null then return v.to_i
+ return 1
+ end
+end
+
+redef class GithubWallet
+ redef fun api do
+ var api = super
+ api.enable_cache = true
+ return api
+ end
+end
+
+class Loader
+
+ var config = new LoaderConfig
+
+ # Jobs repository
+ var jobs: LoaderJobRepo is lazy do
+ return new LoaderJobRepo(config.db.collection("loader_status"))
+ end
+
+ var repos: RepoRepo is lazy do
+ return new RepoRepo(config.db.collection("repos"))
+ end
+
+ var branches: BranchRepo is lazy do
+ return new BranchRepo(config.db.collection("branches"))
+ end
+
+ var commits: CommitRepo is lazy do
+ return new CommitRepo(config.db.collection("commits"))
+ end
+
+ var issues: IssueRepo is lazy do
+ return new IssueRepo(config.db.collection("issues"))
+ end
+
+ var pulls: PullRequestRepo is lazy do
+ return new PullRequestRepo(config.db.collection("pull_requests"))
+ end
+
+ var issue_comments: IssueCommentRepo is lazy do
+ return new IssueCommentRepo(config.db.collection("issue_comments"))
+ end
+
+ var issue_events: IssueEventRepo is lazy do
+ return new IssueEventRepo(config.db.collection("issue_events"))
+ end
+
+ fun start(repo_full_name: String) do
+ var job = jobs.find_by_id(repo_full_name)
+ if job == null then
+ log.info "Creating new job for `{repo_full_name}`"
+ job = add_job(repo_full_name)
+ else
+ log.info "Resuming pending job for `{repo_full_name}`"
+ end
+ print "Load history for {job}..."
+ load_branches(job)
+ load_issues(job)
+ finish_job(job)
+ end
+
+ fun remove(repo_full_name: String) do
+ var job = jobs.find_by_id(repo_full_name)
+ if job == null then
+ log.info "No job found for `{repo_full_name}`"
+ else
+ jobs.remove_by_id(repo_full_name)
+ log.info "Deleted job for `{repo_full_name}`"
+ end
+ end
+
+ # Show wallet status
+ fun show_wallet do config.wallet.show_status
+
+ # Show jobs status
+ fun show_jobs do
+ var jobs = jobs.find_all
+ print "{jobs.length} jobs pending..."
+ for job in jobs do
+ print " * {job}"
+ end
+ print "\nUse `loader <job> to start a new or resume a pending one"
+ end
+
+ # Add a new job
+ fun add_job(repo_full_name: String): LoaderJob do
+ var repo = config.wallet.api.load_repo(repo_full_name)
+ assert repo != null else
+ error "Repository `{repo_full_name}` not found"
+ end
+ repos.save repo
+ var job = new LoaderJob(repo, config.start_from_issue)
+ jobs.save job
+ return job
+ end
+
+ # Finish a job
+ fun finish_job(job: LoaderJob) do
+ print "Finished job {job}"
+ jobs.remove_by_id(job.id)
+ end
+
+ fun load_branches(job: LoaderJob) do
+ if config.no_branches then return
+
+ var api = config.wallet.api
+ var repo = job.repo
+ for branch in api.load_repo_branches(repo) do
+ branch.repo = repo
+ branches.save branch
+ load_commits(job, branch)
+ end
+ end
+
+ fun load_commits(job: LoaderJob, branch: Branch) do
+ if config.no_commits then return
+ load_commit(job, branch.commit.sha)
+ end
+
+ fun load_commit(job: LoaderJob, commit_sha: String) do
+ if commits.find_by_id(commit_sha) != null then return
+ var api = config.wallet.api
+ var commit = api.load_commit(job.repo, commit_sha)
+ # print commit or else "NULL"
+ if commit == null then return
+ var message = commit.message or else "no message"
+ log.info "Load commit {commit_sha}: {message.split("\n").first}"
+ commit.repo = job.repo
+ commits.save commit
+ var parents = commit.parents
+ if parents == null then return
+ for parent in parents do
+ load_commit(job, parent.sha)
+ end
+ end
+
+ # Load game for `repo_name`.
+ fun load_issues(job: LoaderJob) do
+ if config.no_issues then return
+
+ var i = job.last_issue
+ var last_issue = load_last_issue(job)
+ if last_issue != null then
+ while i <= last_issue.number do
+ load_issue(job, i)
+ job.last_issue = i
+ jobs.save job
+ i += 1
+ end
+ end
+ end
+
+ # Load the `repo` last issue or abort.
+ private fun load_last_issue(job: LoaderJob): nullable Issue do
+ var api = config.wallet.api
+ return api.load_repo_last_issue(job.repo)
+ end
+
+ # Load an issue or abort.
+ private fun load_issue(job: LoaderJob, issue_number: Int) do
+ if issues.find_by_id("{job.repo.mongo_id}/{issue_number}") != null then return
+
+ var api = config.wallet.api
+ var issue = api.load_issue(job.repo, issue_number)
+ assert issue != null else
+ check_error(api, "Issue #{issue_number} not found")
+ end
+ if issue.is_pull_request then
+ load_pull(job, issue)
+ else
+ log.info "Load issue #{issue.number}: {issue.title.split("\n").first}"
+ issue.repo = job.repo
+ issues.save issue
+ load_issue_events(job, issue)
+ end
+ load_issue_comments(job, issue)
+ end
+
+ # Load issue comments.
+ private fun load_issue_comments(job: LoaderJob, issue: Issue) do
+ if config.no_comments then return
+ var api = config.wallet.api
+ for comment in api.load_issue_comments(job.repo, issue) do
+ comment.repo = job.repo
+ issue_comments.save comment
+ end
+ end
+
+ # Load issue events.
+ private fun load_issue_events(job: LoaderJob, issue: Issue) do
+ if config.no_events then return
+
+ var api = config.wallet.api
+ for event in api.load_issue_events(job.repo, issue) do
+ event.repo = job.repo
+ issue_events.save event
+ end
+ end
+
+ # Load a pull request or abort.
+ private fun load_pull(job: LoaderJob, issue: Issue): PullRequest do
+ var api = config.wallet.api
+ var pr = api.load_pull(job.repo, issue.number)
+ assert pr != null else
+ check_error(api, "Pull request #{issue.number} not found")
+ end
+ log.info "Load pull request #{issue.number}: {pr.title.split("\n").first}"
+ pr.repo = job.repo
+ pulls.save pr
+ load_pull_events(job, pr)
+ return pr
+ end
+
+ # Load pull events.
+ private fun load_pull_events(job: LoaderJob, pull: PullRequest) do
+ if config.no_events then return
+
+ var api = config.wallet.api
+ for event in api.load_issue_events(job.repo, pull) do
+ event.repo = job.repo
+ issue_events.save event
+ end
+ end
+
+ # Check if the API is in error state then abort
+ fun check_error(api: GithubAPI, message: nullable String) do
+ var err = api.last_error
+ if err != null then
+ error message or else err.message
+ end
+ end
+
+ # Logger shortcut
+ fun log: ConsoleLog do return config.logger
+
+ # Display a error and exit
+ fun error(msg: String) do
+ log.error "Error: {msg}"
+ exit 1
+ end
+end
+
+# Loader status by repo
+class LoaderJob
+ super RepoObject
+ serialize
+
+ # Repo this status is about
+ var repo: Repo
+
+ # Primary key: the repo id
+ redef var id is lazy, serialize_as("_id") do return repo.full_name
+
+ # Last issue loaded
+ var last_issue: Int
+end
+
+# Loader status repository
+class LoaderJobRepo
+ super MongoRepository[LoaderJob]
+end
+
+class RepoEntity
+ serialize
+
+ var repo: nullable Repo = null is writable
+end
+
+redef class Repo
+ serialize
+
+ var mongo_id: String is lazy, serialize_as("_id") do return full_name
+end
+
+class RepoRepo
+ super MongoRepository[Repo]
+end
+
+redef class Branch
+ super RepoEntity
+ serialize
+
+ var mongo_id: String is lazy, serialize_as("_id") do
+ var repo = self.repo
+ if repo == null then return name
+ return "{repo.mongo_id}/{name}"
+ end
+end
+
+class BranchRepo
+ super MongoRepository[Branch]
+
+ fun find_by_repo(repo: Repo): Array[Branch] do
+ return find_all((new MongoMatch).eq("repo.full_name", repo.full_name))
+ end
+end
+
+redef class Commit
+ super RepoEntity
+ serialize
+
+ var mongo_id: String is lazy, serialize_as("_id") do return sha
+end
+
+class CommitRepo
+ super MongoRepository[Commit]
+
+ fun find_by_repo(repo: Repo): Array[Commit] do
+ return find_all((new MongoMatch).eq("repo.full_name", repo.full_name))
+ end
+end
+
+redef class Issue
+ super RepoEntity
+ serialize
+
+ var mongo_id: String is lazy, serialize_as("_id") do
+ var repo = self.repo
+ if repo == null then return number.to_s
+ return "{repo.mongo_id}/{number}"
+ end
+end
+
+class IssueRepo
+ super MongoRepository[Issue]
+
+ fun find_by_repo(repo: Repo): Array[Issue] do
+ return find_all((new MongoMatch).eq("repo.full_name", repo.full_name))
+ end
+end
+
+class PullRequestRepo
+ super MongoRepository[PullRequest]
+
+ fun find_by_repo(repo: Repo): Array[Issue] do
+ return find_all((new MongoMatch).eq("repo.full_name", repo.full_name))
+ end
+end
+
+redef class IssueComment
+ super RepoEntity
+ serialize
+
+ var mongo_id: String is lazy, serialize_as("_id") do return id.to_s
+end
+
+class IssueCommentRepo
+ super MongoRepository[IssueComment]
+
+ fun find_by_repo(repo: Repo): Array[IssueComment] do
+ return find_all((new MongoMatch).eq("repo.full_name", repo.full_name))
+ end
+end
+
+redef class IssueEvent
+ super RepoEntity
+ serialize
+
+ var mongo_id: String is lazy, serialize_as("_id") do return id.to_s
+end
+
+class IssueEventRepo
+ super MongoRepository[IssueEvent]
+
+ fun find_by_repo(repo: Repo): Array[IssueEvent] do
+ return find_all((new MongoMatch).eq("repo.full_name", repo.full_name))
+ end
+end
+
+# Init options
+var loader = new Loader
+loader.config.parse_options(args)
+
+# TODO TMP
+loader.jobs.clear
+loader.repos.clear
+loader.branches.clear
+loader.commits.clear
+loader.issues.clear
+loader.pulls.clear
+loader.issue_comments.clear
+loader.issue_events.clear
+
+if loader.config.help then
+ loader.config.usage
+ exit 0
+end
+
+if loader.config.opt_show_wallet.value then
+ loader.show_wallet
+end
+
+var args = loader.config.args
+if loader.config.opt_show_jobs.value or args.is_empty then
+ loader.show_jobs
+end
+
+if args.is_empty then return
+
+if loader.config.opt_clear.value then
+ loader.remove args.first
+else
+ loader.start args.first
+
+ var repo = loader.config.wallet.api.load_repo(args.first)
+ if repo == null then return
+ print "Loaded"
+ print "* {if loader.repos.find_by_id(args.first) != null then 1 else 0} repos"
+ print "* {loader.branches.find_by_repo(repo).length} branches"
+ print "* {loader.commits.find_by_repo(repo).length} commits"
+ print "* {loader.issues.find_by_repo(repo).length} issues"
+ print "* {loader.pulls.find_by_repo(repo).length} pulls"
+ print "* {loader.issue_comments.find_by_repo(repo).length} comments"
+ print "* {loader.issue_events.find_by_repo(repo).length} events"
+end
do
if not plain_json or not first_attribute then
stream.write ","
- first_attribute = false
end
+ first_attribute = false
new_line_and_indent
stream.write "\""
# Is there data are stored under `key`.
fun has_key(key: String): Bool do
- return (store_dir / "{key}.json").file_exists
+ return ("{store_dir}/{key}.json".simplify_path).file_exists
end
# Save `json` object under `key`.
# Only `JsonObject` and `JsonArray` are allowed in a json file.
# Use `store_object` or `store_array` instead.
private fun store_json(key: String, json: Jsonable) do
- var path = store_dir / "{key}.json"
+ var path = "{store_dir}/{key}.json".simplify_path
path.dirname.mkdir
var file = new FileWriter.open(path)
file.write(json.to_json)
# Ensure `has_data(key)`
private fun load_json(key: String): nullable Jsonable do
assert has_key(key)
- var path = store_dir / "{key}.json"
+ var path = "{store_dir}/{key}.json".simplify_path
var file = new FileReader.open(path)
var text = file.read_all
file.close
# Get the list of keys stored under the collection `key`.
fun list_collection(key: String): JsonArray do
var res = new JsonArray
- var coll = (store_dir / "{key}").to_path
+ var coll = ("{store_dir}/{key}".simplify_path).to_path
if not coll.exists or not coll.stat.is_dir then return res
for file in coll.files do
if file.to_s.has_suffix(".json") then
# Does `key` matches a collection?
fun has_collection(key: String): Bool do
- var path = (store_dir / "{key}").to_path
+ var path = ("{store_dir}/{key}".simplify_path).to_path
return path.exists and path.stat.is_dir
end
end
end
end
+
+# An atomic Int
+extern class AtomicInt in "C" `{ int* `}
+ new(i: Int)`{
+ int* v = malloc(sizeof(int));
+ return v;
+ `}
+
+ # Get the value and increment it by `i`
+ fun get_and_increment_by(i: Int): Int `{
+ return __sync_fetch_and_add(self, i);
+ `}
+
+ # Get the value and decrement it by `i`
+ fun get_and_decrement_by(i: Int): Int `{
+ return __sync_fetch_and_sub(self, i);
+ `}
+
+ # Get the value and increment it
+ fun get_and_increment: Int `{
+ return __sync_fetch_and_add(self, 1);
+ `}
+
+ # Get the value and decrement it
+ fun get_and_decrement: Int `{
+ return __sync_fetch_and_sub(self, 1);
+ `}
+
+ # Increment by `i` and get the new value
+ fun increment_by_and_get(i: Int): Int `{
+ return __sync_add_and_fetch(self, i);
+ `}
+
+ # Decrement by `i` and get the new value
+ fun decrement_by_and_get(i: Int): Int `{
+ return __sync_sub_and_fetch(self, i);
+ `}
+
+ # Increment the value and get the new one
+ fun increment_and_get: Int `{
+ return __sync_add_and_fetch(self, 1);
+ `}
+
+ # Decrement the value and get the new one
+ fun decrement_and_get: Int `{
+ return __sync_sub_and_fetch(self,1);
+ `}
+
+ # Get the current value
+ fun value: Int `{
+ return *self;
+ `}
+
+end
+
private extern class NativePthread in "C" `{ pthread_t * `}
new create(nit_thread: Thread) import Thread.main_intern `{
fun equal(other: NativePthread): Bool `{ return pthread_equal(*self, *other); `}
- fun kill(signal: Int) `{ pthread_kill(*self, (int)signal); `}
+ fun kill(signal: Int): Int `{ return pthread_kill(*self, (int)signal); `}
end
private extern class NativePthreadAttr in "C" `{ pthread_attr_t * `}
fun destroy `{ pthread_cond_destroy(self); `}
- fun signal `{ pthread_cond_signal(self); `}
+ fun signal: Int `{ return pthread_cond_signal(self); `}
fun broadcast `{ pthread_cond_broadcast(self); `}
end
end
+# Condition variable
+class PthreadCond
+ super FinalizableOnce
+
+ private var native = new NativePthreadCond
+
+ # Destroy `self`
+ redef fun finalize_once do native.destroy
+
+ # Signal at least one thread waiting to wake up
+ fun signal: Int do return native.signal
+
+ # Signal all the waiting threads to wake up
+ fun broadcast do native.broadcast
+
+ # Make the current thread waiting for a signal ( `mutex` should be locked)
+ fun wait(mutex: Mutex) do native.wait(mutex.native.as(not null))
+end
+
# Barrier synchronization tool
#
# Ensures that `count` threads call and block on `wait` before releasing them.
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# 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.
+
+# Generate a support module for each module that contain a class annotated with `is actor`
+# See `nit/lib/actors/actors.nit` for the abstraction on which the generated classes are based
+module actors_generation_phase
+
+import modelize
+import gen_nit
+import modelbuilder
+
+private class ActorPhase
+ super Phase
+
+ # Source code of the actor classes to generate
+ var actors = new Array[String]
+
+ # Source code of the message classes to generate
+ var messages = new Array[String]
+
+ # Source code of the proxy classes to generate
+ var proxys = new Array[String]
+
+ # Redefinitions of annotated classes
+ var redef_classes = new Array[String]
+
+ redef fun process_annotated_node(nclass, nat)
+ do
+ if nat.n_atid.n_id.text != "actor" then return
+
+ if not nclass isa AStdClassdef then
+ toolcontext.error(nat.location, "Syntax Error: only a class can be annotated as an actor.")
+ return
+ end
+
+ # Get the module associated with this class
+ var mclassdef = nclass.mclassdef
+ assert mclassdef != null
+
+ var mmod = mclassdef.mmodule
+ if not mmod.generate_actor_submodule then mmod.generate_actor_submodule = true
+
+ # Get the name of the annotated class
+ var classname = mclassdef.name
+
+ # Generate the actor class
+ actors.add(
+"""
+class Actor{{{classname}}}
+ super Actor
+ redef type E: nullable {{{classname}}}
+end
+""")
+ ######## Generate the Messages classes ########
+
+ # Get all the methods definitions
+ var propdefs = mclassdef.mpropdefs
+ var methods = new Array[MMethodDef]
+ for p in propdefs do
+ if p isa MMethodDef then
+ # TODO: find a better way to exclude constructors,
+ # getters/setters and the "async" (the actor)
+ if p.name.has("=") or p.name.has("async") or p.mproperty.is_init then continue
+ methods.add(p)
+ end
+ end
+
+ # Generate the superclass for all Messages classes (each actor has its own Message super class)
+ var msg_class_name = "Message" + classname
+ messages.add(
+"""
+class {{{msg_class_name}}}
+ super Message
+ redef type E: {{{classname}}}
+end
+""")
+ # Generate every Message class based on the methods of the annotated class
+ var proxy_calls = new Array[String]
+ for m in methods do
+ # Signature of the method
+ var signature = m.msignature
+
+ # Name of the method
+ var method_name = m.name
+
+ # Attributes of the `Message` class if needed
+ # Corresponds to the parameters of the proxied method
+ var msg_attributes = new Array[String]
+
+ # Signature of the proxy corresponding method
+ var proxy_sign = ""
+
+ # Values for the body of the `invoke` method of the generated Message class
+ # Used if the call must return a value
+ var return_value = ""
+ var return_parenthesis = ""
+
+ # Params to send to `instance` in the `invoke` method
+ var params = ""
+
+ # Values for the generated proxy method
+ var return_signature = ""
+ var return_statement = ""
+
+ if signature != null then
+ var proxy_params= new Array[String]
+
+ # Deal with parameters
+ var mparameters = signature.mparameters
+ if mparameters.length > 0 then
+ var parameters = new Array[String]
+ proxy_sign += "("
+ for p in mparameters do
+ var n = p.name
+ var t = p.mtype.name
+ msg_attributes.add("var " + n + ": " + t)
+ proxy_params.add(n + ": " + t)
+ parameters.add(n)
+ end
+ proxy_sign += proxy_params.join(", ") + ")"
+ params = "(" + parameters.join(", ") + ")"
+ end
+
+ # Deal with the return if any
+ var ret_type = signature.return_mtype
+ if ret_type != null then
+ msg_attributes.add("var ret = new Future[{ret_type.name}]")
+ return_value = "ret.set_value("
+ return_parenthesis = ")"
+ return_signature = ": Future[{ret_type.name}]"
+ return_statement = "return msg.ret"
+ end
+ end
+
+ # Name of the Message class
+ var name = classname + "Message" + method_name
+
+ # The effective Message Class
+ messages.add(
+"""
+class {{{name}}}
+ super {{{msg_class_name}}}
+
+ {{{msg_attributes.join("\n")}}}
+
+ redef fun invoke(instance) do {{{return_value}}}instance.{{{method_name}}}{{{params}}}{{{return_parenthesis}}}
+end
+""")
+
+
+ # The actual proxy call
+ proxy_calls.add(
+"""
+redef fun {{{method_name}}}{{{proxy_sign}}}{{{return_signature}}} do
+ var msg = new {{{name}}}{{{params}}}
+ actor.mailbox.push(msg)
+ {{{return_statement}}}
+end
+""")
+ end
+
+ # At this point, all msg classes should be good
+ # All of the functions of the proxy too
+
+ # Let's generate the proxy class then
+ proxys.add(
+"""
+redef class Proxy{{{classname}}}
+
+ redef type E: Actor{{{classname}}}
+ #var actor: Actor{{{classname}}} is noinit
+
+ init proxy(base_class: {{{classname}}}) do
+ actor = new Actor{{{classname}}}(base_class)
+ actor.start
+ end
+
+ {{{proxy_calls.join("\n\n")}}}
+end
+""")
+
+ redef_classes.add(
+"""
+redef class {{{classname}}}
+redef var async is lazy do return new Proxy{{{classname}}}.proxy(self)
+end
+""")
+ end
+
+ redef fun process_nmodule_after(nmodule) do
+ var first_mmodule = nmodule.mmodule
+ if first_mmodule == null then return
+
+ # Be careful not to generate useless submodules !
+ if not first_mmodule.generate_actor_submodule then return
+
+ # Name of the support module
+ var module_name
+
+ # Path to the support module
+ module_name = "actors_{first_mmodule.name}"
+
+ # We assume a module using actors has a `filepath` not null
+ var mmodule_path = first_mmodule.filepath.as(not null).dirname
+
+ var module_path = "{mmodule_path}/{module_name}.nit"
+
+ var nit_module = new NitModule(module_name)
+ nit_module.annotations.add "no_warning(\"missing-doc\")"
+
+ nit_module.header = """
+ # This file is generated by nitactors (threaded version)
+ # Do not modify, instead use the generated services.
+ """
+
+ # for mmod in mmodules do nit_module.imports.add mmod.name
+ nit_module.imports.add first_mmodule.name
+
+ nit_module.content.add "####################### Redef classes #########################"
+ for c in redef_classes do nit_module.content.add( c + "\n\n" )
+
+ nit_module.content.add "####################### Actor classes #########################"
+ for c in actors do nit_module.content.add( c + "\n\n" )
+
+ nit_module.content.add "####################### Messages classes ######################"
+ for c in messages do nit_module.content.add( c + "\n\n" )
+
+ nit_module.content.add "####################### Proxy classes #########################"
+ for c in proxys do nit_module.content.add( c + "\n\n" )
+
+ # Write support module
+ nit_module.write_to_file module_path
+
+ actors = new Array[String]
+ messages = new Array[String]
+ proxys = new Array[String]
+ redef_classes = new Array[String]
+
+ toolcontext.modelbuilder.inject_module_subimportation(first_mmodule, module_path)
+ end
+end
+
+redef class MModule
+ # Do we need to generate the actor submodule ?
+ var generate_actor_submodule = false
+end
+
+redef class ToolContext
+ # Generate actors
+ var actor_phase: Phase = new ActorPhase(self, [modelize_class_phase])
+end
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# 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.
+
+# Injects model for the classes annotated with "is actor" so
+# that the later generated code can be properly used for compilation
+module actors_injection_phase
+
+import modelize_class
+intrude import modelize_property
+import parser_util
+
+redef class ModelBuilder
+ redef fun build_a_mclass(nmodule, nclassdef)
+ do
+ super
+
+ # Catch the wanted annotation
+ var at = nclassdef.get_single_annotation("actor", self)
+ if at == null then return
+
+ # Get context information
+ var mod = nmodule.mmodule
+ if mod == null then return
+ var mclass = nclassdef.mclass
+ if mclass == null then return
+ if mclass.intro_mmodule != mod then
+ error(at, "`actor` can only be used at introductions.")
+ return
+ end
+
+ var l = at.location
+
+ var injected_name = "Proxy"
+
+ # Create the actor class
+ var actor_class = new MClass(mod, injected_name + mclass.name, l, null, concrete_kind, public_visibility)
+ var actor_class_definition = new MClassDef(mod, actor_class.mclass_type, l)
+ actor_class_definition.set_supertypes([mod.object_type])
+ var proxy_classes = mclass.model.get_mclasses_by_name("Proxy")
+ assert proxy_classes != null
+ var proxy_class = proxy_classes.first
+ actor_class_definition.supertypes.add(proxy_class.mclass_type)
+
+ # Register it
+ mclass.actor = actor_class
+ end
+
+ redef fun build_properties(nclassdef)
+ do
+ if nclassdef.build_properties_is_done then return
+ super
+
+ # Get context information
+ var mclass = nclassdef.mclass
+ if mclass == null then return
+ var mclass_def = nclassdef.mclassdef
+ if mclass_def == null then return
+
+ var actor_mclass = mclass.actor
+ if actor_mclass == null then return
+ var actor_mclass_def = actor_mclass.mclassdefs.first
+
+ # Adds an `async` attribute in the worker class which is the actor class
+ if mclass_def.is_intro then
+ var async = new MMethod(mclass_def, "async", mclass.location, public_visibility)
+ var async_def = new MMethodDef(mclass_def, async, mclass.location)
+ async_def.msignature = new MSignature(new Array[MParameter], actor_mclass.mclass_type)
+ async_def.is_abstract = true
+ end
+
+ # For each introduced property
+ for method in mclass_def.mpropdefs do
+ if not method isa MMethodDef then continue
+ if not method.is_intro then continue
+ if method.name == "async" then continue
+
+ # Create a proxied method
+ var actor_method = new MMethod(actor_mclass_def, method.name, actor_mclass.location, method.mproperty.visibility)
+ var actor_method_def = new MMethodDef(actor_mclass_def, actor_method, actor_mclass.location)
+
+ # Get the signature of the method ( replacing the return value with a Future if there is one)
+ var signature = method.msignature
+ if signature != null then
+ var parameters = signature.mparameters
+ var s_return_type = signature.return_mtype
+ if s_return_type != null then
+ var future_mclasses = mclass_def.model.get_mclasses_by_name("Future")
+ assert future_mclasses != null
+ var future_mclass = future_mclasses.first
+ var return_type = future_mclass.get_mtype([s_return_type])
+ actor_method_def.msignature = new MSignature(parameters, return_type)
+ else
+ actor_method_def.msignature = new MSignature(parameters, null)
+ end
+ end
+ actor_method_def.is_abstract = true
+ end
+ end
+end
+
+redef class MClass
+ # Adding the actor class to a class annotated with "is actor"
+ var actor: nullable MClass = null
+end
import parallelization_phase
import i18n_phase
import regex_phase
+import actors_generation_phase
+import actors_injection_phase
redef class ToolContext
# FIXME: there is conflict in linex in nitc, so use this trick to force invocation
if errcount != self.error_count then
self.check_errors
end
+ phase.process_nmodule_after(nmodule)
end
self.check_errors
end
# Note that the order of the visit is the one of the file
# @toimplement
fun process_annotated_node(node: ANode, nat: AAnnotation) do end
+
+ # Specific actions to execute on the whole tree of a module
+ # Called at the end of a phase on a module
+ # Last called hook
+ # @toimplement
+ fun process_nmodule_after(nmodule: AModule) do end
end
read_entire_file
letter_frequency
threadpool_example
+chameneosredux
+actors_
--- /dev/null
+228
+Pfannfuchen(7) = 16
--- /dev/null
+foo
+foo
+25
+25
echo ""
echo "NIT_NO_STACK=1 $ff.bin" $args
fi
- NIT_NO_STACK=1 LD_LIBRARY_PATH=$JNI_LIB_PATH \
+ NIT_NO_STACK=1 LD_LIBRARY_PATH=$JNI_LIB_PATH WRITE="$ff.write" \
saferun -a -o "$ff.time.out" "$ff.bin" $args < "$inputs" > "$ff.res" 2>"$ff.err"
mv "$ff.time.out" "$ff.times.out"
awk '{ SUM += $1} END { print SUM }' "$ff.times.out" > "$ff.time.out"