Merge: Toplevel for sys and exit
authorJean Privat <jean@pryen.org>
Wed, 30 Jul 2014 18:08:58 +0000 (14:08 -0400)
committerJean Privat <jean@pryen.org>
Wed, 30 Jul 2014 18:08:58 +0000 (14:08 -0400)
Because the new c_src can deal with it

Pull-Request: #628
Reviewed-by: Alexandre Terrasa <alexandre@moz-code.org>
Reviewed-by: Lucas Bajolet <r4pass@hotmail.com>

44 files changed:
.gitignore
benchmarks/bench_strings.sh [new file with mode: 0755]
benchmarks/strings/chain_concat.nit [new file with mode: 0644]
benchmarks/strings/iteration_bench.nit [new file with mode: 0644]
benchmarks/strings/substr_bench.nit [new file with mode: 0644]
examples/mnit_simple/org.nitlanguage.test_all.txt [moved from examples/mnit_simple/org.nitlanguage.simple.txt with 100% similarity]
examples/mnit_simple/src/complete_simple_android.nit
examples/mnit_simple/src/simple.nit
examples/mnit_simple/src/simple_android.nit
examples/nitcorn/Makefile [new file with mode: 0644]
examples/nitcorn/src/file_server_on_port_80.nit [new file with mode: 0644]
examples/nitcorn/src/nitcorn_hello_world.nit [new file with mode: 0644]
examples/nitcorn/www/hello_world/dir/a.txt [new file with mode: 0644]
examples/nitcorn/www/hello_world/dir/b.txt [new file with mode: 0644]
examples/nitcorn/www/hello_world/favicon.ico [new file with mode: 0644]
lib/bufferized_ropes.nit
lib/libevent.nit [new file with mode: 0644]
lib/nitcorn/README.md [new file with mode: 0644]
lib/nitcorn/file_server.nit [new file with mode: 0644]
lib/nitcorn/http_request.nit [new file with mode: 0644]
lib/nitcorn/http_response.nit [new file with mode: 0644]
lib/nitcorn/media_types.nit [new file with mode: 0644]
lib/nitcorn/nitcorn.nit [new file with mode: 0644]
lib/nitcorn/reactor.nit [new file with mode: 0644]
lib/nitcorn/server_config.nit [new file with mode: 0644]
lib/nitcorn/sessions.nit [new file with mode: 0644]
lib/opts.nit
lib/sqlite3/sqlite3.nit
lib/standard/file.nit
lib/standard/ropes.nit
lib/standard/string.nit
lib/standard/string_search.nit
lib/string_experimentations/README [new file with mode: 0644]
lib/string_experimentations/string_experimentations.nit [new file with mode: 0644]
lib/string_experimentations/utf8.nit [new file with mode: 0644]
src/android_annotations.nit
src/common_ffi/java.nit
tests/.gitignore [new file with mode: 0644]
tests/README
tests/sav/file_server_on_port_80.res [new file with mode: 0644]
tests/sav/test_ffi_java_minimal.res
tests/sav/utf_test.res [new file with mode: 0644]
tests/test_ffi_java_minimal.nit
tests/utf_test.nit [new file with mode: 0644]

index eed3179..9b98202 100644 (file)
@@ -10,13 +10,11 @@ EIFGENs
 bin/nit*
 doc/stdlib
 doc/nitc
-doc/newmodel
 
 *.aux
 *.log
 *.out
 *.toc
-doc/nit_version.sty
 
 src/parser/.nit.sablecc3
 src/parser/.nit.sablecc3.dump
