Merge: gamnit: use SDL2 windows and events
authorJean Privat <jean@pryen.org>
Tue, 28 Feb 2017 13:25:59 +0000 (08:25 -0500)
committerJean Privat <jean@pryen.org>
Tue, 28 Feb 2017 13:25:59 +0000 (08:25 -0500)
_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>

43 files changed:
contrib/inkscape_tools/src/svg_to_png_and_nit.nit
contrib/nitcc/src/autom.nit
contrib/nitcc/src/nitcc.nit
contrib/nitcc/src/nitcc_semantic.nit
contrib/nitcc/src/re2nfa.nit
contrib/re_parser/.gitignore [new file with mode: 0644]
contrib/re_parser/Makefile [new file with mode: 0644]
contrib/re_parser/README.md [new file with mode: 0644]
contrib/re_parser/package.ini [new file with mode: 0644]
contrib/re_parser/src/re_app.nit [new file with mode: 0644]
contrib/re_parser/src/re_parser.nit [new file with mode: 0644]
contrib/re_parser/src/re_parser.sablecc [new file with mode: 0644]
lib/actors/README.md [new file with mode: 0644]
lib/actors/actors.nit [new file with mode: 0644]
lib/actors/examples/.gitignore [new file with mode: 0644]
lib/actors/examples/chameneos-redux/chameneosredux.nit [new file with mode: 0644]
lib/actors/examples/chameneos-redux/makefile [new file with mode: 0644]
lib/actors/examples/fannkuchredux/fannkuchredux.nit [new file with mode: 0644]
lib/actors/examples/fannkuchredux/makefile [new file with mode: 0644]
lib/actors/examples/mandelbrot/makefile [new file with mode: 0644]
lib/actors/examples/mandelbrot/mandelbrot.nit [new file with mode: 0644]
lib/actors/examples/simple/makefile [new file with mode: 0644]
lib/actors/examples/simple/simple.nit [new file with mode: 0644]
lib/actors/examples/thread-ring/makefile [new file with mode: 0644]
lib/actors/examples/thread-ring/thread_ring.nit [new file with mode: 0644]
lib/core/exec.nit
lib/core/file.nit
lib/github/api.nit
lib/github/github_curl.nit
lib/github/loader.nit [new file with mode: 0644]
lib/json/serialization_write.nit
lib/json/store.nit
lib/pthreads/pthreads.nit
src/frontend/actors_generation_phase.nit [new file with mode: 0644]
src/frontend/actors_injection_phase.nit [new file with mode: 0644]
src/frontend/frontend.nit
src/phase.nit
tests/exec.skip
tests/sav/fannkuchredux.res [new file with mode: 0644]
tests/sav/mandelbrot.res [new file with mode: 0644]
tests/sav/simple.res [new file with mode: 0644]
tests/sav/thread_ring.res [new file with mode: 0644]
tests/tests.sh

index 982d7f4..fafdc43 100644 (file)
@@ -255,7 +255,7 @@ end
 
 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")
@@ -414,7 +414,7 @@ for drawing in drawings do
        # 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"])
index aa8c218..3248e17 100644 (file)
@@ -252,8 +252,8 @@ class Automaton
                                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
 
@@ -466,8 +466,10 @@ class Automaton
                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
@@ -476,25 +478,27 @@ class Automaton
                        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
@@ -511,20 +515,26 @@ class Automaton
                        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.
@@ -537,7 +547,7 @@ class Automaton
 
                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]
@@ -607,7 +617,7 @@ class Automaton
                                        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
@@ -628,7 +638,7 @@ class Automaton
                        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
@@ -649,7 +659,7 @@ class Automaton
                                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
index 529985b..3456690 100644 (file)
@@ -46,7 +46,7 @@ var node = p.parse
 
 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
@@ -111,14 +111,14 @@ f.close
 
 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(" ")}"
index 299b46c..1fafab8 100644 (file)
@@ -221,7 +221,7 @@ redef class Nexpr
                end
                is_building = false
                var nre = self.children[2]
-               res = nre.make_rfa
+               res = nre.make_nfa
                nfa = res
                return res
        end
