Merge: android/audio: fix multiple music playing when pausing app
authorJean Privat <jean@pryen.org>
Wed, 16 Sep 2015 17:11:50 +0000 (13:11 -0400)
committerJean Privat <jean@pryen.org>
Wed, 16 Sep 2015 17:11:50 +0000 (13:11 -0400)
Asked by @privat because friendz's app played multiple musics when pausing/resuming the app

Pull-Request: #1721
Reviewed-by: Jean Privat <jean@pryen.org>
Reviewed-by: Alexis Laferrière <alexis.laf@xymus.net>
Reviewed-by: Lucas Bajolet <r4pass@hotmail.com>

23 files changed:
contrib/friendz/src/friendz.nit
contrib/friendz/src/grid.nit
contrib/friendz/src/level.nit
contrib/opportunity/src/opportunity_controller.nit
contrib/opportunity/src/opportunity_model.nit
examples/rosettacode/sha_1.nit
lib/base64.nit
lib/core/bytes.nit
lib/core/stream.nit
lib/core/text/flat.nit
lib/matrix/matrix.nit [new file with mode: 0644]
lib/matrix/package.ini [new file with mode: 0644]
lib/matrix/projection.nit [new file with mode: 0644]
lib/sha1.nit
lib/websocket/websocket.nit
misc/README.md
misc/vim/plugin/nit.vim
src/nitcatalog.nit
tests/sav/nitcg/test_text_stat.res
tests/sav/nitserial_args1.res
tests/sav/test_bytes_hexdigit.res [new file with mode: 0644]
tests/sav/test_text_stat.res
tests/test_bytes_hexdigit.nit [new file with mode: 0644]

index 9425abe..c0554fe 100644 (file)
@@ -19,7 +19,6 @@ import realtime
 import solver
 import mnit::tileset
 import app::data_store
-import md5
 
 intrude import grid
 intrude import level
@@ -217,11 +216,11 @@ class LevelButton
 
                self.over = self.level.fullname
                if self.level.get_state >= l.l_won then
-                       if game.levels[9].get_state >= l.l_won then self.over += " --- {self.level.score}/{self.level.par}"
+                       if game.levels[9].get_state >= l.l_won then self.over += " --- {self.level.score}/{self.level.gold}"
                else if self.level.get_state >= l.l_open then
-                       if game.levels[9].get_state >= l.l_open then self.over +=  " --- ?/{self.level.par}"
+                       if game.levels[9].get_state >= l.l_open then self.over +=  " --- ?/{self.level.gold}"
                end
-               #self.enabled = l.get_state >= l.l_open
+               self.enabled = l.get_state >= l.l_open or game.cheated
        end
 
        redef fun draw(ctx)
@@ -242,7 +241,7 @@ class LevelButton
                end
                ctx.blit(game.img[ix,iy], self.x, self.y)
 
-               if s == l.l_par then
+               if s == l.l_gold then
                        ctx.blit(game.img2[7,0], self.x + bw*5/8, self.y-bh*1/8)
                end
                ctx.textx(self.level.name, self.x+5, self.y+5, 24, null, null)
@@ -730,9 +729,9 @@ class Score
                end
                if game.levels[9].get_state >= level.l_won then
                        if level.is_challenge then
-                               ctx.textx("GOAL: {level.par}",self.x,self.y+44,21,"yellow",null)
+                               ctx.textx("GOAL: {level.gold}",self.x,self.y+44,21,"yellow",null)
                        else
-                               ctx.textx("PAR: {level.par}",self.x,self.y+44,21,"yellow",null)
+                               ctx.textx("GOLD: {level.gold}",self.x,self.y+44,21,"yellow",null)
                        end
                end
        end
@@ -777,7 +776,7 @@ class StatusBar
        do
                print "***STATUS** {txt}"
                self.tmp_txt = txt
-               self.tmp_txt_ttl = 20
+               self.tmp_txt_ttl = 60
                self.tmp_txt_color = color
        end
 
@@ -873,26 +872,6 @@ redef class Game
        # Font
        var font = new TileSetFont(app.load_image("deltaforce_font.png"), 16, 17, "ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789.:;!?\"'() -,/")
 
-       var xxx = """
-       fun save_cookie(name, val:String) do
-       var days = 365
-       var date = new Date()
-       date.setTime(date.getTime()+(days*24*60*60*1000))
-       document.cookie = name+"="+val+"; expires="+date.toGMTString()+"; path=/"
-       end
-
-       fun read_cookie(name:String):String do
-       var key = name + "="
-       var ca = document.cookie.split(';')
-       for(var i=0; i<ca.length; i++) do
-       var c = ca[i]
-       while (c[0]==' ') c = c.substring(1, c.length)
-       if (c.indexOf(key) == 0) return c.substring(key.length)
-       end
-       return null
-       end
-       """
-
        # DISPLAY *****************************************************************
 
        # Is the game in editing mode
@@ -904,10 +883,10 @@ redef class Game
        # SOUND
 
        # Is the music muted?
-       var music_muted: Bool = false #read_cookie("music_muted")
+       var music_muted: Bool = app.data_store["music_muted"] == true
 
        # Is the sound effects muted?
-       var sfx_muted: Bool = false #read_cookie("sfx_muted")
+       var sfx_muted: Bool = app.data_store["sfx_muted"] == true
 
        # The background music resource. */
        var music = new Music("music.ogg")
@@ -957,12 +936,16 @@ redef class Game
        # ResizeButton
        var button_size = new ResizeButton(self)
 
+       # Cheat mode enabled?
+       var cheated = false
+
        init
        do
-               load_levels
                init_buttons
                entities.clear
                title
+
+               if not music_muted then music.play
        end
 
        # fill `buttons`
@@ -976,8 +959,9 @@ redef class Game
        # Play a level in player mode.
        fun play(l: Level)
        do
+               save # save the previous level grid
                level = l
-               grid.load(level.str)
+               grid.load(level.saved_str or else level.str)
                init_play_menu(false)
                if level.status != "" then
                        statusbar.main_txt = level.status
@@ -1071,6 +1055,7 @@ redef class Game
        # Helper function to initialize the menu (and tile) screen
        fun init_menu
        do
+               save # save the previous level grid
                init_game
                level = null
                var i = levels.first
@@ -1094,7 +1079,7 @@ redef class Game
                end
                t = new Achievement(self, 0, "Training")
                entities.push(t)
-               t = new Achievement(self, 1, "Par")
+               t = new Achievement(self, 1, "Gold")
                entities.push(t)
                t = new Achievement(self, 2, "Editor")
                entities.push(t)
@@ -1225,8 +1210,19 @@ redef class Game
        fun onKeyDown(ev: Event) do
                var kc = ev.char_code
                if kc == "e" then
+                       set_tmp("RUN EDITOR")
                        grid_edit = grid.copy(true)
                        edit_grid(grid)
+               else if kc == "c" then
+                       if cheated then
+                               set_tmp("CHEAT: OFF")
+                               snd_duh.play
+                               cheated = false
+                       else
+                               set_tmp("CHEAT: ON")
+                               snd_win.play
+                               cheated = true
+                       end
                else if kc == "s" then
                        if solver == null then
                                solver = (new FriendzProblem(grid)).solve
@@ -1234,20 +1230,28 @@ redef class Game
                        else
                                solver_pause = not solver_pause
                        end
+                       if solver_pause then
+                               set_tmp("SOLVER: PAUSED")
+                       else
+                               set_tmp("SOLVER: ON")
+                       end
                        #solver.step
                else if kc == "d" then
                        if solver == null then
                                solver = (new FriendzProblem(grid)).solve
                                solver_pause = true
+                               set_tmp("SOLVER: ON")
                        else
+                               solver_pause = true
                                solver.run_steps(1)
+                               set_tmp("SOLVER: ONE STEP")
                        end
                else if kc == "+" then
                        solver_steps += 100
-                       print solver_steps
+                       set_tmp("SOLVER: {solver_steps} STEPS")
                else if kc == "-" then
                        solver_steps -= 100