@@ -28,6 +26,7 @@ src/*.dot
 src/*.dat
 src/*.gnu
 src/*.bin
+src/nitg_0
 
 c_src/*.o
 c_src/*.cksum
@@ -47,6 +46,7 @@ tests/alt
 tests/errlist
 tests/out
 tests/*.xml
+tests/nitunit
 
 *.stub.nit.[ch]
 
diff --git a/benchmarks/bench_strings.sh b/benchmarks/bench_strings.sh
new file mode 100755 (executable)
index 0000000..811eddd
--- /dev/null
@@ -0,0 +1,236 @@
+#!/bin/bash
+# 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.
+
+source ./bench_common.sh
+source ./bench_plot.sh
+
+# Default number of times a command must be run with bench_command
+# Can be overrided with 'the option -n'
+count=2
+
+function usage()
+{
+       echo "run_bench: [options]* bench_name args"
+       echo "  -v: verbose mode"
+       echo "  -n count: number of execution for each bar (default: $count)"
+       echo "  -h: this help"
+       echo ""
+       echo "Benches : "
+       echo "  all : all benches"
+       echo "    - usage : * max_nb_cct loops strlen"
+       echo "  iter: bench iterations"
+       echo "    - usage : iter max_nb_cct loops strlen"
+       echo "  cct: concatenation benching"
+       echo "    - usage : cct max_nb_cct loops strlen"
+       echo "  substr: substring benching"
+       echo "    - usage : substr max_nb_cct loops strlen"
+}
+
+function benches()
+{
+       bench_concat $@;
+       bench_iteration $@;
+       bench_substr $@;
+}
+
+function bench_concat()
+{
+       if $verbose; then
+               echo "*** Benching concat performance ***"
+       fi
+
+       prepare_res concat_ropes.out concat_ropes ropes
+       if $verbose; then
+               echo "Ropes :"
+       fi
+       for i in `seq 1 "$1"`; do
+               if $verbose; then
+                       echo "String length = $i, Concats/loop = $2, Loops = $3"
+               fi
+               bench_command $i ropes$i ./chain_concat -m rope --loops $2 --strlen $3 --ccts $i "NIT_GC_CHOOSER=large"
+       done
+
+       prepare_res concat_flat.out concat_flat flatstring
+       if $verbose; then
+               echo "FlatStrings :"
+       fi
+       for i in `seq 1 "$1"`; do
+               if $verbose; then
+                       echo "String length = $i, Concats/loop = $2, Loops = $3"
+               fi
+               bench_command $i flatstring$i ./chain_concat -m flatstr --loops $2 --strlen $3 --ccts $i "NIT_GC_CHOOSER=large"
+       done
+
+       prepare_res concat_buf.out concat_buf flatbuffer
+       if $verbose; then
+               echo "FlatBuffers :"
+       fi
+       for i in `seq 1 "$1"`; do
+               if $verbose; then
+                       echo "String length = $i, Concats/loop = $2, Loops = $3"
+               fi
+               bench_command $i flatbuffer$i ./chain_concat -m flatbuf --loops $2 --strlen $3 --ccts $i "NIT_GC_CHOOSER=large"
+       done
+
+       plot concat.gnu
+}
+
+function bench_iteration()
+{
+       if $verbose; then
+               echo "*** Benching iteration performance ***"
+       fi
+
+       prepare_res iter_ropes_iter.out iter_ropes_iter ropes_iter
+       if $verbose; then
+               echo "Ropes by iterator :"
+       fi
+       for i in `seq 1 "$1"`; do
+               if $verbose; then
+                       echo "String base length = $1, Concats (depth of the rope) = $i, Loops = $3"
+               fi
+               bench_command $i ropes_iter$i ./iteration_bench -m rope --iter-mode iterator --loops $2 --strlen $3 --ccts $i "NIT_GC_CHOOSER=large"
+       done
+
+       prepare_res iter_ropes_index.out iter_ropes_index ropes_index
+       if $verbose; then
+               echo "Ropes by index :"
+       fi
+       for i in `seq 1 "$1"`; do
+               if $verbose; then
+                       echo "String base length = $1, Concats (depth of the rope) = $i, Loops = $3"
+               fi
+               bench_command $i ropes_index$i ./iteration_bench -m rope --iter-mode index --loops $2 --strlen $3 --ccts $i "NIT_GC_CHOOSER=large"
+       done
+
+       prepare_res iter_flat_iter.out iter_flat_iter flatstring_iter
+       if $verbose; then
+               echo "FlatStrings by iterator :"
+       fi
+       for i in `seq 1 "$1"`; do
+               if $verbose; then
+                       echo "String base length = $1, Concats = $i, Loops = $3"
+               fi
+               bench_command $i flatstr_iter$i ./iteration_bench -m flatstr --iter-mode iterator --loops $2 --strlen $3 --ccts $i "NIT_GC_CHOOSER=large"
+       done
+
+       prepare_res iter_flat_index.out iter_flat_index flatstring_index
+       if $verbose; then
+               echo "FlatStrings by index :"
+       fi
+       for i in `seq 1 "$1"`; do
+               if $verbose; then
+                       echo "String base length = $1, Concats = $i, Loops = $3"
+               fi
+               bench_command $i flatstr_index$i ./iteration_bench -m flatstr --iter-mode index --loops $2 --strlen $3 --ccts $i "NIT_GC_CHOOSER=large"
+       done
+
+       prepare_res iter_buf_iter.out iter_buf_iter flatbuffer_iter
+       if $verbose; then
+               echo "FlatBuffers by iterator :"
+       fi
+       for i in `seq 1 "$1"`; do
+               if $verbose; then
+                       echo "String base length = $1, Concats = $i, Loops = $3"
+               fi
+               bench_command $i flatbuf_iter$i ./iteration_bench -m flatbuf --iter-mode iterator --loops $2 --strlen $3 --ccts $i "NIT_GC_CHOOSER=large"
+       done
+
+       prepare_res iter_buf_index.out iter_buf_index flatbuffer_index
+       if $verbose; then
+               echo "FlatBuffers by index:"
+       fi
+       for i in `seq 1 "$1"`; do
+               if $verbose; then
+                       echo "String base length = $1, Concats = $i, Loops = $3"
+               fi
+               bench_command $i flatbuf_index$i ./iteration_bench -m flatbuf --iter-mode index --loops $2 --strlen $3 --ccts $i "NIT_GC_CHOOSER=large"
+       done
+
+       plot iter.gnu
+}
+
+function bench_substr()
+{
+       if $verbose; then
+               echo "*** Benching substring performance ***"
+       fi
+
+       prepare_res substr_ropes.out substr_ropes ropes
+       if $verbose; then
+               echo "Ropes :"
+       fi
+       for i in `seq 1 "$1"`; do
+               if $verbose; then
+                       echo "String length = $i, loops = $2, Loops = $3"
+               fi
+               bench_command $i ropes$i ./substr_bench -m rope --loops $2 --strlen $3 --ccts $i "NIT_GC_CHOOSER=large"
+       done
+
+       prepare_res substr_flat.out substr_flat flatstring
+       if $verbose; then
+               echo "FlatStrings :"
+       fi
+       for i in `seq 1 "$1"`; do
+               if $verbose; then
+                       echo "String length = $i, loops = $2, Loops = $3"
+               fi
+               bench_command $i flatstring$i ./substr_bench -m flatstr --loops $2 --strlen $3 --ccts $i "NIT_GC_CHOOSER=large"
+       done
+
+       prepare_res substr_buf.out substr_buf flatbuffer
+       if $verbose; then
+               echo "FlatBuffers :"
+       fi
+       for i in `seq 1 "$1"`; do
+               if $verbose; then
+                       echo "String length = $i, loops = $2, Loops = $3"
+               fi
+               bench_command $i flatbuffer$i ./substr_bench -m flatbuf --loops $2 --strlen $3 --ccts $i "NIT_GC_CHOOSER=large"
+       done
+
+       plot substr.gnu
+}
+
+stop=false
+while [ "$stop" = false ]; do
+       case "$1" in
+               -v) verbose=true; shift;;
+               -h) usage; exit;;
+               -n) count="$2"; shift; shift;;
+               *) stop=true
+       esac
+done
+
+if test $# -ne 4; then
+       usage
+       exit
+fi
+
+if $verbose; then
+       echo "Compiling"
+fi
+
+../bin/nitg --global ./strings/chain_concat.nit --make-flags "CFLAGS=\"-g -O2 -DNOBOEHM\""
+../bin/nitg --global ./strings/iteration_bench.nit --make-flags "CFLAGS=\"-g -O2 -DNOBOEHM\""
+../bin/nitg --global ./strings/substr_bench.nit --make-flags "CFLAGS=\"-g -O2 -DNOBOEHM\""
+
+case "$1" in
+       iter) shift; bench_iteration $@ ;;
+       cct) shift; bench_concat $@ ;;
+       substr) shift; bench_substr $@ ;;
+       all) shift; benches $@ ;;
+       *) usage; exit;;
+esac
diff --git a/benchmarks/strings/chain_concat.nit b/benchmarks/strings/chain_concat.nit
new file mode 100644 (file)
index 0000000..35466f9
--- /dev/null
@@ -0,0 +1,78 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This file is free software, which comes along with NIT.  This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without  even  the implied warranty of  MERCHANTABILITY or  FITNESS FOR A
+# PARTICULAR PURPOSE.  You can modify it is you want,  provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You  are  allowed  to  redistribute it and sell it, alone or is a part of
+# another product.
+
+# Benches measuring the performance of several concatenations on Text variants
+module chain_concat
+
+import opts
+
+fun bench_flatstr(str_size: Int, nb_ccts: Int, loops: Int)
+do
+       var lft = "a" * str_size
+
+       for i in [0..loops] do
+               var str: String = lft
+               for j in [0..nb_ccts] do
+                       str += lft
+               end
+       end
+end
+
+fun bench_rope(str_size: Int, nb_ccts: Int, loops: Int)
+do
+       var lft = "a" * str_size
+
+       for i in [0..loops] do
+               var str: String = new RopeString.from(lft)
+               for j in [0..nb_ccts] do
+                       str += lft
+               end
+       end
+end
+
+fun bench_flatbuf(str_size: Int, nb_ccts: Int, loops: Int)
+do
+       var lft = "a" * str_size
+
+       for i in [0..loops] do
+               var buf = new FlatBuffer.from(lft)
+               for j in [0..nb_ccts] do
+                       buf.append(lft)
+               end
+               buf.to_s
+       end
+end
+
+var opts = new OptionContext
+var mode = new OptionEnum(["rope", "flatstr", "flatbuf"], "Mode", -1, "-m")
+var nb_ccts = new OptionInt("Number of concatenations per loop", -1, "--ccts")
+var loops = new OptionInt("Number of loops to be done", -1, "--loops")
+var strlen = new OptionInt("Length of the base string", -1, "--strlen")
+opts.add_option(mode, nb_ccts, loops, strlen)
+
+opts.parse(args)
+
+if nb_ccts.value == -1 or loops.value == -1 or strlen.value == -1 then
+       opts.usage
+       exit -1
+end
+
+var modval = mode.value
+
+if modval == 0 then
+       bench_rope(strlen.value, nb_ccts.value, loops.value)
+else if modval == 1 then
+       bench_flatstr(strlen.value, nb_ccts.value, loops.value)
+else if modval == 2 then
+       bench_flatbuf(strlen.value, nb_ccts.value, loops.value)
+else
+       opts.usage
+       exit -1
+end
diff --git a/benchmarks/strings/iteration_bench.nit b/benchmarks/strings/iteration_bench.nit
new file mode 100644 (file)
index 0000000..a8047d2
--- /dev/null
@@ -0,0 +1,163 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This file is free software, which comes along with NIT.  This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without  even  the implied warranty of  MERCHANTABILITY or  FITNESS FOR A
+# PARTICULAR PURPOSE.  You can modify it is you want,  provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You  are  allowed  to  redistribute it and sell it, alone or is a part of
+# another product.
+
+# Benches for iteration on variants of Text
+module iteration_bench
+
+import opts
+
+fun bench_rope_iter(nb_cct: Int, loops: Int, strlen: Int)
+do
+       var a = "a" * strlen
+       var x:String = new RopeString.from(a)
+       for i in [0 .. nb_cct] do x += a
+       var cnt = 0
+       var c: Char
+       while cnt != loops do
+               for i in x do
+                       c = i
+               end
+               cnt += 1
+       end
+end
+
+fun bench_rope_index(nb_cct: Int, loops: Int, strlen: Int)
+do
+       var a = "a" * strlen
+       var x:String = new RopeString.from(a)
+       for i in [0 .. nb_cct] do x += a
+       var cnt = 0
+       var c: Char
+       var pos = 0
+       while cnt != loops do
+               pos = 0
+               while pos < x.length do
+                       c = x[pos]
+                       pos += 1
+               end
+               cnt += 1
+       end
+end
+
+fun bench_flatstr_iter(nb_cct: Int, loops: Int, strlen: Int)
+do
+       var a = "a" * strlen
+       var x = a
+       for i in [0 .. nb_cct] do x += a
+       var cnt = 0
+       var c: Char
+       while cnt != loops do
+               for i in x do
+                       c = i
+               end
+               cnt += 1
+       end
+end
+
+fun bench_flatstr_index(nb_cct: Int, loops: Int, strlen: Int)
+do
+       var a = "a" * strlen
+       var x = a
+       for i in [0 .. nb_cct] do x += a
+       var cnt = 0
+       var c: Char
+       var pos = 0
+       while cnt != loops do
+               pos = 0
+               while pos < x.length do
+                       c = x[pos]
+                       pos += 1
+               end
+               cnt += 1
+       end
+end
+
+fun bench_flatbuf_iter(nb_cct: Int, loops: Int, strlen: Int)
+do
+       var a = "a" * strlen
+       var x = new FlatBuffer.from(a)
+       for i in [0 .. nb_cct] do x.append a
+       var cnt = 0
+       var c: Char
+       while cnt != loops do
+               for i in x do
+                       c = i
+               end
+               cnt += 1
+       end
+end
+
+fun bench_flatbuf_index(nb_cct: Int, loops: Int, strlen: Int)
+do
+       var a = "a" * strlen
+       var x = new FlatBuffer.from(a)
+       for i in [0 .. nb_cct] do x.append a
+       var cnt = 0
+       var c: Char
+       var pos = 0
+       while cnt != loops do
+               pos = 0
+               while pos < x.length do
+                       c = x[pos]
+                       pos += 1
+               end
+               cnt += 1
+       end
+end
+
+var opts = new OptionContext
+var mode = new OptionEnum(["rope", "flatstr", "flatbuf"], "Mode", -1, "-m")
+var access_mode = new OptionEnum(["iterator", "index"], "Iteration mode", -1, "--iter-mode")
+var nb_ccts = new OptionInt("Number of concatenations done to the string (in the case of the rope, this will increase its depth)", -1, "--ccts")
+var loops = new OptionInt("Number of loops to be done", -1, "--loops")
+var strlen = new OptionInt("Length of the base string", -1, "--strlen")
+opts.add_option(mode, nb_ccts, loops, strlen, access_mode)
+
+opts.parse(args)
+
+if nb_ccts.value == -1 or loops.value == -1 or strlen.value == -1 then
+       opts.usage
+       exit(-1)
+end
+
+var modval = mode.value
+var iterval = access_mode.value
+
+if modval == 0 then
+       if iterval == 0 then
+               bench_rope_iter(nb_ccts.value, loops.value, strlen.value)
+       else if iterval == 1 then
+               bench_rope_index(nb_ccts.value, loops.value, strlen.value)
+       else
+               opts.usage
+               exit(-1)
+       end
+else if modval == 1 then
+       if iterval == 0 then
+               bench_flatstr_iter(nb_ccts.value, loops.value, strlen.value)
+       else if iterval == 1 then
+               bench_flatstr_index(nb_ccts.value, loops.value, strlen.value)
+       else
+               opts.usage
+               exit(-1)
+       end
+else if modval == 2 then
+       if iterval == 0 then
+               bench_flatbuf_iter(nb_ccts.value, loops.value, strlen.value)
+       else if iterval == 1 then
+               bench_flatbuf_index(nb_ccts.value, loops.value, strlen.value)
+       else
+               opts.usage
+               exit(-1)
+       end
+else
+       opts.usage
+       exit(-1)
+end
diff --git a/benchmarks/strings/substr_bench.nit b/benchmarks/strings/substr_bench.nit
new file mode 100644 (file)
index 0000000..5021e66
--- /dev/null
@@ -0,0 +1,77 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This file is free software, which comes along with NIT.  This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without  even  the implied warranty of  MERCHANTABILITY or  FITNESS FOR A
+# PARTICULAR PURPOSE.  You can modify it is you want,  provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You  are  allowed  to  redistribute it and sell it, alone or is a part of
+# another product.
+
+# Benches on the substring operation on variants of Text
+module substr_bench
+
+import opts
+
+fun bench_rope(nb_cct: Int, loops: Int, strlen: Int)
+do
+       var a = "a" * strlen
+       var x:String = new RopeString.from(a)
+       for i in [0 .. nb_cct] do x += a
+       var cnt = 0
+       while cnt != loops do
+               x.substring(0,5)
+               cnt += 1
+       end
+end
+
+fun bench_flatstr(nb_cct: Int, loops: Int, strlen: Int)
+do
+       var a = "a" * strlen
+       var x = a
+       for i in [0 .. nb_cct] do x += a
+       var cnt = 0
+       while cnt != loops do
+               x.substring(0,5)
+               cnt += 1
+       end
+end
+
+fun bench_flatbuf(nb_cct: Int, loops: Int, strlen: Int)
+do
+       var a = "a" * strlen
+       var x = new FlatBuffer.from(a)
+       for i in [0 .. nb_cct] do x.append a
+       var cnt = 0
+       while cnt != loops do
+               x.substring(0,5)
+               cnt += 1
+       end
+end
+
+var opts = new OptionContext
+var mode = new OptionEnum(["rope", "flatstr", "flatbuf"], "Mode", -1, "-m")
+var nb_ccts = new OptionInt("Number of concatenations done to the string (in the case of the rope, this will increase its depth)", -1, "--ccts")
+var loops = new OptionInt("Number of loops to be done", -1, "--loops")
+var strlen = new OptionInt("Length of the base string", -1, "--strlen")
+opts.add_option(mode, nb_ccts, loops, strlen)
+
+opts.parse(args)
+
+if nb_ccts.value == -1 or loops.value == -1 or strlen.value == -1 then
+       opts.usage
+       exit(-1)
+end
+
+var modval = mode.value
+
+if modval == 0 then
+       bench_rope(nb_ccts.value, loops.value, strlen.value)
+else if modval == 1 then
+       bench_flatstr(nb_ccts.value, loops.value, strlen.value)
+else if modval == 2 then
+       bench_flatbuf(nb_ccts.value, loops.value, strlen.value)
+else
+       opts.usage
+       exit(-1)
+end
index 57eead6..9ee8b06 100644 (file)
@@ -1,8 +1,21 @@
-#FIXME: Improper way of resolving unjustified metadata conflict
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2014 Frédéric Vachon <fredvac@gmail.com>
+#
+# 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 complete_simple_android is
-    app_name("test all")
        java_package("org.nitlanguage.test_all")
-       app_version(1, 0)
 end
 
 import test_bundle
index dd6f65d..6bfc9b1 100644 (file)
@@ -16,7 +16,7 @@
 
 # Very simple application
 module simple is
-       app_name("mnit Simple example")
+       app_name("mnit Simple example") # On Android, this name is hidden by the value in `res/values/strings.xml`
        app_version(0, 2, git_revision)
 end
 
index d92ecc6..833d832 100644 (file)
@@ -15,7 +15,6 @@
 # limitations under the License.
 
 module simple_android is
-       java_package("org.nitlanguage.simple")
        android_manifest("""<uses-permission android:name="android.permission.VIBRATE" />""")
 end
 
diff --git a/examples/nitcorn/Makefile b/examples/nitcorn/Makefile
new file mode 100644 (file)
index 0000000..96a6ee3
--- /dev/null
@@ -0,0 +1,3 @@
+all:
+       mkdir -p bin/
+       ../../bin/nitg --dir bin src/nitcorn_hello_world.nit src/file_server_on_port_80.nit
diff --git a/examples/nitcorn/src/file_server_on_port_80.nit b/examples/nitcorn/src/file_server_on_port_80.nit
new file mode 100644 (file)
index 0000000..cf91d9f
--- /dev/null
@@ -0,0 +1,52 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
+#
+# 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.
+
+# Basic file server on port 80, usually requires `root` to execute
+#
+# To be safe, it is recommended to run this program with its own username:
+# `sudo file_server -u nitcorn:www`
+module file_server_on_port_80
+
+import nitcorn
+import privileges
+
+# Prepare options
+var opts = new OptionContext
+var opt_drop = new OptionUserAndGroup.for_dropping_privileges
+opt_drop.mandatory = true
+var opt_help = new OptionBool("Print this message", "--help", "-h")
+opts.add_option(opt_drop, opt_help)
+opts.parse(args)
+
+# Check options errors and help
+if not opts.errors.is_empty or opt_help.value then
+       print opts.errors
+       print "Usage: file_server [Options]"
+       opts.usage
+       exit 1
+end
+
+# Serve everything with a standard FilesHandler
+var vh = new VirtualHost("localhost:80")
+vh.routes.add new Route(null, new FileServer("www/hello_world/"))
+var factory = new HttpFactory.and_libevent
+factory.config.virtual_hosts.add vh
+
+# Drop to a low-privileged user
+var user_group = opt_drop.value
+if user_group != null then user_group.drop_privileges
+
+factory.run
diff --git a/examples/nitcorn/src/nitcorn_hello_world.nit b/examples/nitcorn/src/nitcorn_hello_world.nit
new file mode 100644 (file)
index 0000000..fd7fe73
--- /dev/null
@@ -0,0 +1,65 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
+#
+# 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.
+
+# Hello World Web server example
+#
+# The main page, `index.html`, is served dynamicly with `MyAction`.
+# The rest of the Web site fetches files from the local directory
+# `www/hello_world/`.
+module nitcorn_hello_world
+
+import nitcorn
+
+class MyAction
+       super Action
+
+       redef fun answer(http_request, turi)
+       do
+               var response = new HttpResponse(200)
+               var title = "Hello World from Nitcorn!"
+               response.body = """
+<!DOCTYPE html>
+<head>
+       <meta charset="utf-8">
+       <meta http-equiv="X-UA-Compatible" content="IE=edge">
+       <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
+       <title>{{{title}}}</title>
+</head>
+<body>
+       <div class="container">
+               <h1>{{{title}}}</h1>
+               <p>See also a <a href="/dir/">directory</a>.</p>
+       </div>
+</body>
+</html>"""
+               return response
+       end
+end
+
+var vh = new VirtualHost("localhost:8080")
+
+# Serve index.html with our custom handler
+vh.routes.add new Route("/index.html", new MyAction)
+
+# Serve everything else with a standard `FileServer` with a root at "www/hello_world/"
+vh.routes.add new Route(null, new FileServer("www/hello_world/"))
+
+# Avoid executing when running tests
+if "NIT_TESTING".environ == "true" then exit 0
+
+var factory = new HttpFactory.and_libevent
+factory.config.virtual_hosts.add vh
+factory.run
diff --git a/examples/nitcorn/www/hello_world/dir/a.txt b/examples/nitcorn/www/hello_world/dir/a.txt
new file mode 100644 (file)
index 0000000..8e512cf
--- /dev/null
@@ -0,0 +1 @@
+aaaAAAAAaaaa
diff --git a/examples/nitcorn/www/hello_world/dir/b.txt b/examples/nitcorn/www/hello_world/dir/b.txt
new file mode 100644 (file)
index 0000000..55fe151
--- /dev/null
@@ -0,0 +1 @@
+BBBBBBbbbbbbbbbbbbbbb
diff --git a/examples/nitcorn/www/hello_world/favicon.ico b/examples/nitcorn/www/hello_world/favicon.ico
new file mode 100644 (file)
index 0000000..0f66f25
Binary files /dev/null and b/examples/nitcorn/www/hello_world/favicon.ico differ
index a47bc0a..18cfa4c 100644 (file)
@@ -105,6 +105,8 @@ redef class RopeString
                end
        end
 
+       redef fun +(o) do return insert_at(o.to_s, length)
+
        # Inserts a String `str` at position `pos`
        redef fun insert_at(str, pos)
        do
diff --git a/lib/libevent.nit b/lib/libevent.nit
new file mode 100644 (file)
index 0000000..525915c
--- /dev/null
@@ -0,0 +1,290 @@
+# This file is part of NIT (http://www.nitlanguage.org).
+#
+# Copyright 2013 Jean-Philippe Caissy <jpcaissy@piji.ca>
+# Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
+#
+# 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.
+
+# Low-level wrapper around the libevent library to manage events on file descriptors
+#
+# For mor information, refer to the libevent documentation at
+# http://monkey.org/~provos/libevent/doxygen-2.0.1/
+module libevent is pkgconfig("libevent")
+
+in "C header" `{
+       #include <sys/stat.h>
+       #include <sys/types.h>
+       #include <fcntl.h>
+       #include <errno.h>
+       #include <sys/socket.h>
+
+       #include <event2/listener.h>
+       #include <event2/bufferevent.h>
+       #include <event2/buffer.h>
+`}
+
+in "C" `{
+       // Callback forwarded to 'Connection.write_callback'
+       static void c_write_cb(struct bufferevent *bev, Connection ctx) {
+               Connection_write_callback(ctx);
+       }
+
+       // Callback forwarded to 'Connection.read_callback_native'
+       static void c_read_cb(struct bufferevent *bev, Connection ctx)
+       {
+               // TODO move to Nit code
+               struct evbuffer *input = bufferevent_get_input(bev);
+               size_t len = evbuffer_get_length(input);
+               char* cstr = malloc(len);
+               evbuffer_remove(input, cstr, len);
+               Connection_read_callback_native(ctx, cstr, len);
+       }
+
+       // Callback forwarded to 'Connection.event_callback'
+       static void c_event_cb(struct bufferevent *bev, short events, Connection ctx)
+       {
+               Connection_event_callback(ctx, events);
+
+               // TODO move to Nit code
+               if (events & BEV_EVENT_ERROR)
+                       perror("Error from bufferevent");
+               if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
+                       bufferevent_free(bev);
+                       Connection_decr_ref(ctx);
+               }
+       }
+
+       // Callback fowarded to 'ConnectionFactory.spawn_connection'
+       static void accept_conn_cb(struct evconnlistener *listener, evutil_socket_t fd,
+               struct sockaddr *address, int socklen, ConnectionFactory ctx)
+       {
+               // TODO move to Nit code
+               struct event_base *base = evconnlistener_get_base(listener);
+               struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
+
+               Connection nit_con = ConnectionFactory_spawn_connection(ctx, bev);
+               Connection_incr_ref(nit_con);
+
+               bufferevent_setcb(bev,
+                       (bufferevent_data_cb)c_read_cb,
+                       (bufferevent_data_cb)c_write_cb,
+                       (bufferevent_event_cb)c_event_cb, nit_con);
+               bufferevent_enable(bev, EV_READ|EV_WRITE);
+       }
+`}
+
+# Structure to hold information and state for a Libevent dispatch loop.
+#
+# The event_base lies at the center of Libevent; every application will
+# have one.  It keeps track of all pending and active events, and
+# notifies your application of the active ones.
+extern class NativeEventBase `{ struct event_base * `}
+
+       # Create a new event_base to use with the rest of Libevent
+       new `{ return event_base_new(); `}
+       fun is_valid: Bool do return not address_is_null
+       #fun creation_ok
+
+       # Event dispatching loop
+       #
+       # This loop will run the event base until either there are no more added
+       # events, or until something calls `exit_loop`.
+       fun dispatch `{ event_base_dispatch(recv); `}
+
+       # Exit the event loop
+       #
+       # TODO support timer
+       fun exit_loop `{ event_base_loopexit(recv, NULL); `}
+
+       # Destroy this instance
+       fun destroy `{ event_base_free(recv); `}
+end
+
+# Spawned to manage a specific connection
+#
+# TODO, use polls
+class Connection
+       # Closing this connection has been requested, but may not yet be `closed`
+       var close_requested = false
+
+       # This connection is closed
+       var closed = false
+
+       # The native libevent linked to `self`
+       var native_buffer_event: NativeBufferEvent
+
+       # Close this connection if possible, otherwise mark it to be closed later
+       fun close
+       do
+               var success = native_buffer_event.destroy
+               close_requested = true
+               closed = success
+       end
+
+       # Callback method on a write event
+       fun write_callback
+       do
+               if close_requested and not closed then close
+       end
+
+       private fun read_callback_native(cstr: NativeString, len: Int)
+       do
+               read_callback(cstr.to_s_with_length(len))
+       end
+
+       # Callback method when data is available to read
+       fun read_callback(content: String)
+       do
+               if close_requested and not closed then close
+       end
+
+       # Callback method on events
+       fun event_callback(events: Int) do end
+
+       # Write a string to the connection
+       fun write(str: String)
+       do
+               var res = native_buffer_event.write(str.to_cstring, str.length)
+       end
+
+       # Write a file to the connection
+       #
+       # require: `path.file_exists`
+       fun write_file(path: String)
+       do
+               assert path.file_exists
+
+               var file = new IFStream.open(path)
+               var output = native_buffer_event.output_buffer
+               var fd = file.fd
+               var length = file.file_stat.size
+
+               output.add_file(fd, 0, length)
+       end
+end
+
+# A buffer event structure, strongly associated to a connection, an input buffer and an output_buffer
+extern class NativeBufferEvent `{ struct bufferevent * `}
+       fun write(line: NativeString, length: Int): Int `{
+               return bufferevent_write(recv, line, length);
+       `}
+
+       # Check if we have anything left in our buffers. If so, we set our connection to be closed
+       # on a callback. Otherwise we close it and free it right away.
+       fun destroy: Bool `{
+               struct evbuffer* out = bufferevent_get_output(recv);
+               struct evbuffer* in = bufferevent_get_input(recv);
+               if(evbuffer_get_length(in) > 0 || evbuffer_get_length(out) > 0) {
+                       return 0;
+               } else {
+                       bufferevent_free(recv);
+                       return 1;
+               }
+       `}
+
+       # The output buffer associated to `self`
+       fun output_buffer: OutputNativeEvBuffer `{ return bufferevent_get_output(recv); `}
+
+       # The input buffer associated to `self`
+       fun input_buffer: InputNativeEvBuffer `{ return bufferevent_get_input(recv); `}
+end
+
+# A single buffer
+extern class NativeEvBuffer `{ struct evbuffer * `}
+       # Length of data in this buffer
+       fun length: Int `{ return evbuffer_get_length(recv); `}
+end
+
+extern class InputNativeEvBuffer
+       super NativeEvBuffer
+
+       # Empty/clear `length` data from buffer
+       fun drain(length: Int) `{ evbuffer_drain(recv, length); `}
+end
+
+extern class OutputNativeEvBuffer
+       super NativeEvBuffer
+
+       # Add file to buffer
+       fun add_file(fd, offset, length: Int): Bool `{
+               return evbuffer_add_file(recv, fd, offset, length);
+       `}
+end
+
+# A listener acting on an interface and port, spawns `Connection` on new connections
+extern class ConnectionListener `{ struct evconnlistener * `}
+
+       private new bind_to(base: NativeEventBase, address: NativeString, port: Int, factory: ConnectionFactory)
+       import ConnectionFactory.spawn_connection, error_callback, Connection.read_callback_native,
+       Connection.write_callback, Connection.event_callback `{
+
+               struct sockaddr_in sin;
+               struct evconnlistener *listener;
+               ConnectionFactory_incr_ref(factory);
+
+               struct hostent *hostent = gethostbyname(address);
+
+               memset(&sin, 0, sizeof(sin));
+               sin.sin_family = hostent->h_addrtype;
+               sin.sin_port = htons(port);
+               memcpy( &(sin.sin_addr.s_addr), (const void*)hostent->h_addr, hostent->h_length );
+
+               listener = evconnlistener_new_bind(base,
+                       (evconnlistener_cb)accept_conn_cb, factory,
+                       LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, -1,
+                       (struct sockaddr*)&sin, sizeof(sin));
+
+               if (listener != NULL) {
+                       evconnlistener_set_error_cb(listener, (evconnlistener_errorcb)ConnectionListener_error_callback);
+               }
+
+               return listener;
+       `}
+
+       # Get the `NativeEventBase` associated to `self`
+       fun base: NativeEventBase `{ return evconnlistener_get_base(recv); `}
+
+       # Callback method on listening error
+       fun error_callback do
+               var cstr = socket_error
+               sys.stderr.write "libevent error: '{cstr}'"
+       end
+
+       # Error with sockets
+       fun socket_error: NativeString `{
+               // TODO move to Nit and maybe NativeEventBase
+               int err = EVUTIL_SOCKET_ERROR();
+               return evutil_socket_error_to_string(err);
+       `}
+end
+
+# Factory to listen on sockets and create new `Connection`
+class ConnectionFactory
+       var event_base: NativeEventBase
+
+       # On new connection, create the handler `Connection` object
+       fun spawn_connection(nat_buf_ev: NativeBufferEvent): Connection
+       do
+               return new Connection(nat_buf_ev)
+       end
+
+       # Listen on `address`:`port` for new connection, which will callback `spawn_connection`
+       fun bind_to(address: String, port: Int): nullable ConnectionListener
+       do
+               var listener = new ConnectionListener.bind_to(event_base, address.to_cstring, port, self)
+               if listener.address_is_null then
+                       sys.stderr.write "libevent warning: Opening {address}:{port} failed\n"
+               end
+               return listener
+       end
+end
diff --git a/lib/nitcorn/README.md b/lib/nitcorn/README.md
new file mode 100644 (file)
index 0000000..f9e952d
--- /dev/null
@@ -0,0 +1,30 @@
+The nitcorn Web server framework creates server-side Web apps in Nit
+
+# Examples
+
+Want to see `nitcorn` in action? Examples are available at ../../examples/nitcorn/src/.
+
+# Features and TODO list
+
+ - [x] Virtual hosts and routes
+ - [x] Configuration change on the fly
+ - [x] Sessions
+ - [x] Reading cookies
+ - [ ] Full cookie support
+ - [ ] Close interfaces on the fly
+ - [ ] Better logging
+ - [ ] Info/status page
+ - [ ] `ProxyAction` to redirect a request to an external server
+ - [ ] `ModuleAction` which forwards the request to an independant Nit program
+
+## Bugs / Limitations
+
+* The size of requests is limited, so no big uploads
+
+# Credits
+
+This nitcorn library is a fork from an independant project originally created in 2013 by
+Jean-Philippe Caissy, Guillaume Auger, Frederic Sevillano, Justin Michaud-Ouellette,
+Stephan Michaud and Maxime Bélanger.
+
+It has been adapted to a library, and is currently maintained, by Alexis Laferrière.
diff --git a/lib/nitcorn/file_server.nit b/lib/nitcorn/file_server.nit
new file mode 100644 (file)
index 0000000..348383e
--- /dev/null
@@ -0,0 +1,128 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2013 Jean-Philippe Caissy <jpcaissy@piji.ca>
+# Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
+#
+# 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.
+
+# Provides the `FileServer` action, which is a standard and minimal file server
+module file_server
+
+import reactor
+import sessions
+import media_types
+
+redef class String
+       # Returns a `String` copy of `self` without any of the prefixed '/'s
+       #
+       # Examples:
+       #
+       #     assert "/home/".strip_start_slashes == "home/"
+       #     assert "////home/".strip_start_slashes == "home/"
+       #     assert "../home/".strip_start_slashes == "../home/"
+       fun strip_start_slashes: String
+       do
+               for i in chars.length.times do if chars[i] != '/' then return substring_from(i)
+               return ""
+       end
+end
+
+# A simple file server
+class FileServer
+       super Action
+
+       # Root of `self` file system
+       var root: String
+
+       redef fun answer(request, turi)
+       do
+               var response
+
+               var local_file = root.join_path(turi.strip_start_slashes)
+               local_file = local_file.simplify_path
+
+               # HACK
+               if turi == "/" then local_file = root
+
+               # Is it reachable?
+               if local_file.has_prefix(root) then
+                       # Does it exists?
+                       if local_file.file_exists then
+                               response = new HttpResponse(200)
+
+                               if local_file.file_stat.is_dir then
+                                       # Show index.html instead of the directory listing
+                                       var index_file = local_file.join_path("index.html")
+                                       if index_file.file_exists then
+                                               local_file = index_file
+                                       else
+                                               index_file = local_file.join_path("index.htm")
+                                               if index_file.file_exists then local_file = index_file
+                                       end
+                               end
+
+                               if local_file.file_stat.is_dir then
+                                       # Show the directory listing
+                                       var title = turi
+                                       var files = local_file.files
+
+                                       var links = new Array[String]
+                                       if local_file.length > 1 then
+                                               # The extra / is a hack
+                                               var path = "/" + (turi + "/..").simplify_path
+                                               links.add "<a href=\"{path}\">..</a>"
+                                       end
+                                       for file in files do
+                                               var path = (turi + "/" + file).simplify_path
+                                               links.add "<a href=\"{path}\">{file}</a>"
+                                       end
+
+                                       response.body = """
+<!DOCTYPE html>
+<head>
+       <meta charset="utf-8">
+       <meta http-equiv="X-UA-Compatible" content="IE=edge">
+       <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
+       <title>{{{title}}}</title>
+</head>
+<body>
+       <div class="container">
+               <h1>{{{title}}}</h1>
+               <ul>
+                       <li>{{{links.join("</li>\n\t\t\t<li>")}}}</li>
+               </ul>
+       </div>
+</body>
+</html>"""
+
+                                       response.header["Content-Type"] = media_types["html"].as(not null)
+                               else
+                                       # It's a single file
+                                       var file = new IFStream.open(local_file)
+                                       response.body = file.read_all
+
+                                       var ext = local_file.file_extension
+                                       if ext != null then
+                                               var media_type = media_types[ext]
+                                               if media_type != null then response.header["Content-Type"] = media_type
+                                       end
+
+                                       file.close
+                               end
+
+                       else response = new HttpResponse(404)
+               else response = new HttpResponse(403)
+
+               return response
+       end
+end
diff --git a/lib/nitcorn/http_request.nit b/lib/nitcorn/http_request.nit
new file mode 100644 (file)
index 0000000..6431ace
--- /dev/null
@@ -0,0 +1,204 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2013 Frederic Sevillano
+# Copyright 2013 Jean-Philippe Caissy <jpcaissy@piji.ca>
+# Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
+#
+# 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.
+
+# Provides the `HttpRequest` class and services to create it
+module http_request
+
+import standard
+
+# A request received over HTTP, is build by `HttpRequestParser`
+class HttpRequest
+       private init do end
+
+       # HTTP protocol version
+       var http_version: String
+
+       # Method of this request (GET or POST)
+       var method: String
+
+       # The host targetter by this request (usually the server)
+       var host: String
+
+       # The full URL requested by the client (including the `query_string`)
+       var url: String
+
+       # The resource requested by the client (only the page, not the `query_string`)
+       var uri: String
+
+       # The string following `?` in the requested URL
+       var query_string = ""
+
+       # The header of this request
+       var header = new HashMap[String, String]
+
+       # The content of the cookie of this request
+       var cookie = new HashMap[String, String]
+
+       # The arguments passed with the GET method,
+       var get_args = new HashMap[String, String]
+
+       # The arguments passed with the POST method
+       var post_args = new HashMap[String, String]
+end
+
+# Utility class to parse a request string and build a `HttpRequest`
+#
+# The main method is `parse_http_request`.
+class HttpRequestParser
+       # The current `HttpRequest` under construction
+       private var http_request: HttpRequest
+
+       # Untreated body
+       private var body = ""
+
+       # Lines of the header
+       private var header_fields = new Array[String]
+
+       # Words of the first line
+       private var first_line = new Array[String]
+
+       init do end
+
+       fun parse_http_request(full_request: String): nullable HttpRequest
+       do
+               clear_data
+
+               var http_request = new HttpRequest
+               self.http_request = http_request
+
+               segment_http_request(full_request)
+
+               # Parse first line, looks like "GET dir/index.html?user=xymus HTTP/1.0"
+               http_request.method = first_line[0]
+               http_request.url = first_line[1]
+               http_request.http_version = first_line[2]
+
+               # GET args
+               if http_request.url.has('?') then
+                       http_request.uri = first_line[1].substring(0, first_line[1].index_of('?'))
+                       http_request.query_string = first_line[1].substring_from(first_line[1].index_of('?')+1)
+                       http_request.get_args = parse_url
+               else
+                       http_request.uri = first_line[1]
+               end
+
+               # POST args
+               if http_request.method == "POST" then
+                       var lines = body.split_with('&')
+                       for line in lines do
+                               var parts = line.split_once_on('=')
+                               if parts.length > 1 then
+                                       var decoded = parts[1].replace('+', " ").from_percent_encoding
+                                       if decoded == null then
+                                               print "decode error"
+                                               continue
+                                       end
+                                       http_request.post_args[parts[0]] = decoded
+                               else
+                                       print "POST Error: {line} format error on {line}"
+                               end
+                       end
+               end
+
+               # Headers
+               for i in header_fields do
+                       var temp_field = i.split_with(": ")
+
+                       if temp_field.length == 2 then
+                               http_request.header[temp_field[0]] = temp_field[1]
+                       end
+               end
+
+               # Cookies
+               if http_request.header.keys.has("Cookie") then
+                       var cookie = http_request.header["Cookie"]
+                       for couple in cookie.split_with(';') do
+                               var words = couple.trim.split_with('=')
+                               if words.length != 2 then continue
+                               http_request.cookie[words[0]] = words[1]
+                       end
+               end
+
+               return http_request
+       end
+
+       private fun clear_data
+       do
+               first_line.clear
+               header_fields.clear
+       end
+
+       private fun segment_http_request(http_request: String): Bool
+       do
+               var header_end = http_request.search("\r\n\r\n")
+
+               if header_end == null then
+                       header_fields = http_request.split_with("\r\n")
+               else
+                       header_fields = http_request.substring(0, header_end.from).split_with("\r\n")
+                       body = http_request.substring(header_end.after, http_request.length-1)
+               end
+
+               # If a line of the http_request is long it may change line, it has " " at the
+               # end to indicate this. This section turns them into 1 line.
+               if header_fields.length > 1 and header_fields[0].has_suffix(" ") then
+                       var temp_req = header_fields[0].substring(0, header_fields[0].length-1) + header_fields[1]
+
+                       first_line  = temp_req.split_with(' ')
+                       header_fields.shift
+                       header_fields.shift
+
+                       if first_line.length != 3 then return false
+               else
+                       first_line = header_fields[0].split_with(' ')
+                       header_fields.shift
+
+                       if first_line.length != 3 then return false
+               end
+
+               # Cut off the header in lines
+               var pos = 0
+               while pos < header_fields.length do
+                       if pos < header_fields.length-1 and header_fields[pos].has_suffix(" ") then
+                               header_fields[pos] = header_fields[pos].substring(0, header_fields[pos].length-1) + header_fields[pos+1]
+                               header_fields.remove_at(pos+1)
+                               pos = pos-1
+                       end
+                       pos = pos+1
+               end
+
+               return true
+       end
+
+       # Extract args from the URL
+       private fun parse_url: HashMap[String, String]
+       do
+               var query_strings = new HashMap[String, String]
+
+               if http_request.url.has('?') then
+                       var get_args = http_request.query_string.split_with("&")
+                       for param in get_args do
+                               var key_value = param.split_with("=")
+                               if key_value.length < 2 then continue
+                               query_strings[key_value[0]] = key_value[1]
+                       end
+               end
+
+               return query_strings
+       end
+end
diff --git a/lib/nitcorn/http_response.nit b/lib/nitcorn/http_response.nit
new file mode 100644 (file)
index 0000000..666974b
--- /dev/null
@@ -0,0 +1,132 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2013 Frederic Sevillano
+# Copyright 2013 Jean-Philippe Caissy <jpcaissy@piji.ca>
+# Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
+#
+# 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.
+
+# Provides the `HttpResponse` class and `http_status_codes`
+module http_response
+
+# A response to send over HTTP
+class HttpResponse
+
+       # HTTP protocol version
+       var http_version = "HTTP/1.0" is writable
+
+       # Status code of this response (200, 404, etc.)
+       var status_code: Int is writable
+
+       # Return the message associated to `status_code`
+       fun status_message: nullable String do return http_status_codes[status_code]
+
+       # Headers of this response as a `Map`
+       var header = new HashMap[String, String]
+
+       # Body of this response
+       var body = "" is writable
+
+       # Finalize this response before sending it over HTTP
+       fun finalize
+       do
+               # Set the content length if not already set
+               if not header.keys.has("Content-Length") then
+                       header["Content-Length"] = body.length.to_s
+               end
+
+               # Set server ID
+               if not header.keys.has("Server") then header["Server"] = "unitcorn"
+       end
+
+       # Get this reponse as a string according to HTTP protocol
+       redef fun to_s: String
+       do
+               finalize
+
+               var buf = new FlatBuffer
+               buf.append("{http_version} {status_code} {status_message or else ""}\r\n")
+               for key, value in header do
+                       buf.append("{key}: {value}\r\n")
+               end
+               buf.append("\r\n{body}")
+               return buf.to_s
+       end
+end
+
+# Helper class to associate HTTP status code to their message
+#
+# You probably want the default instance available as the top-level method
+# `http_status_codes`.
+class HttpStatusCodes
+
+       # All know code and their message
+       var codes = new HashMap[Int, String]
+
+       protected init do insert_status_codes
+
+       # Get the message associated to the status `code`, return `null` in unknown
+       fun [](code: Int): nullable String
+       do
+               if codes.keys.has(code) then
+                       return codes[code]
+               else return null
+       end
+
+       private fun insert_status_codes
+       do
+               codes[100] = "Continue"
+               codes[101] = "Switching Protocols"
+               codes[200] = "OK"
+               codes[201] = "Created"
+               codes[202] = "Accepted"
+               codes[203] = "Non-Authoritative Information"
+               codes[204] = "No Content"
+               codes[205] = "Reset Content"
+               codes[206] = "Partial Content"
+               codes[300] = "Multiple Choices"
+               codes[301] = "Moved Permanently"
+               codes[302] = "Found"
+               codes[303] = "See Other"
+               codes[304] = "Not Modified"
+               codes[305] = "Use Proxy"
+               codes[307] = "Temporary Redirect"
+               codes[400] = "Bad Request"
+               codes[401] = "Unauthorized"
+               codes[402] = "Payment Requred"
+               codes[403] = "Forbidden"
+               codes[404] = "Not Found"
+               codes[405] = "Method Not Allowed"
+               codes[406] = "Not Acceptable"
+               codes[407] = "Proxy Authentication Required"
+               codes[408] = "Request Timeout"
+               codes[409] = "Conflict"
+               codes[410] = "Gone"
+               codes[411] = "Length Required"
+               codes[412] = "Precondition Failed"
+               codes[413] = "Request Entity Too Large"
+               codes[414] = "Request-URI Too Long"
+               codes[415] = "Unsupported Media Type"
+               codes[416] = "Requested Range Not Satisfiable"
+               codes[417] = "Expectation Failed"
+               codes[500] = "Internal Server Error"
+               codes[501] = "Not Implemented"
+               codes[502] = "Bad Gateway"
+               codes[503] = "Service Unavailable"
+               codes[504] = "Gateway Timeout"
+               codes[505] = "HTTP Version Not Supported"
+       end
+end
+
+# Get the default instance of `HttpStatusCodes`
+fun http_status_codes: HttpStatusCodes do return once new HttpStatusCodes
diff --git a/lib/nitcorn/media_types.nit b/lib/nitcorn/media_types.nit
new file mode 100644 (file)
index 0000000..bf5d74a
--- /dev/null
@@ -0,0 +1,101 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2013 Justin Michaud-Ouellette
+# Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
+#
+# 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.
+
+# Services to identify Internet media types (or MIME types, Content-types)
+module media_types
+
+# Map of known MIME types
+class MediaTypes
+       protected var types = new HashMap[String, String]
+
+       # Get the type/subtype associated to a file extension `ext`
+       fun [](ext: String): nullable String
+       do
+               if types.keys.has(ext) then return types[ext]
+               return null
+       end
+
+       init
+       do
+               types["html"]       = "text/html"
+               types["htm"]        = "text/html"
+               types["shtml"]      = "text/html"
+               types["css"]        = "text/css"
+               types["xml"]        = "text/xml"
+               types["rss"]        = "text/xml"
+               types["gif"]        = "image/gif"
+               types["jpg"]        = "image/jpeg"
+               types["jpeg"]       = "image/jpeg"
+               types["js"]         = "application/x-javascript"
+               types["txt"]        = "text/plain"
+               types["htc"]        = "text/x-component"
+               types["mml"]        = "text/mathml"
+               types["png"]        = "image/png"
+               types["ico"]        = "image/x-icon"
+               types["jng"]        = "image/x-jng"
+               types["wbmp"]       = "image/vnd.wap.wbmp"
+               types["jar"]        = "application/java-archive"
+               types["war"]        = "application/java-archive"
+               types["ear"]        = "application/java-archive"
+               types["hqx"]        = "application/mac-binhex40"
+               types["pdf"]        = "application/pdf"
+               types["cco"]        = "application/x-cocoa"
+               types["jardiff"]    = "application/x-java-archive-diff"
+               types["jnlp"]       = "application/x-java-jnlp-file"
+               types["run"]        = "application/x-makeself"
+               types["pl"]         = "application/x-perl"
+               types["pm"]         = "application/x-perl"
+               types["pdb"]        = "application/x-pilot"
+               types["prc"]        = "application/x-pilot"
+               types["rar"]        = "application/x-rar-compressed"
+               types["rpm"]        = "application/x-redhat-package-manager"
+               types["sea"]        = "application/x-sea"
+               types["swf"]        = "application/x-shockwave-flash"
+               types["sit"]        = "application/x-stuffit"
+               types["tcl"]        = "application/x-tcl"
+               types["tk"]         = "application/x-tcl"
+               types["der"]        = "application/x-x509-ca-cert"
+               types["pem"]        = "application/x-x509-ca-cert"
+               types["crt"]        = "application/x-x509-ca-cert"
+               types["xpi"]        = "application/x-xpinstall"
+               types["zip"]        = "application/zip"
+               types["deb"]        = "application/octet-stream"
+               types["bin"]        = "application/octet-stream"
+               types["exe"]        = "application/octet-stream"
+               types["dll"]        = "application/octet-stream"
+               types["dmg"]        = "application/octet-stream"
+               types["eot"]        = "application/octet-stream"
+               types["iso"]        = "application/octet-stream"
+               types["img"]        = "application/octet-stream"
+               types["msi"]        = "application/octet-stream"
+               types["msp"]        = "application/octet-stream"
+               types["msm"]        = "application/octet-stream"
+               types["mp3"]        = "audio/mpeg"
+               types["ra"]         = "audio/x-realaudio"
+               types["mpeg"]       = "video/mpeg"
+               types["mpg"]        = "video/mpeg"
+               types["mov"]        = "video/quicktime"
+               types["flv"]        = "video/x-flv"
+               types["avi"]        = "video/x-msvideo"
+               types["wmv"]        = "video/x-ms-wmv"
+               types["asx"]        = "video/x-ms-asf"
+               types["asf"]        = "video/x-ms-asf"
+               types["mng"]        = "video/x-mng"
+       end
+end
+
+fun media_types: MediaTypes do return once new MediaTypes
diff --git a/lib/nitcorn/nitcorn.nit b/lib/nitcorn/nitcorn.nit
new file mode 100644 (file)
index 0000000..2a5b43a
--- /dev/null
@@ -0,0 +1,63 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
+#
+# 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.
+
+# The nitcorn Web server framework creates server-side Web apps in Nit
+#
+# The main classes are:
+# * `Action` to answer to requests.
+# * `Route` to represent a path to an action.
+# * `VirtualHost` to listen on a specific interface and behave accordingly
+# * `HttpFactory` which is the base dispatcher class.
+#
+# Basic usage example:
+# ~~~~
+# class MyAction
+#      super Action
+#
+#      redef fun answer(http_request, turi)
+#      do
+#              var response = new HttpResponse(200)
+#              response.body = """
+#              <!DOCTYPE html>
+#              <head>
+#                      <meta charset="utf-8">
+#                      <title>Hello World</title>
+#              </head>
+#              <body>
+#                      <p>Hello World</p>
+#              </body>
+#              </html>"""
+#              return response
+#      end
+# end
+#
+# var vh = new VirtualHost("localhost:80")
+#
+# # Serve index.html with our custom handler
+# vh.routes.add new Route("/index.html", new MyAction)
+#
+# # Serve everything else with a standard `FileServer`
+# vh.routes.add new Route(null, new FileServer("/var/www/"))
+#
+# var factory = new HttpFactory.and_libevent
+# factory.config.virtual_hosts.add vh
+# factory.run
+# ~~~~
+module nitcorn
+
+import reactor
+import file_server
+import sessions
diff --git a/lib/nitcorn/reactor.nit b/lib/nitcorn/reactor.nit
new file mode 100644 (file)
index 0000000..a8f9236
--- /dev/null
@@ -0,0 +1,182 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2013 Jean-Philippe Caissy <jpcaissy@piji.ca>
+# Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
+#
+# 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.
+
+# Core of the `nitcorn` project, provides `HttpFactory` and `Action`
+module reactor
+
+import more_collections
+import libevent
+
+import server_config
+import http_request
+import http_response
+
+# A server handling a single connection
+class HttpServer
+       super Connection
+
+       # The associated `HttpFactory`
+       var factory: HttpFactory
+
+       init(buf_ev: NativeBufferEvent, factory: HttpFactory) do self.factory = factory
+
+       private var parser = new HttpRequestParser is lazy
+
+       redef fun read_callback(str)
+       do
+               # TODO support bigger inputs (such as big forms and file upload)
+
+               var request_object = parser.parse_http_request(str.to_s)
+
+               if request_object != null then delegate_answer request_object
+       end
+
+       # Answer to a request
+       fun delegate_answer(request: HttpRequest)
+       do
+               # Find target virtual host
+               var virtual_host = null
+               if request.header.keys.has("Host") then
+                       var host = request.header["Host"]
+                       if host.index_of(':') == -1 then host += ":80"
+                       for vh in factory.config.virtual_hosts do
+                               for i in vh.interfaces do if i.to_s == host then
+                                       virtual_host = vh
+                                       break label
+                               end
+                       end label
+               end
+
+               # Get a response from the virtual host
+               var response
+               if virtual_host != null then
+                       var route = virtual_host.routes[request.uri]
+                       if route != null then
+                               var handler = route.handler
+                               var root = route.path
+                               var turi
+                               if root != null then
+                                       turi = ("/" + request.uri.substring_from(root.length)).simplify_path
+                               else turi = request.uri
+                               response = handler.answer(request, turi)
+                       else response = new HttpResponse(405)
+               else response = new HttpResponse(405)
+
+               # Send back a response
+               write response.to_s
+               close
+       end
+end
+
+redef abstract class Action
+       # Handle a request with the relative URI `truncated_uri`
+       #
+       # `request` is fully formed request object and has a reference to the session
+       # if one preexists.
+       #
+       # `truncated_uri` is the ending of the fulle request URI, truncated from the route
+       # leading to this `Action`.
+       fun answer(request: HttpRequest, truncated_uri: String): HttpResponse is abstract
+end
+
+# Factory to create `HttpServer` instances, and hold the libevent base handler
+class HttpFactory
+       super ConnectionFactory
+
+       # Configuration of this server
+       #
+       # It should be populated after this object has instanciated
+       var config = new ServerConfig.with_factory(self)
+
+       # Instanciate a server and libvent
+       #
+       # You can use this to create the first `HttpFactory`, which is the most common.
+       init and_libevent do init(new NativeEventBase)
+
+       redef fun spawn_connection(buf_ev) do return new HttpServer(buf_ev, self)
+
+       # Launch the main loop of this server
+       fun run
+       do
+               event_base.dispatch
+               event_base.destroy
+       end
+end
+
+redef class ServerConfig
+       # Handle to retreive the `HttpFactory` on config change
+       private var factory: HttpFactory
+
+       private init with_factory(factory: HttpFactory) do self.factory = factory
+end
+
+redef class Sys
+       # Active listeners
+       private var listeners = new HashMap2[String, Int, ConnectionListener]
+
+       # Hosts needong each listener
+       private var listeners_count = new HashMap2[String, Int, Int]
+
+       # Activate a listener on `interfac` if there's not already one
+       private fun listen_on(interfac: Interface, factory: HttpFactory)
+       do
+               if interfac.registered then return
+
+               var name = interfac.name
+               var port = interfac.port
+
+               var listener = listeners[name, port]
+               if listener == null then
+                       listener = factory.bind_to(name, port)
+                       if listener != null then
+                               sys.listeners[name, port] = listener
+                               listeners_count[name, port] = 1
+                       end
+               else
+                       listeners_count[name, port] += 1
+               end
+
+               interfac.registered = true
+       end
+
+       # TODO close listener
+end
+
+redef class Interface
+       # Has `self` been registered by `listen_on`?
+       private var registered = false
+end
+
+redef class Interfaces
+       redef fun add(e)
+       do
+               super
+               if vh.server_config != null then sys.listen_on(e, vh.server_config.factory)
+       end
+
+       # TODO remove
+end
+
+redef class VirtualHosts
+       redef fun add(e)
+       do
+               super
+               for i in e.interfaces do sys.listen_on(i, config.factory)
+       end
+
+       # TODO remove
+end
diff --git a/lib/nitcorn/server_config.nit b/lib/nitcorn/server_config.nit
new file mode 100644 (file)
index 0000000..2decb16
--- /dev/null
@@ -0,0 +1,134 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
+#
+# 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.
+
+# Classes and services to configure the server
+#
+# The classes of interest are `VirtualHost`, `Interface`, `Route` and `Action`
+module server_config
+
+# Server instance configuration
+class ServerConfig
+       # Virtual hosts list
+       var virtual_hosts = new VirtualHosts(self)
+
+       # TODO implement serialization or something like that
+end
+
+# A `VirtualHost` configuration
+class VirtualHost
+       # Back reference to the associated server configuration
+       var server_config: nullable ServerConfig = null
+
+       # Interfaces on which `self` is active
+       var interfaces = new Interfaces(self)
+
+       # Routes and thus `Action`s active on `self`
+       var routes = new Routes(self)
+
+       # Create a virtual host from interfaces as strings
+       init(interfaces: String ...)
+       do
+               for i in interfaces do self.interfaces.add_from_string i
+       end
+end
+
+# An interface composed of a `name`:`port`
+class Interface
+       # Name of this interface (such as "localhost", "example.org", etc.)
+       var name: String
+
+       # The port to open
+       var port: Int
+
+       redef fun to_s do return "{name}:{port}"
+end
+
+# A route to an `Action` according to a `path`
+class Route
+       # Path to this action present in the URI
+       var path: nullable String
+
+       # `Action` to activate when this route is traveled
+       var handler: Action
+end
+
+# Action executed to answer a request
+abstract class Action
+end
+
+### Intelligent lists ###
+
+# A list of interfaces with dynamic port listeners
+class Interfaces
+       super Array[Interface]
+
+       # Back reference to the associtated `VirtualHost`
+       var vh: VirtualHost
+
+       # Add an `Interface` described by `text` formatted as `interface.name.com:port`
+       fun add_from_string(text: String)
+       do
+               assert text.chars.count(':') <= 1
+
+               var words = text.split(':')
+               var name = words[0]
+               var port
+               if words.length > 1 then
+                       port = words[1].to_i
+               else port = 80
+
+               add new Interface(name, port)
+       end
+end
+
+# A list of virtual hosts with dynamic port listeners
+class VirtualHosts
+       super Array[VirtualHost]
+
+       # Back reference to the server config
+       var config: ServerConfig
+
+       redef fun add(e)
+       do
+               super
+
+               e.server_config = config
+       end
+end
+
+# A list of routes with the search method `[]`
+class Routes
+       # Back reference to the config of the virtual host
+       var config: VirtualHost
+
+       private var array = new Array[Route]
+
+       # Add `e` to `self`
+       fun add(e: Route) do array.add e
+
+       # Remove `e` from `self`
+       fun remove(e: Route) do array.remove e
+
+       # Get the first `Route` than has `key` as prefix to its path
+       fun [](key: String): nullable Route
+       do
+               for route in array do
+                       var path = route.path
+                       if path == null or key.has_prefix(path) then return route
+               end
+               return null
+       end
+end
diff --git a/lib/nitcorn/sessions.nit b/lib/nitcorn/sessions.nit
new file mode 100644 (file)
index 0000000..31bae5d
--- /dev/null
@@ -0,0 +1,109 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
+#
+# 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.
+
+# Automated session management
+#
+# When parsing a request, this module associate a pre-existing session
+# to the request if there is one. It will also send the required cookie
+# with the response if a session has been associated to the response object.
+module sessions
+
+import md5
+
+import server_config
+import http_request
+import http_response
+
+# A server side session
+class Session
+
+       # Hashed id used both client and server side to identify this `Session`
+       var id_hash: String
+
+       init
+       do
+               self.id_hash = sys.next_session_hash
+               sys.sessions[self.id_hash] = self
+       end
+end
+
+redef class Sys
+       # Active sessions
+       var sessions = new HashMap[String, Session]
+
+       # Get the next session hash available, and increment the session id cache
+       fun next_session_hash: String
+       do
+               var id = next_session_id_cache
+               # On firt evocation, seed the pseudo random number generator
+               if id == null then
+                       srand
+                       id = 1000000.rand
+               end
+
+               next_session_id_cache = id + 1
+
+               return id.to_id_hash
+       end
+
+       private var next_session_id_cache: nullable Int = null
+
+       # Salt used to hash the session id
+       protected var session_salt = "Default unitcorn session salt"
+end
+
+redef class Int
+       # Salt and hash and id to use as `Session.id_hash`
+       private fun to_id_hash: String do return (self.to_s+sys.session_salt).md5
+end
+
+redef class HttpResponse
+       # A `Session` to associate with a response
+       var session: nullable Session = null is writable
+
+       redef fun finalize
+       do
+               super
+
+               var session = self.session
+               if session != null then
+                       header["Set-Cookie"] = "session={session.id_hash}; HttpOnly"
+               end
+       end
+end
+
+redef class HttpRequest
+       # The `Session` associated to this request
+       var session: nullable Session = null
+end
+
+redef class HttpRequestParser
+       redef fun parse_http_request(text)
+       do
+               var request = super
+               if request != null then
+                       if request.cookie.keys.has("session") then
+                               var id_hash = request.cookie["session"]
+
+                               if sys.sessions.keys.has(id_hash) then
+                                       # Restore the session
+                                       request.session = sys.sessions[id_hash]
+                               end
+                       end
+               end
+               return request
+       end
+end
index d6efd3e..19b8ff3 100644 (file)
@@ -245,23 +245,15 @@ end
 # Context where the options process
 class OptionContext
        # Options present in the context
-       var options: Array[Option]
+       var options = new Array[Option]
 
        # Rest of the options after `parse` is called
-       var rest: Array[String]
+       var rest = new Array[String]
 
        # Errors found in the context after parsing
-       var errors: Array[String]
+       var errors = new Array[String]
 
-       private var optmap: Map[String, Option]
-
-       init
-       do
-               options = new Array[Option]
-               optmap = new HashMap[String, Option]
-               rest = new Array[String]
-               errors = new Array[String]
-       end
+       private var optmap = new HashMap[String, Option]
 
        # Add one or more options to the context
        fun add_option(opts: Option...) do
index d194362..d016a4c 100644 (file)
@@ -127,7 +127,6 @@ class Statement
        fun iterator: StatementIterator
        do
                native_statement.reset
-               native_statement.step
                return new StatementIterator(self)
        end
 end
@@ -228,7 +227,7 @@ class StatementEntry
 
                var native_string = statement.native_statement.column_text(index)
                if native_string.address_is_null then return ""
-               return native_string.to_s
+               return native_string.to_s_with_copy
        end
 
        # Get this entry as `Blob`
@@ -260,11 +259,13 @@ class StatementIterator
        do
                self.statement = s
                self.item = new StatementRow(s)
+
+               self.is_ok = statement.native_statement.step.is_row
        end
 
        redef var item: StatementRow
 
-       redef var is_ok = true
+       redef var is_ok: Bool
 
        # require: `self.statement.is_open`
        redef fun next
@@ -290,7 +291,17 @@ interface Sqlite3Data end
 
 redef universal Int super Sqlite3Data end
 redef universal Float super Sqlite3Data end
-redef class String super Sqlite3Data end
+redef class String
+       super Sqlite3Data
+
+       # Return `self` between `'`s and escaping any extra `'`
+       #
+       #     assert "'; DROP TABLE students".to_sql_string == "'''; DROP TABLE students'"
+       fun to_sql_string: String
+       do
+               return "'{self.replace('\'', "''")}'"
+       end
+end
 
 # A Sqlite3 blob
 class Blob
index 862f145..f7883f6 100644 (file)
@@ -26,6 +26,7 @@ in "C Header" `{
        #include <sys/types.h>
        #include <sys/stat.h>
        #include <unistd.h>
+       #include <stdio.h>
 `}
 
 # File Abstract Stream
@@ -37,8 +38,10 @@ abstract class FStream
        # The FILE *.
        var _file: nullable NativeFile = null
 
-       fun file_stat: FileStat
-       do return _file.file_stat end
+       fun file_stat: FileStat do return _file.file_stat
+
+       # File descriptor of this file
+       fun fd: Int do return _file.fileno
 end
 
 # File input stream
@@ -514,6 +517,7 @@ private extern class NativeFile `{ FILE* `}
        fun io_write(buf: NativeString, len: Int): Int is extern "file_NativeFile_NativeFile_io_write_2"
        fun io_close: Int is extern "file_NativeFile_NativeFile_io_close_0"
        fun file_stat: FileStat is extern "file_NativeFile_NativeFile_file_stat_0"
+       fun fileno: Int `{ return fileno(recv); `}
 
        new io_open_read(path: NativeString) is extern "file_NativeFileCapable_NativeFileCapable_io_open_read_1"
        new io_open_write(path: NativeString) is extern "file_NativeFileCapable_NativeFileCapable_io_open_write_1"
index c17e9bd..ebc3377 100644 (file)
@@ -99,6 +99,14 @@ private class StringLeaf
        redef fun to_leaf do return self
 end
 
+# Used as a cache when using indexed access to a substring in the Rope
+private class LeafCache
+       # Cached leaf
+       var leaf: Leaf
+       # Position in Rope
+       var pos: Int
+end
+
 # Basic structure, binary tree with a root node.
 #
 # Also shared services by subsequent implementations.
@@ -111,6 +119,8 @@ abstract class Rope
        # Cached version of self as a flat String
        private var str_representation: nullable NativeString = null
 
+       private var leaf_cache: nullable LeafCache = null
+
        # Empty Rope
        init do from("")
 
@@ -225,7 +235,10 @@ abstract class Rope
        private fun get_node_from(node: RopeNode, curr_pos: Int, seek_pos: Int, stack: List[PathElement]): Path
        do
                assert curr_pos >= 0
-               if node isa Leaf then return new Path(node, seek_pos - curr_pos, stack)
+               if node isa Leaf then
+                       self.leaf_cache = new LeafCache(node, curr_pos)
+                       return new Path(node, seek_pos - curr_pos, stack)
+               end
                node = node.as(Concat)
 
                if node.left != null then
@@ -296,7 +309,18 @@ class RopeString
                return ret
        end
 
-       redef fun +(o) do return insert_at(o.to_s, length)
+       redef fun +(o) do
+               if self.length == 0 then return o.to_s
+               if o.length == 0 then return self
+               var str = o.to_s
+               if str isa FlatString then
+                       return new RopeString.from_root(new Concat(root, new StringLeaf(str)))
+               else if str isa RopeString then
+                       return new RopeString.from_root(new Concat(root, str.root))
+               else
+                       abort
+               end
+       end
 
        redef fun *(n)
        do
@@ -439,6 +463,7 @@ private class RopeStringChars
        redef fun [](pos)
        do
                assert pos < tgt.length
+               if tgt.leaf_cache != null and pos >= tgt.leaf_cache.pos and (tgt.leaf_cache.pos + tgt.leaf_cache.leaf.length) > pos then return tgt.leaf_cache.leaf.str.chars[pos - tgt.leaf_cache.pos]
                var path = tgt.node_at(pos)
                return path.leaf.str.chars[path.offset]
        end
index 5d158b5..45e5712 100644 (file)
@@ -293,6 +293,21 @@ abstract class Text
                return true
        end
 
+       # Returns `true` if the string contains only Hex chars
+       #
+       #     assert "048bf".is_hex  == true
+       #     assert "ABCDEF".is_hex  == true
+       #     assert "0G".is_hex == false
+       fun is_hex: Bool
+       do
+               for c in self.chars do
+                       if not (c >= 'a' and c <= 'f') and
+                          not (c >= 'A' and c <= 'F') and
+                          not (c >= '0' and c <= '9') then return false
+               end
+               return true
+       end
+
        # Are all letters in `self` upper-case ?
        #
        #     assert "HELLO WORLD".is_upper == true
@@ -474,6 +489,71 @@ abstract class Text
                return res.to_s
        end
 
+       # Encode `self` to percent (or URL) encoding
+       #
+       #     assert "aBc09-._~".to_percent_encoding == "aBc09-._~"
+       #     assert "%()< >".to_percent_encoding == "%25%28%29%3c%20%3e"
+       #     assert ".com/post?e=asdf&f=123".to_percent_encoding == ".com%2fpost%3fe%3dasdf%26f%3d123"
+       fun to_percent_encoding: String
+       do
+               var buf = new FlatBuffer
+
+               for c in self.chars do
+                       if (c >= '0' and c <= '9') or
+                          (c >= 'a' and c <= 'z') or
+                          (c >= 'A' and c <= 'Z') or
+                          c == '-' or c == '.' or
+                          c == '_' or c == '~'
+                       then
+                               buf.add c
+                       else buf.append "%{c.ascii.to_hex}"
+               end
+
+               return buf.to_s
+       end
+
+       # Decode `self` from percent (or URL) encoding to a clear string
+       #
+       # Replace invalid use of '%' with '?'.
+       #
+       #     assert "aBc09-._~".from_percent_encoding == "aBc09-._~"
+       #     assert "%25%28%29%3c%20%3e".from_percent_encoding == "%()< >"
+       #     assert ".com%2fpost%3fe%3dasdf%26f%3d123".from_percent_encoding == ".com/post?e=asdf&f=123"
+       #     assert "%25%28%29%3C%20%3E".from_percent_encoding == "%()< >"
+       #     assert "incomplete %".from_percent_encoding == "incomplete ?"
+       #     assert "invalid % usage".from_percent_encoding == "invalid ? usage"
+       fun from_percent_encoding: String
+       do
+               var buf = new FlatBuffer
+
+               var i = 0
+               while i < length do
+                       var c = chars[i]
+                       if c == '%' then
+                               if i + 2 >= length then
+                                       # What follows % has been cut off
+                                       buf.add '?'
+                               else
+                                       i += 1
+                                       var hex_s = substring(i, 2)
+                                       if hex_s.is_hex then
+                                               var hex_i = hex_s.to_hex
+                                               buf.add hex_i.ascii
+                                               i += 1
+                                       else
+                                               # What follows a % is not Hex
+                                               buf.add '?'
+                                               i -= 1
+                                       end
+                               end
+                       else buf.add c
+
+                       i += 1
+               end
+
+               return buf.to_s
+       end
+
        # Equality of text
        # Two pieces of text are equals if thez have the same characters in the same order.
        #
index 8dcf90a..0786ec9 100644 (file)
@@ -355,6 +355,17 @@ redef class Text
        # @deprecated alias for `split`
        fun split_with(p: Pattern): Array[SELFTYPE] do return self.split(p)
 
+       # Split `self` on the first `=`
+       #
+       #     assert "hello".split_once_on('l') == ["he", "lo"]
+       #     assert "a, b, c, d, e".split_once_on(", ") == ["a", "b, c, d, e"]
+       fun split_once_on(p: Pattern): Array[SELFTYPE]
+       do
+               var m = p.search_in(self, 0)
+               if m == null then return [self]
+               return new Array[SELFTYPE].with_items(substring(0, m.from), substring_from(m.after))
+       end
+
        # Replace all occurences of a pattern with a string
        #
        #     assert "hlelo".replace("le", "el")             ==  "hello"
diff --git a/lib/string_experimentations/README b/lib/string_experimentations/README
new file mode 100644 (file)
index 0000000..fa09366
--- /dev/null
@@ -0,0 +1,17 @@
+This project is a collection of modules used to experiment on different variations of Text and its subclasses.
+This is only temporary as these modules will eventually be merged into standard library or discarded for those bringing no real improvements to the language.
+
+The modules contained here are :
+
+ * utf8: A draft of implementation of UTF-8 as internal encoding for Strings with automatic indexing.
+
+TODO :
+
+ * utf8:
+  * Support for the whole API of Text
+  * Any kind of normalization form for equality (NFC probably)
+  * Compatibility versions of equality test
+  * Locale support
+  * Comparisons
+  * to_upper/lower fully-compatible with Unicode
+  * Lazy indexing version
diff --git a/lib/string_experimentations/string_experimentations.nit b/lib/string_experimentations/string_experimentations.nit
new file mode 100644 (file)
index 0000000..1eeb10d
--- /dev/null
@@ -0,0 +1,18 @@
+# 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.
+
+# General module for all kinds of string experimentations
+module string_experimentations
+
+import utf8
diff --git a/lib/string_experimentations/utf8.nit b/lib/string_experimentations/utf8.nit
new file mode 100644 (file)
index 0000000..7261820
--- /dev/null
@@ -0,0 +1,413 @@
+# 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.
+
+# Introduces UTF-8 as internal encoding for Strings in Nit.
+module utf8
+
+intrude import standard::string
+intrude import standard::file
+
+in "C Header" `{
+
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+
+typedef struct {
+       long pos;
+       char* ns;
+} UTF8Char;
+
+`}
+
+# UTF-8 char as defined in RFC-3629, e.g. 1-4 Bytes
+#
+# A UTF-8 char has its bytes stored in a NativeString (char*)
+extern class UnicodeChar `{ UTF8Char* `}
+
+       new(pos: Int, ns: NativeString) `{
+               UTF8Char* u = malloc(sizeof(UTF8Char));
+               u->pos = pos;
+               u->ns = ns;
+               return u;
+       `}
+
+       # Real length of the char in UTF8
+       #
+       # As per the specification :
+       #
+       #  Length  |        UTF-8 octet sequence
+       #          |              (binary)
+       # ---------+-------------------------------------------------
+       #  1       | 0xxxxxxx
+       #  2       | 110xxxxx 10xxxxxx
+       #  3       | 1110xxxx 10xxxxxx 10xxxxxx
+       #  4       | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+       private fun len: Int `{
+               char* ns = recv->ns;
+               int pos = recv->pos;
+               char nspos = ns[pos];
+               if((nspos & 0x80) == 0x00){ return 1;}
+               if((nspos & 0xE0) == 0xC0){ return 2;}
+               if((nspos & 0xF0) == 0xE0){ return 3;}
+               if((nspos & 0xF7) == 0xF0){ return 4;}
+               // Invalid character
+               return 1;
+       `}
+
+       # Position in containing NativeString
+       private fun pos: Int `{
+               return recv->pos;
+       `}
+
+       private fun pos=(p: Int) `{recv->pos = p;`}
+
+       # C char* wrapping the char
+       fun ns: NativeString `{
+               return recv->ns;
+       `}
+
+       # Returns the Unicode code point representing the character
+       #
+       # Note : A unicode character might not be a visible glyph, but it will be used to determine canonical equivalence
+       fun code_point: Int import UnicodeChar.len `{
+               switch(UnicodeChar_len(recv)){
+                       case 1:
+                               return (long)(0x7F & (unsigned char)recv->ns[recv->pos]);
+                       case 2:
+                               return 0 | ((0x1F & (unsigned char)recv->ns[recv->pos]) << 6) | (0x3F & (unsigned char)recv->ns[recv->pos+1]);
+                       case 3:
+                               return 0 | ((0x0F & (unsigned char)recv->ns[recv->pos]) << 12) |
+                               ((0x3F & (unsigned char)recv->ns[recv->pos+1]) << 6) |
+                               (0x3F & (unsigned char)recv->ns[recv->pos+2]);
+                       case 4:
+                               return 0 | ((0x07 & (unsigned char)recv->ns[recv->pos]) << 18) |
+                               ((0x3F & (unsigned char)recv->ns[recv->pos+1]) << 12) |
+                               ((0x3F & (unsigned char)recv->ns[recv->pos+2]) << 6) |
+                               (0x3F & (unsigned char)recv->ns[recv->pos+3]);
+               }
+       `}
+
+       # Returns an upper-case version of self
+       #
+       # NOTE : Works only on ASCII chars
+       # TODO : Support unicode for to_upper
+       fun to_upper: UnicodeChar import UnicodeChar.code_point `{
+               int cp = UnicodeChar_code_point(recv);
+               if(cp < 97 || cp > 122){ return recv; }
+               char* ns = malloc(2);
+               ns[1] = '\0';
+               char c = recv->ns[recv->pos];
+               ns[0] = c - 32;
+               UTF8Char* ret = malloc(sizeof(UTF8Char));
+               ret->ns = ns;
+               ret->pos = 0;
+               return ret;
+       `}
+
+       # Returns an lower-case version of self
+       #
+       # NOTE : Works only on ASCII chars
+       # TODO : Support unicode for to_upper
+       fun to_lower: UnicodeChar import UnicodeChar.code_point `{
+               int cp = UnicodeChar_code_point(recv);
+               if(cp < 65 || cp > 90){ return recv; }
+               char* ns = malloc(2);
+               ns[1] = '\0';
+               char c = recv->ns[recv->pos];
+               ns[0] = c + 32;
+               UTF8Char* ret = malloc(sizeof(UTF8Char));
+               ret->ns = ns;
+               ret->pos = 0;
+               return ret;
+       `}
+
+       redef fun ==(o)
+       do
+               if o isa Char then
+                       if len != 1 then return false
+                       if code_point == o.ascii then return true
+               else if o isa UnicodeChar then
+                       if len != o.len then return false
+                       if code_point == o.code_point then return true
+               end
+               return false
+       end
+
+       redef fun output import UnicodeChar.code_point `{
+               switch(UnicodeChar_len(recv)){
+                       case 1:
+                               printf("%c", recv->ns[recv->pos]);
+                               break;
+                       case 2:
+                               printf("%c%c", recv->ns[recv->pos], recv->ns[recv->pos + 1]);
+                               break;
+                       case 3:
+                               printf("%c%c%c", recv->ns[recv->pos], recv->ns[recv->pos + 1], recv->ns[recv->pos + 2]);
+                               break;
+                       case 4:
+                               printf("%c%c%c%c", recv->ns[recv->pos], recv->ns[recv->pos + 1], recv->ns[recv->pos + 2], recv->ns[recv->pos + 3]);
+                               break;
+               }
+       `}
+
+       redef fun to_s import NativeString.to_s_with_length `{
+               int len = utf8___UnicodeChar_len___impl(recv);
+               char* r = malloc(len + 1);
+               r[len] = '\0';
+               char* src = (recv->ns + recv->pos);
+               memcpy(r, src, len);
+               return NativeString_to_s_with_length(r, len);
+       `}
+end
+
+# A `StringIndex` is used to keep track of the position of characters in a `FlatString` object
+#
+# It becomes mandatory for UTF-8 strings since characters do not have a fixed size.
+private extern class StringIndex `{ UTF8Char* `}
+
+       new(size: Int) `{ return malloc(size*sizeof(UTF8Char)); `}
+
+       # Sets the character at `index` as `item`
+       fun []=(index: Int, item: UnicodeChar) `{ recv[index] = *item; `}
+
+       # Gets the character at position `id`
+       fun [](id: Int): UnicodeChar `{ return &recv[id]; `}
+
+       # Copies a part of self starting at index `my_from` of length `length` into `other`, starting at `its_from`
+       fun copy_to(other: StringIndex, my_from: Int, its_from: Int, length: Int)`{
+               UTF8Char* myfrom = recv + my_from*(sizeof(UTF8Char));
+               UTF8Char* itsfrom = other + its_from*(sizeof(UTF8Char));
+               memcpy(itsfrom, myfrom, length);
+       `}
+end
+
+redef class FlatString
+
+       # Index of the characters of the FlatString
+       private var index: StringIndex
+
+       # Length in bytes of the string (e.g. the length of the C string)
+       var bytelen: Int
+
+       private init with_infos_index(items: NativeString, len: Int, index_from: Int, index_to: Int, index: StringIndex, bytelen: Int)
+       do
+               self.items = items
+               length = len
+               self.index_from = index_from
+               self.index_to = index_to
+               self.index = index
+               self.bytelen = bytelen
+       end
+
+       redef fun to_cstring
+       do
+               if real_items != null then return real_items.as(not null)
+               var new_items = calloc_string(bytelen + 1)
+               self.items.copy_to(new_items, bytelen, index[index_from].pos, 0)
+               new_items[bytelen] = '\0'
+               self.real_items = new_items
+               return new_items
+       end
+
+       redef fun substring(from, count)
+       do
+               assert count >= 0
+
+               if from < 0 then
+                       count += from
+                       if count < 0 then count = 0
+                       from = 0
+               end
+
+               if count == 0 then return empty
+
+               var real_from = index_from + from
+               var real_to = real_from + count - 1
+
+               if real_to > index_to then real_to = index_to
+
+               var sub_bytelen = (index[real_to].pos - index[from].pos) + index[from].len
+
+               return new FlatString.with_infos_index(items, count, real_from, real_to, index, sub_bytelen)
+       end
+
+       redef fun reversed
+       do
+               var native = calloc_string(self.bytelen + 1)
+               var length = self.length
+               var index = self.index
+               var pos = 0
+               var i = 0
+               var ipos = bytelen
+               var new_index = new StringIndex(length)
+               var pos_index = length
+               while i < length do
+                       var uchar = index[i]
+                       var uchar_len = uchar.len
+                       ipos -= uchar_len
+                       new_index[pos_index] = new UnicodeChar(ipos, native)
+                       pos_index -= 1
+                       items.copy_to(native, uchar_len, pos, ipos)
+                       pos += uchar_len
+                       i += 1
+               end
+               return new FlatString.with_infos_index(native, length, 0, length-1, new_index, bytelen)
+       end
+
+       redef fun *(i)
+       do
+               assert i >= 0
+
+               var mylen = self.bytelen
+               var finlen = mylen * i
+
+               var my_items = self.items
+
+               var my_real_len = length
+               var my_real_fin_len = my_real_len * i
+
+               var target_string = calloc_string((finlen) + 1)
+
+               var my_index = index
+               var new_index = new StringIndex(my_real_fin_len)
+
+               target_string[finlen] = '\0'
+
+               var current_last = 0
+               var curr_index = 0
+
+               for iteration in [1 .. i] do
+                       my_items.copy_to(target_string, mylen, index_from, current_last)
+                       my_index.copy_to(new_index, length, 0, curr_index)
+                       current_last += mylen
+               end
+
+               return new FlatString.with_infos_index(target_string, my_real_fin_len, 0, my_real_fin_len -1, new_index, finlen)
+
+       end
+
+       redef fun to_upper
+       do
+               var outstr = calloc_string(self.bytelen + 1)
+
+               var out_index = 0
+               var index = self.index
+               var ipos = 0
+               var max = length
+               var items = self.items
+
+               while ipos < max do
+                       var u = index[ipos].to_upper
+                       u.ns.copy_to(outstr, u.len, u.pos, out_index)
+                       out_index += u.len
+                       ipos += 1
+               end
+
+               outstr[self.bytelen] = '\0'
+
+               return outstr.to_s_with_length(self.bytelen)
+       end
+
+       redef fun to_lower
+       do
+               var outstr = calloc_string(self.bytelen + 1)
+
+               var out_index = 0
+               var index = self.index
+               var ipos = 0
+               var max = length
+               var items = self.items
+
+               while ipos < max do
+                       var u = index[ipos].to_lower
+                       u.ns.copy_to(outstr, u.len, u.pos, out_index)
+                       out_index += u.len
+                       ipos += 1
+               end
+
+               outstr[self.bytelen] = '\0'
+
+               return outstr.to_s_with_length(self.bytelen)
+       end
+
+       redef fun output
+       do
+               var i = self.index_from
+               var imax = self.index_to
+               while i <= imax do
+                       index[i].output
+                       i += 1
+               end
+       end
+
+end
+
+redef class NativeString
+
+       # Creates the index for said NativeString
+       # `length` is the size of the CString (in bytes, up to the first \0)
+       # real_len is just a way to store the length (UTF-8 characters)
+       private fun make_index(length: Int, real_len: Container[Int]): StringIndex import Container[Int].item=, UnicodeChar.len `{
+               int pos = 0;
+               int index_pos = 0;
+               UTF8Char* index = malloc(length*sizeof(UTF8Char));
+               while(pos < length){
+                       UTF8Char* curr = &index[index_pos];
+                       curr->pos = pos;
+                       curr->ns = recv;
+                       pos += UnicodeChar_len(curr);
+                       index_pos ++;
+               }
+               Container_of_Int_item__assign(real_len, index_pos);
+               return index;
+       `}
+
+       redef fun to_s: FlatString
+       do
+               var len = cstring_length
+               return to_s_with_length(len)
+       end
+
+       redef fun to_s_with_length(len: Int): FlatString
+       do
+               var real_len = new Container[Int](0)
+               var x = make_index(len, real_len)
+               return new FlatString.with_infos_index(self, real_len.item, 0, real_len.item - 1, x, len)
+       end
+
+       redef fun to_s_with_copy
+       do
+               var real_len = new Container[Int](0)
+               var length = cstring_length
+               var x = make_index(length, real_len)
+               var new_self = calloc_string(length + 1)
+               copy_to(new_self, length, 0, 0)
+               return new FlatString.with_infos_index(new_self, real_len.item, 0, real_len.item - 1, x, length)
+       end
+end
+
+redef class OFStream
+       redef fun write(s)
+       do
+               assert _writable
+               if s isa FlatText then
+                       if s isa FlatString then
+                               write_native(s.to_cstring, s.bytelen)
+                       else
+                               write_native(s.to_cstring, s.length)
+                       end
+               else for i in s.substrings do write_native(i.to_cstring, i.length)
+       end
+end
index cad98bd..7e9f43a 100644 (file)
@@ -133,21 +133,21 @@ redef class ModelBuilder
                        end
                end
 
-               var sources = new Array[MModule]
-               var annotations = null
+               var annotations = new HashSet[AAnnotation]
                for mmod in mmodule.in_importation.direct_greaters do
                        var res = priority_annotation_on_modules(name, mmod)
-                       if res != null then
-                               sources.add mmod
-                               annotations = res
-                       end
+                       if res != null then annotations.add res
                end
-               if sources.length > 1 then
+               if annotations.length > 1 then
+                       var locs = new Array[Location]
+                       for annot in annotations do locs.add(annot.location)
+
                        toolcontext.error(mmodule.location,
-                               "Priority conflict on annotation {name}, it has been defined in: {sources.join(", ")}")
+                               "Priority conflict on annotation {name}, it has been defined in: {locs.join(", ")}")
                        return null
-               end
-               return annotations
+               else if annotations.length == 1 then
+                       return annotations.first
+               else return null
        end
 end
 
index eefb5c6..a43cab6 100644 (file)
@@ -119,7 +119,11 @@ class JavaLanguage
                        end
                end
 
-               for p in signature.mparameters do params.add(p.name)
+               for p in signature.mparameters do
+                       var param_mtype = p.mtype
+                       param_mtype = param_mtype.resolve_for(mclass_type, mclass_type, mmodule, true)
+                       params.add(to_java_call_context.cast_to(param_mtype, p.name))
+               end
 
                var cname = "(*nit_ffi_jni_env)->CallStatic{jni_signature_alt}Method"
                var ccall
diff --git a/tests/.gitignore b/tests/.gitignore
new file mode 100644 (file)
index 0000000..68eac53
--- /dev/null
@@ -0,0 +1,14 @@
+example.org
+fulltest
+listtest.sh
+modifs
+nit.html
+nitcc0.lr.dot
+nitcc_lexer.nit
+nitcc_parser.nit
+sometests
+test.db
+test_jvm/Queue.class
+test_jvm/Test2.class
+test_jvm/TestJvm.class
+test_nity.db
index c41d71d..7042a5c 100644 (file)
@@ -5,3 +5,8 @@ This directory contains small NIT programs divided in some categories:
 * 'example' are examples 
 * 'test' are others tests
 * 'shootout' are benchmarks from http://shootout.alioth.debian.org/
+
+The .gitignore contains specific artefacts produced by some tests.
+However, it is better to produce these artifacts in the `out` directory instead,
+because `out` is cleaned before runing tests (so that old artefacts do not
+interfere with new executions of tests)
diff --git a/tests/sav/file_server_on_port_80.res b/tests/sav/file_server_on_port_80.res
new file mode 100644 (file)
index 0000000..1d69303
--- /dev/null
@@ -0,0 +1,4 @@
+Mandatory option -u, --usergroup not found.
+Usage: file_server [Options]
+  -u, --usergroup   Drop privileges to user:group or simply user
+  --help, -h        Print this message
diff --git a/tests/sav/utf_test.res b/tests/sav/utf_test.res
new file mode 100644 (file)
index 0000000..4055d93
--- /dev/null
@@ -0,0 +1,11 @@
+28
+すでa語A本日a 𐍆,A ᓂ . ᓀ 界世a𐍃ーЖロaハ
+ハaロЖー𐍃a世界 ᓀ . ᓂ A,𐍆 a日本A語aです
+ー𐍃a世
+30fc
+10343
+61
+4e16
+ハAロЖー𐍃A世界 ᓀ . ᓂ A,𐍆 A日本A語Aです
+ハaロЖー𐍃a世界 ᓀ . ᓂ a,𐍆 a日本a語aです
+ハaロЖー𐍃a世界 ᓀ . ᓂ A,𐍆 a日本A語aですハaロЖー𐍃a世界 ᓀ . ᓂ A,𐍆 a日本A語aです
index 3705de2..3c80b7c 100644 (file)
@@ -21,6 +21,12 @@ class MyClass
        fun foo in "Java" `{
                System.out.println("Hello world");
        `}
+
+       fun bar(i: Int) in "Java" `{
+               System.out.println(i);
+       `}
 end
 
-(new MyClass).foo
+var a = new MyClass
+a.foo
+a.bar(777)
diff --git a/tests/utf_test.nit b/tests/utf_test.nit
new file mode 100644 (file)
index 0000000..88474c4
--- /dev/null
@@ -0,0 +1,42 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# This file is free software, which comes along with NIT.  This software is
+# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without  even  the implied warranty of  MERCHANTABILITY or  FITNESS FOR A
+# PARTICULAR PURPOSE.  You can modify it is you want,  provided this header
+# is kept unaltered, and a notification of the changes is added.
+# You  are  allowed  to  redistribute it and sell it, alone or is a part of
+# another product.
+
+import standard
+intrude import string_experimentations::utf8
+
+var s = "aàハ𐍆".as(FlatString)
+assert s.index[0].code_point == 97
+assert s.index[1].code_point == 224
+assert s.index[2].code_point == 12495
+assert s.index[3].code_point == 66374
+
+var str = "ハaロЖー𐍃a世界 ᓀ . ᓂ A,𐍆 a日本A語aです".as(FlatString)
+
+print str.length
+
+print str.reversed
+
+str.output
+
+print ""
+
+var x = str.substring(4,4).as(FlatString)
+
+print x
+
+for i in [0..x.length[ do
+       print x.index[i + x.index_from].code_point.to_hex
+end
+
+print str.to_upper
+
+print str.to_lower
+
+print str * 2