NITCOPT=
-all: tools doc/stdlib/index.html
+all: tools
docs: doc/stdlib/index.html doc/nitc/index.html
- #cd doc; make
tools:
cd src; make
--- /dev/null
+default: linux
+
+linux:
+ mkdir -p bin
+ ../../bin/nitg -o bin/friendz src/friendz_linux.nit
+
+android:
+ mkdir -p bin
+ ../../bin/nitg -o bin/friendz.apk src/friendz_android.nit
+
+doc:
+ mkdir -p doc
+ ../../bin/nitdoc -d doc/ src/friendz.nit src/friendz_linux.nit
+
+clean:
+ rm -rf bin/ doc/
--- /dev/null
+Chainz of Friendz
+
+A puzzle game
+
+# Objectives
+
+Place monsters to create big chains of friends. You cannot add or
+remove monsters on a metal block. Monsters cannot have more that two
+direct friends. You must build one chain for each type of monster.
+
+# Controls
+
+Select a monster in the right pane and click on the board to add or
+remove the monster. Keep the mouse button down on the map to place
+multiple monsters. Click on a monster on a metal block to automatically
+select the monster.
+
+Keyboard shortcuts:
+
+* 1 to 9 : select a monster
+* 0 : eraser
+* q : metal block (editor only)
+
+# Editor
+
+Once the editor is unlocked you can create your own levels.
+
+# Challenge levels
+
+In challenge level you have to place as much as monster as possible.
+
+# Misc
+
+This game was originally developed for the [Casual Gameplay Design Competition
+\#9](http://jayisgames.com/cgdc9).
--- /dev/null
+Categories:Nit,Games
+License:WTFPL
+Web Site:http://nitlanguage.org
+Source Code:http://nitlanguage.org/nit.git/tree/HEAD:/contrib/friendz
+Issue Tracker:https://github.com/privat/nit/issues
+
+Summary:Puzzle game
+Description:
+Connect colored monsters to from bit continuous chains of friends.
+.
--- /dev/null
+# Monsterz - Chains of Friends
+#
+# 2010-2014 (c) Jean Privat <jean@pryen.org>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the Do What The Fuck You Want To
+# Public License, Version 2, as published by Sam Hocevar. See
+# http://sam.zoy.org/projects/COPYING.WTFPL for more details.
+
+# Full UI for the game
+module friendz is
+ app_name("ChainZ of FriendZ")
+ app_version(0, 1, git_revision)
+end
+
+import mnit
+import realtime
+import solver
+import mnit::tileset
+
+intrude import grid
+intrude import level
+
+redef class Grid
+ # Zoom level in %
+ # higher means more dense grid
+ var ratio = 100
+
+ # Various grid sizes from large to small (16x16, 12x12, 8x8, 6x6)
+ var ratios: Array[Int] = [200, 150, 100, 75]
+
+ redef fun resize(w,h)
+ do
+ super
+ for r in ratios do
+ if w*100/r <= 8 and h*100/r <= 8 then self.ratio = r
+ end
+ end
+end
+
+# * ENTITIES ****************************************************************
+
+# A game entity is something that is displayed and may interact with the player.
+abstract class Entity
+ # The associated game
+ var game: Game
+
+ # X left coordinate (in pixel).
+ var x: Int
+
+ # Y top coordinate (in pixel).
+ var y: Int
+
+ # X right coordinate (in pixel).
+ fun x2: Int do return x + width
+
+ # Y bottom coordinate (in pixel).
+ fun y2: Int do return y + height
+
+ # Width
+ var width: Int
+
+ # Height
+ var height: Int
+
+ # Tool tip text (if any)
+ var over: nullable String = null
+
+ # can the entity intercepts drag ang drop events?
+ var draggable = false
+
+ # Draw function. To implement
+ fun draw(ctx: Display) do end
+
+ # Update function. Called each loop. To implement
+ fun update do end
+
+ # Enter function. Called when the cursor enter in the element. To implement
+ fun enter(ev: Event) do end
+
+ # Click function. Called when the player click in the element.
+ # (or activate it with a shortcut).
+ fun click(ev: Event) do end
+
+ # keyboard shortcut do activate the entity, if any
+ var shortcut: nullable String = null
+
+ # Are events received?
+ var enabled = true
+
+ fun bw: Int do return game.bw
+ fun bh: Int do return game.bh
+
+ # Should the entity be redrawn
+ var dirty = true
+end
+
+# TEXT BUTTONS ***********************************************************/
+
+# Button entity displayed as a simple text.
+# if `over1` is null, then the button is a simple pasive label
+# if `over1` is set but `over2` is null, then the button is a normal button
+# if both `over1` and `over2` arew set, then the button is a toggleable button with two states
+class TextButton
+ super Entity
+ var str: String
+ init(game: Game, str: String, x,y: Int, color: nullable String, over, over2: nullable String)
+ do
+ var w = 10 # TODO
+ super(game, x,y,w,24)
+ self.str = str
+ self.color = color or else "purple"
+ self.over = over
+ self.over1 = over
+ self.over2 = over2
+ self.textx = x
+ if self.toggleable then
+ self.x -= bw/2 + 4
+ end
+ end
+
+ var color: String
+
+ # The description of the button action
+ var over1: nullable String
+ # The description of the state2 button action
+ var over2: nullable String
+
+ # is the button a two-state button
+ fun toggleable: Bool do return over2 != null
+
+ # is the toggleable button in its second state?
+ var toggled = false
+
+ # ttl for highlighting
+ var ttl = 0
+
+ # position of the start of the text
+ # in a toggleable button, there is space for the mark between `x` and `textx`
+ var textx: Int
+
+ redef fun draw(ctx) do
+ var x = self.x
+ if self.toggleable then
+ var w
+ if self.toggled or not self.enabled then w = 6 else w = 7
+ ctx.blit(game.img2[w,0], self.x, self.y)
+ end
+ var c
+ if self.enabled then c = self.color else c = "gray"
+ var c2= null
+ if self.ttl > 0 then c2 = "rgba(255,255,255,{self.ttl/10})"
+ ctx.textx(self.str, self.textx, self.y, self.height, c, c2)
+ self.width = ctx.measureText(self.str, self.height)
+ if self.toggleable then self.width += bw/2 + 4
+ end
+
+ redef fun update
+ do
+ if game.statusbar.over_what != self and self.ttl > 0 then
+ self.ttl-=1
+ self.dirty = true
+ end
+ end
+
+ redef fun enter(ev)
+ do
+ if over1 == null then return
+ if not self.enabled then return
+ game.snd_click.replay
+ self.ttl = 10
+ self.dirty = true
+ self.enter2
+ end
+
+ # Called by `enter` do perform additionnal work if the button is active
+ # Specific button should implement this instead of `enter`
+ fun enter2 do end
+
+ redef fun click(ev)
+ do
+ if not self.enabled then
+ game.snd_bing.replay
+ else
+ if self.toggleable then
+ self.toggled = not self.toggled
+ if self.toggled then self.over = self.over2 else self.over = self.over1
+ game.statusbar.over_txt = self.over
+ end
+ game.snd_whip.replay
+ end
+ self.click2(ev)
+ end
+
+ # Called by `click` do perform additionnal work if the button is active
+ # Specific button should implement this instead of `click`
+ fun click2(ev: Event) do end
+
+end
+
+# LEVEL BUTTONS ***********************************************************/
+
+# button to play a level in the menu screen
+class LevelButton
+ super Entity
+
+ # The associated level to play
+ var level: Level
+
+ init(l: Level)
+ do
+ self.level = l
+ var i = l.number
+ super(l.game, (i%5)*56 + 54, (i/5)*56 + 55, l.game.bw, l.game.bh)
+
+ 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}"
+ 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}"
+ end
+ #self.enabled = l.get_state >= l.l_open
+ end
+
+ redef fun draw(ctx)
+ do
+ var l = level
+ var s = self.level.get_state
+ var ix = 5 + l.number % 2
+ var iy = 0
+ if s == l.l_disabled then
+ ix = 3
+ iy = 3
+ else if s == l.l_open then
+ ix = 1
+ iy = 1
+ ctx.blit(game.img[ix,iy], self.x, self.y)
+ ix = 0
+ iy = 0
+ end
+ ctx.blit(game.img[ix,iy], self.x, self.y)
+
+ if s == l.l_par 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)
+ end
+
+ redef fun click(ev)
+ do
+ if self.enabled then
+ game.snd_whip.replay
+ game.play(self.level)
+ else
+ game.snd_bing.replay
+ game.statusbar.set_tmp("Locked level", "red")
+ end
+ end
+
+end
+
+# ACHIEVEMENTS ************************************************************/
+
+# Achievement (monster-like) button in the menu screen
+class Achievement
+ super Button
+
+ # The number of the achievement (0 is first)
+ var number: Int
+
+ # The name of the achievement
+ var name: String
+
+ init(game: Game, i: Int, name: String)
+ do
+ super(game, 5*56 + 54, i*56 + 55, game.bw, game.bh)
+ self.over = name
+ self.number = i
+ self.name = name
+ var l = game.levels[number*5+4]
+ enabled = l.get_state >= l.l_won
+ if self.enabled then self.over = name + " (unlocked)" else self.over = name + " (locked)"
+ end
+
+ redef fun draw(ctx)
+ do
+ var w
+ if self.enabled then w = 5 else w = 3
+ ctx.blit(game.img[w,self.number+5], self.x, self.y)
+ end
+
+ redef fun click(ev)
+ do
+ if not self.enabled then
+ game.snd_bing.replay
+ game.statusbar.set_tmp("Locked achievement!", "red")
+ else
+ game.snd_whip.replay
+ self.click2(ev)
+ end
+ end
+
+ fun click2(ev: Event) do
+ # TODO
+ end
+end
+
+
+# BOARD (THE GRID) *******************************************************/
+
+# The board game element.
+class Board
+ super Entity
+ init(game: Game)
+ do
+ super(game, game.xpad, game.ypad, 8*game.bw, 8*game.bh)
+ draggable = true
+ end
+
+ redef fun draw(ctx)
+ do
+ var grid = game.grid
+ var bwr = bw*100/grid.ratio
+ var bhr = bh*100/grid.ratio
+ var w = grid.width
+ var h = grid.height
+ if game.selected_button == game.button_size then
+ bwr = bw/2
+ bhr = bh/2
+ w = game.gw
+ h = game.gh
+ end
+ self.x = game.xpad+(48*8/2)-w*bwr/2
+ self.y = game.ypad+(48*8/2)-h*bhr/2
+ self.width = w*bwr
+ self.height = h*bhr
+ for i in [0..w[ do
+ for j in [0..h[ do
+ var t = grid.grid[i][j]
+ var dx = i * bwr + self.x
+ var dy = j * bhr + self.y
+ if (i+j)%2 == 0 then
+ ctx.blit_scaled(game.img[5,0], dx, dy, bwr, bhr)
+ else
+ ctx.blit_scaled(game.img[6,0], dx, dy, bwr, bhr)
+ end
+ if t.fixed then
+ if t.shape != null and not game.editing then
+ #ctx.drawImage(game.img, t.shape.x*bw, (2+t.shape.y)*bh, bw, bh, i * bwr + self.x, j * bhr + self.y, bwr, bhr)
+ ctx.blit_scaled(game.img[3,3], dx, dy, bwr, bhr)
+ else
+ ctx.blit_scaled(game.img[3,3], dx, dy, bwr, bhr)
+ end
+ end
+ if t.kind>0 then
+ var m = grid.monsters[t.kind]
+ var s = 0
+ if t.blink > 0 then s = 1
+ if t.nexts > 2 then s = 3
+ if t.nexts == 0 then s = 6
+ if m.chain then s = 5
+ if t.shocked>0 then s = 2
+ ctx.blit_scaled(game.img[s,(4+t.kind)], dx, dy, bwr, bhr)
+ end
+ #ctx.textx(t.chain_mark.to_s, dx, dy, 20, "", null)
+ end
+ end
+ if game.selected_button == game.button_size then
+ var x0 = self.x
+ var x1 = (grid.width) * bwr - bwr/2 + self.x
+ var y0 = self.y
+ var y1 = (grid.height) * bhr - bhr/2 + self.y
+ ctx.blit_scaled(game.img2[0,0], x0, y0, bwr/2, bhr/2)
+ ctx.blit_scaled(game.img2[1,0], x1, y0, bwr/2, bhr/2)
+ ctx.blit_scaled(game.img2[1,1], x1, y1, bwr/2, bhr/2)
+ ctx.blit_scaled(game.img2[0,1], x0, y1, bwr/2, bhr/2)
+ ctx.textx("{grid.width}x{grid.height}",self.x + grid.width*bwr/2,self.y+grid.height*bhr/2,20,"orange",null)
+ end
+ end
+
+ redef fun update
+ do
+ var grid = game.grid
+ for i in [0..grid.width[ do
+ for j in [0..grid.height[ do
+ var t = grid.grid[i][j]
+ if t.kind == 0 then continue
+ if t.blink > 0 then
+ t.blink-=1
+ self.dirty=true
+ end
+ if t.shocked > 0 then
+ t.shocked-=1
+ self.dirty=true
+ else if 100.rand == 0 then
+ t.blink = 5
+ self.dirty=true
+ end
+ end
+ end
+ end
+
+ # Last clicked tile
+ # Uded to filter drag events
+ private var last: nullable Tile = null
+
+ redef fun click(ev)
+ do
+ var grid = game.grid
+ var r = grid.ratio
+ if game.selected_button == game.button_size then r = 200
+ var x = ev.game_x * r / bw / 100
+ var y = ev.game_y * r / bh / 100
+ var t = grid.grid[x][y]
+
+ if ev.drag and last == t then return
+ last = t
+
+ if game.selected_button != game.button_size and (x>=grid.width or y>=grid.height) then return
+
+ # print "{ev.game_x},{ev.game_y},{ev.drag} -> {x},{y}:{t}"
+
+ if game.selected_button != null then
+ game.selected_button.click_board(ev, t)
+ end
+ end
+end
+
+# BUTTONS *****************************************************************/
+
+# A in-game selectable button for monsters or tools
+class Button
+ super Entity
+
+ # The x tile
+ var imgx: Int = 0
+
+ # The y tile
+ var imgy: Int = 0
+
+ # The associated monster tile
+ # >0 for monsters, <=0 for tools
+ var kind = 0
+
+ redef fun draw(ctx)
+ do
+ ctx.blit(game.img[self.imgx, self.imgy], self.x, self.y)
+ if game.selected_button == self then ctx.blit(game.img[0, 0], self.x, self.y)
+ end
+
+ redef fun click(ev)
+ do
+ var sel = game.selected_button
+ if game.selected_button == game.button_size then game.board.dirty=true
+ if sel != null then sel.dirty=true
+ game.selected_button = self
+ game.snd_click.replay
+ end
+
+ # Current inputed chain
+ # Used for drag
+ private var chain = new Array[Tile]
+
+ # Board click. Called when the player click on the board with the button selected.
+ fun click_board(ev: Event, t: Tile)
+ do
+ game.score.dirty = true
+ if ev.drag and self.kind>0 and not chain.is_empty then
+ if self.chain.length >= 2 and self.chain[1] == t then
+ var t2 = self.chain.shift
+ game.snd_click.replay
+ if t2.fixed and not game.editing then return
+ t2.update(0)
+ return
+ end
+ if t.fixed and t.kind == self.kind then
+ self.chain.unshift(t)
+ game.snd_click.replay
+ return
+ end
+ if (self.chain[0].x - t.x).abs + (self.chain[0].y - t.y).abs != 1 then return
+ if t.fixed and not game.editing then
+ game.snd_bing.replay
+ return
+ end
+ if t.kind != 0 and t.kind != self.kind then
+ t.shocked = 5
+ game.snd_duh.replay
+ return
+ end
+ self.chain.unshift(t)
+ if t.kind == self.kind then return
+ game.snd_click.replay
+ t.update(self.kind)
+ return
+ end
+
+ if t.fixed and not game.editing then
+ if t.kind == 0 then
+ game.snd_bing.replay
+ return
+ end
+ if t.kind != self.kind and not ev.drag then
+ game.buttons[t.kind].click(ev)
+ game.buttons[t.kind].chain = [t]
+ else
+ self.chain = [t]
+ game.snd_bing.replay
+ end
+ return
+ end
+ if t.fixed and game.editing and self == game.button_erase and t.kind == 0 then
+ t.fixed = false
+ game.snd_click.replay
+ return
+ end
+
+ var nkind = 0 # the new kind
+ if ev.drag then
+ # Here we clean
+ if t.kind == 0 then return
+ if self.kind != 0 and t.kind != self.kind then
+ t.shocked = 5
+ game.snd_duh.replay
+ return
+ end
+ nkind = 0
+ else if t.kind != self.kind then
+ nkind = self.kind
+ self.chain = [t]
+ else if t.kind != 0 then
+ nkind = 0
+ self.chain.clear
+ end
+ if nkind == t.kind then return
+ game.snd_click.replay
+ t.update(nkind)
+ end
+end
+
+# A monster button
+class MonsterButton
+ super Button
+
+ # TTL for the monster being angry
+ var angries = 0
+ # TTL for the monster being happy
+ var happy = 0
+ # TTL for the monster being shocked
+ var shocked = 0
+ # TTL for the monster blinking
+ var blink = 0
+
+ init(game: Game, i: Int)
+ do
+ self.game = game
+ var x = 440 + 58 * ((i-1).abs%3)
+ var y = 150 + (bh+5) * ((i-1)/3)
+ super(game, x, y, game.bw, game.bh)
+ if i == 0 then return
+ kind = i
+ imgx = 0
+ imgy = (4+i)
+ over = game.colors[i] + " monster ({i})"
+ shortcut = i.to_s # code for 1 trough 9
+ end
+
+ redef fun click(ev)
+ do
+ super
+ self.shocked = 5
+ end
+
+ redef fun update
+ do
+ if self.happy > 0 then
+ self.happy-=1
+ self.dirty=true
+ end
+ if self.shocked > 0 then
+ self.shocked-=1
+ self.dirty=true
+ end
+ if self.blink > 0 then
+ self.blink-=1
+ self.dirty=true
+ else if 100.rand == 0 then
+ self.blink = 5
+ self.dirty=true
+ end
+ end
+
+ redef fun draw(ctx)
+ do
+ var s = self.imgx
+ if self.angries>0 then
+ s += 3
+ else if self.happy > 5 then
+ s += 5
+ else if self.shocked > 0 then
+ s += 5
+ else if self.blink > 0 then
+ s += 1
+ end
+ ctx.blit(game.img[s, self.imgy], self.x, self.y)
+ if game.selected_button == self then ctx.blit(game.img[0, 0], self.x, self.y)
+ end
+end
+
+# Erase button.
+class EraseButton
+ super Button
+ init(game: Game) do
+ super(game, 440, 92, game.bh, 22+game.bh)
+ imgx = 4
+ imgy = 13
+ kind = 0
+ over = "Eraser (0)"
+ shortcut = "0"
+ end
+end
+
+# Metal (fixed) button.
+class MetalButton
+ super Button
+ init(game: Game)
+ do
+ super(game, 498, 92, game.bh, 20+game.bh)
+ imgx = 3
+ imgy = 3
+ kind = -1
+ over = "Metal block (q)"
+ shortcut = "q"
+ end
+
+ private var fixed = false
+
+ redef fun click_board(ev,t)
+ do
+ if not ev.drag then self.fixed = not t.fixed
+ if t.fixed == self.fixed then return
+ t.fixed = self.fixed
+ game.snd_click.replay
+ end
+end
+
+# Resize button.
+class ResizeButton
+ super Button
+
+ init(game: Game)
+ do
+ super(game,556, 92, game.bh, 20+game.bh)
+ kind = -2
+ over = "Resize the grid"
+ end
+
+ redef fun draw(ctx)
+ do
+ for i in [0..3[ do
+ for j in [0..3[ do
+ var x = self.x + i*bw/3
+ var y = self.y + j*bh/3
+ ctx.blit_scaled(game.img[5+(i+j)%2,0], x, y, bw/3, bh/3)
+ end
+ end
+ if game.selected_button == self then ctx.blit(game.img[0, 0], self.x, self.y)
+ end
+
+ redef fun click(ev)
+ do
+ if game.selected_button != game.button_size then
+ super
+ else
+ game.selected_button = null
+ game.board.dirty=true
+ end
+ end
+
+ redef fun click_board(evt, t)
+ do
+ var grid = t.grid
+ var w = t.x+1
+ var h = t.y+1
+ if w < 3 or h < 3 then
+ game.snd_bing.replay
+ game.statusbar.set_tmp("Too small!", "red")
+ return
+ end
+ var aborts = false
+ for i in [0..grid.width[ do
+ for j in [0..grid.height[ do
+ if i>=w or j>=h then
+ var t2 = grid.grid[i][j]
+ if t2.kind > 0 then
+ aborts = true
+ t2.shocked = 5
+ end
+ end
+ end
+ end
+ if aborts then
+ game.snd_duh.replay
+ game.statusbar.set_tmp("Monsters on the way!", "red")
+ return
+ end
+ game.snd_click.replay
+ grid.resize(w,h)
+ end
+end
+
+# Inactive area used to display the score
+class Score
+ super Entity
+ init(game: Game)
+ do
+ super(game,440,310,199,62)
+ end
+ redef fun draw(ctx)
+ do
+ ctx.textx("MONSTERS: {game.grid.number}",self.x,self.y+1,21,"cyan",null)
+ var level = game.level
+ if level == null then return
+ if level.get_state >= level.l_won then
+ ctx.textx("BEST: {level.score}",self.x,self.y+22,21,"pink", null)
+ else
+ ctx.textx("BEST: -",self.x,self.y+22,21,"pink", null)
+ 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)
+ else
+ ctx.textx("PAR: {level.par}",self.x,self.y+44,21,"yellow",null)
+ end
+ end
+ end
+end
+
+# Status bar element.
+class StatusBar
+ super Entity
+ init(game: Game)
+ do
+ super(game,24, 440, 418-24, 30)
+ end
+
+ # Permanant text, if any
+ var main_txt: nullable String = null
+
+ # Text to display when the cursor if over an entity (`over_what`), if any
+ var over_txt: nullable String = null
+
+ # What is the entity for `over_txt`
+ var over_what: nullable Entity
+
+ # Text to temporally display, for some game event, if any
+ var tmp_txt: nullable String = null
+
+ # time-to-live for the `tmp_txt`
+ var tmp_txt_ttl = 0
+
+ # Color used to display `tmp_txt`
+ var tmp_txt_color: nullable String = null
+
+ # reset the status
+ fun clear do
+ self.main_txt = null
+ self.over_txt = null
+ self.tmp_txt = null
+ self.over = null
+ end
+
+ # set a temporary text
+ fun set_tmp(txt, color: String)
+ do
+ print "***STATUS** {txt}"
+ self.tmp_txt = txt
+ self.tmp_txt_ttl = 20
+ self.tmp_txt_color = color
+ end
+
+ redef fun draw(ctx)
+ do
+ var tmp_txt = self.tmp_txt
+ var over_txt = self.over_txt
+ var main_txt = self.main_txt
+ if tmp_txt != null and self.tmp_txt_ttl>0 then
+ ctx.textx(tmp_txt,24,442,24,self.tmp_txt_color,null)
+ else if over_txt != null then
+ ctx.textx(over_txt,24,442,24,"yellow",null)
+ else if main_txt != null then
+ ctx.textx(main_txt,24,442,24,"white",null)
+ end
+ end
+
+ redef fun update
+ do
+ if self.tmp_txt_ttl>0 then
+ self.tmp_txt_ttl-=1
+ self.dirty=true
+ end
+ end
+end
+
+# ************************************************************************/
+
+# Simple audio asset
+class Audio
+ var path: String
+
+ # placebo
+ fun play do end
+
+ # placebo
+ fun pause do end
+
+ # Play a sound.
+ fun replay
+ do
+ sys.system("aplay assets/{path} &")
+ end
+end
+
+redef class Display
+ # Display a text
+ fun textx(str: String, x, y, height: Int, color, color2: nullable String)
+ do
+ #var w = measureText(str, height)
+ #rect(x,y,w,height)
+ text(str.to_upper, app.game.font, x, y)
+ end
+
+ # give the width for a giver text
+ fun measureText(str: String, height: Int): Int
+ do
+ var font = app.game.font
+ return str.length * (app.game.font.width + app.game.font.hspace)
+ end
+
+ # displays a debug rectangle
+ fun rect(x,y,w,h:Int)
+ do
+ var image = once app.load_image("hitbox.png")
+ blit_scaled(image, x, y, w, h)
+ end
+end
+
+# Simple basic class for event
+class Event
+ # Is a drag event?
+ var drag = false
+ # screen x
+ var offset_x: Int
+ # screen y
+ var offset_y: Int
+ # entity x
+ var game_x = 0
+ # entity y
+ var game_y = 0
+ # key pressed
+ var char_code: String
+end
+
+redef class Game
+ # width of a tile, used for most width reference in the game
+ var bw = 48
+ # height a tile, used for most width reference in the game
+ var bh = 48
+ # x-coordinate of the board (padding)
+ var xpad = 24
+ # y-coordinate of the board (padding)
+ var ypad = 24
+
+ # Load tiles
+
+ # Basic tileset
+ var img = new TileSet(app.load_image("tiles2.png"),48,48)
+
+ # Sub tileset (for marks or other)
+ var img2 = new TileSet(app.load_image("tiles2.png"),24,24)
+
+ # background image
+ var back: Image = app.load_image("background.png")
+
+ # Logo image
+ var logo: Image = app.load_image("logo.png")
+
+ # 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
+ var editing = false
+
+ # The selected button, if any
+ var selected_button: nullable Button = null
+
+ # SOUND
+
+ # Is the music muted?
+ var music_muted: Bool = true #read_cookie("music_muted")
+
+ # Is the sound effects muted?
+ var sfx_muted: Bool = true #read_cookie("sfx_muted")
+
+ # The background music resource. */
+ var music = new Audio("music.ogg")
+
+ # Click sound
+ var snd_click = new Audio("click.wav")
+
+ # Wining soulf
+ var snd_win = new Audio("level.wav")
+
+ # Shocked sound
+ var snd_duh = new Audio("duh.wav")
+
+ # metal sound
+ var snd_bing = new Audio("bing.wav")
+
+ # transition sound
+ var snd_whip = new Audio("whip.wav")
+
+ # INPUT ******************************************************************
+
+ # Current grid edited (if any).
+ var grid_edit: nullable Grid = null
+
+ # Sequence of current entities
+ var entities = new Array[Entity]
+
+ # The current statusbar
+ var statusbar = new StatusBar(self)
+
+ # The grid board
+ var board = new Board(self)
+
+ # The current score board
+ var score = new Score(self)
+
+ # Monster button game elements.
+ var buttons = new Array[MonsterButton]
+
+ # MetalButton
+ var button_wall = new MetalButton(self)
+
+ # EraseButton
+ var button_erase = new EraseButton(self)
+
+ # ResizeButton
+ var button_size = new ResizeButton(self)
+
+ # fill `buttons`
+ fun init_buttons
+ do
+ for i in [0..9] do
+ buttons[i] = new MonsterButton(self, i)
+ end
+ end
+
+ # Play a level in player mode.
+ fun play(l: Level)
+ do
+ level = l
+ grid.load(level.str)
+ init_play_menu(false)
+ if level.status != "" then
+ statusbar.main_txt = level.status
+ else
+ statusbar.main_txt = level.fullname
+ end
+ var t = new NextLevelButton(self)
+ entities.push(t)
+ run
+ end
+
+ # Play the next level.
+ fun play_next
+ do
+ play(levels[level.number+1])
+ end
+
+
+ # Helper function to initialize all states.
+ # Set up buttons for music and SFX.
+ fun init_game
+ do
+ editing = false
+ solver = null
+ entities.clear
+ entities.push(new MusicButton(self))
+ entities.push(new SFXButton(self))
+ entities.push(new MenuButton(self))
+ statusbar.clear
+ entities.push(statusbar)
+ end
+
+ # Helper function to initialize monster menu entries.
+ fun init_play_menu(full: Bool)
+ do
+ init_game
+ entities.push(board)
+ entities.push(new ResetButton(self))
+ entities.push(button_erase)
+ # Push monster buttons and determine the selected one
+ var sel: nullable Button = null
+ for i in [1..monsters] do
+ if grid.monsters[i].number > 0 or full then
+ if selected_button == buttons[i] or sel == null then
+ sel = buttons[i]
+ end
+ entities.push(buttons[i])
+ end
+ end
+ selected_button = sel
+ entities.push(score)
+ end
+
+ # Play a arbitrary grid in try mode.
+ fun play_grid(g: Grid)
+ do
+ grid = g
+ init_play_menu(false)
+ statusbar.main_txt = "User level"
+ if grid_edit != null then
+ entities.push(new EditButton(self))
+ end
+ entities.push(new WonButton(self))
+ run
+ end
+
+ # Launch the editor starting with a grid.
+ fun edit_grid(g: Grid)
+ do
+ grid = g
+ init_play_menu(true)
+ editing = true
+ statusbar.main_txt = "Level editor"
+ if level != null then statusbar.main_txt += ": level "+level.name
+ entities.push(button_wall)
+ entities.push(button_size)
+ entities.push(new TestButton(self))
+ entities.push(new SaveButton(self))
+ entities.push(new LoadButton(self))
+ run
+ end
+
+ # Launch the title screen
+ fun title
+ do
+ init_menu
+ entities.push(new Splash(self))
+ run
+ end
+
+ # Helper function to initialize the menu (and tile) screen
+ fun init_menu
+ do
+ init_game
+ level = null
+ var i = levels.first
+ for l in levels do
+ if l.get_state == l.l_open then break
+ i = l
+ end
+ entities.push(new StartButton(self, i))
+ end
+
+ # Launch the menu.
+ fun menu
+ do
+ init_menu
+ var t
+ t = new TextButton(self,"LEVEL SELECT", 120, ypad, "white", null, null)
+ entities.push(t)
+ for i in [0..levels.length[ do
+ var b = new LevelButton(levels[i])
+ entities.push(b)
+ end
+ t = new Achievement(self, 0, "Training")
+ entities.push(t)
+ t = new Achievement(self, 1, "Par")
+ entities.push(t)
+ t = new Achievement(self, 2, "Editor")
+ entities.push(t)
+ t = new Achievement(self, 3, "Challenge")
+ entities.push(t)
+ t = new Achievement(self, 4, "Congraturation")
+ entities.push(t)
+ t = new Achievement(self, 5, "Awesome")
+ entities.push(t)
+ run
+ end
+
+ # Last function called when the lauch state is ready
+ fun run do
+ dirty_all = true
+ end
+
+ init
+ do
+ load_levels
+ init_buttons
+ entities.clear
+ title
+ end
+
+ # Should all entity redrawn?
+ var dirty_all = true
+
+ # Draw all game entities.
+ fun draw(display: Display) do
+ dirty_all = true
+ if dirty_all then display.blit(back, 0, 0)
+ for g in entities do
+ if g.dirty or dirty_all then
+ g.dirty = false
+ #if g.x2-g.x>0 and g.y2-g.y>0 then ctx.drawImage(back, g.x, g.y, g.x2-g.x, g.y2-g.y, g.x, g.y, g.x2-g.x, g.y2-g.y)
+ g.draw(display)
+ #ctx.rect(g.x, g.y, g.width, g.height)
+ end
+ end
+ var ev = lastev
+ if ev isa Event then
+ display.blit(img[4,0],ev.offset_x-42,ev.offset_y-6)
+ end
+ dirty_all = false
+ end
+
+ # Update all game entities.
+ fun step do
+ if solver != null and not solver_pause then
+ for i in [0..solver_steps[ do
+ if solver.step then
+ solver_pause = true
+ break
+ end
+ end
+ solver.dump
+ if solver.is_over then solver = null
+ end
+ for g in entities do
+ g.update
+ end
+ end
+
+ # Return the game entity located at a mouse event.
+ fun get_game_element(ev: Event): nullable Entity
+ do
+ var x = ev.offset_x
+ var y = ev.offset_y
+ for g in entities do
+ if x>=g.x and x<g.x2 and y>g.y and y<g.y2 then
+ ev.game_x = x-g.x
+ ev.game_y = y-g.y
+ #print "get {g}"
+ return g
+ end
+ end
+ return null
+ end
+
+ # The game entlty the mouse went down on
+ var drag: nullable Entity = null
+
+ # Last mouse event. Used to dray the cursor
+ var lastev: nullable Event = null
+
+ # Callback when the mouse is pressed
+ fun onMouseDown(ev: Event) do
+ lastev = ev
+ var g = get_game_element(ev)
+ if g != null then
+ g.click(ev)
+ g.dirty = true
+ end
+ drag = g
+ end
+
+ # Callback when the mouse is releassed
+ fun onMouseUp(ev: Event) do
+ drag = null
+ end
+
+ # Callback when the mouse if moved while pressed
+ fun onMouseMove(ev: Event) do
+ lastev = ev
+ var g = get_game_element(ev)
+ if g == null then
+ statusbar.dirty = true
+ statusbar.over_txt = null
+ statusbar.over_what = null
+ return
+ end
+ if statusbar.over_what != g then
+ statusbar.dirty = true
+ var go = g.over
+ statusbar.over_txt = go
+ statusbar.over_what = g
+ g.enter(ev)
+ if go != null then print "***OVER*** {go}"
+ end
+ # We moved abode a element that accepts drag event
+ if drag == g and g.draggable then
+ # print "DRAG {g}"
+ ev.drag = true
+ g.click(ev)
+ g.dirty = true
+ end
+ end
+
+ # Current solver, if any
+ var solver: nullable Solver = null
+
+ # Is the solver paused?
+ var solver_pause = false
+
+ # Number of solver steps played in a single game `update`
+ var solver_steps = 20000
+
+ # Callback when a keyboard event is recieved
+ fun onKeyDown(ev: Event) do
+ var kc = ev.char_code
+ if kc == "e" then
+ grid_edit = grid.copy(true)
+ edit_grid(grid)
+ else if kc == "s" then
+ if solver == null then
+ solver = new Solver(grid)
+ solver_pause = false
+ else
+ solver_pause = not solver_pause
+ end
+ #solver.step
+ else if kc == "d" then
+ if solver == null then
+ solver = new Solver(grid)
+ solver_pause = true
+ else
+ solver.step
+ end
+ else if kc == "+" then
+ solver_steps += 100
+ print solver_steps
+ else if kc == "-" then
+ solver_steps -= 100
+ print solver_steps
+ else for g in entities do
+ if kc == g.shortcut then
+ g.click(ev)
+ g.dirty = true
+ end
+ end
+ end
+end
+
+# The spash title image
+class Splash
+ super Entity
+ init(game: Game)
+ do
+ super(game,game.xpad,game.ypad,380,350)
+ end
+ redef fun draw(ctx)
+ do
+ ctx.blit(game.logo, game.xpad, game.ypad)
+ end
+ redef fun click(ev)
+ do
+ game.snd_whip.replay
+ game.menu
+ end
+end
+
+class NextLevelButton
+ super TextButton
+ init(game: Game)
+ do
+ super(game, "NEXT LEVEL", 440, 24, "cyan", "Play next level", null)
+ enabled = false
+ end
+
+ redef fun update
+ do
+ var w = game.level.check_won(game.grid)
+ if self.enabled != w then
+ self.dirty = true
+ self.enabled = w
+ if w then
+ game.snd_win.replay
+ game.statusbar.set_tmp("Level solved!", "cyan")
+ end
+ end
+ end
+
+ redef fun click(ev)
+ do
+ if not self.enabled then
+ game.snd_duh.replay
+ var grid = game.grid
+ var monsters = grid.monsters
+ var angry = new Array[Tile]
+ var lonely = new Array[Tile]
+ var edges = new Array[Tile]
+ for i in [0..grid.width[ do
+ for j in [0..grid.height[ do
+ var t = grid.grid[i][j]
+ if t.kind == 0 then continue
+ if t.nexts == 0 then lonely.push(t)
+ if t.nexts == 1 and not monsters[t.kind].chain then edges.push(t)
+ if t.nexts > 2 then angry.push(t)
+ end
+ end
+
+ var l
+ if angry.length>0 then
+ l = angry
+ else if lonely.length>0 then
+ l = lonely
+ else
+ l = edges
+ end
+ for i in l do i.shocked=5
+
+ if angry.length>0 then
+ game.statusbar.set_tmp("Angry monsters!", "red")
+ else if lonely.length>0 then
+ game.statusbar.set_tmp("Lonely monsters!", "red")
+ else if not grid.won then
+ game.statusbar.set_tmp("Unconnected monsters!", "red")
+ else
+ game.statusbar.set_tmp("Not enough monsters!", "red")
+ end
+ return
+ end
+
+ game.snd_whip.replay
+ game.play_next
+ end
+end
+
+class MusicButton
+ super TextButton
+ init(game: Game)
+ do
+ super(game, "MUSIC", 470, 412, "purple", "Mute the music", "Unmute the music")
+ 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":"")
+ end
+end
+
+class SFXButton
+ super TextButton
+ init(game: Game)
+ do
+ super(game, "SOUND FX", 470, 382, "purple", "Mute the sound effects", "Unmute the sound effects")
+ end
+
+ redef fun click2(ev)
+ do
+ game.sfx_muted = self.toggled
+ if not game.sfx_muted then game.snd_whip.replay # Because the automatic one was muted
+ #save_cookie("sfx_muted",sfx_muted?"true":"")
+ end
+end
+
+class MenuButton
+ super TextButton
+ init(game: Game)
+ do
+ super(game, "MENU", 470, 442, "purple", "Exit to the main menu", null)
+ shortcut = "back"
+ end
+
+ redef fun click2(ev)
+ do
+ game.menu
+ end
+end
+
+class ResetButton
+ super TextButton
+ init(game: Game)
+ do
+ super(game,"RESET", 440, 61, "purple", "Clear the grid",null)
+ end
+
+ var count = 0
+
+ redef fun click2(ev)
+ do
+ self.count += 1
+ if self.count==1 then
+ game.statusbar.set_tmp("Click again to reset","white")
+ else if self.count==2 then
+ game.grid.reset(false)
+ if game.editing then
+ game.statusbar.set_tmp("Click again to clear all","white")
+ end
+ else if game.editing then
+ game.grid.reset(true)
+ end
+ game.dirty_all = true
+ end
+
+ redef fun enter2
+ do
+ self.count = 0
+ end
+end
+
+class EditButton
+ super TextButton
+ init(game: Game)
+ do
+ super(game,"EDIT", 540, 24, "purple", "Return to the editor",null)
+ end
+
+ redef fun click2(ev)
+ do
+ var ge = game.grid_edit
+ assert ge != null
+ game.edit_grid(ge)
+ end
+end
+
+class WonButton
+ super TextButton
+ init(game: Game)
+ do
+ super(game,"WON", 440, 24, "cyan", "", null)
+ enabled = false
+ end
+ redef fun click2(ev)
+ do
+ var ge = game.grid_edit
+ if not self.enabled then
+ game.statusbar.set_tmp("Solve the level first!", "red")
+ else if ge != null then
+ game.snd_whip.replay
+ game.edit_grid(ge)
+ else
+ game.snd_whip.replay
+ game.menu
+ end
+ end
+
+ redef fun update
+ do
+ var w = game.grid.won
+ if self.enabled != w then
+ self.dirty = true
+ self.enabled = w
+ if w then
+ game.snd_win.replay
+ game.statusbar.set_tmp("Level solved!", "cyan")
+ end
+ end
+ end
+end
+
+class TestButton
+ super TextButton
+ init(game: Game)
+ do
+ super(game,"TEST", 440, 24, "cyan", "Try to play the level", null)
+ end
+
+ redef fun click2(ev)
+ do
+ game.grid_edit = game.grid
+ game.play_grid(game.grid.copy(false))
+ end
+end
+
+class SaveButton
+ super TextButton
+ init(game: Game)
+ do
+ super(game, "SAVE", 540, 24, "purple", "Save the level", null)
+ end
+
+ redef fun click2(ev)
+ do
+ var res = game.grid.save
+ print "SAVE: {res}"
+ end
+end
+
+class LoadButton
+ super TextButton
+ init(game: Game)
+ do
+ super(game,"LOAD", 540, 61, "purple", "Load an user level", null)
+ end
+
+ redef fun click2(ev)
+ do
+ var grid2 = new Grid(game.gw,game.gh,game.monsters)
+ if grid2.load("") then
+ game.grid = grid2
+ end
+ game.dirty_all = true
+ end
+end
+
+class ContinueButton
+ super TextButton
+ init(game: Game)
+ do
+ super(game,"CONTINUE", 440, 24, "purple", "Continue playing", null)
+ end
+
+ redef fun click2(ev)
+ do
+ game.play_next
+ end
+end
+
+class StartButton
+ super TextButton
+ var level: Level
+ init(game: Game, level: Level)
+ do
+ self.level = level
+ if level.number == 0 then
+ super(game,"START", 440, 24, "purple", "Play the first level", null)
+ else
+ super(game,"CONTINUE", 440, 24, "purple", "Continue from level "+level.name, null)
+ end
+ end
+
+ redef fun click2(ev)
+ do
+ game.play(level)
+ end
+end
+
+#
+
+redef class App
+
+ # The game
+ var game: Game
+
+ # Wanted screen width
+ var screen_width = 640
+
+ # Wanted screen height
+ var screen_height = 480
+
+ redef fun window_created
+ do
+ super
+ game = new Game
+ game.font.hspace = -2
+ if args.length > 0 then
+ game.play(game.levels[args.first.to_i])
+ end
+ # img loading?
+ end
+
+ # Maximum wanted frame per second
+ var max_fps = 30
+
+ # clock used to track FPS
+ private var clock = new Clock
+
+ redef fun frame_core(display)
+ do
+ game.step
+ game.draw(display)
+ var dt = clock.lapse
+ var target_dt = 1000000000 / max_fps
+ if dt.sec == 0 and dt.nanosec < target_dt then
+ var sleep_t = target_dt - dt.nanosec
+ sys.nanosleep(0, sleep_t)
+ end
+ end
+
+ redef fun input(input_event)
+ do
+ #print input_event
+ if input_event isa QuitEvent then # close window button
+ quit = true # orders system to quit
+ else if input_event isa PointerEvent then
+ var ev = new Event(input_event.x.to_i, input_event.y.to_i, "")
+ if input_event.is_motion then
+ game.onMouseMove(ev)
+ else if input_event.pressed then
+ game.onMouseDown(ev)
+ else
+ game.onMouseUp(ev)
+ end
+ return true
+ else if input_event isa KeyEvent and input_event.is_down then
+ var ev = new Event(0, 0, input_event.key_name)
+ game.onKeyDown(ev)
+ return true
+ end
+
+ return false
+ end
+end
+
+redef class PointerEvent
+ fun is_motion: Bool do return false
+end
+
+redef class KeyEvent
+ fun key_name: String
+ do
+ var c = to_c
+ if c != null then return c.to_s
+ return "unknown"
+ end
+end
--- /dev/null
+# Monsterz - Chains of Friends
+#
+# 2010-2014 (c) Jean Privat <jean@pryen.org>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the Do What The Fuck You Want To
+# Public License, Version 2, as published by Sam Hocevar. See
+# http://sam.zoy.org/projects/COPYING.WTFPL for more details.
+
+# android version of the game
+module friendz_android
+
+import friendz
+import mnit_android
+
+redef class App
+ redef fun window_created
+ do
+ super
+ var w = screen_width
+ display.set_viewport(0,0,w,w*display.height/display.width)
+ end
+end
+
+redef class AndroidPointerEvent
+ redef fun is_motion do return not motion_event.just_went_down
+end
--- /dev/null
+# Monsterz - Chains of Friends
+#
+# 2010-2014 (c) Jean Privat <jean@pryen.org>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the Do What The Fuck You Want To
+# Public License, Version 2, as published by Sam Hocevar. See
+# http://sam.zoy.org/projects/COPYING.WTFPL for more details.
+
+# Linux (SDL) version of the game
+module friendz_linux
+
+import friendz
+import mnit_linux
+
+redef class Display
+ redef fun wanted_width do return app.screen_width
+ redef fun wanted_height do return app.screen_height
+end
+
+redef class SDLDisplay
+ redef fun enable_mouse_motion_events do return true
+end
+
+redef class SDLMouseMotionEvent
+ redef fun is_motion do return true
+end
--- /dev/null
+# Monsterz - Chains of Friends
+#
+# 2010-2014 (c) Jean Privat <jean@pryen.org>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the Do What The Fuck You Want To
+# Public License, Version 2, as published by Sam Hocevar. See
+# http://sam.zoy.org/projects/COPYING.WTFPL for more details.
+
+# Game login on the grid of monsters
+module grid
+
+# Grid of monsters.
+class Grid
+ # width of the current grid
+ var width: Int
+
+ # maximum width of the grid
+ var max_width: Int
+
+ # height of the current grid
+ var height: Int
+
+ # maximum height of the grid
+ var max_height: Int
+
+ # nm is the number of monster + 1 for the empty tile
+ var nb_monsters: Int
+
+ # the data grid
+ private var grid = new Array[Array[Tile]]
+
+ init(mw,mh,nm: Int)
+ do
+ self.max_width = mw
+ self.max_height = mh
+ self.nb_monsters = mh
+ clear
+ end
+
+ # Reinitialize the grid with new empty tiles and monsters info
+ fun clear
+ do
+ self.number = 0
+ self.error = 0
+ self.won = false
+ for i in [0..max_width[ do
+ self.grid[i] = new Array[Tile]
+ for j in [0..max_height[ do
+ var t = new Tile(self, i, j)
+ self.grid[i][j] = t
+ end
+ end
+ self.monsters = new Array[MonsterInfo]
+ for i in [0..nb_monsters] do
+ self.monsters[i] = new MonsterInfo
+ end
+ self.resize(max_width,max_height)
+ end
+
+ # Clear all monsters
+ # if `fixed` is false, fixed monsters remains
+ fun reset(fixed: Bool): Bool
+ do
+ var r = false
+ for i in [0..width[ do
+ for j in [0..height[ do
+ var t = self.grid[i][j]
+ if fixed then t.fixed = false
+ if not t.fixed and t.kind>0 then
+ t.update(0)
+ r = true
+ end
+ end
+ end
+ return r
+ end
+
+ # Total number of monsters in the grid
+ var number = 0
+
+ # Number of monsters alone or with >=3 next
+ var error = 0
+
+ # information about each kind of monsters
+ var monsters = new Array[MonsterInfo]
+
+ # Last result of win.
+ var won = false
+
+ # Check that the monster of `kind` form a complete chain.
+ # if not null, `t` is used as a starting tile to check the chain
+ fun check_chain(kind: Int, t: nullable Tile): Bool
+ do
+ var m = monsters[kind]
+ if m.angry > 0 or m.lonely > 0 or m.single > 2 then
+ # easy case for no chain
+ # print "easy {kind} chain: false"
+ m.chain = false
+ return false
+ else
+ if t == null then
+ # Search for a starting
+ for x in [0..width[ do for y in [0..height[ do
+ var t2 = get(x,y)
+ if t2 == null or t2.kind != kind then continue
+ t = t2
+ break label found
+ end label found
+ if t == null then
+ assert m.number == 0
+ m.chain = true
+ return m.chain
+ end
+ # print "get neighbor {t}"
+ end
+ assert t.kind == kind
+ var c = count_chain(t, 1000.rand)
+ # print "old {kind} chain? {c} / {m.number}"
+ m.chain = c == m.number
+ return m.chain
+ end
+ end
+
+ # The total number of monsters connected to the chain of `t`
+ # `mark` is a uniq number used to mark tiles
+ fun count_chain(t: Tile, mark: Int): Int
+ do
+ t.chain_mark = mark
+ var res = 1
+ for i in [-1..1] do
+ for j in [-1..1] do
+ if (i==0 and j==0) or (i!=0 and j!=0) then continue
+ var t2 = get(t.x+i,t.y+j)
+ if t2 == null then continue
+ if t2.chain_mark == mark or t2.kind != t.kind then continue
+ res += count_chain(t2, mark)
+ end
+ end
+ return res
+ end
+
+ # Resize the grid. Do not touch the content.
+ fun resize(w,h: Int)
+ do
+ self.width = w
+ self.height = h
+ end
+
+ # Try to get the tila at `x`,`y`.
+ # Returns null if the position is out of bound.
+ fun get(x,y: Int): nullable Tile
+ do
+ if x<0 or x>=self.width or y<0 or y>=self.height then return null
+ return self.grid[x][y]
+ end
+
+
+ var fixed_shaped = """[
+ [{x:1,y:0},{x:2,y:0},{x:1,y:1},{x:2,y:1}],
+ [{x:0,y:0},{x:0,y:1},{x:0,y:2}],
+ [{x:1,y:2},{x:2,y:2},{x:3,y:2}],
+ [{x:4,y:1},{x:4,y:2}],
+ [{x:3,y:0},{x:4,y:0}],
+ [{x:3,y:1}]
+ ]"""
+
+ # Set shapes for the fixed blocks.
+ fun metalize
+ do
+ for i in [0..width[ do
+ for j in [0..height[ do
+ var t = self.grid[i][j]
+ if t.fixed then t.shape = null
+ end
+ end
+ for shape in fixed_shaped.split(",") do
+ for i in [0..width[ do
+ for j in [0..height[ do
+ var ts = new Array[Tile]
+ for l in [0..shape.length[ do
+ #var t = self.get(i+shape[l].x-shape[0].x,j+shape[l].y-shape[0].y)
+ var t = self.get(i,j)
+ if t != null and t.fixed and t.shape == null then ts.push(t)
+ end
+ if ts.length == shape.length then
+ for l in [0..shape.length[ do
+ ts[l].shape = shape[l]
+ end
+ end
+ end
+ end
+ end
+ end
+
+ # Return the serialization of the fixed tiles. */
+ fun save: String
+ do
+ var res = ""
+ var str = ".#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
+ if tk == last and rle<9 then
+ rle += 1
+ else
+ if last != null then
+ if rle>1 then res += rle.to_s
+ res += str.chars[last].to_s
+ end
+ rle = 1
+ last = tk
+ end
+ end
+ if last != null then
+ if rle>1 then res += rle.to_s
+ res += str.chars[last].to_s
+ end
+ res += "|"
+ end
+ return res
+ end
+
+ # Load a new grid from a seialization.
+ fun load(str: String): Bool
+ do
+ self.clear
+ var l = str.length
+ var x = 0
+ var y = 0
+ var mx = 1
+ var my = 1
+ var rle = 1
+ for i in [0..l[ do
+ var z = rle
+ while z > 0 do
+ z -= 1
+ rle = 1
+ var c = str.chars[i]
+ if c == '|' then
+ if x > mx then mx = x
+ x = 0
+ y += 1
+ else if c == '.' then
+ x += 1
+ else if c == '#' then
+ var t = self.get(x,y)
+ 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)
+ t.fixed = true
+ x += 1
+ else if c >= '1' and c <= '9' then
+ rle = c.to_i
+ else
+ abort
+ end
+ end
+ end
+ 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
+ return false
+ end
+ self.resize(mx,my)
+ self.metalize
+ return true
+ end
+
+ # A ASCII version of the grid.
+ redef fun to_s: String
+ do
+ var ansicols = once ["37;1","31","36;1","32;1","35;1","33;1","33","34;1","31;1","37"]
+ var b = new FlatBuffer
+ b.append("{width}x{height}\n")
+ for j in [0..height[ do
+ for i in [0..width[ do
+ var t = grid[i][j]
+ var k = t.kind
+ var c = ' '
+ if k == 0 then
+ if t.fixed then c = '#'
+ else
+ b.add(0x1b.ascii)
+ b.add('[')
+ b.append ansicols[k]
+ c = (k + 'a'.ascii - 1).ascii
+ if t.fixed then c = c.to_upper
+ b.append("m")
+ end
+ b.add(c)
+ if k != 0 then
+ b.add(0x1b.ascii)
+ b.append("[0m")
+
+ end
+ end
+ b.append "|\n"
+ end
+ return b.to_s
+ end
+
+ # Return a copy of the current grid.
+ # if (!no_fixed) copy only the fixed tiles.
+ fun copy(no_fixed: Bool): Grid
+ do
+ var g = new Grid(self.max_width, self.max_height, self.nb_monsters)
+ g.resize(width, height)
+ for y in [0..height[ do
+ for x in [0..width[ do
+ var t = self.grid[x][y]
+ if no_fixed or t.fixed then
+ var t2 = g.grid[x][y]
+ t2.update(t.kind)
+ t2.fixed = t.fixed
+ end
+ end
+ end
+ g.metalize
+ return g
+ end
+
+ # Internal check of the validity of tile and monster informations
+ fun check_grid
+ do
+ var m2 = new Array[MonsterInfo]
+ for m in [0..nb_monsters] do
+ m2[m] = new MonsterInfo
+ end
+ for x in [0..width[ do
+ for y in [0..height[ do
+ var n = 0
+ var f = 0
+ var t = get(x,y)
+ assert t != null
+ assert t.x == x
+ assert t.y == y
+ var k = t.kind
+ if k == 0 then continue
+
+ for i in [-1..1] do
+ for j in [-1..1] do
+ if i == j or (i != 0 and j != 0) then continue
+ var t2 = get(x+i, y+j)
+ if t2 == null then continue
+ if t2.kind == k then
+ n += 1
+ else if t2.kind == 0 and not t2.fixed then
+ f += 1
+ end
+ end
+ end
+ assert n == t.nexts else
+ print self
+ print "{t} says {t.nexts} nexts, found {n}"
+ end
+ #assert f == t.frees else
+
+ var m = m2[k]
+ m.number += 1
+ if n == 0 then
+ m.lonely += 1
+ else if n == 1 then
+ m.single += 1
+ else if n > 2 then
+ m.angry += 1
+ end
+ end
+ end
+ for m in [1..nb_monsters] do
+ assert m2[m].number == monsters[m].number
+ assert m2[m].lonely == monsters[m].lonely
+ assert m2[m].single == monsters[m].single
+ assert m2[m].angry == monsters[m].angry
+ end
+ end
+end
+
+# Information about each kind of monsters
+class MonsterInfo
+ # number of monsters of this kind on board
+ var number = 0
+ # number of monsters of this kind to place, -1 if no limit
+ var remains: Int = -1
+ # number of monsters that have exactly 1 next
+ var single = 0
+ # number of monsters that have exactly 0 next
+ var lonely = 0
+ # number of monsters that have 3 or more next
+ var angry = 0
+ # Are all monsters form a wining chain?
+ var chain = false
+end
+
+# A localized tile of a grid, can contain a monster and be fixed.
+class Tile
+ # The grid of the tile.
+ var grid: Grid
+
+ # The x coordinate in the grid (starting from 0).
+ var x: Int
+
+ # The y coordinate in the grid (starting from 0).
+ var y: Int
+
+ # The kind of monster in the grid. 0 means empty.
+ var kind = 0
+
+ # blink time to live (0 means no blinking).
+ var blink = 0
+
+ # shocked time to live (0 means not shocked)
+ var shocked = 0
+
+ # number of neighbors of the same kind.
+ var nexts = 0
+
+ # number of free non fixed next tiles
+ var frees = 0
+
+ # is the tile editable (metal block)
+ var fixed = false
+
+ redef fun to_s
+ do
+ var s
+ if fixed then
+ s = "#ABCDEFGHI"
+ else
+ s = ".abcdefghi"
+ end
+ return "\{{x},{y}:{s.chars[kind]}\}"
+ end
+
+ # Shape for metal block
+ var shape: nullable Object = null
+
+ # Flag for `count_chain` computation.
+ private var chain_mark = 0
+
+ # Set a new kind of monster on tile
+ # Return true is the move made the grid unsolvable (bad move)
+ fun update(nkind: Int): Bool
+ do
+ var t = self
+ var g = self.grid
+ var res = false
+ var okind = t.kind
+ if okind == nkind then return false
+
+
+ # First, remove it and update info.
+ if okind > 0 then
+ var m = g.monsters[okind]
+ var n = t.nexts
+ if n > 2 then
+ g.error -= 1
+ m.angry -= 1
+ else if n == 1 then
+ m.single -= 1
+ else if n == 0 then
+ g.error -= 1
+ m.lonely -= 1
+ end
+ m.number -= 1
+ g.number -= 1
+ end
+ t.nexts = 0
+ t.blink = 5
+ t.frees = 0
+
+ var a_neigbor: nullable Tile = null
+ # update neighbors
+ for i in [-1..1] do
+ for j in [-1..1] do
+ if (i==0 and j==0) or (i!=0 and j!=0) then continue
+ var t2 = g.get(t.x+i,t.y+j)
+ if t2 == null then continue
+ if t2.kind == 0 then
+ if not t2.fixed then t.frees += 1
+ continue
+ end
+ var m = g.monsters[t2.kind]
+
+ if t2.kind == okind then
+ if a_neigbor == null then a_neigbor = t2
+ # same than old, thus dec neighbors
+ t2.nexts -=1
+ var n = t2.nexts
+ if n == 2 then
+ g.error -= 1
+ m.angry -= 1
+ else if n == 1 then
+ m.single += 1
+ g.error += 1
+ else if n == 0 then
+ m.single -= 1
+ m.lonely += 1
+ end
+ # print "+ {t} one less next: {t2} ; +({i}x{j})"
+ end
+
+ if t2.kind == nkind then
+ # Same than new, thus inc neighbors
+ t2.nexts += 1
+ t.nexts += 1
+ var n = t2.nexts
+ if n > 3 then
+ res = true
+ else if n == 3 then
+ g.error += 1
+ m.angry += 1
+ res = true
+ else if n == 2 then
+ m.single -= 1
+ g.error -= 1
+ else if n == 1 then
+ m.single += 1
+ m.lonely -= 1
+ end
+ # print "+ {t} one more next: {t2}"
+ end
+ end
+ end
+
+ # Add and update neighbors info
+ t.kind = nkind
+ if nkind > 0 then
+ var m = g.monsters[nkind]
+ var n = t.nexts
+ if n > 2 then
+ g.error += 1
+ m.angry += 1
+ else if n == 1 then
+ m.single += 1
+ g.error += 1
+ else if n == 0 then
+ g.error += 1
+ m.lonely += 1
+ end
+ m.number+=1
+ g.number+=1
+
+ g.check_chain(nkind, t)
+ end
+
+ # check if the old kind broke, or create a chain
+ if okind > 0 then
+ g.check_chain(okind, a_neigbor)
+ end
+
+ # update win status
+ g.won = true
+ for m in g.monsters do
+ if m.number > 0 and not m.chain then g.won = false
+ end
+
+ #grid.check_grid
+
+ return res
+ end
+end
+
--- /dev/null
+# Monsterz - Chains of Friends
+#
+# 2010-2014 (c) Jean Privat <jean@pryen.org>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the Do What The Fuck You Want To
+# Public License, Version 2, as published by Sam Hocevar. See
+# http://sam.zoy.org/projects/COPYING.WTFPL for more details.
+
+# Level managment
+module level
+
+import grid
+
+# A level in the game
+class Level
+ # The associated game
+ var game: Game
+
+ init(game: Game, i: Int, code: String)
+ do
+ self.game = game
+ var ls = code.split(";")
+ self.number = i
+ self.str = ls[0]
+ self.par = ls[1].to_i
+ if ls.length >= 3 then
+ self.status = ls[2]
+ end
+ self.is_tutorial = i<=4
+ self.is_challenge = i>=25
+ if self.is_tutorial then
+ self.name = "T{i+1}"
+ self.fullname = "Tutorial level {i+1}"
+ else if not self.is_challenge then
+ self.name = (i-4).to_s
+ self.fullname = "Level {i-4}"
+ else
+ self.name = "C{i-24}"
+ self.fullname = "Challenge level {i-24}"
+ end
+ end
+
+ # number the level (0 for first)
+ var number: Int
+
+ # initial grid position
+ var str: String
+
+ # top score
+ var par: Int
+
+ # Help message if any
+ var status: String = ""
+
+ # Is the level a tutorial level?
+ var is_tutorial: Bool
+
+ # Is the level a challenge level?
+ var is_challenge: Bool
+
+ # The short name of the level (eg. "T1")
+ var name: String
+
+ # The long name of the level (eg. "Tutorial level 1")
+ var fullname: String
+
+ # The player best wining score.
+ # 0 it not yet won
+ var score = 0
+
+ var l_disabled = 1
+ var l_open = 2
+ var l_won = 3
+ var l_par = 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
+ return l_won
+ else return l_par
+ 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)
+ if not w then return false
+ if g.number > self.score then
+ self.score = g.number
+ self.save
+ end
+ return true
+ end
+
+ fun save
+ do
+ #save_cookie("s"+self.hash, self.score>0?self.score:"")
+ end
+end
+
+# main game object
+class Game
+ # Game version
+ var version = "1.99"
+
+ # Names of kind of monsters @constant
+ var colors: Array[nullable String] = [null, "Red", "Cyan", "Green", "Purple", "Yellow", "Orange", "Blue", "Pink", "White"]
+
+ # max grid width
+ var gw = 16
+
+ # max grid height
+ var gh = 16
+
+ # Number of monster kinds (+1 for empty) @constant
+ var monsters = 9
+
+ # The grid to play on
+ var grid = new Grid(gw, gh, monsters)
+
+ # LEVELS ******************************************************************
+
+ # Raw level description
+ var levels_code: Array[String] = [
+ ".3#|A2.A|3#.|;6;Connect two monsters",
+ ".#.|A.#|#.A|;5;Diagonals do not count",
+ "2.A|A#.|2.A|;8;Connect all monsters",
+ "3A.|3.A|2.A.|;10;Only 1 or 2 neighbors",
+ "2.A.|.B.B|2.A.|;12;Build two chains",
+
+ "A.A#|4.|#.#.|A3.|;11",
+ "A2.#A|5.|2.A2.|4.#|4.A|;17",
+ "A2.B2.|.B2.A.|6.|.AB2.B|6.|6.|;36",
+ "A5.|B5.|#.A3.|6.|2.A.B.|.A4.|;26",
+ "8.|BA2.B.A.|2.C.C.B.|6.#.|;29",
+
+ ".B3.B|6.|.#3.#|.C3.B|.#4.|6.|.C3.C|;30",
+ ".A.#4.|.C3.A.E|.A6.|6.C.|8.|2.6#|6.E#|5.C2#|;54",
+ ".A5.A|.A.#3.A|2.#A.#.#|.#.#4.|2.A3.#.|8.|4.A2.A|2.A2.A.#|;37",
+ "AB3.C|2.AC2.|6.|.B.CB.|6.|;30",
+ "AC5#CA|2.#3.#2.|4.#4.|7.#.|5.#.#.|9.|2.G6.|6.2#.|9.|H#F#G#F#H|;72",
+
+ "AG3.C|2.AC2.|6.|.G2.G.|2.C3.|6.|;32",
+ "7.#2.|3.#.#4.|.#8.|7.D2.|2.#.3#3.|2.#3.2#FC|9..|2.2#3.FCD|9..|;71",
+ "2.#A4.|6.B.|.C6.|.A.A.AC.|#7.|5.B2.|8.|#A5.C|;56",
+ "9.3.|8.#3.|2.B.#3.BA2.|2.AC4.DC2.|2.EA8.|8.#3.|.2#3.#5.|7.#4.|2#3.#B5.|5.CD5.|.2#9.|3.E8.|;119",
+ "9.7.|.2#B8#E2#.|.E8.#3.#.|.#3.4#.#.#.#.|.#.#.#4.#.#.#.|.C.#3.2#.#.#.#.|.#.#.4#3.#.#.|.D8.3#.#.|.#.#.4#5.A.|.A5.#2.#.3#.|.3#.#.2#.#2.2#.|.D3.#4.2#2.#.|.#.3#.#.4#.#.|.#5.#6.#.|.3#C6#B3#.|9.7.|;130",
+
+ "8.|.#C2.C#.|.#E2.E#.|8.|8.|.B#2.#A.|.#C2.C#.|8.|8.|.BF2.FA.|.2#2.2#.|8.|8.|.2#2.2#.|.A#2.B#.|8.|;92",
+ ".A9..|D9.F.|7.CA3.|3.E8.|9.2.H|9.2.#|7.G4.|4.BG2.F3.|2.B6.E2.|9.G2.|.H8.C.|8.D3.|;140",
+ "2.F9..|2.C9..|9..C2.|8.AG3.|9.4.|4.2#5.F.|7.E4.#|3.A9.|.G.CF4.C.#.|9.2.E.|;125",
+ "9.HCB|.DH9.|.GC9.|9.3.|4.G7.|3.#8.|9.3.|3.B4.D3.|.G9..|8.#3.|2.C2.2#5.|9.3.|;135",
+ "9.6.A|6.C9.|2.A.B4.E3.#I.|9.I.#4.|9.7.|2.E3.C9.|7.B8.|5.G6.G3.|6.C6.G2.|9..H3.B.|.CH.#5.#2.C2.|4.H3.#3.H3.|.B3.F5.F4.|.D.D4.D5.D.|9.7.|6.C9.|;244",
+
+ "E6.|7.|.F.E.F.|.F.#.E.|.F.E.E.|7.|6.F|;48",
+ "3.2#3.|2.A2.A2.|.C4.C.|8.|3.2#3.|2.A2.A2.|.C4.C.|8.|;56",
+ "3.#3.A4.|5.#6.|3.#5.#2.|.#4.#4.#|4.#7.|#6.#4.|8.#3.|.#9..|.#3.#4.#.|3.2#3.#3.|;74",
+ "2.A7.|2.D7.|2.A4.A2.|2.G4.G2.|2.A4.D2.|2.D7.|2.A7.|2.G7.|2.D7.|2.G7.|;95",
+ "E9.6.|9.H4.#.|9.E2.#3.|9.F2.#2.#|9.3.H2.#|HEF9.3.#|9.7.|6.#9.|6.#2.H3.HEF|.6#2.E6.|9.F6.|5#9.2.|4.#9.#.|.F#3.7#.#.|9.5.#.|9.7.|;225"
+ ]
+
+ # The loaded levels
+ var levels = new Array[Level]
+
+ # Load levels
+ # used duting `init`
+ fun load_levels
+ do
+ # Transform level strings into level objects. */
+ for i in [0..levels_code.length[ do
+ var l = new Level(self,i, levels_code[i])
+ levels[i] = l
+ #var v = read_cookie("s"+l.hash)
+ #l = v
+ end
+ end
+
+ # The current played level (if any)
+ var level: nullable Level = null
+
+ init
+ do
+ load_levels
+ end
+end
+
--- /dev/null
+# Monsterz - Chains of Friends
+#
+# 2010-2014 (c) Jean Privat <jean@pryen.org>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the Do What The Fuck You Want To
+# Public License, Version 2, as published by Sam Hocevar. See
+# http://sam.zoy.org/projects/COPYING.WTFPL for more details.
+
+# Basic game solver
+module solver
+intrude import grid
+
+# A solver with a deep-first search algorithm
+# Instead of storig grids in memory, it plays by placing monsters and backtrack by removing placed monsters.
+class Solver
+ # Return true if grid accepts kind at tile t.
+ # Accepts means that
+ # * the tile is playble (free and not fixed)
+ # * the new monster will not be angry
+ # * the new monster will not make neighbor angry
+ fun accepts(t: Tile, kind: Int): Bool
+ do
+ var grid = t.grid
+ if t.kind != 0 or t.fixed then return false
+ #if t.x<0 or t.x>=grid.width or t.y<0 or t.y>=grid.height then return false
+ var cpt = 0
+ for i in [-1..1] do
+ for j in [-1..1] do
+ if i==0 and j==0 or i!=0 and j!=0 then continue
+ var x2 = t.x+i
+ var y2 = t.y+j
+ if x2<0 or x2>=grid.width or y2<0 or y2>=grid.height then continue
+ var t2 = grid.grid[x2][y2]
+ if t2.kind == kind then
+ if t2.nexts >= 2 then return false
+ cpt += 1
+ if cpt>=3 then return false
+ end
+ end
+ end
+ return true
+ end
+
+ # Return the list of accepting neighbors of t
+ fun frees(t: Tile): Array[Tile]
+ do
+ var grid = t.grid
+ var res = new Array[Tile]
+ for i in [-1..1] do
+ for j in [-1..1] do
+ if i==0 and j==0 or i!=0 and j!=0 then continue
+ var x2 = t.x+i
+ var y2 = t.y+j
+ if x2<0 or x2>=grid.width or y2<0 or y2>=grid.height then continue
+ var t2 = grid.grid[x2][y2]
+ if accepts(t2, t.kind) then res.push(t2)
+ end
+ end
+ return res
+ end
+
+ # The grid played on
+ var grid: Grid
+
+ # Open moves to play
+ # If empty, it means the next move will be backtraking
+ var tries = new Array[Tile]
+
+ # The color played for `tries`
+ var kind = 1
+
+ # Search free tiles next to lonely monsters
+ # Used to initialize `tries`
+ fun look_start: Array[Tile]
+ do
+ var start = new Array[Tile]
+ var min = 1
+ kind = 0
+
+ var tile: nullable Tile = null
+
+ for x in [0..grid.width[ do
+ for y in [0..grid.height[ do
+ var t = grid.grid[x][y]
+ var k = t.kind
+ if k == 0 or grid.monsters[k].chain then continue
+ var n = t.nexts
+
+ if n > min then continue
+
+ var fs = frees(t)
+ var l = fs.length
+ if l == 0 then continue
+
+ tile = t
+ if n == 0 then
+ if min == 1 or start.length > l then
+ start = fs
+ kind = k
+ end
+ else if kind == 0 or kind == k then
+ start.add_all fs
+ kind = k
+ end
+
+ min = n
+ end
+ end
+ #print "-------------"
+ #print grid
+ #dump
+ #print "START: {tile} -> {start.join(",")}"
+ #print "-------------"
+ return start
+ end
+
+ # compute and print some metrics about the problem
+ fun size_problem
+ do
+ var free = 0
+ for x in [0..grid.width[ do
+ for y in [0..grid.height[ do
+ var t = grid.grid[x][y]
+ if t.kind == 0 and not t.fixed then free += 1
+ end
+ end
+ print "FREE: {free}"
+ var ms = 0
+ for m in grid.monsters do
+ if m.number > 0 then ms += 1
+ end
+ print "KINDS: {ms}"
+ print "SIZE: {(ms+1).to_f.pow(free.to_f)}"
+ end
+
+ init(grid: Grid)
+ do
+ self.grid = grid
+ size_problem
+ tries = look_start
+ end
+
+ # Zipper in the search tree as a FIFO
+ var history = new Array[Move]
+
+ # Perform a backtrack step
+ # Remove the placed monsters and go for the other tries
+ fun backtrack: Bool
+ do
+ if history.is_empty then
+ is_over = true
+ return true
+ end
+ var h = history.pop
+ tries = h.tries
+ opens -= tries.length
+ h.tile.update(0)
+ kind = h.kind
+ return false
+ end
+
+ # Number of player steps
+ var steps = 0
+
+ # Number of open in the
+ # real opens is `opens+tries.length`
+ var opens = 0
+
+ fun dump
+ do
+ print "STEPS: {steps}"
+ print "OPENS: {opens+tries.length}"
+ print "DEPTH: {history.length}"
+ print "NEXTS: {tries.join(", ")}"
+ end
+
+ # Is the last step exhausted all the possibilities?
+ var is_over = false
+
+ # Compute the next step.
+ # Return tru on a wining position (`grid.won`) or when the solver `is_over`
+ fun step: Bool
+ do
+ #print "=========="
+ #print grid
+ #dump
+ #print "=========="
+ steps += 1
+ if tries.is_empty then
+ return backtrack
+ end
+ var t = tries.pop
+ var eval = 0
+ var fail = t.update(kind)
+ if not fail then
+ #eval = evaluate_new_tile(t)
+ #fail = (eval == -1)
+ end
+ if not fail then
+ opens += tries.length
+ var h = new Move(t, tries, kind)
+ history.push(h)
+ if grid.won then
+ tries = new Array[Tile]
+ return true
+ end
+ if t.nexts == 2 then
+ tries = look_start
+ else
+ tries = frees(t)
+ end
+ else
+ t.update(0)
+ end
+ return false
+ end
+end
+
+# A stored move in the `Solver::history`
+class Move
+ # The tile played
+ var tile: Tile
+
+ # The remainig alternatives to try
+ var tries: Array[Tile]
+
+ # to color for the moves
+ var kind: Int
+end
--- /dev/null
+default:
+ mkdir -p bin
+ make -C ../nitcc
+ cp ../nitcc/src/nitcc bin/
+ ./bin/nitcc ./grammar/javap.sablecc
+ ../../bin/nitg ./src/javap_visitor.nit -o ./bin/jwrapper
+ mv *.nit ./src/
+ mkdir -p gen
+ mv javap* ./gen/
+
+clean:
+ rm -f bin/javap_test_parser bin/jwrapper
+ rm -f gen/*
+ rm -rf .nit_compile/
--- /dev/null
+Grammar javap;
+
+Lexer
+
+identifier = ('a'..'z'|'A'..'Z'|'_'|'$') ('a'..'z'|'A'..'Z'|'_'|'$'|'0'..'9')*;
+blank = (' '|'\n'|'\t'|'\r')+;
+separator = ('.'|'/');
+
+Parser
+Ignored blank;
+
+multi_files = class_or_interface*;
+
+class_or_interface = class_declaration | interface_declaration;
+
+class_declaration = class_header '{' field_declaration* '}';
+
+class_header = modifier* 'class' full_class_name extends_declaration?
+ implements_declaration? throws_declaration?;
+interface_declaration = modifier* 'interface' full_class_name extends_interface_declaration?
+ '{' field_declaration* '}';
+
+modifier = 'public'|'private'|'protected'|'static'|'final'|'native'|'synchronized'|'abstract'|'threadsafe'|'transient'|'volatile';
+type = type_specifier '[]'*;
+type_specifier = 'boolean'|'byte'|'char'|'short'|'int'|'float'|'long'|'double' | type_ref;
+
+type_ref = full_class_name | generic_identifier 'extends' full_class_name | '?';
+type_refs = {tail:} type_refs ',' type_ref | {head:} type_ref;
+
+generic_param = '<' generic_parameter_list '>';
+generic_parameter_list = {tail:} generic_parameter_list ',' parameter | {head:} parameter;
+generic_identifier = full_class_name | '?';
+
+full_class_name = full_class_name separator class_name | class_name;
+class_name = identifier generic_param?;
+
+interface_name = full_class_name;
+interface_list = {tail:} interface_list ',' interface_name | {head:} interface_name;
+
+parameter = type '...'?;
+parameter_list_comp = {tail:} parameter_list_comp ',' parameter | {head:} parameter;
+parameter_list = parameter_list_comp;
+
+exception = type;
+exception_list = exception_list ',' exception | exception;
+
+statement = variable_declaration | statement_block | ';';
+statement_block = '{' statement* '}';
+
+variable_id = identifier '[]'*;
+method_id = identifier;
+
+field_declaration = method_declaration | constructor_declaration | variable_declaration | static_declaration | ';';
+variable_declaration = modifier* type variable_id throws_declaration? ';';
+method_declaration = modifier* generic_param? type method_id '(' parameter_list? ')' throws_declaration? ';';
+constructor_declaration = modifier* full_class_name '(' parameter_list? ')' throws_declaration? ';';
+implements_declaration = 'implements' interface_list*;
+extends_interface_declaration = 'extends' interface_list*;
+extends_declaration = 'extends' type;
+static_declaration = modifier* '{' '}' ';';
+throws_declaration = 'throws' exception_list?;
--- /dev/null
+# 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.
+
+# Services to generate extern class `in "Java"`
+module code_generator
+
+intrude import types
+
+class CodeGenerator
+
+ var file_out: OFStream
+ var java_class: JavaClass
+ fun code_warehouse: CodeWarehouse do return once new CodeWarehouse
+
+ init (file_name: String, jclass: JavaClass)
+ do
+ file_out = new OFStream.open(file_name)
+ self.java_class = jclass
+ end
+
+ fun generate
+ do
+ var jclass = self.java_class
+
+ file_out.write("import mnit_android\n")
+ gen_class_header(jclass.name)
+
+ # Attributes generation
+ for id, jtype in jclass.attributes do gen_attribute(id, jtype)
+
+ for id, methods_info in jclass.methods do
+ for method_info in methods_info do
+ var nid = id
+ if methods_info.length > 1 then nid += "{methods_info.index_of(method_info)}"
+ gen_method(id, nid, method_info.return_type, method_info.params)
+ end
+ end
+
+ file_out.write("\nend")
+ end
+
+ fun gen_class_header(full_class_name: Array[String])
+ do
+ file_out.write("extern class Native{full_class_name.last} in \"Java\" `\{ {full_class_name.join(".")} `\}\n")
+ file_out.write("\tsuper JavaObject\n\tredef type SELF: Native{full_class_name.last}\n\n")
+ end
+
+ fun gen_attribute(jid: String, jtype: JavaType)
+ do
+ file_out.write("\tvar {jid.to_snake_case}: {jtype.to_nit_type}\n")
+ end
+
+ fun gen_method(jmethod_id: String, nmethod_id: String, jreturn_type: JavaType, jparam_list: Array[JavaType])
+ do
+ var java_params = ""
+ var nit_params = ""
+ var nit_id = "arg"
+ var nit_id_no = 0
+ var nit_types = new Array[NitType]
+ var comment = ""
+
+ # Parameters
+ for i in [0..jparam_list.length[ do
+ var jparam = jparam_list[i]
+ var nit_type = jparam.to_nit_type
+ var cast = ""
+
+ if not jparam.is_collection then cast = jparam.param_cast
+
+ nit_types.add(nit_type)
+ nit_type.arg_id = "{nit_id}{nit_id_no}"
+
+ if i == jparam_list.length - 1 then
+ java_params += "{cast}{nit_id}{nit_id_no}"
+ nit_params += "{nit_id}{nit_id_no}: {nit_type}"
+ else
+ java_params += "{cast}{nit_id}{nit_id_no}" + ", "
+ nit_params += "{nit_id}{nit_id_no}: {nit_type}, "
+ end
+
+ nit_id_no += 1
+ # Comment if one type is unknown
+ if not nit_type.is_complete then comment = "#"
+ end
+
+ # Method identifier
+ var method_id = nmethod_id.to_snake_case
+ var nit_signature = new Array[String]
+
+ nit_signature.add "\tfun {method_id}"
+
+ if not jparam_list.is_empty then
+ nit_signature.add "({nit_params})"
+ end
+
+ var return_type = null
+
+ if not jreturn_type.is_void then
+ return_type = jreturn_type.to_nit_type
+ if not return_type.is_complete then comment = "#"
+ nit_signature.add ": {return_type} "
+ end
+
+ file_out.write(comment + nit_signature.join(""))
+
+ var param_to_copy = param_to_copy(jparam_list, nit_types)
+
+ # Copy one parameter, the return value, one parameter and the return value or nothing
+ if return_type != null then
+ if return_type.is_complete and jreturn_type.is_collection then
+ if param_to_copy != null then
+ var rtype_couple = new Couple[JavaType, NitType](jreturn_type, return_type)
+ file_out.write(code_warehouse.param_return_copy(rtype_couple, param_to_copy, jmethod_id, java_params))
+ else
+ file_out.write(code_warehouse.return_type_copy(jreturn_type, return_type, jmethod_id, java_params))
+ end
+ else if param_to_copy != null then
+ file_out.write(code_warehouse.param_type_copy(param_to_copy.first, param_to_copy.second, jmethod_id, java_params, true))
+ else
+ file_out.write(" in \"Java\" `\{\n\t\t{comment}return {jreturn_type.return_cast} recv.{jmethod_id}({java_params}); \n\t{comment}`\}\n")
+ end
+ else if jreturn_type.is_void then
+ if param_to_copy != null then
+ file_out.write(code_warehouse.param_type_copy(param_to_copy.first, param_to_copy.second, jmethod_id, java_params, false))
+ else
+ file_out.write(" in \"Java\" `\{\n\t\t{comment}recv.{jmethod_id}({java_params}); \n\t{comment}`\}\n")
+ end
+ else
+ file_out.write(" in \"Java\" `\{\n\t\t{comment}recv.{jmethod_id}({java_params}); \n\t{comment}`\}\n")
+ end
+ end
+
+ # Only one collection type parameter can be copied
+ # If there's none or more than one then `null` is returned
+ fun param_to_copy(jtypes: Array[JavaType], ntypes: Array[NitType]): nullable Couple[JavaType, NitType]
+ do
+ var counter = 0
+ var couple = null
+ for i in [0..jtypes.length[ do
+ if jtypes[i].is_collection and ntypes[i].is_complete then
+ counter += 1
+ if counter > 1 then return null
+ couple = new Couple[JavaType, NitType](jtypes[i], ntypes[i])
+ end
+ end
+
+ return couple
+ end
+end
+
+# Contains raw code mostly used to copy collections
+class CodeWarehouse
+
+ # Collection as return value
+ fun return_type_copy(java_type: JavaType, nit_type: NitType, jmethod_id, params_id: String): String
+ do
+ var narray_id = "nit_array"
+ var loop_ = create_loop(java_type, nit_type, false, "java_array", narray_id)
+ var imports = create_imports(nit_type, false)
+
+ return """{{{imports}}} in "Java" `{
+ {{{java_type.to_s}}} java_array = recv.{{{jmethod_id}}}({{{params_id}}});
+ int {{{narray_id}}} = new_{{{nit_type.id}}}_of_{{{nit_type.generic_params.join("_")}}}();
+
+ {{{loop_}}}
+
+ return {{{narray_id}}};
+ `}
+ """
+ end
+
+ # Collection as parameter
+ fun param_type_copy(java_type: JavaType, nit_type: NitType, jmethod_id, params_id: String, has_return: Bool): String
+ do
+ var narray_id = "nit_array"
+ var jarray_id = "java_array"
+ var loop_ = create_loop(java_type, nit_type, true, jarray_id, narray_id)
+ var imports = create_imports(nit_type, true)
+ var jtype = java_type.to_s
+ var jinstanciation = create_array_instance(java_type, nit_type, jarray_id)
+ var return_str = ""
+
+ if has_return then
+ return_str = "return "
+ end
+
+ params_id = params_id.replace(nit_type.arg_id, jarray_id)
+
+ return """{{{imports}}} in "Java" `{
+ {{{jinstanciation}}}
+ int {{{narray_id}}} = new_{{{nit_type.id}}}_of_{{{nit_type.generic_params.join("_")}}}();
+
+ {{{loop_}}}
+
+ {{{return_str}}}recv.{{{jmethod_id}}}({{{params_id}}});
+ `}
+ """
+ end
+
+ # One collection parameter and the return type will be copied
+ fun param_return_copy(return_types, param_types: Couple[JavaType, NitType], jmethod_id, params_id: String): String
+ do
+ var narray_id = "nit_array"
+ var narray_id2 = "nit_array2"
+
+ var r_jtype = return_types.first
+ var r_ntype = return_types.second
+
+ var p_jtype = param_types.first
+ var p_ntype = param_types.second
+
+ var r_loop = create_loop(r_jtype, r_ntype, false, "java_array", narray_id)
+ var p_loop = create_loop(p_jtype, p_ntype, true, "java_array2", narray_id2)
+
+ var imports = new Array[String]
+
+ # Avoid import duplication
+ if p_ntype.to_s != r_ntype.to_s then
+ imports.add create_imports(p_ntype, true)
+ end
+
+ imports.add create_imports(r_ntype, false)
+
+ params_id = params_id.replace(p_ntype.arg_id, narray_id)
+
+ var jinstanciation = create_array_instance(p_jtype, p_ntype, "java_array")
+
+ return """{{{imports.join(", ")}}} in "Java" `{
+ {{{jinstanciation}}}
+
+ {{{p_loop}}}
+
+ {{{r_jtype.to_s}}} java_array2 = recv.{{{jmethod_id}}}({{{params_id}}});
+ int {{{narray_id2}}} = new_{{{r_ntype.id}}}_of_{{{r_ntype.generic_params.join("_")}}}();
+
+ {{{r_loop}}}
+
+ return {{{narray_id2}}};
+ `}
+ """
+ end
+
+ private fun create_array_instance(java_type: JavaType, nit_type: NitType, jarray_id: String): String
+ do
+ var jtype = java_type.to_s
+ var instanciation = ""
+
+ if java_type.is_primitive_array then
+ instanciation = "{jtype} {jarray_id} = new {java_type.full_id}[Array_of_{nit_type.generic_params[0]}_length({nit_type.arg_id})];"
+ else
+ instanciation = "{jtype} {jarray_id} = new {jtype}();"
+ end
+
+ return instanciation
+ end
+
+ private fun create_imports(nit_type: NitType, is_param: Bool): String
+ do
+ var imports = ""
+ var ntype = nit_type.to_s
+ var gen_type = nit_type.generic_params.join(", ")
+
+ if not is_param then
+ if nit_type.is_map then
+ imports = """import {{{ntype}}}, {{{ntype}}}.[]="""
+ else
+ imports = """import {{{ntype}}}, {{{ntype}}}.add"""
+ end
+ else if nit_type.id == "Array" then
+ imports = """import {{{ntype}}}.length, {{{ntype}}}.[]"""
+ else if nit_type.is_map then
+ imports = """import {{{ntype}}}.iterator, Iterator[{{{gen_type}}}].is_ok, Iterator[{{{gen_type}}}].next, Iterator[{{{gen_type}}}].item, Iterator[{{{gen_type}}}].key"""
+ else
+ imports = """import {{{ntype}}}.iterator, Iterator[{{{gen_type}}}].is_ok, Iterator[{{{gen_type}}}].next, Iterator[{{{gen_type}}}].item"""
+ end
+
+ return imports
+ end
+
+ private fun create_loop(java_type: JavaType, nit_type: NitType, is_param: Bool, jarray_id, narray_id: String): String
+ do
+ var loop_header = ""
+ var loop_body = ""
+ var gen_type = nit_type.generic_params.join("_")
+
+ if is_param then
+ if java_type.is_primitive_array then
+ loop_header = "for(int i=0; i < {jarray_id}.length; ++i)"
+ loop_body = """\t\t\t{{{jarray_id}}}[i] = {{{java_type.param_cast}}}Array_of_{{{gen_type}}}__index({{{nit_type.arg_id}}}, i);"""
+ else if nit_type.id == "Array" then
+ loop_header = """int length = Array_of_{{{gen_type}}}_length({{{nit_type.arg_id}}});\n\t\tfor(int i=0; i < length; ++i)"""
+ loop_body = """\t\t\t{{{jarray_id}}}.add({{{java_type.param_cast}}}Array_of_{{{gen_type}}}__index({{{narray_id}}}, i));"""
+ else
+ loop_header = """int itr = {{{nit_type.id}}}_of_{{{gen_type}}}_iterator({{{nit_type.arg_id}}});\n\t\twhile(Iterator_of_{{{gen_type}}}_is_ok(itr)) {"""
+ if nit_type.is_map then
+ var key_cast = java_type.to_cast(java_type.generic_params[0].id, true)
+ var value_cast = java_type.to_cast(java_type.generic_params[1].id, true)
+ loop_body = """\t\t\t{{{jarray_id}}}[{{{key_cast}}}iterator_of_{{{nit_type.id}}}_key(itr)] = {{{value_cast}}}iterator_of_{{{nit_type.id}}}_item(itr);\n\t\t\titerator_of_{{{gen_type}}}_next(itr);\n\t\t}"""
+ else
+ loop_body = """\t\t\t{{{jarray_id}}}.add({{{java_type.param_cast}}}iterator_of_{{{nit_type.id}}}_item(itr));\n\t\t\titerator_of_{{{gen_type}}}_next(itr);\n\t\t}"""
+ end
+ end
+ else
+ if nit_type.is_map then
+ var key_cast = java_type.to_cast(java_type.generic_params[0].id, false)
+ var value_cast = java_type.to_cast(java_type.generic_params[1].id, false)
+ loop_header = """for (java.util.Map.Entry<{{{java_type.generic_params[0]}}}, {{{java_type.generic_params[1]}}}> e: {{{jarray_id}}})"""
+ loop_body = """\t\t\t{{{nit_type.id}}}_of_{{{gen_type}}}_{{{nit_type.generic_params[1]}}}__index_assign({{{narray_id}}}, {{{key_cast}}}e.getKey(), {{{value_cast}}}e.getValue()); """
+ else if java_type.is_iterable then
+ loop_header = """for ({{{java_type.generic_params[0]}}} e: {{{jarray_id}}})"""
+ loop_body = """\t\t\t{{{nit_type.id}}}_of_{{{gen_type}}}_add({{{narray_id}}}, {{{java_type.return_cast}}}e);"""
+ else
+ loop_header = "for(int i=0; i < {jarray_id}.length; ++i)"
+ loop_body = """\t\t\t{{{nit_type.id}}}_of_{{{gen_type}}}_add({{{narray_id}}}, {{{java_type.return_cast}}}{{{jarray_id}}}[i]);"""
+ end
+ end
+
+ return loop_header + "\n" + loop_body
+ end
+end
--- /dev/null
+# 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.
+
+# Uses a visitor to extract data from the javap output AST
+# It sends the data to `code_generator` module
+module javap_visitor
+
+import javap_test_parser
+import code_generator
+intrude import types
+
+class JavaVisitor
+ super Visitor
+
+ var java_class = new JavaClass
+ var declaration_type: nullable String = null
+ var declaration_element: nullable String = null
+ var full_class_name = new Array[String]
+
+ var variable_id = ""
+ var variable_type = new JavaType
+
+ var is_generic_param = false
+ var gen_params_index = 0
+
+ var is_primitive_array = false
+
+ var method_id = ""
+ var method_return_type = new JavaType
+ var method_params = new Array[JavaType]
+ var param_index = 0
+
+ redef fun visit(n) do n.accept_visitor(self)
+end
+
+redef class Node
+ fun accept_visitor(v: JavaVisitor) do visit_children(v)
+end
+
+redef class Nidentifier
+ redef fun accept_visitor(v)
+ do
+ if v.declaration_type == "class_header" then
+
+ if v.declaration_element == "id" then
+ v.full_class_name.add(self.text)
+ end
+
+ else if v.declaration_type == "variable" then
+
+ if v.declaration_element == "id" then
+ v.variable_id += self.text
+ else if v.declaration_element == "type" then
+ if v.is_generic_param then
+ v.variable_type.generic_params[v.gen_params_index].identifier.add(self.text)
+ else
+ v.variable_type.identifier.add(self.text)
+ end
+ end
+
+ else if v.declaration_type == "method" then
+
+ if v.declaration_element == "id" then
+ v.method_id = self.text
+ else if v.declaration_element == "return_type" then
+ if self.text == "void" then
+ v.method_return_type.is_void = true
+ else if v.is_generic_param then
+ v.method_return_type.generic_params[v.gen_params_index].identifier.add(self.text)
+ else
+ v.method_return_type.identifier.add(self.text)
+ end
+ else if v.declaration_element == "parameter_list" then
+ if v.is_generic_param then
+ v.method_params[v.param_index].generic_params[v.gen_params_index].identifier.add(self.text)
+ else
+ v.method_params[v.param_index].identifier.add(self.text)
+ end
+ end
+
+ end
+
+ super
+ end
+end
+
+# Primitive array node
+redef class N_39d_91d_93d_39d
+ redef fun accept_visitor(v)
+ do
+ if v.declaration_type == "variable" then
+ if v.declaration_element == "type" then
+ if v.is_generic_param then
+ v.variable_type.generic_params[v.gen_params_index].array_dimension += 1
+ else
+ v.variable_type.array_dimension += 1
+ end
+ end
+
+ else if v.declaration_type == "method" then
+
+ if v.declaration_element == "return_type" then
+ if v.is_generic_param then
+ v.method_return_type.generic_params[v.gen_params_index].array_dimension += 1
+ else
+ v.method_return_type.array_dimension += 1
+ end
+ else if v.declaration_element == "parameter_list" then
+ if v.is_generic_param then
+ v.method_params[v.param_index].generic_params[v.gen_params_index].array_dimension += 1
+ else
+ v.method_params[v.param_index].array_dimension += 1
+ end
+ end
+
+ end
+
+ super
+ end
+end
+
+redef class N_39dchar_39d
+ redef fun accept_visitor(v)
+ do
+ if v.declaration_type == "variable" then
+ if v.declaration_element == "type" then
+ v.variable_type.identifier.add(self.text)
+ end
+ else if v.declaration_type == "method" then
+ if v.declaration_element == "return_type" then
+ v.method_return_type.identifier.add(self.text)
+ else if v.declaration_element == "parameter_list" then
+ v.method_params[v.param_index].identifier.add(self.text)
+ end
+ end
+ end
+end
+
+redef class N_39dboolean_39d
+ redef fun accept_visitor(v)
+ do
+ if v.declaration_type == "variable" then
+ if v.declaration_element == "type" then
+ v.variable_type.identifier.add(self.text)
+ end
+ else if v.declaration_type == "method" then
+ if v.declaration_element == "return_type" then
+ v.method_return_type.identifier.add(self.text)
+ else if v.declaration_element == "parameter_list" then
+ v.method_params[v.param_index].identifier.add(self.text)
+ end
+ end
+ end
+end
+
+redef class N_39dfloat_39d
+ redef fun accept_visitor(v)
+ do
+ if v.declaration_type == "variable" then
+ if v.declaration_element == "type" then
+ v.variable_type.identifier.add(self.text)
+ end
+ else if v.declaration_type == "method" then
+ if v.declaration_element == "return_type" then
+ v.method_return_type.identifier.add(self.text)
+ else if v.declaration_element == "parameter_list" then
+ v.method_params[v.param_index].identifier.add(self.text)
+ end
+ end
+ end
+end
+
+redef class N_39ddouble_39d
+ redef fun accept_visitor(v)
+ do
+ if v.declaration_type == "variable" then
+ if v.declaration_element == "type" then
+ v.variable_type.identifier.add(self.text)
+ end
+ else if v.declaration_type == "method" then
+ if v.declaration_element == "return_type" then
+ v.method_return_type.identifier.add(self.text)
+ else if v.declaration_element == "parameter_list" then
+ v.method_params[v.param_index].identifier.add(self.text)
+ end
+ end
+ end
+end
+
+redef class N_39dbyte_39d
+ redef fun accept_visitor(v)
+ do
+ if v.declaration_type == "variable" then
+ if v.declaration_element == "type" then
+ v.variable_type.identifier.add(self.text)
+ end
+ else if v.declaration_type == "method" then
+ if v.declaration_element == "return_type" then
+ v.method_return_type.identifier.add(self.text)
+ else if v.declaration_element == "parameter_list" then
+ v.method_params[v.param_index].identifier.add(self.text)
+ end
+ end
+ end
+end
+
+redef class N_39dshort_39d
+ redef fun accept_visitor(v)
+ do
+ if v.declaration_type == "variable" then
+ if v.declaration_element == "type" then
+ v.variable_type.identifier.add(self.text)
+ end
+ else if v.declaration_type == "method" then
+ if v.declaration_element == "return_type" then
+ v.method_return_type.identifier.add(self.text)
+ else if v.declaration_element == "parameter_list" then
+ v.method_params[v.param_index].identifier.add(self.text)
+ end
+ end
+ end
+end
+
+redef class N_39dint_39d
+ redef fun accept_visitor(v)
+ do
+ if v.declaration_type == "variable" then
+ if v.declaration_element == "type" then
+ v.variable_type.identifier.add(self.text)
+ end
+ else if v.declaration_type == "method" then
+ if v.declaration_element == "return_type" then
+ v.method_return_type.identifier.add(self.text)
+ else if v.declaration_element == "parameter_list" then
+ v.method_params[v.param_index].identifier.add(self.text)
+ end
+ end
+ end
+end
+
+redef class N_39dlong_39d
+ redef fun accept_visitor(v)
+ do
+ if v.declaration_type == "variable" then
+ if v.declaration_element == "type" then
+ v.variable_type.identifier.add(self.text)
+ end
+ else if v.declaration_type == "method" then
+ if v.declaration_element == "return_type" then
+ v.method_return_type.identifier.add(self.text)
+ else if v.declaration_element == "parameter_list" then
+ v.method_params[v.param_index].identifier.add(self.text)
+ end
+ end
+ end
+end
+
+# #
+# C L A S S H E A D E R #
+# #
+redef class Nclass_header
+ redef fun accept_visitor(v)
+ do
+ v.declaration_type = "class_header"
+ v.declaration_element = "id"
+ super
+
+ # Exit class declaration
+ v.declaration_type = null
+ v.declaration_element = null
+
+ v.java_class.name = v.full_class_name
+ end
+end
+
+# Extends declaration in the class header
+redef class Nextends_declaration
+ redef fun accept_visitor(v)
+ do
+ v.declaration_element = "extends"
+ super
+ v.declaration_element = null
+ end
+end
+
+# Implements declaration in the class header
+redef class Nimplements_declaration
+ redef fun accept_visitor(v)
+ do
+ v.declaration_element = "implements"
+ super
+ v.declaration_element = null
+ end
+end
+
+# #
+# F I E L D D E C L A R A T I O N S #
+# #
+
+# Method declaration in the field declarations
+redef class Nmethod_declaration
+ redef fun accept_visitor(v)
+ do
+ v.declaration_type = "method"
+ super
+ v.declaration_type = null
+
+ v.java_class.add_method(v.method_id, v.method_return_type, v.method_params)
+
+ v.method_params.clear
+ v.method_id = ""
+ v.method_return_type = new JavaType
+ end
+end
+
+# Constructor declaration in the field declarations
+redef class Nconstructor_declaration
+ redef fun accept_visitor(v)
+ do
+ v.declaration_type = "constructor"
+ super
+ v.declaration_type = null
+ end
+end
+
+# Variable declaration in the field declarations
+redef class Nvariable_declaration
+ redef fun accept_visitor(v)
+ do
+ v.declaration_type = "variable"
+ super
+ v.declaration_type = null
+
+ v.java_class.attributes[v.variable_id] = v.variable_type
+
+ v.variable_id = ""
+ v.variable_type = new JavaType
+ end
+end
+
+# Static declaration in the field declarations
+redef class Nstatic_declaration
+ redef fun accept_visitor(v)
+ do
+ v.declaration_type = "static"
+ super
+ v.declaration_type = null
+ end
+end
+
+# Identifier of the field
+redef class Nvariable_id
+ redef fun accept_visitor(v)
+ do
+ v.declaration_element = "id"
+ super
+ v.declaration_element = null
+ end
+end
+
+# Identifier of the method
+redef class Nmethod_id
+ redef fun accept_visitor(v)
+ do
+ v.declaration_element = "id"
+ super
+ v.declaration_element = null
+ end
+end
+
+redef class Ntype
+ redef fun accept_visitor(v)
+ do
+ if v.declaration_type == "variable" and v.declaration_element != "id" then
+ v.declaration_element = "type"
+ end
+
+ if v.declaration_type == "method" and v.declaration_element == null then
+ v.declaration_element = "return_type"
+ end
+
+ super
+
+ if v.declaration_element == "variable" then
+ v.declaration_element = null
+ end
+ end
+end
+
+redef class Ngeneric_param
+ redef fun accept_visitor(v)
+ do
+ # Ignore the weird generic return type declaration
+ if v.declaration_type == "method" then
+ if v.declaration_element == null then
+ v.declaration_element = "ignore"
+ else
+ v.is_generic_param = true
+ v.gen_params_index = 0
+
+ if v.declaration_element == "return_type" then
+ v.method_return_type.generic_params = new Array[JavaType]
+ else if v.declaration_element == "parameter_list" then
+ v.method_params[v.param_index].generic_params = new Array[JavaType]
+ end
+ end
+ else if v.declaration_type == "variable" then
+ if v.declaration_element == "type" then
+ v.is_generic_param = true
+ v.gen_params_index = 0
+ v.variable_type.generic_params = new Array[JavaType]
+ end
+ end
+
+ super
+
+ v.declaration_element = null
+ v.is_generic_param = false
+ end
+end
+
+redef class Nparameter_list
+ redef fun accept_visitor(v)
+ do
+ v.declaration_element = "parameter_list"
+ v.param_index = 0
+ super
+ v.declaration_element = null
+ v.param_index = 0
+ end
+end
+
+redef class Nparameter
+ redef fun accept_visitor(v)
+ do
+ if v.declaration_type == "method" then
+ if v.declaration_element == "parameter_list" then
+ if v.is_generic_param then
+ v.method_params[v.param_index].generic_params.add(new JavaType)
+
+ super
+
+ v.gen_params_index += 1
+ else
+ v.method_params.add(new JavaType)
+
+ super
+
+ v.param_index += 1
+ end
+ else if v.declaration_element == "return_type" and v.is_generic_param then
+
+ v.method_return_type.generic_params.add(new JavaType)
+
+ super
+
+ v.gen_params_index += 1
+ end
+ else if v.declaration_type == "variable" then
+ if v.declaration_element == "type" and v.is_generic_param then
+ v.variable_type.generic_params.add(new JavaType)
+
+ super
+
+ v.gen_params_index += 1
+ end
+ else
+ super
+ end
+ end
+end
+
+var p = new TestParser_javap
+var tree = p.main
+
+var visitor = new JavaVisitor
+visitor.enter_visit(tree)
+
+var generator = new CodeGenerator("bundle.nit", visitor.java_class)
+generator.generate
--- /dev/null
+# 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.
+
+# Services to convert java type to nit type and get casts if needed
+module jtype_converter
+
+class JavaTypeConverter
+
+ var type_map = new HashMap[String, String]
+ var param_cast_map = new HashMap[String, String]
+ var return_cast_map = new HashMap[String, String]
+
+ init
+ do
+ # Java type to nit type
+ type_map["byte"] = "Int"
+ type_map["Byte"] = "Int"
+ type_map["short"] = "Int"
+ type_map["Short"] = "Int"
+ type_map["int"] = "Int"
+ type_map["Integer"] = "Int"
+ type_map["long"] = "Int"
+ type_map["Long"] = "Int"
+ type_map["char"] = "Char"
+ type_map["Character"] = "Char"
+ type_map["float"] = "Float"
+ type_map["Float"] = "Float"
+ type_map["double"] = "Float"
+ type_map["Double"] = "Float"
+ type_map["boolean"] = "Bool"
+ type_map["Boolean"] = "Bool"
+ type_map["Object"] = "JavaObject"
+ type_map["Bundle"] = "NativeBundle"
+ type_map["String"] = "JavaString"
+ type_map["CharSequence"] = "JavaString"
+
+ # Collections
+ type_map["List"] = "Array"
+ type_map["ArrayList"] = "Array"
+ type_map["LinkedList"] = "List"
+ type_map["Vector"] = "Array"
+
+ type_map["Set"] = "HashSet"
+ type_map["SortedSet"] = "Still have to make my mind on this one"
+ type_map["HashSet"] = "HashSet"
+ type_map["TreeSet"] = "HashSet"
+ type_map["LinkedHashSet"] = "HashSet"
+ type_map["Map"] = "HashMap"
+ type_map["SortedMap"] = "RBTreeMap"
+ type_map["HashMap"] = "HashMap"
+ type_map["TreeMap"] = "RBTreeMap"
+ type_map["Hashtable"] = "HashMap"
+ type_map["LinkedHashMap"] = "HashMap"
+
+ # Cast if the type is given as a parameter
+ param_cast_map["byte"] = "(byte)"
+ param_cast_map["Byte"] = "(Byte)"
+ param_cast_map["short"] = "(short)"
+ param_cast_map["Short"] = "(short)"
+ param_cast_map["float"] = "(float)"
+ param_cast_map["Float"] = "(float)"
+ # FIXME: Uncomment as soon as Nit `Int` will be equivalent to Java `long`
+ # param_cast_map["int"] = "int"
+ # param_cast_map["Integer"] = "int"
+
+ # Cast if the type is given as a return value
+ return_cast_map["CharSequence"] = "(String)"
+ # FIXME: Erase as soon as the Nit `Int` type will become a Java `long`
+ return_cast_map["long"] = "(int)"
+ end
+
+ fun to_nit_type(java_type: String): nullable String
+ do
+ return self.type_map.get_or_null(java_type)
+ end
+
+ fun cast_as_param(java_type: String): String
+ do
+ return self.param_cast_map.get_or_default(java_type, "")
+ end
+
+ fun cast_as_return(java_type: String): String
+ do
+ return self.return_cast_map.get_or_default(java_type, "")
+ end
+end
--- /dev/null
+# 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.
+
+# Contains the java and nit type representation used to convert java to nit code
+module types
+
+import jtype_converter
+
+class JavaType
+ private var converter = new JavaTypeConverter
+ var identifier: Array[String] = new Array[String]
+ var generic_params: nullable Array[JavaType] = null
+ var is_void = false
+ var array_dimension = 0
+
+ fun collections_list: Array[String] is cached do return ["List", "ArrayList", "LinkedList", "Vector", "Set", "SortedSet", "HashSet", "TreeSet", "LinkedHashSet", "Map", "SortedMap", "HashMap", "TreeMap", "Hashtable", "LinkedHashMap"]
+ fun iterable: Array[String] is cached do return ["ArrayList", "Set", "HashSet", "LinkedHashSet", "LinkedList", "Stack", "TreeSet", "Vector"]
+ fun maps: Array[String] is cached do return ["Map", "SortedMap", "HashMap", "TreeMap", "Hashtable", "LinkedHashMap"]
+ fun has_generic_params: Bool do return not generic_params == null
+ fun is_primitive_array: Bool do return array_dimension > 0
+ fun full_id: String do return identifier.join(".")
+ fun id: String do return identifier.last
+
+ fun return_cast: String
+ do
+ if self.has_generic_params then
+ return converter.cast_as_return(self.generic_params[0].id)
+ end
+
+ return converter.cast_as_return(self.id)
+ end
+
+ fun param_cast: String
+ do
+ if self.has_generic_params then
+ return converter.cast_as_param(self.generic_params[0].id)
+ end
+
+ return converter.cast_as_param(self.id)
+ end
+
+ fun to_nit_type: NitType
+ do
+ var nit_type: NitType
+
+ if self.is_primitive_array then
+ return self.convert_primitive_array
+ end
+
+ var type_id = converter.to_nit_type(self.id)
+
+ if type_id == null then
+ nit_type = new NitType(self.full_id)
+ nit_type.is_complete = false
+ else
+ nit_type = new NitType(type_id)
+ end
+
+ if not self.has_generic_params then return nit_type
+
+ nit_type.generic_params = new Array[NitType]
+
+ for param in generic_params do
+ var nit_param = param.to_nit_type
+
+ nit_type.generic_params.add(nit_param)
+
+ if not nit_param.is_complete then nit_type.is_complete = false
+ end
+
+ return nit_type
+ end
+
+ fun convert_primitive_array: NitType
+ do
+ var nit_type = new NitType("Array")
+
+ var last_nit_type = nit_type
+
+ for i in [1..array_dimension] do
+ var temp: NitType
+ last_nit_type.generic_params = new Array[NitType]
+
+ if i == array_dimension then
+ var temp_type = converter.to_nit_type(self.id)
+
+ if temp_type == null then
+ temp_type = self.full_id
+ nit_type.is_complete = false
+ end
+
+ temp = new NitType(temp_type)
+ else
+ temp = new NitType("Array")
+ end
+
+ last_nit_type.generic_params.add(temp)
+
+ last_nit_type = temp
+ end
+
+ return nit_type
+ end
+
+ fun is_iterable: Bool do return iterable.has(self.id)
+
+ fun is_collection: Bool do return is_primitive_array or collections_list.has(self.id)
+
+ fun is_map: Bool do return maps.has(self.id)
+
+ redef fun to_s: String
+ do
+ var id = self.full_id
+
+ if self.is_primitive_array then
+ for i in [0..array_dimension[ do
+ id += "[]"
+ end
+ else if self.has_generic_params then
+ var gen_list = new Array[String]
+
+ for param in generic_params do
+ gen_list.add(param.to_s)
+ end
+
+ id += "<{gen_list.join(", ")}>"
+ end
+
+ return id
+ end
+
+ fun to_cast(jtype: String, is_param: Bool): String
+ do
+ if is_param then
+ return converter.cast_as_param(jtype)
+ end
+
+ return converter.cast_as_return(jtype)
+ end
+end
+
+class NitType
+ var identifier: String
+ var arg_id: String
+ var generic_params: nullable Array[NitType] = null
+
+ # Returns `true` if all types have been successfully converted to Nit type
+ var is_complete: Bool = true
+
+ fun has_generic_params: Bool do return not generic_params == null
+ fun maps: Array[String] is cached do return ["HashMap", "RBTreeMap"]
+
+ fun id: String do return identifier
+
+ init (id: String)
+ do
+ self.identifier = id
+ end
+
+ fun is_map: Bool do return maps.has(self.identifier)
+
+ redef fun to_s: String
+ do
+ var id = self.identifier
+
+ if self.has_generic_params then
+ var gen_list = new Array[String]
+
+ for param in generic_params do
+ gen_list.add(param.to_s)
+ end
+
+ id += "[{gen_list.join(", ")}]"
+ end
+
+ return id
+ end
+end
+
+class JavaClass
+ var name = new Array[String]
+ var attributes = new HashMap[String, JavaType]
+ var methods = new HashMap[String, Array[JReturnAndParams]]
+
+ fun add_method(id: String, return_type: JavaType, params: Array[JavaType])
+ do
+ var ret_and_params = methods.get_or_default(id, new Array[JReturnAndParams])
+
+ ret_and_params.add(new JReturnAndParams(return_type, new Array[JavaType].from(params)))
+ methods[id] = ret_and_params
+ end
+end
+
+class JReturnAndParams
+ var return_type: JavaType
+ var params: Array[JavaType]
+
+ init(return_type: JavaType, params: Array[JavaType])
+ do
+ self.return_type = return_type
+ self.params = params
+ end
+end
+++ /dev/null
-all:
- ./git-gen-version.sh
- rubber --pdf --inplace nitreference/nitreference.tex
- rubber --pdf --inplace developpez/nit.tex
-
-clean:
- rm nit_version.sty 2> /dev/null || true
- rubber --clean --inplace nitreference/nitreference.tex
- rubber --clean --inplace developpez/nit.tex
-
-distclean: clean
- rm nitreference/nitreference.pdf developpez/nit.pdf 2> /dev/null || true
+++ /dev/null
-#!/bin/sh
-
-# This file is part of NIT ( http://www.nitlanguage.org ).
-#
-# Copyright 2008 Jean Privat <jean@pryen.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.
-
-# This program is used to generate version number from git refs.
-# The version number is stored in a dedicated Nit module.
-
-gen_version() {
- if ! grep "$*" nit_version.sty >/dev/null 2>&1; then
- cat > nit_version.sty<<END
-% This file was generated by git-gen-version.sh
-\\newcommand\\nitversion{$*}
-END
- echo "Version $*"
- fi
-}
-
-VN=$(git describe --always HEAD 2>/dev/null)
-if [ "$?" != "0" ]; then
- if [ -r ../VERSION ]; then
- VN="$(cat ../VERSION)"
- else
- echo >&2 "Error: no VERSION file and not a .git repository."
- exit 1
- fi
-fi
-if [ -z "$VN" ]; then
- VN="undefined"
-fi
-if [ -n "$1" ]; then
- VN="${VN}-$1"
-fi
-if [ -n "$(git diff HEAD 2>/dev/null)" ]; then
- VN="${VN}-dirty"
-fi
-
-gen_version "$VN"
fun nodes_with_label(lbl: String): Array[NeoNode] do
var res = get("{base_url}/db/data/label/{lbl}/nodes")
var nodes = new Array[NeoNode]
- var batch = new NeoBatch(self)
- for obj in res.as(JsonArray) do
- var node = new NeoNode.from_json(self, obj.as(JsonObject))
- batch.load_node(node)
+ for json in res.as(JsonArray) do
+ var obj = json.as(JsonObject)
+ var node = load_node(obj["self"].to_s)
+ node.internal_properties = obj["data"].as(JsonObject)
nodes.add node
end
- batch.execute
return nodes
end
fun from: NeoNode do return internal_from or else load_from
private fun load_from: NeoNode do
- var node = new NeoNode.from_neo(neo, internal_from_url.to_s)
+ var node = neo.load_node(internal_from_url.to_s)
internal_from = node
return node
end
fun to: NeoNode do return internal_to or else load_to
private fun load_to: NeoNode do
- var node = new NeoNode.from_neo(neo, internal_to_url.to_s)
+ var node = neo.load_node(internal_to_url.to_s)
internal_to = node
return node
end
# Load a node in batch mode also load labels, data and edges
fun load_node(node: NeoNode) do
- load_node_data(node)
- load_node_labels(node)
- load_node_out_edges(node)
- end
-
- # Load data into node
- private fun load_node_data(node: NeoNode) do
var job = new_job(node)
job.action = load_node_data_action
job.method = "GET"
else
job.to = "\{{node.batch_id.to_s}\}"
end
- end
-
- # Load labels into node
- private fun load_node_labels(node: NeoNode) do
- var job = new_job(node)
+ job = new_job(node)
job.action = load_node_labels_action
job.method = "GET"
if node.id != null then
end
end
- # Load out edges into node
- private fun load_node_out_edges(node: NeoNode) do
+ # Load in and out edges into node
+ fun load_node_edges(node: NeoNode) do
var job = new_job(node)
+ job.action = load_node_in_edges_action
+ job.method = "GET"
+ if node.id != null then
+ job.to = "/node/{node.id.to_s}/relationships/in"
+ else
+ job.to = "\{{node.batch_id.to_s}\}/relationships/in"
+ end
+ job = new_job(node)
job.action = load_node_out_edges_action
job.method = "GET"
if node.id != null then
job.to = "\{{node.batch_id.to_s}\}/labels"
job.body = new JsonArray.from(node.labels)
# add edges
- save_edges(node.out_edges)
+ #save_edges(node.out_edges)
end
# Create multiple nodes
var labels = new Array[String]
for l in res["body"].as(JsonArray) do labels.add l.to_s
node.internal_labels = labels
+ else if job.action == load_node_in_edges_action then
+ var node = job.entity.as(NeoNode)
+ var edges = res["body"].as(JsonArray)
+ node.internal_in_edges = new List[NeoEdge]
+ for edge in edges do
+ node.internal_in_edges.add client.load_edge(edge.as(JsonObject)["self"].to_s)
+ end
else if job.action == load_node_out_edges_action then
var node = job.entity.as(NeoNode)
var edges = res["body"].as(JsonArray)
node.internal_out_edges = new List[NeoEdge]
for edge in edges do
- node.internal_out_edges.add new NeoEdge.from_json(client, edge.as(JsonObject))
+ node.internal_out_edges.add client.load_edge(edge.as(JsonObject)["self"].to_s)
end
end
end
private fun create_edge_action: Int do return 2
private fun load_node_data_action: Int do return 3
private fun load_node_labels_action: Int do return 4
- private fun load_node_out_edges_action: Int do return 5
+ private fun load_node_in_edges_action: Int do return 5
+ private fun load_node_out_edges_action: Int do return 6
end
# A job that can be executed in a `NeoBatch`
# limitations under the License.
# Low-level Sqlite3 features
-module sqlite3 is pkgconfig("sqlite3")
+module native_sqlite3 is pkgconfig("sqlite3")
in "C header" `{
#include <sqlite3.h>
`}
+redef class Sys
+ # Last error raised when calling `Sqlite3::open`
+ var sqlite_open_error: nullable Sqlite3Code = null
+end
+
extern class Sqlite3Code `{int`}
new ok `{ return SQLITE_OK; `} # 0 /* Successful result */
fun is_ok: Bool `{ return recv == SQLITE_OK; `}
end
# A prepared statement
-extern class Statement `{sqlite3_stmt*`}
+extern class NativeStatement `{sqlite3_stmt*`}
# Evaluate the statement
fun step: Sqlite3Code `{
return NativeString_to_s(ret);
`}
+ # Number of bytes in the blob or string at row `i`
fun column_bytes(i: Int) : Int `{
return sqlite3_column_bytes(recv, i);
`}
return sqlite3_column_int(recv, i);
`}
- fun column_text(i: Int) : String import NativeString.to_s `{
- char * ret = (char *) sqlite3_column_text(recv, i);
- if( ret == NULL ){
- ret = "";
- }
- return NativeString_to_s(ret);
+ fun column_text(i: Int): NativeString `{
+ return (char *)sqlite3_column_text(recv, i);
`}
- fun column_type(i: Int) : Int `{
+ # Type of the entry at row `i`
+ fun column_type(i: Int): DataType `{
return sqlite3_column_type(recv, i);
`}
end
# A database connection
-extern class Sqlite3 `{sqlite3 *`}
+extern class NativeSqlite3 `{sqlite3 *`}
# Open a connection to a database in UTF-8
- new open(filename: String) import String.to_cstring `{
+ new open(filename: String) import String.to_cstring, set_sys_sqlite_open_error `{
sqlite3 *self = NULL;
- sqlite3_open(String_to_cstring(filename), &self);
+ int err = sqlite3_open(String_to_cstring(filename), &self);
+ NativeSqlite3_set_sys_sqlite_open_error(self, (void*)(long)err);
+ // The previous cast is a hack, using non pointers in extern classes is not
+ // yet in the spec of the FFI.
return self;
`}
+ # Utility method to set `Sys.sqlite_open_error`
+ private fun set_sys_sqlite_open_error(err: Sqlite3Code) do sys.sqlite_open_error = err
+
# Has this DB been correctly opened?
#
# To know if it has been closed or interrupted, you must check for errors with `error`.
`}
# Prepare a SQL statement
- fun prepare(sql: String): nullable Statement import String.to_cstring, Statement.as nullable `{
+ fun prepare(sql: String): nullable NativeStatement import String.to_cstring, NativeStatement.as nullable `{
sqlite3_stmt *stmt;
int res = sqlite3_prepare_v2(recv, String_to_cstring(sql), -1, &stmt, 0);
if (res == SQLITE_OK)
- return Statement_as_nullable(stmt);
+ return NativeStatement_as_nullable(stmt);
else
- return null_Statement();
+ return null_NativeStatement();
`}
fun last_insert_rowid: Int `{
return sqlite3_errcode(recv);
`}
end
+
+# Sqlite data types
+extern class DataType `{ int `}
+ fun is_integer: Bool `{ return recv == SQLITE_INTEGER; `}
+ fun is_float: Bool `{ return recv == SQLITE_FLOAT; `}
+ fun is_blob: Bool `{ return recv == SQLITE_BLOB; `}
+ fun is_null: Bool `{ return recv == SQLITE_NULL; `}
+ fun is_text: Bool `{ return recv == SQLITE_TEXT; `}
+
+ fun to_i: Int `{ return recv; `}
+end
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 201 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 manipulate a Sqlite3 database
+#
+# For more information, refer to the documentation of http://www.sqlite.org/docs.html
+module sqlite3
+
+private import native_sqlite3
+import standard
+
+# A connection to a Sqlite3 database
+class Sqlite3DB
+ private var native_connection: NativeSqlite3
+
+ # Is this connection to the DB open?
+ var is_open = false
+
+ # All `Statement` opened from this connection that must be closed with this connection
+ private var open_statements = new Array[Statement]
+
+ # Open a connection to the database file at `path`
+ init open(path: Text)
+ do
+ native_connection = new NativeSqlite3.open(path.to_s)
+ if native_connection.is_valid then is_open = true
+ end
+
+ # Close this connection to the DB and all open statements
+ fun close
+ do
+ is_open = false
+
+ # close open statements
+ for stmt in open_statements do if stmt.is_open then
+ stmt.close
+ end
+
+ native_connection.close
+ end
+
+ # Prepare and return a `Statement`, return `null` on error
+ fun prepare(sql: Text): nullable Statement
+ do
+ var native_stmt = native_connection.prepare(sql.to_s)
+ if native_stmt == null then return null
+
+ var stmt = new Statement(native_stmt)
+ open_statements.add stmt
+ return stmt
+ end
+
+ # Execute the `sql` statement and return `true` on success
+ fun execute(sql: Text): Bool
+ do
+ var err = native_connection.exec(sql.to_s)
+ return err.is_ok
+ end
+
+ # Create a table on the DB with a statement beginning with "CREATE TABLE ", followed by `rest`
+ #
+ # This method does not escape special characters.
+ fun create_table(rest: Text): Bool do return execute("CREATE TABLE " + rest)
+
+ # Insert in the DB with a statement beginning with "INSERT ", followed by `rest`
+ #
+ # This method does not escape special characters.
+ fun insert(rest: Text): Bool do return execute("INSERT " + rest)
+
+ # Replace in the DB with a statement beginning with "REPLACE", followed by `rest`
+ #
+ # This method does not escape special characters.
+ fun replace(rest: Text): Bool do return execute("REPLACE " + rest)
+
+ # Select from the DB with a statement beginning with "SELECT ", followed by `rest`
+ #
+ # This method does not escape special characters.
+ fun select(rest: Text): nullable Statement do return prepare("SELECT " + rest)
+
+ # TODO add more prefix here as needed
+
+ # The latest error message, or `null` if there is none
+ fun error: nullable String
+ do
+ if not native_connection.is_valid then
+ var err = sys.sqlite_open_error
+ if err == null then return null
+ return err.to_s
+ end
+
+ var err = native_connection.error
+ if err.is_ok then return null
+ return err.to_s
+ end
+end
+
+# A prepared Sqlite3 statement, created from `Sqlite3DB::prepare` or `Sqlite3DB::select`
+class Statement
+ private var native_statement: NativeStatement
+
+ private init(ns: NativeStatement) do self.native_statement = ns
+
+ # Is this statement usable?
+ var is_open = true
+
+ # Close and finalize this statement
+ fun close
+ do
+ is_open = false
+ native_statement.finalize
+ end
+
+ # Reset this statement and return a `StatementIterator` to iterate over the result
+ fun iterator: StatementIterator
+ do
+ native_statement.reset
+ native_statement.step
+ return new StatementIterator(self)
+ end
+end
+
+class StatementRow
+ # Statement linked to `self`
+ var statement: Statement
+
+ private init(s: Statement) do self.statement = s
+
+ # Number of entries in this row
+ #
+ # require: `self.statement.is_open`
+ fun length: Int
+ do
+ assert statement_closed: statement.is_open
+
+ return statement.native_statement.column_count
+ end
+
+ # Returns the `i`th entry on this row
+ fun [](i: Int): StatementEntry do return new StatementEntry(statement, i)
+end
+
+# An entry on a `StatementRow`
+class StatementEntry
+ # Statement linked to `self`
+ var statement: Statement
+
+ private var index: Int
+
+ private init(s: Statement, i: Int)
+ do
+ self.statement = s
+ self.index = i
+ end
+
+ # Name of the column
+ #
+ # require: `self.statement.is_open`
+ fun name: String is cached do
+ assert statement_closed: statement.is_open
+
+ return statement.native_statement.column_name(index)
+ end
+
+ # Get the value of this entry according to its Sqlite type
+ #
+ # require: `self.statement.is_open`
+ fun value: nullable Sqlite3Data
+ do
+ assert statement_closed: statement.is_open
+
+ var data_type = statement.native_statement.column_type(index)
+ if data_type.is_integer then return to_i
+ if data_type.is_float then return to_f
+ if data_type.is_blob then return to_blob
+ if data_type.is_null then return null
+ if data_type.is_text then return to_s
+ abort
+ end
+
+ # Get this entry as `Int`
+ #
+ # If the Sqlite type of this entry is not an integer, it will be `CAST` to
+ # integer. If `null`, returns 0.
+ #
+ # require: `self.statement.is_open`
+ fun to_i: Int
+ do
+ assert statement_closed: statement.is_open
+
+ return statement.native_statement.column_int(index)
+ end
+
+ # Get this entry as `Float`
+ #
+ # If the Sqlite type of this entry is not a floating point, it will be `CAST`
+ # to float. If `null`, returns 0.0.
+ #
+ # require: `self.statement.is_open`
+ fun to_f: Float
+ do
+ assert statement_closed: statement.is_open
+
+ return statement.native_statement.column_double(index)
+ end
+
+ # Get this entry as `String`
+ #
+ # If the Sqlite type of this entry is not text, it will be `CAST` to text.
+ # If null, returns an empty string.
+ #
+ # require: `self.statement.is_open`
+ redef fun to_s
+ do
+ assert statement_closed: statement.is_open
+
+ var native_string = statement.native_statement.column_text(index)
+ if native_string.address_is_null then return ""
+ return native_string.to_s
+ end
+
+ # Get this entry as `Blob`
+ #
+ # If the Sqlite type of this entry is not a blob, it will be `CAST` to text.
+ # If null, returns a NULL pointer.
+ #
+ # require: `self.statement.is_open`
+ fun to_blob: Blob
+ do
+ assert statement_closed: statement.is_open
+
+ # By spec, we must get the pointer before the byte count
+ var pointer = statement.native_statement.column_blob(index)
+ var length = statement.native_statement.column_bytes(index)
+
+ return new Blob(pointer, length)
+ end
+end
+
+# Iterator over the rows of a statement result
+class StatementIterator
+ super Iterator[StatementRow]
+
+ # Statement linked to `self`
+ var statement: Statement
+
+ private init(s: Statement)
+ do
+ self.statement = s
+ self.item = new StatementRow(s)
+ end
+
+ redef var item: StatementRow
+
+ redef var is_ok = true
+
+ # require: `self.statement.is_open`
+ redef fun next
+ do
+ assert statement_closed: statement.is_open
+
+ var err = statement.native_statement.step
+ if err.is_row then
+ is_ok = true
+ else if err.is_done then
+ # Clean complete
+ is_ok = false
+ else
+ # error
+ # FIXME do something with the error?
+ is_ok = false
+ end
+ end
+end
+
+# A data type supported by Sqlite3
+interface Sqlite3Data end
+
+redef universal Int super Sqlite3Data end
+redef universal Float super Sqlite3Data end
+redef class String super Sqlite3Data end
+
+# A Sqlite3 blob
+class Blob
+ super Sqlite3Data
+
+ private init(pointer: Pointer, length: Int)
+ do
+ self.pointer = pointer
+ self.length = length
+ end
+
+ var pointer: Pointer
+ var length: Int
+end
protected fun compile_header_structs is abstract
# Declaration of structures for nitni undelying the FFI
- protected fun compile_nitni_structs is abstract
+ protected fun compile_nitni_structs
+ do
+ self.header.add_decl """
+/* Native reference to Nit objects */
+/* This structure is used to represent every Nit type in extern methods and custom C code. */
+struct nitni_ref {
+ struct nitni_ref *next,
+ *prev; /* adjacent global references in global list */
+ int count; /* number of time this global reference has been marked */
+};
+
+/* List of global references from C code to Nit objects */
+/* Instanciated empty at init of Nit system and filled explicitly by user in C code */
+struct nitni_global_ref_list_t {
+ struct nitni_ref *head, *tail;
+};
+extern struct nitni_global_ref_list_t *nitni_global_ref_list;
+
+/* Initializer of global reference list */
+extern void nitni_global_ref_list_init();
+
+/* Intern function to add a global reference to the list */
+extern void nitni_global_ref_add( struct nitni_ref *ref );
+
+/* Intern function to remove a global reference from the list */
+extern void nitni_global_ref_remove( struct nitni_ref *ref );
+
+/* Increase count on an existing global reference */
+extern void nitni_global_ref_incr( struct nitni_ref *ref );
+
+/* Decrease count on an existing global reference */
+extern void nitni_global_ref_decr( struct nitni_ref *ref );
+"""
+ end
# Generate the main C function.
# This function:
v.add("glob_argc = argc; glob_argv = argv;")
v.add("initialize_gc_option();")
+
+ v.add "initialize_nitni_global_refs();"
+
var main_type = mainmodule.sys_type
if main_type != null then
var mainmodule = v.compiler.mainmodule
v.add("\}")
end
+ # Copile all C functions related to the [incr|decr]_ref features of the FFI
+ fun compile_nitni_global_ref_functions
+ do
+ var v = self.new_visitor
+ v.add """
+struct nitni_global_ref_list_t *nitni_global_ref_list;
+void initialize_nitni_global_refs() {
+ nitni_global_ref_list = (struct nitni_global_ref_list_t*)nit_alloc(sizeof(struct nitni_global_ref_list_t));
+ nitni_global_ref_list->head = NULL;
+ nitni_global_ref_list->tail = NULL;
+}
+
+void nitni_global_ref_add( struct nitni_ref *ref ) {
+ if ( nitni_global_ref_list->head == NULL ) {
+ nitni_global_ref_list->head = ref;
+ ref->prev = NULL;
+ } else {
+ nitni_global_ref_list->tail->next = ref;
+ ref->prev = nitni_global_ref_list->tail;
+ }
+ nitni_global_ref_list->tail = ref;
+
+ ref->next = NULL;
+}
+
+void nitni_global_ref_remove( struct nitni_ref *ref ) {
+ if ( ref->prev == NULL ) {
+ nitni_global_ref_list->head = ref->next;
+ } else {
+ ref->prev->next = ref->next;
+ }
+
+ if ( ref->next == NULL ) {
+ nitni_global_ref_list->tail = ref->prev;
+ } else {
+ ref->next->prev = ref->prev;
+ }
+}
+
+extern void nitni_global_ref_incr( struct nitni_ref *ref ) {
+ if ( ref->count == 0 ) /* not registered */
+ {
+ /* add to list */
+ nitni_global_ref_add( ref );
+ }
+
+ ref->count ++;
+}
+
+extern void nitni_global_ref_decr( struct nitni_ref *ref ) {
+ if ( ref->count == 1 ) /* was last reference */
+ {
+ /* remove from list */
+ nitni_global_ref_remove( ref );
+ }
+
+ ref->count --;
+}
+"""
+ end
+
# List of additional files required to compile (FFI)
var extern_bodies = new Array[ExternFile]
var manifest_application_lines = new Array[String]
# Minimum API level required for the application to run
- var min_sdk: nullable Int = null
+ var min_api: nullable Int = null
# Build target API level
- var target_sdk: nullable Int = null
+ var target_api: nullable Int = null
# Maximum API level on which the application will be allowed to run
- var max_sdk: nullable Int = null
+ var max_api: nullable Int = null
redef fun to_s do return """
name: {{{name or else "null"}}}
annot = priority_annotation_on_modules("java_package", mmodule)
if annot != null then project.java_package = annot.arg_as_string(self)
- var annots = collect_annotations_on_modules("min_sdk_version", mmodule)
- for an in annots do project.min_sdk = an.arg_as_int(self)
+ var annots = collect_annotations_on_modules("min_api_version", mmodule)
+ for an in annots do project.min_api = an.arg_as_int(self)
- annots = collect_annotations_on_modules("max_sdk_version", mmodule)
- for an in annots do project.max_sdk = an.arg_as_int(self)
+ annots = collect_annotations_on_modules("max_api_version", mmodule)
+ for an in annots do project.max_api = an.arg_as_int(self)
- annots = collect_annotations_on_modules("target_sdk_version", mmodule)
- for an in annots do project.target_sdk = an.arg_as_int(self)
+ annots = collect_annotations_on_modules("target_api_version", mmodule)
+ for an in annots do project.target_api = an.arg_as_int(self)
annots = collect_annotations_on_modules("android_manifest", mmodule)
for an in annots do project.manifest_lines.add an.arg_as_string(self) or else ""
var app_version = project.version
if app_version == null then app_version = "1.0"
- var app_min_sdk = project.min_sdk
- if app_min_sdk == null then app_min_sdk = 10
+ var app_min_api = project.min_api
+ if app_min_api == null then app_min_api = 10
- var app_target_sdk = project.target_sdk
- if app_target_sdk == null then app_target_sdk = app_min_sdk
+ var app_target_api = project.target_api
+ if app_target_api == null then app_target_api = app_min_api
- var app_max_sdk = ""
- if project.max_sdk != null then app_max_sdk = "android:maxSdkVersion=\"{app_max_sdk}\""
+ var app_max_api = ""
+ if project.max_api != null then app_max_api = "android:maxSdkVersion=\"{project.max_api.as(not null)}\""
# Clear the previous android project, so there is no "existing project warning"
# or conflict between Java files of different projects
var args = ["android", "-s",
"create", "project",
"--name", short_project_name,
- "--target", "android-{app_target_sdk}",
+ "--target", "android-{app_target_api}",
"--path", android_project_root,
"--package", app_package,
"--activity", short_project_name]
<!-- This is the platform API where NativeActivity was introduced. -->
<uses-sdk
- android:minSdkVersion="{{{app_min_sdk}}}"
- android:targetSdkVersion="{{{app_target_sdk}}}"
- {{{app_max_sdk}}} />
+ android:minSdkVersion="{{{app_min_api}}}"
+ android:targetSdkVersion="{{{app_target_api}}}"
+ {{{app_max_api}}} />
<application
android:label="@string/app_name"
ensure_compile_nitni_base(v)
nitni_ccu.header_c_types.add("#include \"{name}._ffi.h\"\n")
+ nitni_ccu.header_c_types.add """
+extern void nitni_global_ref_incr(void*);
+extern void nitni_global_ref_decr(void*);
+"""
nitni_ccu.write_as_nitni(self, v.compiler.modelbuilder.compile_dir)
arguments_for_c.add(arg.name)
else
v.add("struct nitni_instance* var_for_c_{a};")
- v.add("var_for_c_{a} = malloc(sizeof(struct nitni_instance));")
+ v.add("var_for_c_{a} = nit_alloc(sizeof(struct nitni_instance));")
v.add("var_for_c_{a}->value = {arg.name};")
arguments_for_c.add("var_for_c_{a}")
end
arguments_for_c.add(arg.name)
else
v.add("struct nitni_instance* var_for_c_{a};")
- v.add("var_for_c_{a} = malloc(sizeof(struct nitni_instance));")
+ v.add("var_for_c_{a} = nit_alloc(sizeof(struct nitni_instance));")
v.add("var_for_c_{a}->value = {arg.name};")
arguments_for_c.add("var_for_c_{a}")
end
add("return {src};")
else
add("struct nitni_instance* ret_for_c;")
- add("ret_for_c = malloc(sizeof(struct nitni_instance));")
+ add("ret_for_c = nit_alloc(sizeof(struct nitni_instance));")
add("ret_for_c->value = {src};")
add("return ret_for_c;")
end
private fun compile_extern_helper_functions(v: AbstractCompilerVisitor, ccu: CCompilationUnit)
do
# actually, we do not need to do anything when using the bohem garbage collector
+ var call_context = from_c_call_context
# incr_ref
- var nitni_visitor = v.compiler.new_visitor
- ccu.header_decl.add("#define {mangled_cname}_incr_ref(from) while(0)\{\}\n")
-
- # incr_ref
- nitni_visitor = v.compiler.new_visitor
- ccu.header_decl.add("#define {mangled_cname}_decr_ref(from) while(0)\{\}\n")
+ ccu.header_decl.add "#ifndef {mangled_cname}_incr_ref\n"
+ ccu.header_decl.add " #define {mangled_cname}_incr_ref(from) nitni_global_ref_incr(({call_context.name_mtype(self)})(from))\n"
+ ccu.header_decl.add "#endif\n"
+
+ # decr_ref
+ ccu.header_decl.add "#ifndef {mangled_cname}_decr_ref\n"
+ ccu.header_decl.add " #define {mangled_cname}_decr_ref(from) nitni_global_ref_decr(({call_context.name_mtype(self)})(from))\n"
+ ccu.header_decl.add "#endif\n"
end
end
var full_internal_csignature = "{cname_blind} {full_cname}()"
nitni_visitor.add("{full_internal_csignature} \{")
nitni_visitor.add("struct nitni_instance* ret_for_c;")
- nitni_visitor.add("ret_for_c = malloc(sizeof(struct nitni_instance));")
+ nitni_visitor.add("ret_for_c = nit_alloc(sizeof(struct nitni_instance));")
nitni_visitor.add("ret_for_c->value = NULL;")
nitni_visitor.add("return ret_for_c;")
nitni_visitor.add("\}")
end
# The main function of the C
+ compiler.compile_nitni_global_ref_functions
compiler.compile_main_function
# Compile until all runtime_functions are visited
redef fun compile_nitni_structs
do
- self.header.add_decl("struct nitni_instance \{ val *value; \};")
+ self.header.add_decl """
+struct nitni_instance \{
+ struct nitni_instance *next,
+ *prev; /* adjacent global references in global list */
+ int count; /* number of time this global reference has been marked */
+ val *value;
+\};"""
+ super
end
end
--- /dev/null
+The meta model of Nit programs
+
+These modules define the entities of the Nit meta-model like modules, classes, types and properties
+They also provide an API to build and query models.
+All model classes starts with the `M` letter (`MModule`, `MClass`, etc.)
+
+The model is used by tools that need a higher view than a AST (see `parser`).
+The model represents What the programmer means.
+
+Because of the specification of the Nit language, the model is complex and sometime difficult to understand.
+
+## POSet
+
+There is a lot of classes and relation in the model.
+Most of there relations are based on posets (from the lib `poset`.)
+
+Posets are *partially-ordered sets*; they are used to modelize hierarchies of things (eg. hierarchies of modules)
+
+The poset is an expressive data structure that generalizes most services about hierarchies.
+This avoid the duplication of code and the over-specialization of services.
+The drawback is that a specific request on the model use an abstract vocabulary.
+
+Example. you want the set of modules directly imported by another module.
+There is no specific method in `MModule` for that, the good way is to use services on the some posets
+
+~~~
+var res = mymodule.in_importation.direct_greaters
+~~~
+
+posets are used in two dual ways :
+
+ - by the whole hierarchy, most are in the `Model` and are named `something_somerelation_hierarchy` (eg. `Model::mmodule_importation_hierarchy`).
+ - by the view on en entity in a specific hierarchy, they are in the `MEntity` subclasses and are name `in_somerelation`. (eg. `MModule::in_importation`).
+
+
+## Refinement
+
+The refinement is the cause of the biggest difficulty with the model since the relations between entities are relative to the module considered.
+
+"How many method in this class?" -- It depends, what modules are you considering?
+"What is called by `x.foo` when `x` is dynamically a `Bar`?" -- It depends, in which main module?
+
+This relativity cause most services on model entities to have an additional parameter for the module considered.
super MConcern
# The model considered
- var model: Model
+ redef var model: Model
# placebo for old module nesting hierarchy
# return null if self is not nested (ie. is a top-level module)
# See the License for the specific language governing permissions and
# limitations under the License.
-# Object model of the Nit language
+# Classes, types and properties
#
-# This module define the entities of the Nit meta-model like modules,
-# classes, types and properties
-#
-# It also provide an API to build and query models.
-#
-# All model classes starts with the M letter (`MModule`, `MClass`, etc.)
-#
-# TODO: better doc
+# All three concepts are defined in this same module because these are strongly connected:
+# * types are based on classes
+# * classes contains properties
+# * some properties are types (virtual types)
#
# TODO: liearization, extern stuff
# FIXME: better handling of the types
#
# This characteristic helps the reasoning about classes in a program since a
# single `MClass` object always denote the same class.
-# However, because a `MClass` is global, it does not really have properties nor
-# belong to a hierarchy since the property and the
-# hierarchy of a class depends of a module.
+#
+# The drawback is that classes (`MClass`) contain almost nothing by themselves.
+# These do not really have properties nor belong to a hierarchy since the property and the
+# hierarchy of a class depends of the refinement in the modules.
+#
+# Most services on classes require the precision of a module, and no one can asks what are
+# the super-classes of a class nor what are properties of a class without precising what is
+# the module considered.
+#
+# For instance, during the typing of a source-file, the module considered is the module of the file.
+# eg. the question *is the method `foo` exists in the class `Bar`?* must be reformulated into
+# *is the method `foo` exists in the class `Bar` in the current module?*
+#
+# During some global analysis, the module considered may be the main module of the program.
class MClass
super MEntity
end
end
+ redef fun model do return intro_mmodule.model
+
# All class definitions (introduction and refinements)
var mclassdefs: Array[MClassDef] = new Array[MClassDef]
#
# A `MClassDef` is associated with an explicit (or almost) definition of a
# class. Unlike `MClass`, a `MClassDef` is a local definition that belong to
-# a specific module
+# a specific class and a specific module, and contains declarations like super-classes
+# or properties.
+#
+# It is the class definitions that are the backbone of most things in the model:
+# ClassDefs are defined with regard with other classdefs.
+# Refinement and specialization are combined to produce a big poset called the `Model::mclassdef_hierarchy`.
+#
+# Moreover, the extension and the intention of types is defined by looking at the MClassDefs.
class MClassDef
super MEntity
# Actually the name of the `mclass`
redef fun name do return mclass.name
+ redef fun model do return mmodule.model
+
# All declared super-types
# FIXME: quite ugly but not better idea yet
var supertypes: Array[MClassType] = new Array[MClassType]
abstract class MType
super MEntity
- # The model of the type
- fun model: Model is abstract
+ redef fun name do return to_s
# Return true if `self` is an subtype of `sup`.
# The typing is done using the standard typing policy of Nit.
# Replace formals generic types in self with resolved values in `mtype`
# If `cleanup_virtual` is true, then virtual types are also replaced
- # with their bounds
+ # with their bounds.
#
# This function returns self if `need_anchor` is false.
#
#
# The resolution can be done because `E` make sense for the class A (see `can_resolve_for`)
#
- # TODO: Explain the cleanup_virtual
- #
# FIXME: the parameter `cleanup_virtual` is just a bad idea, but having
# two function instead of one seems also to be a bad idea.
#
# It's mean that all refinements of a same class "share" the parameter type,
# but that a generic subclass has its on parameter types.
#
-# However, in the sense of the meta-model, the a parameter type of a class is
-# a valid types in a subclass. The "in the sense of the meta-model" is
+# However, in the sense of the meta-model, a parameter type of a class is
+# a valid type in a subclass. The "in the sense of the meta-model" is
# important because, in the Nit language, the programmer cannot refers
# directly to the parameter types of the super-classes.
#
# A parameter in a signature
class MParameter
+ super MEntity
+
# The name of the parameter
- var name: String
+ redef var name: String
# The static type of the parameter
var mtype: MType
# Is the parameter a vararg?
var is_vararg: Bool
+ init(name: String, mtype: MType, is_vararg: Bool) do
+ self.name = name
+ self.mtype = mtype
+ self.is_vararg = is_vararg
+ end
+
redef fun to_s
do
if is_vararg then
var res = new MParameter(self.name, newtype, self.is_vararg)
return res
end
+
+ redef fun model do return mtype.model
end
# A service (global property) that generalize method, attribute, etc.
# associated definition, this method will abort
fun intro: MPROPDEF do return mpropdefs.first
+ redef fun model do return intro.model
+
# Alias for `name`
redef fun to_s do return name
# Actually the name of the `mproperty`
redef fun name do return mproperty.name
+ redef fun model do return mclassdef.model
+
# Internal name combining the module, the class and the property
# Example: "mymodule#MyClass#mymethod"
redef var to_s: String
abstract class MEntity
# The short (unqualified) name of this model entity
fun name: String is abstract
+
+ # A Model Entity has a direct link to its model
+ fun model: Model is abstract
end
# Something that represents a concern
redef var name: String
# The model of the project
- var model: Model
+ redef var model: Model
# The root of the group tree
var root: nullable MGroup writable = null
end
end
+ redef fun model do return mproject.model
+
redef fun parent_concern do
if not is_root then return parent
return mproject
+++ /dev/null
-This directory contains the nit parser. It is generated from a grammar for sablecc3 ( http://www.sablecc.org ).
-In order to generate nit parser, you need the alternate SableCC3 generator ( http://www.mare.ee/indrek/sablecc/ ).
-
-Contents:
-
- fact_parser.pl: Script used to factorize parser.nit
- Makefile: Update grammar and generate .nit files
- nit.sablecc3xx: Extended sablecc3 grammar (see prescc.pl)
- prescc.pl: Program to transform an extended sablecc3 to a standard one
- parser_nodes.nit: token and nodes classes hierarchy used by the parser and the lexer
- tables.nit, tables_nit.h: Interfaces to access the tables needed by the parser and the lexer
- test_parser.nit:
- xss/*.xss: alternate SableCC3 template files for the Nit language
-
-
-The following are generated but present to avoid the need of sablecc3:
-
- lexer.nit: generated lexer
- parser.nit: generated parser
- parser_prod.nit: All production with generated visit methods
- tables_nit.c: The tables needed by the parser and the lexer
- parser_abs.nit: Raw generated token and nodes classes used to maintain coherence of parser_nodes.nit
-
-
-Other temp files produced by the Makefile:
-
- .nit.sablecc3: Sablecc3 grammar after processing
- .nit.sablecc3.dump: Dump of the grammar to improve sablecc3 multiple runs
- .parser-nofact.nit: The parser generated by SableCC3 before factorization by fact_parser.pl
-
--- /dev/null
+Parser and AST for the Nit language
+
+The parser ans the AST are mostly used by all tools.
+
+The `parser` is the tool that transform source-files into abstract syntax trees (AST) (see `parser_nodes`)
+While the AST is a higher abstraction than blob of text, the AST is still limited and represents only *What the programmer says*.
+
+Classes of nodes of the AST starts with the letter `A` (for most things, eg. `AClassdef`) or `T` (for token eg. `TId`), there is no real reason except historical that might be solved with a new parser.
+
+Variable names of the AST usually starts with `n` (for node). This is also historical but some names with a `a` (to mimic the class name) remains.
+
+## SableCC
+
+Most files in this directory are generated from a grammar for sablecc3 ( http://www.sablecc.org ).
+In order to generate nit parser, you need the alternate SableCC3 generator ( http://www.mare.ee/indrek/sablecc/ ).
+
+## Contents
+
+* fact_parser.pl: Script used to factorize parser.nit
+* Makefile: Update grammar and generate .nit files
+* nit.sablecc3xx: Extended sablecc3 grammar (see prescc.pl)
+* prescc.pl: Program to transform an extended sablecc3 to a standard one
+* parser_nodes.nit: token and nodes classes hierarchy used by the parser and the lexer
+* tables.nit, tables_nit.h: Interfaces to access the tables needed by the parser and the lexer
+* xss/*.xss: alternate SableCC3 template files for the Nit language
+
+
+The following are generated but present to avoid the need of sablecc3:
+
+* lexer.nit: generated lexer
+* parser.nit: generated parser
+* parser_prod.nit: All production with generated visit methods
+* tables_nit.c: The tables needed by the parser and the lexer
+* parser_abs.nit: Raw generated token and nodes classes used to maintain coherence of parser_nodes.nit
+
+
+Other temp files produced by the Makefile:
+
+* .nit.sablecc3: Sablecc3 grammar after processing
+* .nit.sablecc3.dump: Dump of the grammar to improve sablecc3 multiple runs
+* .parser-nofact.nit: The parser generated by SableCC3 before factorization by fact_parser.pl
+
# The main function of the C
compiler.new_file("{mainmodule.name}.main")
+ compiler.compile_nitni_global_ref_functions
compiler.compile_main_function
# compile methods
redef fun compile_nitni_structs
do
- self.header.add_decl("struct nitni_instance \{struct instance *value;\};")
+ self.header.add_decl """
+struct nitni_instance \{
+ struct nitni_instance *next,
+ *prev; /* adjacent global references in global list */
+ int count; /* number of time this global reference has been marked */
+ struct instance *value;
+\};
+"""
+ super
end
-
+
redef fun finalize_ffi_for_module(mmodule)
do
var old_module = self.mainmodule
# The main function of the C
compiler.new_file("{mainmodule.name}.main")
+ compiler.compile_nitni_global_ref_functions
compiler.compile_main_function
# compile methods
for(i=0; i<size; i++)
attributes[i] = null_instance;
+ Instance_incr_ref(null_instance);
return attributes;
`}
var id = mproperty.intro_mclassdef.mclass.vtable.id
- # TODO : ugly hack
- recv.attributes[mproperty] = value
-
# Replace the old value of mproperty in recv
write_attribute_ph(recv.internal_attributes, recv.vtable.internal_vtable,
recv.vtable.mask, id, mproperty.offset, value)
int absolute_offset = *(pointer + 1);
((Instance *)instance)[absolute_offset + offset] = value;
+ Instance_incr_ref(value);
`}
end
return vtable;
`}
-end
\ No newline at end of file
+end
websocket_server
converter
mnit_linux
+friendz_linux
--- /dev/null
+Runtime error: Abstract method `generate_input` called on `App` (../lib/mnit/mnit_app.nit:63)
--- /dev/null
+Not executable (platform?)
--- /dev/null
+####
+uname: Bob
+pass: zzz
+activated: 1
+perc: 77.7
+####
+uname: Guillaume
+pass: xxx
+activated: 1
+perc: 88.8
+true
+false
--- /dev/null
+Runtime error: Assert failed (alt/test_sqlite3_nity_alt1.nit:27)
+unable to open database file
--- /dev/null
+Runtime error: Assert failed (alt/test_sqlite3_nity_alt2.nit:39)
+SQL logic error or missing database
--- /dev/null
+# 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.
+
+extern class As `{ A* `}
+ new (size: Int) `{
+ return malloc(sizeof(A) * size);
+ `}
+
+ fun []=(i: Int, v: A) `{
+ recv[i] = v;
+ A_incr_ref(v);
+ `}
+
+ fun [](i: Int): A `{
+ return recv[i];
+ `}
+end
+
+class A
+ var i: Int
+ var s: String
+
+ init(i: Int)
+ do
+ self.i = i
+ self.s = i.to_s
+ end
+
+ redef fun to_s do return "<{i} {s}>"
+end
+
+var length = 1000000
+var aaa = new As(length)
+for i in length.times do aaa[i] = new A(i)
+sys.force_garbage_collection
+for i in length.times do assert aaa[i].to_s == "<{i} {i}>"
# See the License for the specific language governing permissions and
# limitations under the License.
-module test_sqlite3
+module test_sqlite3_native
-import sqlite3
+import sqlite3::native_sqlite3
var filename = "test.db"
filename.file_delete
var insert_req_2 = "INSERT INTO users VALUES('Guillaume', 'xxx', 1)"
var select_req = "SELECT * FROM users"
-var db = new Sqlite3.open(filename)
+var db = new NativeSqlite3.open(filename)
assert sqlite_open: db.error.is_ok
db.exec(create_req)
db.close
-db = new Sqlite3.open(filename)
+db = new NativeSqlite3.open(filename)
assert sqlite_reopen: db.error.is_ok
stmt = db.prepare(select_req)
assert sqlite_reselect: db.error.is_ok
assert stmt != null
stmt.step
-assert sqlite_column_0_0_reopened: stmt.column_text(0) == "Bob"
+assert sqlite_column_0_0_reopened: stmt.column_text(0).to_s == "Bob"
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2013 Guillaume Auger <jeho@resist.ca>
+# Copyright 2013-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.
+
+import sqlite3
+
+var path = "test_nity.db"
+#alt1#path = "/../invalid_path.db"
+if path.file_exists then path.file_delete
+
+var db = new Sqlite3DB.open(path)
+assert db.is_open else print db.error or else "no error?"
+
+assert db.create_table("IF NOT EXISTS users (uname TEXT PRIMARY KEY, pass TEXT NOT NULL, activated INTEGER, perc FLOAT)") else
+ print db.error or else "no error?"
+end
+
+assert db.insert("INTO users VALUES('Bob', 'zzz', 1, 77.7)") else
+ print db.error or else "no error?"
+end
+
+assert db.insert("INTO users VALUES('Guillaume', 'xxx', 1, 88.8)") else
+ print db.error or else "no error?"
+end
+
+#alt2#assert db.insert("INTO notable VALUES('Alexis', 'asdf', 2, 99.9)") else
+#alt2# print db.error or else "no error?"
+#alt2#end
+
+for row in db.select("* FROM users") do
+ print "####"
+
+ printn "{row[0].name}: "
+ print row[0]
+
+ printn "{row[1].name}: "
+ var val = row[1].value
+ assert val isa Text
+ print val.to_s
+
+ printn "{row[2].name}: "
+ print row[2].to_i
+
+ printn "{row[3].name}: "
+ print row[3].to_f
+end
+
+print db.is_open
+db.close
+print db.is_open