-                       print solver_steps
+                       set_tmp("SOLVER: {solver_steps} STEPS")
                else for g in entities do
                        if kc == g.shortcut then
                                g.click(ev)
@@ -1256,15 +1260,33 @@ redef class Game
                end
        end
 
+       fun set_tmp(s: String)
+       do
+               statusbar.set_tmp(s, "cyan")
+       end
+
        redef fun load_levels
        do
                super
 
                for level in levels do
-                       var score = app.data_store["s{level.str.md5}"]
+                       var score = app.data_store["s{level.str}"]
                        if score isa Int then
                                level.score = score
                        end
+                       var saved_str = app.data_store["g{level.str}"]
+                       if saved_str isa String then
+                               print "LOAD {level.name}: {saved_str}"
+                               level.saved_str = saved_str
+                       end
+               end
+       end
+
+       fun save
+       do
+               var l = level
+               if l != null then
+                       l.save
                end
        end
 end
@@ -1359,12 +1381,13 @@ class MusicButton
        init(game: Game)
        do
                super(game, "MUSIC", 470, 412, "purple", "Mute the music", "Unmute the music")
+               toggled = game.music_muted
        end
        redef fun click2(ev)
        do
                game.music_muted = self.toggled
                if game.music_muted then game.music.pause else game.music.play
-               #game.save_cookie("music_muted",music_muted?"true":"")
+               app.data_store["music_muted"] = game.music_muted
        end
 end
 
@@ -1373,13 +1396,14 @@ class SFXButton
        init(game: Game)
        do
                super(game, "SOUND FX", 470, 382, "purple", "Mute the sound effects", "Unmute the sound effects")
+               toggled = game.sfx_muted
        end
 
        redef fun click2(ev)
        do
                game.sfx_muted = self.toggled
                if not game.sfx_muted then game.snd_whip.play # Because the automatic one was muted
-               #save_cookie("sfx_muted",sfx_muted?"true":"")
+               app.data_store["sfx_muted"] = game.sfx_muted
        end
 end
 
@@ -1579,6 +1603,12 @@ redef class App
                # img loading?
        end
 
+       redef fun on_pause
+       do
+               super
+               game.save
+       end
+
        # Maximum wanted frame per second
        var max_fps = 30
 
@@ -1636,8 +1666,16 @@ redef class KeyEvent
 end
 
 redef class Level
-       redef fun save
+       # Save the score and grid of the level
+       fun save
        do
-               app.data_store["s{str.md5}"] = if score > 0 then score else null
+               app.data_store["s{str}"] = if score > 0 then score else null
+               var saved = game.grid.save
+               saved_str = saved
+               app.data_store["g{str}"] = saved
+               print "SAVE: {name}: {saved}"
        end
+
+       # The saved player grid (to continue games)
+       var saved_str: nullable String = null
 end
index a097ff5..3a141f6 100644 (file)
@@ -191,14 +191,14 @@ class Grid
        fun save: String
        do
                var res = ""
