From: Jean Privat Date: Tue, 28 Feb 2017 13:25:59 +0000 (-0500) Subject: Merge: gamnit: use SDL2 windows and events X-Git-Url: http://nitlanguage.org?hp=b71fb0f4cfb4c1573ea7a2f4b878e1261bbeff5b Merge: gamnit: use SDL2 windows and events _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 --- diff --git a/contrib/inkscape_tools/src/svg_to_png_and_nit.nit b/contrib/inkscape_tools/src/svg_to_png_and_nit.nit index 982d7f4..fafdc43 100644 --- a/contrib/inkscape_tools/src/svg_to_png_and_nit.nit +++ b/contrib/inkscape_tools/src/svg_to_png_and_nit.nit @@ -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"]) diff --git a/contrib/nitcc/src/autom.nit b/contrib/nitcc/src/autom.nit index aa8c218..3248e17 100644 --- a/contrib/nitcc/src/autom.nit +++ b/contrib/nitcc/src/autom.nit @@ -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 diff --git a/contrib/nitcc/src/nitcc.nit b/contrib/nitcc/src/nitcc.nit index 529985b..3456690 100644 --- a/contrib/nitcc/src/nitcc.nit +++ b/contrib/nitcc/src/nitcc.nit @@ -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(" ")}" diff --git a/contrib/nitcc/src/nitcc_semantic.nit b/contrib/nitcc/src/nitcc_semantic.nit index 299b46c..1fafab8 100644 --- a/contrib/nitcc/src/nitcc_semantic.nit +++ b/contrib/nitcc/src/nitcc_semantic.nit @@ -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 diff --git a/contrib/nitcc/src/re2nfa.nit b/contrib/nitcc/src/re2nfa.nit index 12cd600..9cc0e7b 100644 --- a/contrib/nitcc/src/re2nfa.nit +++ b/contrib/nitcc/src/re2nfa.nit @@ -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 index 0000000..ca56bb2 --- /dev/null +++ b/contrib/re_parser/.gitignore @@ -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 index 0000000..4997227 --- /dev/null +++ b/contrib/re_parser/Makefile @@ -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 index 0000000..82ee878 --- /dev/null +++ b/contrib/re_parser/README.md @@ -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 http://localhost:3000. + +## 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 index 0000000..bc61d8c --- /dev/null +++ b/contrib/re_parser/package.ini @@ -0,0 +1,11 @@ +[package] +name=re_parser +tags=re +maintainer=Alexandre Terrasa +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 index 0000000..1e6560d --- /dev/null +++ b/contrib/re_parser/src/re_app.nit @@ -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 """ + + + + + + + REParser + + + +
+

Regular Expressions to NFA and DFA

+

+ +
+ + + + +
+

""" + + var error = self.error + if error != null then + add """

{{{error}}}

""" + end + + var nfa = self.nfa + if nfa != null then + add """ +

NFA

+
{{{nfa}}}


""" + end + + var dfa = self.dfa + if dfa != null then + add """ +

DFA

+
{{{dfa}}}


""" + end + + add """
+
+

Powered by nit!