@@ -230,7 +230,7 @@ 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
@@ -251,7 +251,7 @@ redef class Nre_id
                v.nexpr.precs.add(node)
        end
 
-       redef fun make_rfa
+       redef fun make_nfa
        do
                return nexpr.nfa.dup
        end
index 12cd600..9cc0e7b 100644 (file)
@@ -20,7 +20,7 @@ import autom
 
 redef class Node
        # Build the NFA of the regular expression
-       fun make_rfa: Automaton do
+       fun make_nfa: Automaton do
                print inspect
                abort
        end
@@ -33,8 +33,8 @@ redef class Node
 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
@@ -46,8 +46,8 @@ redef class Nstr
 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
@@ -55,8 +55,8 @@ redef class Nch_dec
 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
@@ -64,28 +64,28 @@ redef class Nch_hex
 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
@@ -107,7 +107,7 @@ redef class Nre_minus
 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
@@ -115,12 +115,12 @@ redef class Nre_end
 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)
 
@@ -141,18 +141,18 @@ redef class Nre_and
 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
@@ -162,9 +162,9 @@ redef class Nre_shortest
 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)
@@ -174,9 +174,9 @@ redef class Nre_longest
 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
@@ -184,51 +184,51 @@ redef class Nre_prefixes
 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
@@ -243,7 +243,7 @@ redef class Nre_class
 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
@@ -257,7 +257,7 @@ redef class Nre_openclass
 end
 
 redef class Nre_any
-       redef fun make_rfa: Automaton
+       redef fun make_nfa
        do
                var a = new Automaton.cla(0, null)
                return a