-               var str = ".#ABCDEFGHI"
+               var str = ".abcdefghi#ABCDEFGHI"
                for y in [0..height[ do
                        var rle = 0
                        var last: nullable Int = null
                        for x in [0..width[ do
                                var t = self.grid[x][y]
-                               var tk = 0
-                               if t.fixed then tk = t.kind + 1
+                               var tk = t.kind
+                               if t.fixed then tk += 10
                                if tk == last and rle<9 then
                                        rle += 1
                                else
@@ -243,6 +243,7 @@ class Grid
                                        x += 1
                                else if c == '#' then
                                        var t = self.get(x,y)
+                                       assert t != null
                                        t.fixed = true
                                        x += 1
                                else if c >= 'A' and c <= 'I' then
@@ -251,6 +252,11 @@ class Grid
                                        t.update(c.ascii-'A'.ascii+1)
                                        t.fixed = true
                                        x += 1
+                               else if c >= 'a' and c <= 'i' then
+                                       var t = self.get(x,y)
+                                       assert t != null
+                                       t.update(c.ascii-'a'.ascii+1)
+                                       x += 1
                                else if c >= '1' and c <= '9' then
                                        rle = c.to_i
                                else
@@ -261,7 +267,7 @@ class Grid
                if x>0 then y += 1
                if x > mx then mx = x
                if y > my then my = y
-               if mx<3 or my<3 or mx>=max_width or my>=max_height then
+               if mx<3 or my<3 or mx>max_width or my>max_height then
                        return false
                end
                self.resize(mx,my)
index 9d8029e..b09f1f2 100644 (file)
@@ -23,7 +23,7 @@ class Level
                var ls = code.split(";")
                self.number = i
                self.str = ls[0]
-               self.par = ls[1].to_i
+               self.gold = ls[1].to_i
                if ls.length >= 3 then
                        self.status = ls[2]
                end
@@ -47,8 +47,8 @@ class Level
        # initial grid position
        var str: String
 
-       # top score
-       var par: Int
+       # top score to get gold
+       var gold: Int
 
        # Help message if any
        var status: String = ""
@@ -72,32 +72,28 @@ class Level
        var l_disabled = 1
        var l_open = 2
        var l_won = 3
-       var l_par = 4
+       var l_gold = 4
 
        fun get_state: Int
        do
                if self.score == 0 then
                        if self.number == 0 or game.levels[self.number-1].score > 0 then return l_open
                        if self.number == 25 and game.levels[19].score > 0 then return l_open else return l_disabled
-               else if self.score < self.par or not game.levels[9].score > 0 then
+               else if self.score < self.gold or not game.levels[9].score > 0 then
                        return l_won
-               else return l_par
+               else return l_gold
        end
 
        # Returns true if g is a wining condition for the level.
        fun check_won(g: Grid): Bool
        do
-               var w = g.won and (not self.is_challenge or g.number >= self.par)
+               var w = g.won and (not self.is_challenge or g.number >= self.gold)
                if not w then return false
                if g.number > self.score then
                        self.score = g.number
-                       self.save
                end
                return true
        end
-
-       # Save the score of the level
-       fun save do end
 end
 
 # main game object
index 868dd9e..26a4ef6 100644 (file)
@@ -16,7 +16,6 @@
 module opportunity_controller
 
 import nitcorn
-import sha1
 import templates
 import opportunity_model
 
index ef7654d..cc72f23 100644 (file)
@@ -247,7 +247,7 @@ class Meetup
        redef fun commit(db) do
                if id == "" then
                        var time = get_time
-                       var tmpid = (name + date + place + time.to_s).sha1_to_s
+                       var tmpid = (name + date + place + time.to_s).sha1.hexdigest
                        if not db.execute("INSERT INTO meetups (id, name, date, place, answer_mode) VALUES({tmpid.to_sql_string}, {name.html_escape.to_sql_string}, {date.html_escape.to_sql_string}, {place.html_escape.to_sql_string}, {answer_mode});") then
                                print "Error recording entry Meetup {self}"
                                print db.error or else "Null error"
index 2eaf471..8776d48 100644 (file)
@@ -9,4 +9,4 @@ module sha_1
 
 import sha1
 
-print "Rosetta Code".sha1_to_s
+print "Rosetta Code".sha1.hexdigest
index 5f1c920..4eae395 100644 (file)
 # Offers the base 64 encoding and decoding algorithms
 module base64
 
-redef class String
-
+redef class NativeString
        # Alphabet used by the base64 algorithm
-       private fun base64_chars : String
+       private fun base64_chars : SequenceRead[Byte]
        do
-               return "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
+               return "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".bytes
        end
+
+       # Reversed alphabet for base64
        private fun inverted_base64_chars : HashMap[Byte, Byte]
        do
                var inv_base64_chars = new HashMap[Byte, Byte]
-               for k in [0..base64_chars.bytelen[ do
-                       inv_base64_chars[base64_chars.bytes[k]] = k.to_b
+               var l = base64_chars.length
+               for k in [0 .. l[ do
+                       inv_base64_chars[base64_chars[k]] = k.to_b
                end
                return inv_base64_chars
        end
 
-       # Encodes the receiver string to base64.
+       # Encodes `self` to base64.
+       #
        # By default, uses "=" for padding.
-       fun encode_base64 : String do return encode_base64_custom_padding('='.ascii.to_b)
-
-       # Encodes the receiver string to base64 using a custom padding character.
        #
-       # If using the default padding character `=`, see `encode_base64`.
-       fun encode_base64_custom_padding(padding : Byte) : String
-       do
-               var base64_bytes = once base64_chars.bytes
-               var length = bytelen
-
+       #     assert "string".encode_base64 == "c3RyaW5n"
+       private fun encode_base64(length: Int, padding: nullable Byte): Bytes do
+               var base64_bytes = once base64_chars
+               if padding == null then padding = '='.ascii.to_b
                var steps = length / 3
                var bytes_in_last_step = length % 3
                var result_length = steps * 4
                if bytes_in_last_step > 0 then result_length += 4
-               var result = new NativeString(result_length + 1)
-               var bytes = self.bytes
-               result[result_length] = 0u8
-
-               var mask_6bit = 0b0011_1111
+               var result = new Bytes.with_capacity(result_length)
 
+               var in_off = 0
                for s in [0 .. steps[ do
-                       var e = 0
-                       for ss in [0 .. 3[ do
-                               e += bytes[s * 3 + ss].to_i << ((2 - ss) * 8)
-                       end
-                       for ss in [0..4[ do
-                               result[s * 4 + 3 - ss] = base64_bytes[(e >> (ss * 6)) & mask_6bit]
-                       end
+                       var ind = ((self[in_off] & 0b1111_1100u8) >> 2).to_i
+                       result.add base64_bytes[ind]
+                       ind = ((self[in_off] & 0b0000_0011u8) << 4).to_i | ((self[in_off + 1] & 0b1111_0000u8) >> 4).to_i
+                       result.add base64_bytes[ind]
+                       ind = ((self[in_off + 1] & 0b0000_1111u8) << 2).to_i | ((self[in_off + 2] & 0b1100_0000u8) >> 6).to_i
+                       result.add base64_bytes[ind]
+                       ind = (self[in_off + 2] & 0b0011_1111u8).to_i
+                       result.add base64_bytes[ind]
+                       in_off += 3
                end
-
-               var out_off = result_length - 4
-               var in_off = length - bytes_in_last_step
                if bytes_in_last_step == 1 then
-                       result[out_off] = base64_bytes[((bytes[in_off] & 0b1111_1100u8) >> 2).to_i]
-                       result[out_off + 1] = base64_bytes[((bytes[in_off] & 0b0000_0011u8) << 4).to_i]
-                       out_off += 2
+                       result.add base64_bytes[((self[in_off] & 0b1111_1100u8) >> 2).to_i]
+                       result.add base64_bytes[((self[in_off] & 0b0000_0011u8) << 4).to_i]
                else if bytes_in_last_step == 2 then
-                       result[out_off] = base64_bytes[((bytes[in_off] & 0b1111_1100u8) >> 2).to_i]
-                       result[out_off + 1] = base64_bytes[(((bytes[in_off] & 0b0000_0011u8) << 4) | ((bytes[in_off + 1] & 0b1111_0000u8) >> 4)).to_i]
-                       result[out_off + 2] = base64_bytes[((bytes[in_off + 1] & 0b0000_1111u8) << 2).to_i]
-                       out_off += 3
-               end
-               if bytes_in_last_step > 0 then
-                       for i in [out_off .. result_length[ do result[i] = padding
+                       result.add base64_bytes[((self[in_off] & 0b1111_1100u8) >> 2).to_i]
+                       result.add base64_bytes[(((self[in_off] & 0b0000_0011u8) << 4) | ((self[in_off + 1] & 0b1111_0000u8) >> 4)).to_i]
+                       result.add base64_bytes[((self[in_off + 1] & 0b0000_1111u8) << 2).to_i]
                end
+               var rempad = if bytes_in_last_step > 0 then 3 - bytes_in_last_step else 0
+               for i in [0 .. rempad[ do result.add padding
 
-               return result.to_s_with_length(result_length)
+               return result
        end
 
-       # Decodes the receiver string from base64.
-       # By default, uses "=" for padding.
-       fun decode_base64 : String do return decode_base64_custom_padding('='.ascii.to_b)
-
-       # Decodes the receiver string to base64 using a custom padding character.
+       # Decodes `self` from base64
        #
-       # If using the default padding character `=`, see `decode_base64`.
-       fun decode_base64_custom_padding(padding : Byte) : String
-       do
+       #      assert "c3RyaW5n".decode_base64 == "string"
+       #
+       # REQUIRE: `length % 4 == 0`
+       private fun decode_base64(length: Int, padding: nullable Byte): Bytes do
+               if padding == null then padding = '='.ascii.to_b
                var inv = once inverted_base64_chars
-               var length = bytelen
-               if length == 0 then return ""
+               if length == 0 then return new Bytes.empty
                assert length % 4 == 0 else print "base64::decode_base64 only supports strings of length multiple of 4"
 
-               var bytes = self.bytes
+               var bytes = self
                var steps = length / 4
                var result_length = steps * 3
 
@@ -113,17 +101,16 @@ redef class String
                if padding_len == 1 then result_length -= 1
                if padding_len == 2 then result_length -= 2
 
-               var result = new NativeString(result_length + 1)
-               result[result_length] = 0u8
+               var result = new Bytes.with_capacity(result_length + 1)
 
                for s in [0 .. steps[ do
                        var c0 = inv[bytes[s * 4]]
                        var c1 = inv[bytes[s * 4 + 1]]
                        var c2 = inv[bytes[s * 4 + 2]]
                        var c3 = inv[bytes[s * 4 + 3]]
-                       result[s * 3] = ((c0 & 0b0011_1111u8) << 2) | ((c1 & 0b0011_0000u8) >> 4)
-                       result[s * 3 + 1] = ((c1 & 0b0000_1111u8) << 4) | ((c2 & 0b0011_1100u8) >> 2)
-                       result[s * 3 + 2] = ((c2 & 0b0000_0011u8) << 6) | (c3 & 0b0011_1111u8)
+                       result.add (((c0 & 0b0011_1111u8) << 2) | ((c1 & 0b0011_0000u8) >> 4))
+                       result.add (((c1 & 0b0000_1111u8) << 4) | ((c2 & 0b0011_1100u8) >> 2))
+                       result.add (((c2 & 0b0000_0011u8) << 6) | (c3 & 0b0011_1111u8))
                end
 
                var last_start = steps * 4
@@ -131,14 +118,52 @@ redef class String
                        var c0 = inv[bytes[last_start]]
                        var c1 = inv[bytes[last_start + 1]]
                        var c2 = inv[bytes[last_start + 2]]
-                       result[result_length - 2] = ((c0 & 0b0011_1111u8) << 2) | ((c1 & 0b0011_0000u8) >> 4)
-                       result[result_length - 1] = ((c1 & 0b0000_1111u8) << 4) | ((c2 & 0b0011_1100u8) >> 2)
+                       result.add (((c0 & 0b0011_1111u8) << 2) | ((c1 & 0b0011_0000u8) >> 4))
+                       result.add (((c1 & 0b0000_1111u8) << 4) | ((c2 & 0b0011_1100u8) >> 2))
                else if padding_len == 2 then
                        var c0 = inv[bytes[last_start]]
                        var c1 = inv[bytes[last_start + 1]]
-                       result[result_length - 1] = ((c0 & 0b0011_1111u8) << 2) | ((c1 & 0b0011_0000u8) >> 4)
+                       result.add (((c0 & 0b0011_1111u8) << 2) | ((c1 & 0b0011_0000u8) >> 4))
                end
 
-               return result.to_s_with_length(result_length)
+               return result
+       end
+end
+
+redef class Bytes
+
+       # Encodes the receiver string to base64 using a custom padding character.
+       #
+       # If using the default padding character `=`, see `encode_base64`.
+       fun encode_base64(padding: nullable Byte): Bytes
+       do
+               return items.encode_base64(length, padding)
+       end
+
+       # Decodes the receiver string to base64 using a custom padding character.
+       #
+       # Default padding character `=`
+       fun decode_base64(padding : nullable Byte) : Bytes
+       do
+               return items.decode_base64(length, padding)
+       end
+end
+
+redef class String
+
+       # Encodes the receiver string to base64 using a custom padding character.
+       #
+       # If using the default padding character `=`, see `encode_base64`.
+       fun encode_base64(padding: nullable Byte): String
+       do
+               return to_cstring.encode_base64(bytelen, padding).to_s
+       end
+
+       # Decodes the receiver string to base64 using a custom padding character.
+       #
+       # Default padding character `=`
+       fun decode_base64(padding : nullable Byte) : String
+       do
+               return to_cstring.decode_base64(bytelen, padding).to_s
        end
 end
index 59c4c5f..356f386 100644 (file)
@@ -19,6 +19,61 @@ import kernel
 import collection::array
 intrude import text::flat
 
+redef class Byte
+       # Write self as a string into `ns` at position `pos`
+       private fun add_digest_at(ns: NativeString, pos: Int) do
+               var tmp = (0xF0u8 & self) >> 4
+               ns[pos] = if tmp >= 0x0Au8 then tmp + 0x37u8 else tmp + 0x30u8
+               tmp = 0x0Fu8 & self
+               ns[pos + 1] = if tmp >= 0x0Au8 then tmp + 0x37u8 else tmp + 0x30u8
+       end
+
+       # Is `self` a valid hexadecimal digit (in ASCII)
+       #
+       # ~~~nit
+       # intrude import core::bytes
+       # assert not '/'.ascii.to_b.is_valid_hexdigit
+       # assert '0'.ascii.to_b.is_valid_hexdigit
+       # assert '9'.ascii.to_b.is_valid_hexdigit
+       # assert not ':'.ascii.to_b.is_valid_hexdigit
+       # assert not '@'.ascii.to_b.is_valid_hexdigit
+       # assert 'A'.ascii.to_b.is_valid_hexdigit
+       # assert 'F'.ascii.to_b.is_valid_hexdigit
+       # assert not 'G'.ascii.to_b.is_valid_hexdigit
+       # assert not '`'.ascii.to_b.is_valid_hexdigit
+       # assert 'a'.ascii.to_b.is_valid_hexdigit
+       # assert 'f'.ascii.to_b.is_valid_hexdigit
+       # assert not 'g'.ascii.to_b.is_valid_hexdigit
+       # ~~~
+       private fun is_valid_hexdigit: Bool do
+               return (self >= 0x30u8 and self <= 0x39u8) or
+                      (self >= 0x41u8 and self <= 0x46u8) or
+                      (self >= 0x61u8 and self <= 0x66u8)
+       end
+
+       # `self` as a hexdigit to its byte value
+       #
+       # ~~~nit
+       # intrude import core::bytes
+       # assert 0x39u8.hexdigit_to_byteval == 0x09u8
+       # assert 0x43u8.hexdigit_to_byteval == 0x0Cu8
+       # ~~~
+       #
+       # REQUIRE: `self.is_valid_hexdigit`
+       private fun hexdigit_to_byteval: Byte do
+               if self >= 0x30u8 and self <= 0x39u8 then
+                       return self - 0x30u8
+               else if self >= 0x41u8 and self <= 0x46u8 then
+                       return self - 0x37u8
+               else if self >= 0x61u8 and self <= 0x66u8 then
+                       return self - 0x57u8
+               end
+               # Happens only if the requirement is not met.
+               # i.e. this abort is here to please the compiler
+               abort
+       end
+end
+
 # A buffer containing Byte-manipulation facilities
 #
 # Uses Copy-On-Write when persisted
@@ -26,7 +81,7 @@ class Bytes
        super AbstractArray[Byte]
 
        # A NativeString being a char*, it can be used as underlying representation here.
-       private var items: NativeString
+       var items: NativeString
 
        # Number of bytes in the array
        redef var length
@@ -63,6 +118,20 @@ class Bytes
                return items[i]
        end
 
+       # Returns self as a hexadecimal digest
+       fun hexdigest: String do
+               var elen = length * 2
+               var ns = new NativeString(elen)
+               var i = 0
+               var oi = 0
+               while i < length do
+                       self[i].add_digest_at(ns, oi)
+                       i += 1
+                       oi += 2
+               end
+               return new FlatString.full(ns, elen, 0, elen - 1, elen)
+       end
+
        #     var b = new Bytes.with_capacity(1)
        #     b[0] = 101u8
        #     assert b.to_s == "e"
@@ -146,80 +215,13 @@ class Bytes
        redef fun to_s do
                persisted = true
                var b = self
-               if not is_utf8 then
-                       b = clean_utf8
-                       persisted = false
-               end
-               return new FlatString.with_infos(b.items, b.length, 0, b.length -1)
+               var r = b.items.to_s_with_length(length)
+               if r != items then persisted = false
+               return r
        end
 
        redef fun iterator do return new BytesIterator.with_buffer(self)
 
-       # Is the byte collection valid UTF-8 ?
-       fun is_utf8: Bool do
-               var charst = once [0x80u8, 0u8, 0xE0u8, 0xC0u8, 0xF0u8, 0xE0u8, 0xF8u8, 0xF0u8]
-               var lobounds = once [0, 0x80, 0x800, 0x10000]
-               var hibounds = once [0x7F, 0x7FF, 0xFFFF, 0x10FFFF]
-               var pos = 0
-               var len = length
-               var mits = items
-               while pos < len do
-                       var nxst = mits.length_of_char_at(pos)
-                       var charst_index = (nxst - 1) * 2
-                       if mits[pos] & charst[charst_index] == charst[charst_index + 1] then
-                               var c = mits.char_at(pos)
-                               var cp = c.ascii
-                               if cp <= hibounds[nxst - 1] and cp >= lobounds[nxst - 1] then
-                                       if cp >= 0xD800 and cp <= 0xDFFF or
-                                          cp == 0xFFFE or cp == 0xFFFF then return false
-                               else
-                                       return false
-                               end
-                       else
-                               return false
-                       end
-                       pos += nxst
-               end
-               return true
-       end
-
-       # Cleans the bytes of `self` to be UTF-8 compliant
-       private fun clean_utf8: Bytes do
-               var charst = once [0x80u8, 0u8, 0xE0u8, 0xC0u8, 0xF0u8, 0xE0u8, 0xF8u8, 0xF0u8]
-               var badchar = once [0xEFu8, 0xBFu8, 0xBDu8]
-               var lobounds = once [0, 0x80, 0x800, 0x10000]
-               var hibounds = once [0x7F, 0x7FF, 0xFFFF, 0x10FFFF]
-               var pos = 0
-               var len = length
-               var ret = new Bytes.with_capacity(len)
-               var mits = items
-               while pos < len do
-                       var nxst = mits.length_of_char_at(pos)
-                       var charst_index = (nxst - 1) * 2
-                       if mits[pos] & charst[charst_index] == charst[charst_index + 1] then
-                               var c = mits.char_at(pos)
-                               var cp = c.ascii
-                               if cp <= hibounds[nxst - 1] and cp >= lobounds[nxst - 1] then
-                                       if cp >= 0xD800 and cp <= 0xDFFF or
-                                          cp == 0xFFFE or cp == 0xFFFF then
-                                               ret.append badchar
-                                               pos += 1
-                                       else
-                                               var pend = pos + nxst
-                                               for i in [pos .. pend[ do ret.add mits[i]
-                                               pos += nxst
-                                       end
-                               else
-                                       ret.append badchar
-                                       pos += 1
-                               end
-                       else
-                               ret.append badchar
-                               pos += 1
-                       end
-               end
-               return ret
-       end
 end
 
 private class BytesIterator
@@ -231,7 +233,7 @@ private class BytesIterator
 
        var max: Int
 
-       init with_buffer(b: Bytes) do init(b.items, 0, b.length - 1)
+       init with_buffer(b: Bytes) do init(b.items, 0, b.length)
 
        redef fun is_ok do return index < max
 
@@ -253,6 +255,15 @@ redef class Text
                return b
        end
 
+       # Is `self` a valid hexdigest ?
+       #
+       #     assert "0B1d3F".is_valid_hexdigest
+       #     assert not "5G".is_valid_hexdigest
+       fun is_valid_hexdigest: Bool do
+               for i in bytes do if not i.is_valid_hexdigit then return false
+               return true
+       end
+
        # Appends `self.bytes` to `b`
        fun append_to_bytes(b: Bytes) do
                for s in substrings do
@@ -260,6 +271,24 @@ redef class Text
                        b.append_ns_from(s.items, s.bytelen, from)
                end
        end
+
+       # Returns a new `Bytes` instance with the digest as content
+       #
+       #     assert "0B1F4D".hexdigest_to_bytes == [0x0Bu8, 0x1Fu8, 0x4Du8]
+       #
+       # REQUIRE: `self` is a valid hexdigest and hexdigest.length % 2 == 0
+       fun hexdigest_to_bytes: Bytes do
+               var b = bytes
+               var pos = 0
+               var max = bytelen
+               var ret = new Bytes.with_capacity(max / 2)
+               while pos < max do
+                       ret.add((b[pos].hexdigit_to_byteval << 4) |
+                       b[pos + 1].hexdigit_to_byteval)
+                       pos += 2
+               end
+               return ret
+       end
 end
 
 redef class FlatText
index 2db319a..4b1e826 100644 (file)
@@ -173,12 +173,13 @@ abstract class Reader
        # ~~~
        fun read_all: String do
                var s = read_all_bytes
-               if not s.is_utf8 then s = s.clean_utf8
                var slen = s.length
                if slen == 0 then return ""
                var rets = ""
                var pos = 0
-               var sits = s.items
+               var str = s.items.clean_utf8(slen)
+               slen = str.bytelen
+               var sits = str.items
                var remsp = slen
                while pos < slen do
                        # The 129 size was decided more or less arbitrarily
index c8b6ecd..52de988 100644 (file)
@@ -985,8 +985,7 @@ redef class NativeString
        redef fun to_s_with_length(length): FlatString
        do
                assert length >= 0
-               var str = new FlatString.with_infos(self, length, 0, length - 1)
-               return str
+               return clean_utf8(length)
        end
 
        redef fun to_s_full(bytelen, unilen) do
@@ -997,6 +996,8 @@ redef class NativeString
        redef fun to_s_with_copy: FlatString
        do
                var length = cstring_length
+               var r = clean_utf8(length)
+               if r.items != self then return r
                var new_self = new NativeString(length + 1)
                copy_to(new_self, length, 0, 0)
                var str = new FlatString.with_infos(new_self, length, 0, length - 1)
@@ -1005,6 +1006,81 @@ redef class NativeString
                return str
        end
 
+       # Cleans a NativeString if necessary
+       fun clean_utf8(len: Int): FlatString do
+               var replacements: nullable Array[Int] = null
+               var end_length = len
+               var pos = 0
+               var chr_ln = 0
+               while pos < len do
+                       var b = self[pos]
+                       var nxst = length_of_char_at(pos)
+                       var ok_st: Bool
+                       if nxst == 1 then
+                               ok_st = b & 0x80u8 == 0u8
+                       else if nxst == 2 then
+                               ok_st = b & 0xE0u8 == 0xC0u8
+                       else if nxst == 3 then
+                               ok_st = b & 0xF0u8 == 0xE0u8
+                       else
+                               ok_st = b & 0xF8u8 == 0xF0u8
+                       end
+                       if not ok_st then
+                               if replacements == null then replacements = new Array[Int]
+                               replacements.add pos
+                               end_length += 2
+                               pos += 1
+                               chr_ln += 1
+                               continue
+                       end
+                       var ok_c: Bool
+                       var c = char_at(pos)
+                       var cp = c.ascii
+                       if nxst == 1 then
+                               ok_c = cp >= 0 and cp <= 0x7F
+                       else if nxst == 2 then
+                               ok_c = cp >= 0x80 and cp <= 0x7FF
+                       else if nxst == 3 then
+                               ok_c = cp >= 0x800 and cp <= 0xFFFF
+                               ok_c = ok_c and not (cp >= 0xD800 and cp <= 0xDFFF) and cp != 0xFFFE and cp != 0xFFFF
+                       else
+                               ok_c = cp >= 0x10000 and cp <= 0x10FFFF
+                       end
+                       if not ok_c then
+                               if replacements == null then replacements = new Array[Int]
+                               replacements.add pos
+                               end_length += 2
+                               pos += 1
+                               chr_ln += 1
+                               continue
+                       end
+                       pos += c.u8char_len
+                       chr_ln += 1
+               end
+               var ret = self
+               if end_length != len then
+                       ret = new NativeString(end_length)
+                       var old_repl = 0
+                       var off = 0
+                       var repls = replacements.as(not null)
+                       var r = repls.items.as(not null)
+                       var imax = repls.length
+                       for i in [0 .. imax[ do
+                               var repl_pos = r[i]
+                               var chkln = repl_pos - old_repl
+                               copy_to(ret, chkln, old_repl, off)
+                               off += chkln
+                               ret[off] = 0xEFu8
+                               ret[off + 1] = 0xBFu8
+                               ret[off + 2] = 0xBDu8
+                               old_repl = repl_pos + 1
+                               off += 3
+                       end
+                       copy_to(ret, len - old_repl, old_repl, off)
+               end
+               return new FlatString.full(ret, end_length, 0, end_length - 1, chr_ln)
+       end
+
        # Sets the next bytes at position `pos` to the value of `c`, encoded in UTF-8
        #
        # Very unsafe, make sure to have room for this char prior to calling this function.
@@ -1109,7 +1185,7 @@ redef class Array[E]
                        end
                        i += 1
                end
-               return ns.to_s_with_length(sl)
+               return new FlatString.with_infos(ns, sl, 0, sl - 1)
        end
 end
 
@@ -1146,7 +1222,7 @@ redef class NativeArray[E]
                        end
                        i += 1
                end
-               return ns.to_s_with_length(sl)
+               return new FlatString.with_infos(ns, sl, 0, sl - 1)
        end
 end
 
diff --git a/lib/matrix/matrix.nit b/lib/matrix/matrix.nit
new file mode 100644 (file)
index 0000000..73131ca
--- /dev/null
@@ -0,0 +1,295 @@
+# 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.
+
+# Services for matrices of `Float` values
+module matrix
+
+# A rectangular array of `Float`
+#
+# Require: `width > 0 and height > 0`
+class Matrix
+       super Cloneable
+
+       # Number of columns
+       var width: Int
+
+       # Number of rows
+       var height: Int
+
+       # Items of this matrix, rows by rows
+       private var items: Array[Float] is lazy do
+               return new Array[Float].filled_with(0.0, width*height)
+       end
+
+       # Create a matrix from nested sequences
+       #
+       # Require: all rows are of the same length
+       #
+       # ~~~
+       # var matrix = new Matrix.from([[1.0, 2.0],
+       #                               [3.0, 4.0]])
+       # assert matrix.to_s == """
+       # 1.0 2.0
+       # 3.0 4.0"""
+       # ~~~
+       init from(items: SequenceRead[SequenceRead[Float]])
+       do
+               if items.is_empty then
+                       init(0, 0)
+                       return
+               end
+
+               init(items.first.length, items.length)
+
+               for j in height.times do assert items[j].length == width
+
+               for j in height.times do
+                       for i in width.times do
+                               self[j, i] = items[j][i]
+                       end
+               end
+       end
+
+       # Get each row of this matrix in nested arrays
+       #
+       # ~~~
+       # var items = [[1.0, 2.0],
+       #              [3.0, 4.0]]
+       # var matrix = new Matrix.from(items)
+       # assert matrix.to_a == items
+       # ~~~
+       fun to_a: Array[Array[Float]]
+       do
+               var a = new Array[Array[Float]]
+               for j in height.times do
+                       var row = new Array[Float]
+                       for i in width.times do
+                               row.add self[j, i]
+                       end
+                       a.add row
+               end
+               return a
+       end
+
+       # Create a matrix from an `Array[Float]` composed of rows after rows
+       #
+       # Require: `width > 0 and height > 0`
+       # Require: `array.length >= width*height`
+       #
+       # ~~~
+       # var matrix = new Matrix.from_array(2, 2, [1.0, 2.0,
+       #                                           3.0, 4.0])
+       # assert matrix.to_s == """
+       # 1.0 2.0
+       # 3.0 4.0"""
+       # ~~~
+       init from_array(width, height: Int, array: SequenceRead[Float])
+       do
+               assert width > 0
+               assert height > 0
+               assert array.length >= width*height
+
+               init(width, height)
+
+               for i in height.times do
+                       for j in width.times do
+                               self[j, i] = array[i + j*width]
+                       end
+               end
+       end
+
+       # Create an identity matrix
+       #
+       # Require: `size >= 0`
+       #
+       # ~~~
+       # var i = new Matrix.identity(3)
+       # assert i.to_s == """
+       # 1.0 0.0 0.0
+       # 0.0 1.0 0.0
+       # 0.0 0.0 1.0"""
+       # ~~~
+       new identity(size: Int)
+       do
+               assert size >= 0
+
+               var matrix = new Matrix(size, size)
+               for i in size.times do
+                       for j in size.times do
+                               matrix[j, i] = if i == j then 1.0 else 0.0
+                       end
+               end
+               return matrix
+       end
+
+       # Create a new clone of this matrix
+       redef fun clone do return new Matrix.from_array(width, height, items.clone)
+
+       # Get the value at column `y` and row `x`
+       #
+       # Require: `x >= 0 and x <= width and y >= 0 and y <= height`
+       #
+       # ~~~
+       # var matrix = new Matrix.from([[0.0, 0.1],
+       #                               [1.0, 1.1]])
+       #
+       # assert matrix[0, 0] == 0.0
+       # assert matrix[0, 1] == 0.1
+       # assert matrix[1, 0] == 1.0
+       # assert matrix[1, 1] == 1.1
+       # ~~~
+       fun [](y, x: Int): Float
+       do
+               assert x >= 0 and x < width
+               assert y >= 0 and y < height
+
+               return items[x + y*width]
+       end
+
+       # Set the `value` at row `y` and column `x`
+       #
+       # Require: `x >= 0 and x <= width and y >= 0 and y <= height`
+       #
+       # ~~~
+       # var matrix = new Matrix.identity(2)
+       #
+       # matrix[0, 0] = 0.0
+       # matrix[0, 1] = 0.1
+       # matrix[1, 0] = 1.0
+       # matrix[1, 1] = 1.1
+       #
+       # assert matrix.to_s == """
+       # 0.0 0.1
+       # 1.0 1.1"""
+       # ~~~
+       fun []=(y, x: Int, value: Float)
+       do
+               assert x >= 0 and x < width
+               assert y >= 0 and y < height
+
+               items[x + y*width] = value
+       end
+
+       # Matrix product (×)
+       #
+       # Require: `self.width == other.height`
+       #
+       # ~~~
+       # var m = new Matrix.from([[3.0, 4.0],
+       #                          [5.0, 6.0]])
+       # var i = new Matrix.identity(2)
+       #
+       # assert m * i == m
+       # assert (m * m).to_s == """
+       # 29.0 36.0
+       # 45.0 56.0"""
+       #
+       # var a = new Matrix.from([[1.0, 2.0, 3.0],
+       #                          [4.0, 5.0, 6.0]])
+       # var b = new Matrix.from([[1.0],
+       #                          [2.0],
+       #                          [3.0]])
+       # var c = a * b
+       # assert c.to_s == """
+       # 14.0
+       # 32.0"""
+       # ~~~
+       fun *(other: Matrix): Matrix
+       do
+               assert self.width == other.height
+
+               var out = new Matrix(other.width, self.height)
+               for j in self.height.times do
+                       for i in other.width.times do
+                               var sum = items.first.zero
+                               for k in self.width.times do sum += self[j, k] * other[k, i]
+                               out[j, i] = sum
+                       end
+               end
+               return out
+       end
+
+       # Get the transpose of this matrix
+       #
+       # ~~~
+       # var matrix = new Matrix.from([[1.0, 2.0, 3.0],
+       #                               [4.0, 5.0, 6.0]])
+       # assert matrix.transposed.to_a == [[1.0, 4.0],
+       #                                   [2.0, 5.0],
+       #                                   [3.0, 6.0]]
+       #
+       # var i = new Matrix.identity(3)
+       # assert i.transposed == i
+       # ~~~
+       fun transposed: Matrix
+       do
+               var out = new Matrix(height, width)
+               for k, v in self do out[k.x, k.y] = v
+               return out
+       end
+
+       # Iterate over the values in this matrix
+       fun iterator: MapIterator[MatrixCoordinate, Float] do return new MatrixIndexIterator(self)
+
+       redef fun to_s
+       do
+               var lines = new Array[String]
+               for y in height.times do
+                       lines.add items.subarray(y*width, width).join(" ")
+               end
+               return lines.join("\n")
+       end
+
+       redef fun ==(other) do return other isa Matrix and other.items == self.items
+       redef fun hash do return items.hash
+end
+
+private class MatrixIndexIterator
+       super MapIterator[MatrixCoordinate, Float]
+
+       var matrix: Matrix
+
+       redef var key = new MatrixCoordinate(0, 0)
+
+       redef fun is_ok do return key.y < matrix.height
+
+       redef fun item
+       do
+               assert is_ok
+               return matrix[key.y, key.x]
+       end
+
+       redef fun next
+       do
+               assert is_ok
+               var key = key
+               if key.x == matrix.width - 1 then
+                       key.x = 0
+                       key.y += 1
+               else
+                       key.x += 1
+               end
+       end
+end
+
+# Position key when iterating over the values of a matrix
+class MatrixCoordinate
+       # Index of the current column
+       var x: Int
+
+       # Index of the current row
+       var y: Int
+
+       redef fun to_s do return "({x},{y})"
+end
diff --git a/lib/matrix/package.ini b/lib/matrix/package.ini
new file mode 100644 (file)
index 0000000..4bd9013
--- /dev/null
@@ -0,0 +1,11 @@
+[package]
+name=matrix
+tags=lib
+maintainer=Alexis Laferrière <alexis.laf@xymus.net>
+license=Apache-2.0
+[upstream]
+browse=https://github.com/nitlang/nit/tree/master/lib/matrix/
+git=https://github.com/nitlang/nit.git
+git.directory=lib/matrix/
+homepage=http://nitlanguage.org
+issues=https://github.com/nitlang/nit/issues
diff --git a/lib/matrix/projection.nit b/lib/matrix/projection.nit
new file mode 100644 (file)
index 0000000..908761a
--- /dev/null
@@ -0,0 +1,153 @@
+# 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.
+
+# Services on `Matrix` to transform and project 3D coordinates
+module projection
+
+intrude import matrix
+
+redef class Matrix
+
+       # Create an orthogonal projection matrix
+       #
+       # `left, right, bottom, top, near, far` defines the world clip planes.
+       new orthogonal(left, right, bottom, top, near, far: Float)
+       do
+               var dx = right - left
+               var dy = top - bottom
+               var dz = far - near
+
+               assert dx != 0.0 and dy != 0.0 and dz != 0.0
+
+               var mat = new Matrix.identity(4)
+               mat[0, 0] = 2.0 / dx
+               mat[3, 0] = -(right + left) / dx
+               mat[1, 1] = 2.0 / dy
+               mat[3, 1] = -(top + bottom) / dy
+               mat[2, 2] = 2.0 / dz
+               mat[3, 2] = -(near + far) / dz
+               return mat
+       end
+
+       # Create a perspective transformation matrix
+       #
+       # Using the given vertical `field_of_view_y` in radians, the `aspect_ratio`
+       # and the `near`/`far` world distances.
+       new perspective(field_of_view_y, aspect_ratio, near, far: Float)
+       do
+               var frustum_height = (field_of_view_y/2.0).tan * near
+               var frustum_width = frustum_height * aspect_ratio
+
+               return new Matrix.frustum(-frustum_width, frustum_width,
+                                         -frustum_height, frustum_height,
+                                         near, far)
+       end
+
+       # Create a frustum transformation matrix
+       #
+       # `left, right, bottom, top, near, far` defines the world clip planes.
+       new frustum(left, right, bottom, top, near, far: Float)
+       do
+               var dx = right - left
+               var dy = top - bottom
+               var dz = far - near
+
+               assert near > 0.0
+               assert far > 0.0
+               assert dx > 0.0
+               assert dy > 0.0
+               assert dz > 0.0
+
+               var mat = new Matrix(4, 4)
+
+               mat[0, 0] = 2.0 * near / dx
+               mat[0, 1] = 0.0
+               mat[0, 2] = 0.0
+               mat[0, 3] = 0.0
+
+               mat[1, 0] = 0.0
+               mat[1, 1] = 2.0 * near / dy
+               mat[1, 2] = 0.0
+               mat[1, 3] = 0.0
+
+               mat[2, 0] = (right + left) / dx
+               mat[2, 1] = (top + bottom) / dy
+               mat[2, 2] = -(near + far) / dz
+               mat[2, 3] = -1.0
+
+               mat[3, 0] = 0.0
+               mat[3, 1] = 0.0
+               mat[3, 2] = -2.0 * near * far / dz
+               mat[3, 3] = 0.0
+
+               return mat
+       end
+
+       # Apply a translation by `x, y, z` to this matrix
+       fun translate(x, y, z: Float)
+       do
+               for i in [0..3] do
+                       self[3, i] = self[3,i] + self[0, i] * x + self[1, i] * y + self[2, i] * z
+               end
+       end
+
+       # Apply scaling on `x, y, z` to this matrix
+       fun scale(x, y, z: Float)
+       do
+               for i in [0..3] do
+                       self[0, i] = self[0, i] * x
+                       self[1, i] = self[1, i] * y
+                       self[2, i] = self[2, i] * z
+               end
+       end
+
+       # Create a rotation matrix by `angle` around the vector defined by `x, y, z`
+       new rotation(angle, x, y, z: Float)
+       do
+               var mat = new Matrix.identity(4)
+
+               var mag = (x*x + y*y + z*z).sqrt
+               var sin = angle.sin
+               var cos = angle.cos
+
+               if mag > 0.0 then
+                       x = x / mag
+                       y = y / mag
+                       z = z / mag
+
+                       var inv_cos = 1.0 - cos
+
+                       mat[0, 0] = inv_cos*x*x + cos
+                       mat[0, 1] = inv_cos*x*y - z*sin
+                       mat[0, 2] = inv_cos*z*x + y*sin
+
+                       mat[1, 0] = inv_cos*x*y + z*sin
+                       mat[1, 1] = inv_cos*y*y + cos
+                       mat[1, 2] = inv_cos*y*z - x*sin
+
+                       mat[2, 0] = inv_cos*z*x - y*sin
+                       mat[2, 1] = inv_cos*y*z + x*sin
+                       mat[2, 2] = inv_cos*z*z + cos
+               end
+               return mat
+       end
+
+       # Apply a rotation of `angle` radians around the vector `x, y, z`
+       fun rotate(angle, x, y, z: Float)
+       do
+               var rotation = new Matrix.rotation(angle, x, y, z)
+               var rotated = self * rotation
+               self.items = rotated.items
+       end
+end
index 8a5acc9..2bd8fbe 100644 (file)
@@ -1,7 +1,5 @@
 # This file is part of NIT (http://www.nitlanguage.org).
 #
-# Copyright 2014 Lucas Bajolet <r4pass@hotmail.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
@@ -219,22 +217,12 @@ in "C Header" `{
        }
 `}
 
-redef class String
-
-       # Computes the SHA1 of the receiver
-       #
-       # Returns a digest of 20 bytes as a String,
-       # note that all the characters are not necessarily ASCII.
-       # If you want the hex string version of the digest, use
-       # sha1_to_s.
-       #
-       #     import base64
-       #     assert "The quick brown fox jumps over the lazy dog".sha1.encode_base64 == "L9ThxnotKPzthJ7hu3bnORuT6xI="
-       fun sha1: String import String.to_cstring, String.length, NativeString.to_s_with_length `{
+redef class NativeString
+       private fun sha1_intern(len: Int): NativeString `{
                sha1nfo s;
 
                sha1_init(&s);
-               sha1_write(&s, String_to_cstring(self), String_length(self));
+               sha1_write(&s, self, len);
                uint8_t* digest = sha1_result(&s);
 
                char* digested = malloc(21);
@@ -243,35 +231,30 @@ redef class String
 
                digested[20] = '\0';
 
-               return NativeString_to_s_with_length(digested, 20);
+               return digested;
        `}
+end
+
+redef class String
+
+       # Computes the SHA1 of the receiver
+       #
+       # Returns a digest of 20 bytes as a NativeString,
+       # note that all the characters are not necessarily ASCII.
+       # If you want the hex string version of the digest, use
+       # sha1_hexdigest.
+       #
+       #     import base64
+       #     assert "The quick brown fox jumps over the lazy dog".sha1 == [0x2Fu8, 0xD4u8, 0xE1u8, 0xC6u8, 0x7Au8, 0x2Du8, 0x28u8, 0xFCu8, 0xEDu8, 0x84u8, 0x9Eu8, 0xE1u8, 0xBBu8, 0x76u8, 0xE7u8, 0x39u8, 0x1Bu8, 0x93u8, 0xEBu8, 0x12u8]
+       fun sha1: Bytes do
+               return new Bytes(to_cstring.sha1_intern(bytelen), 20, 20)
+       end
 
        # Computes the SHA1 of the receiver.
        #
        # Returns a 40 char String containing the Hexadecimal
        # Digest in its Char form.
        #
-       #     assert "The quick brown fox jumps over the lazy dog".sha1_to_s == "2FD4E1C67A2D28FCED849EE1BB76E7391B93EB12"
-       fun sha1_to_s: String import String.to_cstring, String.length, NativeString.to_s_with_length `{
-               sha1nfo s;
-
-               sha1_init(&s);
-               sha1_write(&s, String_to_cstring(self), String_length(self));
-               uint8_t* digest = sha1_result(&s);
-
-               char* ret_str = malloc(41);
-               char* hexmap = "0123456789ABCDEF";
-
-               int i;
-               for(i=0;i<20;i++){
-                       uint8_t q = digest[i];
-                       ret_str[i*2] = hexmap[q >> 4];
-                       ret_str[(i*2)+1] = hexmap[q & 0x0F];
-               }
-               ret_str[40] = '\0';
-
-               return NativeString_to_s_with_length(ret_str, 40);
-       `}
-
+       #     assert "The quick brown fox jumps over the lazy dog".sha1_hexdigest == "2FD4E1C67A2D28FCED849EE1BB76E7391B93EB12"
+       fun sha1_hexdigest: String do return sha1.hexdigest
 end
-
index c3fdaad..2a3c32f 100644 (file)
@@ -114,7 +114,7 @@ class WebsocketConnection
                resp_map["Connection:"] = "Upgrade"
                var key = heads["Sec-WebSocket-Key"]
                key += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
-               key = key.sha1.encode_base64
+               key = key.sha1.encode_base64.to_s
                resp_map["Sec-WebSocket-Accept:"] = key
                var resp = resp_map.join("\r\n", " ")
                resp += "\r\n\r\n"
index 9b4bf06..8d09d74 100644 (file)
@@ -102,7 +102,7 @@ The command `:Nitdoc` searches the documentation for the word under the cursor.
 The results are displayed in the preview window in order of relevance.
 You can search for any word by passing it as an argument, as in `:Nitdoc modulo`.
 The Nitdoc command uses the same metadata files as the omnifunc.
-You may want to map the function to a shortcut by adding the following code to `~/.vimrc`.
+You may want to map the command to a shortcut by adding the following code to `~/.vimrc`.
 
 ~~~
 " Map displaying Nitdoc to Ctrl-D
@@ -119,3 +119,18 @@ You may want to map the function to a shortcut by adding the following code to `
 " Map the NitGitGrep function to Ctrl-G
 map <C-g> :call NitGitGrep()<enter>
 ~~~
+
+## Execute the current file
+
+The command `:NitExecute` calls `nit` to interpret the current file.
+
+If modified, the current buffer is saved to a temporary file before being executed.
+This may cause failures if the current buffer imports modules relative to the source package.
+In such cases, save the file before calling `:NitExecute`.
+
+You may want to map the command to a shortcut by adding the following code to `~/.vimrc`.
+
+~~~
+" Map the NitExecute function to Ctrl-F
+map <C-f> :NitExecute<enter>
+~~~
index 363a8f9..6824882 100644 (file)
@@ -382,6 +382,19 @@ fun NitGitGrep()
        redraw!
 endfun
 
+" Call `nit` on the current file
+fun NitExecute()
+       let path = expand('%')
+
+       if &modified
+               let path = tempname() . '.nit'
+               execute '%write '. path
+       endif
+
+       execute '!nit "' . path . '"'
+endfun
+command NitExecute call NitExecute()
+
 if !exists("g:nit_disable_omnifunc") || !g:nit_disable_omnifunc
        " Activate the omnifunc on Nit files
        autocmd FileType nit set omnifunc=NitOmnifunc
index ba0cb1c..28ae918 100644 (file)
@@ -86,6 +86,12 @@ class CatalogPage
        # Placeholder to include additional things before the `</head>`.
        var more_head = new Template
 
+       # Relative path to the root directory (with the index file).
+       #
+       # Use "" for pages in the root directory
+       # Use ".." for pages in a subdirectory
+       var rootpath: String
+
        redef init
        do
                add """
@@ -94,7 +100,7 @@ class CatalogPage
 <head>
        <meta charset="utf-8">
        <link rel="stylesheet" media="all" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css">
-       <link rel="stylesheet" media="all" href="style.css">
+       <link rel="stylesheet" media="all" href="{{{rootpath / "style.css"}}}">
 """
                add more_head
 
@@ -116,7 +122,7 @@ class CatalogPage
     </div>
     <div class='collapse navbar-collapse' id='topmenu-collapse'>
      <ul class='nav navbar-nav'>
-      <li><a href="index.html">Catalog</a></li>
+      <li><a href="{{{rootpath / "index.html"}}}">Catalog</a></li>
      </ul>
     </div>
    </div>
@@ -270,7 +276,7 @@ class Catalog
        # Compute information and generate a full HTML page for a package
        fun package_page(mpackage: MPackage): Writable
        do
-               var res = new CatalogPage
+               var res = new CatalogPage("..")
                var score = score[mpackage].to_f
                var name = mpackage.name.html_escape
                res.more_head.add """<title>{{{name}}}</title>"""
@@ -369,7 +375,7 @@ class Catalog
                                if cat == null then cat = t
                                tag2proj[t].add mpackage
                                t = t.html_escape
-                               ts2.add "<a href=\"index.html#tag_{t}\">{t}</a>"
+                               ts2.add "<a href=\"../index.html#tag_{t}\">{t}</a>"
                        end
                        res.add_list(ts2, ", ", ", ")
                end
@@ -377,7 +383,7 @@ class Catalog
                        var t = "none"
                        cat = t
                        tag2proj[t].add mpackage
-                       res.add "<a href=\"index.html#tag_{t}\">{t}</a>"
+                       res.add "<a href=\"../index.html#tag_{t}\">{t}</a>"
                end
                if cat != null then cat2proj[cat].add mpackage
                score += ts2.length.score
@@ -496,7 +502,7 @@ class Catalog
        fun li_package(p: MPackage): String
        do
                var res = ""
-               var f = "{p.name}.html"
+               var f = "p/{p.name}.html"
                res += "<a href=\"{f}\">{p}</a>"
                var d = p.mdoc_or_fallback
                if d != null then res += " - {d.html_synopsis.write_to_string}"
@@ -614,7 +620,7 @@ class Catalog
                res.add "</tr></thead>"
                for p in mpackages do
                        res.add "<tr>"
-                       res.add "<td><a href=\"{p.name}.html\">{p.name}</a></td>"
+                       res.add "<td><a href=\"p/{p.name}.html\">{p.name}</a></td>"
                        var maint = "?"
                        if p.maintainers.not_empty then maint = p.maintainers.first
                        res.add "<td>{maint}</td>"
@@ -700,7 +706,7 @@ if not opt_no_model.value then
 end
 
 var out = opt_dir.value or else "catalog.out"
-out.mkdir
+(out/"p").mkdir
 
 # Generate the css (hard coded)
 var css = """
@@ -806,13 +812,13 @@ css.write_to_file(out/"style.css")
 
 for p in model.mpackages do
        # print p
-       var f = "{p.name}.html"
+       var f = "p/{p.name}.html"
        catalog.package_page(p).write_to_file(out/f)
 end
 
 # INDEX
 
-var index = new CatalogPage
+var index = new CatalogPage("")
 index.more_head.add "<title>Packages in Nit</title>"
 
 index.add """
@@ -859,7 +865,7 @@ index.write_to_file(out/"index.html")
 
 # PEOPLE
 
-var page = new CatalogPage
+var page = new CatalogPage("")
 page.more_head.add "<title>People of Nit</title>"
 page.add """<div class="content">\n<h1>People of Nit</h1>\n"""
 page.add "<h2>By Maintainer</h2>\n"
@@ -871,7 +877,7 @@ page.write_to_file(out/"people.html")
 
 # TABLE
 
-page = new CatalogPage
+page = new CatalogPage("")
 page.more_head.add "<title>Projets of Nit</title>"
 page.add """<div class="content">\n<h1>People of Nit</h1>\n"""
 page.add "<h2>Table of Projets</h2>\n"
index 1066456..ff641cc 100644 (file)
@@ -21,7 +21,7 @@ Calls to bytepos for each type:
        FlatString = 18
 Calls to first_byte on FlatString 153
 Calls to last_byte on FlatString 103
-FlatStrings allocated with length 81 (85.417%)
+FlatStrings allocated with length 82 (86.458%)
 Length of travel for index distribution:
 * null = 20 => occurences 83.333%, cumulative 83.333% 
 * 1 = 8 => occurences 21.053%, cumulative 73.684% 
index fe675df..ce7118b 100644 (file)
@@ -13,6 +13,7 @@ redef class Deserializer
                if name == "Array[nullable Object]" then return new Array[nullable Object].from_deserializer(self)
                if name == "Array[Serializable]" then return new Array[Serializable].from_deserializer(self)
                if name == "Array[Object]" then return new Array[Object].from_deserializer(self)
+               if name == "Array[Int]" then return new Array[Int].from_deserializer(self)
                if name == "Array[Match]" then return new Array[Match].from_deserializer(self)
                if name == "Array[nullable Match]" then return new Array[nullable Match].from_deserializer(self)
                return super
diff --git a/tests/sav/test_bytes_hexdigit.res b/tests/sav/test_bytes_hexdigit.res
new file mode 100644 (file)
index 0000000..f00af0c
--- /dev/null
@@ -0,0 +1,3 @@
+0x0b
+0x1f
+0x4d
index 49e9adc..f6e7b69 100644 (file)
@@ -21,7 +21,7 @@ Calls to bytepos for each type:
        FlatString = 18
 Calls to first_byte on FlatString 153
 Calls to last_byte on FlatString 103
-FlatStrings allocated with length 81 (85.417%)
+FlatStrings allocated with length 82 (86.458%)
 Length of travel for index distribution:
 * 0 = 20 => occurences 83.333%, cumulative 83.333% 
 * 1 = 8 => occurences 21.053%, cumulative 73.684% 
diff --git a/tests/test_bytes_hexdigit.nit b/tests/test_bytes_hexdigit.nit
new file mode 100644 (file)
index 0000000..28f51e3
--- /dev/null
@@ -0,0 +1,17 @@
+# 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.
+
+var s = "0B1F4D".hexdigest_to_bytes
+
+for i in s do print i