+
+ +""" + 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 index 0000000..683bc61 --- /dev/null +++ b/contrib/re_parser/src/re_parser.nit @@ -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 " + 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 index 0000000..8fb78ac --- /dev/null +++ b/contrib/re_parser/src/re_parser.sablecc @@ -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 index 0000000..f99f1aa --- /dev/null +++ b/lib/actors/README.md @@ -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 index 0000000..41ea816 --- /dev/null +++ b/lib/actors/actors.nit @@ -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 index 0000000..adbb2a7 --- /dev/null +++ b/lib/actors/examples/.gitignore @@ -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 index 0000000..0364586 --- /dev/null +++ b/lib/actors/examples/chameneos-redux/chameneosredux.nit @@ -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 index 0000000..6afa533 --- /dev/null +++ b/lib/actors/examples/chameneos-redux/makefile @@ -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 index 0000000..62bb094 --- /dev/null +++ b/lib/actors/examples/fannkuchredux/fannkuchredux.nit @@ -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 index 0000000..9cb369f --- /dev/null +++ b/lib/actors/examples/fannkuchredux/makefile @@ -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 index 0000000..cd626b7 --- /dev/null +++ b/lib/actors/examples/mandelbrot/makefile @@ -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 index 0000000..316b54a --- /dev/null +++ b/lib/actors/examples/mandelbrot/mandelbrot.nit @@ -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 index 0000000..9699264 --- /dev/null +++ b/lib/actors/examples/simple/makefile @@ -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 index 0000000..8c98b40 --- /dev/null +++ b/lib/actors/examples/simple/simple.nit @@ -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 index 0000000..60895c1 --- /dev/null +++ b/lib/actors/examples/thread-ring/makefile @@ -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 index 0000000..2da490d --- /dev/null +++ b/lib/actors/examples/thread-ring/thread_ring.nit @@ -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 diff --git a/lib/core/exec.nit b/lib/core/exec.nit index 97ee3fa..d5ab952 100644 --- a/lib/core/exec.nit +++ b/lib/core/exec.nit @@ -24,18 +24,21 @@ in "C" `{ #include #include #include -#ifndef _WIN32 + #include + +#ifdef _WIN32 + #include + #include +#else #include #endif -`} - -in "C Header" `{ - #include - // 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); diff --git a/lib/core/file.nit b/lib/core/file.nit index baa3403..6701588 100644 --- a/lib/core/file.nit +++ b/lib/core/file.nit @@ -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 `{ diff --git a/lib/github/api.nit b/lib/github/api.nit index d236427..17de935 100644 --- a/lib/github/api.nit +++ b/lib/github/api.nit @@ -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 diff --git a/lib/github/github_curl.nit b/lib/github/github_curl.nit index f2d5737..026456c 100644 --- a/lib/github/github_curl.nit +++ b/lib/github/github_curl.nit @@ -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 index 0000000..fb74f12 --- /dev/null +++ b/lib/github/loader.nit @@ -0,0 +1,596 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Copyright 2016 Alexandre Terrasa +# +# 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 " + 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 + 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 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 diff --git a/lib/json/serialization_write.nit b/lib/json/serialization_write.nit index 28e79a8..9a2c9e5 100644 --- a/lib/json/serialization_write.nit +++ b/lib/json/serialization_write.nit @@ -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 "\"" diff --git a/lib/json/store.nit b/lib/json/store.nit index b8ce0f9..2f81726 100644 --- a/lib/json/store.nit +++ b/lib/json/store.nit @@ -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 diff --git a/lib/pthreads/pthreads.nit b/lib/pthreads/pthreads.nit index 8fed1bb..585005b 100644 --- a/lib/pthreads/pthreads.nit +++ b/lib/pthreads/pthreads.nit @@ -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 index 0000000..b67d390 --- /dev/null +++ b/src/frontend/actors_generation_phase.nit @@ -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 index 0000000..ded6a70 --- /dev/null +++ b/src/frontend/actors_injection_phase.nit @@ -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 diff --git a/src/frontend/frontend.nit b/src/frontend/frontend.nit index 42cbccd..19490b3 100644 --- a/src/frontend/frontend.nit +++ b/src/frontend/frontend.nit @@ -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 diff --git a/src/phase.nit b/src/phase.nit index 2d400c9..2a9d7b8 100644 --- a/src/phase.nit +++ b/src/phase.nit @@ -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 diff --git a/tests/exec.skip b/tests/exec.skip index 4de2858..d64ecc0 100644 --- a/tests/exec.skip +++ b/tests/exec.skip @@ -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 index 0000000..3f6fa73 --- /dev/null +++ b/tests/sav/fannkuchredux.res @@ -0,0 +1,2 @@ +228 +Pfannfuchen(7) = 16 diff --git a/tests/sav/mandelbrot.res b/tests/sav/mandelbrot.res new file mode 100644 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 index 0000000..2b10d5a --- /dev/null +++ b/tests/sav/simple.res @@ -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 index 0000000..f9aaa4d --- /dev/null +++ b/tests/sav/thread_ring.res @@ -0,0 +1 @@ +498 diff --git a/tests/tests.sh b/tests/tests.sh index 738ccd2..8168d0d 100755 --- a/tests/tests.sh +++ b/tests/tests.sh @@ -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"