diff --git a/contrib/re_parser/.gitignore b/contrib/re_parser/.gitignore
new file mode 100644 (file)
index 0000000..ca56bb2
--- /dev/null
@@ -0,0 +1,8 @@
+*.dot
+*parser_parser.nit
+*parser_lexer.nit
+*_test_parser*
+*.out
+
+re_parser
+re_app
diff --git a/contrib/re_parser/Makefile b/contrib/re_parser/Makefile
new file mode 100644 (file)
index 0000000..4997227
--- /dev/null
@@ -0,0 +1,27 @@
+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
diff --git a/contrib/re_parser/README.md b/contrib/re_parser/README.md
new file mode 100644 (file)
index 0000000..82ee878
--- /dev/null
@@ -0,0 +1,75 @@
+# 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.
diff --git a/contrib/re_parser/package.ini b/contrib/re_parser/package.ini
new file mode 100644 (file)
index 0000000..bc61d8c
--- /dev/null
@@ -0,0 +1,11 @@
+[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
diff --git a/contrib/re_parser/src/re_app.nit b/contrib/re_parser/src/re_app.nit
new file mode 100644 (file)
index 0000000..1e6560d
--- /dev/null
@@ -0,0 +1,135 @@
+# 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)
diff --git a/contrib/re_parser/src/re_parser.nit b/contrib/re_parser/src/re_parser.nit
new file mode 100644 (file)
index 0000000..683bc61
--- /dev/null
@@ -0,0 +1,216 @@
+# 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`"
diff --git a/contrib/re_parser/src/re_parser.sablecc b/contrib/re_parser/src/re_parser.sablecc
new file mode 100644 (file)
index 0000000..8fb78ac
--- /dev/null
@@ -0,0 +1,25 @@
+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;
diff --git a/lib/actors/README.md b/lib/actors/README.md
new file mode 100644 (file)
index 0000000..f99f1aa
--- /dev/null
@@ -0,0 +1,72 @@
+# 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`.
diff --git a/lib/actors/actors.nit b/lib/actors/actors.nit
new file mode 100644 (file)
index 0000000..41ea816
--- /dev/null
@@ -0,0 +1,241 @@
+# 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
diff --git a/lib/actors/examples/.gitignore b/lib/actors/examples/.gitignore
new file mode 100644 (file)
index 0000000..adbb2a7
--- /dev/null
@@ -0,0 +1 @@
+actors_*.nit
diff --git a/lib/actors/examples/chameneos-redux/chameneosredux.nit b/lib/actors/examples/chameneos-redux/chameneosredux.nit
new file mode 100644 (file)
index 0000000..0364586
--- /dev/null
@@ -0,0 +1,183 @@
+# 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)
diff --git a/lib/actors/examples/chameneos-redux/makefile b/lib/actors/examples/chameneos-redux/makefile
new file mode 100644 (file)
index 0000000..6afa533
--- /dev/null
@@ -0,0 +1,16 @@
+file= chameneosredux
+
+default: threaded
+
+threaded:
+       nitc $(file).nit
+
+test:
+       ./$(file) 1000
+
+bm:
+       time ./$(file) 50000000
+
+clean:
+       rm $(file)
+       rm actors_$(file).nit
diff --git a/lib/actors/examples/fannkuchredux/fannkuchredux.nit b/lib/actors/examples/fannkuchredux/fannkuchredux.nit
new file mode 100644 (file)
index 0000000..62bb094
--- /dev/null
@@ -0,0 +1,211 @@
+# 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)
diff --git a/lib/actors/examples/fannkuchredux/makefile b/lib/actors/examples/fannkuchredux/makefile
new file mode 100644 (file)
index 0000000..9cb369f
--- /dev/null
@@ -0,0 +1,15 @@
+file= fannkuchredux
+
+default: threaded
+
+threaded:
+       nitc $(file).nit
+
+test:
+       ./$(file) 7
+bm:
+       time ./$(file) 12
+
+clean:
+       rm $(file)
+       rm actors_$(file).nit
diff --git a/lib/actors/examples/mandelbrot/makefile b/lib/actors/examples/mandelbrot/makefile
new file mode 100644 (file)
index 0000000..cd626b7
--- /dev/null
@@ -0,0 +1,16 @@
+file= mandelbrot
+
+default: threaded
+
+threaded:
+       nitc $(file).nit
+
+test:
+       ./$(file) 200
+
+bm:
+       time ./$(file) 16000
+
+clean:
+       rm $(file)
+       rm actors_$(file).nit
diff --git a/lib/actors/examples/mandelbrot/mandelbrot.nit b/lib/actors/examples/mandelbrot/mandelbrot.nit
new file mode 100644 (file)
index 0000000..316b54a
--- /dev/null
@@ -0,0 +1,121 @@
+# 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
diff --git a/lib/actors/examples/simple/makefile b/lib/actors/examples/simple/makefile
new file mode 100644 (file)
index 0000000..9699264
--- /dev/null
@@ -0,0 +1,11 @@
+default: compile
+
+compile:
+       nitc simple.nit
+
+run:
+       ./simple
+
+clean:
+       rm actors_simple.nit
+       rm simple
diff --git a/lib/actors/examples/simple/simple.nit b/lib/actors/examples/simple/simple.nit
new file mode 100644 (file)
index 0000000..8c98b40
--- /dev/null
@@ -0,0 +1,50 @@
+# 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)
diff --git a/lib/actors/examples/thread-ring/makefile b/lib/actors/examples/thread-ring/makefile
new file mode 100644 (file)
index 0000000..60895c1
--- /dev/null
@@ -0,0 +1,15 @@
+file= thread_ring
+
+default: threaded
+
+threaded:
+       nitc $(file).nit
+
+test:
+       ./$(file) 1000
+bm:
+       time ./$(file) 50000000
+
+clean:
+       rm $(file)
+       rm actors_$(file).nit
diff --git a/lib/actors/examples/thread-ring/thread_ring.nit b/lib/actors/examples/thread-ring/thread_ring.nit
new file mode 100644 (file)
index 0000000..2da490d
--- /dev/null
@@ -0,0 +1,58 @@
+# This file is part of NIT (http://www.nitlanguage.org).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# 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
index 97ee3fa..d5ab952 100644 (file)
@@ -24,18 +24,21 @@ in "C" `{
        #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;
@@ -98,26 +101,136 @@ class Process
        # 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;
@@ -356,20 +469,30 @@ end
 
 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 */
@@ -377,17 +500,28 @@ private extern class NativeProcess `{ se_exec_data_t* `}
                                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);
index baa3403..6701588 100644 (file)
@@ -1509,7 +1509,12 @@ private extern class NativeFile `{ FILE* `}
        `}
 
        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 `{
index d236427..17de935 100644 (file)
@@ -200,7 +200,9 @@ class GithubAPI
                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.
@@ -519,7 +521,7 @@ end
 # 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.
@@ -591,10 +593,10 @@ class Commit
        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
@@ -621,6 +623,46 @@ class Commit
 
        # 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.
@@ -697,7 +739,7 @@ class 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.
@@ -810,31 +852,35 @@ class Milestone
        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
@@ -1079,7 +1125,7 @@ class GithubDeserializer
        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"
@@ -1087,8 +1133,12 @@ class GithubDeserializer
                        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
@@ -1116,6 +1166,11 @@ class GithubDeserializer
                                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
index f2d5737..026456c 100644 (file)
@@ -53,7 +53,7 @@ class GithubCurl
                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
@@ -83,7 +83,7 @@ class GithubCurl
                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)
diff --git a/lib/github/loader.nit b/lib/github/loader.nit
new file mode 100644 (file)
index 0000000..fb74f12
--- /dev/null
@@ -0,0 +1,596 @@
+# 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
index 28e79a8..9a2c9e5 100644 (file)
@@ -99,8 +99,8 @@ class JsonSerializer
        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 "\""
index b8ce0f9..2f81726 100644 (file)
@@ -111,7 +111,7 @@ class JsonStore
 
        # 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`.
@@ -129,7 +129,7 @@ class JsonStore
        # 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)
@@ -151,7 +151,7 @@ class JsonStore
        # 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
@@ -161,7 +161,7 @@ class JsonStore
        # 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
@@ -173,7 +173,7 @@ class JsonStore
 
        # 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
index 8fed1bb..585005b 100644 (file)
@@ -90,6 +90,61 @@ redef class Sys
        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 `{
@@ -128,7 +183,7 @@ private extern class NativePthread in "C" `{ pthread_t * `}
 
        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 * `}
@@ -238,7 +293,7 @@ private extern class NativePthreadCond in "C" `{ pthread_cond_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);  `}
 
@@ -377,6 +432,25 @@ class Mutex
        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.
diff --git a/src/frontend/actors_generation_phase.nit b/src/frontend/actors_generation_phase.nit
new file mode 100644 (file)
index 0000000..b67d390
--- /dev/null
@@ -0,0 +1,261 @@
+# 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
diff --git a/src/frontend/actors_injection_phase.nit b/src/frontend/actors_injection_phase.nit
new file mode 100644 (file)
index 0000000..ded6a70
--- /dev/null
@@ -0,0 +1,114 @@
+# 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
index 42cbccd..19490b3 100644 (file)
@@ -28,6 +28,8 @@ import glsl_validation
 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
index 2d400c9..2a9d7b8 100644 (file)
@@ -146,6 +146,7 @@ redef class ToolContext
                                if errcount != self.error_count then
                                        self.check_errors
                                end
+                               phase.process_nmodule_after(nmodule)
                        end
                        self.check_errors
                end
@@ -254,4 +255,10 @@ abstract class Phase
        # 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
index 4de2858..d64ecc0 100644 (file)
@@ -8,3 +8,5 @@ loops_infinite
 read_entire_file
 letter_frequency
 threadpool_example
+chameneosredux
+actors_
diff --git a/tests/sav/fannkuchredux.res b/tests/sav/fannkuchredux.res
new file mode 100644 (file)
index 0000000..3f6fa73
--- /dev/null
@@ -0,0 +1,2 @@
+228
+Pfannfuchen(7) = 16
diff --git a/tests/sav/mandelbrot.res b/tests/sav/mandelbrot.res
new file mode 100644 (file)
index 0000000..2f7bbbc
Binary files /dev/null and b/tests/sav/mandelbrot.res differ
diff --git a/tests/sav/simple.res b/tests/sav/simple.res
new file mode 100644 (file)
index 0000000..2b10d5a
--- /dev/null
@@ -0,0 +1,4 @@
+foo
+foo
+25
+25
diff --git a/tests/sav/thread_ring.res b/tests/sav/thread_ring.res
new file mode 100644 (file)
index 0000000..f9aaa4d
--- /dev/null
@@ -0,0 +1 @@
+498
index 738ccd2..8168d0d 100755 (executable)
@@ -678,7 +678,7 @@ END
                                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"