furnace-xray 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. data/.gitignore +18 -0
  2. data/Gemfile +5 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +60 -0
  5. data/Rakefile +1 -0
  6. data/bin/furnace-xray +23 -0
  7. data/furnace-xray.gemspec +33 -0
  8. data/lib/furnace-xray.rb +1 -0
  9. data/lib/furnace-xray/app/app.rb +56 -0
  10. data/lib/furnace-xray/app/assets/images/chosen-sprite.png +0 -0
  11. data/lib/furnace-xray/app/assets/images/evil_martians_bw_logo.svg +3 -0
  12. data/lib/furnace-xray/app/assets/images/evil_martians_logo.svg +3 -0
  13. data/lib/furnace-xray/app/assets/images/ui/ui-bg_diagonal-maze_20_6e4f1c_10x10.png +0 -0
  14. data/lib/furnace-xray/app/assets/images/ui/ui-bg_diagonal-maze_40_000000_10x10.png +0 -0
  15. data/lib/furnace-xray/app/assets/images/ui/ui-bg_fine-grain_10_eceadf_60x60.png +0 -0
  16. data/lib/furnace-xray/app/assets/images/ui/ui-bg_fine-grain_10_f8f7f6_60x60.png +0 -0
  17. data/lib/furnace-xray/app/assets/images/ui/ui-bg_fine-grain_15_eceadf_60x60.png +0 -0
  18. data/lib/furnace-xray/app/assets/images/ui/ui-bg_fine-grain_15_f7f3de_60x60.png +0 -0
  19. data/lib/furnace-xray/app/assets/images/ui/ui-bg_fine-grain_15_ffffff_60x60.png +0 -0
  20. data/lib/furnace-xray/app/assets/images/ui/ui-bg_fine-grain_65_654b24_60x60.png +0 -0
  21. data/lib/furnace-xray/app/assets/images/ui/ui-bg_fine-grain_68_b83400_60x60.png +0 -0
  22. data/lib/furnace-xray/app/assets/images/ui/ui-icons_222222_256x240.png +0 -0
  23. data/lib/furnace-xray/app/assets/images/ui/ui-icons_3572ac_256x240.png +0 -0
  24. data/lib/furnace-xray/app/assets/images/ui/ui-icons_8c291d_256x240.png +0 -0
  25. data/lib/furnace-xray/app/assets/images/ui/ui-icons_b83400_256x240.png +0 -0
  26. data/lib/furnace-xray/app/assets/images/ui/ui-icons_fbdb93_256x240.png +0 -0
  27. data/lib/furnace-xray/app/assets/images/ui/ui-icons_ffffff_256x240.png +0 -0
  28. data/lib/furnace-xray/app/assets/javascripts/application.js.coffee +219 -0
  29. data/lib/furnace-xray/app/assets/javascripts/lib/drawer.js.coffee +206 -0
  30. data/lib/furnace-xray/app/assets/javascripts/lib/graph.js.coffee +26 -0
  31. data/lib/furnace-xray/app/assets/javascripts/lib/input.js.coffee +134 -0
  32. data/lib/furnace-xray/app/assets/javascripts/lib/input_state.js.coffee +8 -0
  33. data/lib/furnace-xray/app/assets/javascripts/lib/map.js.coffee +45 -0
  34. data/lib/furnace-xray/app/assets/javascripts/lib/nodes/argument.js.coffee +7 -0
  35. data/lib/furnace-xray/app/assets/javascripts/lib/nodes/block.js.coffee +76 -0
  36. data/lib/furnace-xray/app/assets/javascripts/lib/nodes/function.js.coffee +15 -0
  37. data/lib/furnace-xray/app/assets/javascripts/lib/nodes/instruction.js.coffee +33 -0
  38. data/lib/furnace-xray/app/assets/javascripts/lib/nodes/operand.js.coffee +21 -0
  39. data/lib/furnace-xray/app/assets/javascripts/lib/nodes/type.js.coffee +12 -0
  40. data/lib/furnace-xray/app/assets/javascripts/vendor/chosen.jquery.js +1090 -0
  41. data/lib/furnace-xray/app/assets/javascripts/vendor/d3.v3.js +7806 -0
  42. data/lib/furnace-xray/app/assets/javascripts/vendor/dagre.js +4053 -0
  43. data/lib/furnace-xray/app/assets/javascripts/vendor/hotkeys.jquery.js +106 -0
  44. data/lib/furnace-xray/app/assets/javascripts/vendor/jquery.js +9472 -0
  45. data/lib/furnace-xray/app/assets/javascripts/vendor/mustache.js +532 -0
  46. data/lib/furnace-xray/app/assets/javascripts/vendor/stacktrace-0.4.js +424 -0
  47. data/lib/furnace-xray/app/assets/javascripts/vendor/sugar-1.3.8.js +8615 -0
  48. data/lib/furnace-xray/app/assets/javascripts/vendor/ui.jquery.js +14850 -0
  49. data/lib/furnace-xray/app/assets/stylesheets/app.css.sass +199 -0
  50. data/lib/furnace-xray/app/assets/stylesheets/chosen.jquery.css +413 -0
  51. data/lib/furnace-xray/app/assets/stylesheets/elements.css.sass +31 -0
  52. data/lib/furnace-xray/app/assets/stylesheets/graph.css.sass +29 -0
  53. data/lib/furnace-xray/app/assets/stylesheets/ui.jquery.css +1174 -0
  54. data/lib/furnace-xray/app/public/fonts/iconic_stroke.afm +170 -0
  55. data/lib/furnace-xray/app/public/fonts/iconic_stroke.eot +0 -0
  56. data/lib/furnace-xray/app/public/fonts/iconic_stroke.otf +0 -0
  57. data/lib/furnace-xray/app/public/fonts/iconic_stroke.svg +553 -0
  58. data/lib/furnace-xray/app/public/fonts/iconic_stroke.ttf +0 -0
  59. data/lib/furnace-xray/app/public/fonts/iconic_stroke.woff +0 -0
  60. data/lib/furnace-xray/app/public/fonts/iconic_stroke_demo.html +1 -0
  61. data/lib/furnace-xray/app/views/index.haml +52 -0
  62. data/lib/furnace-xray/app/views/nodes/argument.jst.mustache +1 -0
  63. data/lib/furnace-xray/app/views/nodes/block.jst.mustache +4 -0
  64. data/lib/furnace-xray/app/views/nodes/diff/added_line.jst.mustache +1 -0
  65. data/lib/furnace-xray/app/views/nodes/diff/changed_line.jst.mustache +2 -0
  66. data/lib/furnace-xray/app/views/nodes/diff/removed_line.jst.mustache +1 -0
  67. data/lib/furnace-xray/app/views/nodes/diff/unchanged_line.jst.mustache +1 -0
  68. data/lib/furnace-xray/app/views/nodes/function.jst.mustache +1 -0
  69. data/lib/furnace-xray/app/views/nodes/function_removed.jst.mustache +1 -0
  70. data/lib/furnace-xray/app/views/nodes/instruction_typed.jst.mustache +3 -0
  71. data/lib/furnace-xray/app/views/nodes/instruction_void.jst.mustache +3 -0
  72. data/lib/furnace-xray/app/views/nodes/operand_argument.jst.mustache +1 -0
  73. data/lib/furnace-xray/app/views/nodes/operand_basic_block.jst.mustache +1 -0
  74. data/lib/furnace-xray/app/views/nodes/operand_constant.jst.mustache +1 -0
  75. data/lib/furnace-xray/app/views/nodes/operand_constant_function.jst.mustache +1 -0
  76. data/lib/furnace-xray/app/views/nodes/operand_instruction.jst.mustache +1 -0
  77. data/lib/furnace-xray/app/views/nodes/type_constant.jst.mustache +1 -0
  78. data/lib/furnace-xray/app/views/nodes/type_parametric.jst.mustache +3 -0
  79. data/lib/furnace-xray/lib/jst_pages.rb +226 -0
  80. data/lib/furnace-xray/version.rb +5 -0
  81. data/sample.json +1 -0
  82. metadata +251 -0
@@ -0,0 +1,206 @@
1
+ #
2
+ # Graph drawing routines.
3
+ # Drawer manages SVG canvas exclusively – avoid interferention
4
+ #
5
+ class @Drawer
6
+
7
+ @canvasPadding = 20
8
+
9
+ @attach: (selector) ->
10
+ @svg = d3.select(selector)
11
+ @group = @svg.append("g").attr("class", "container")
12
+ @zoom = d3.behavior.zoom()
13
+
14
+ @zoom.on "zoom", =>
15
+ @group.attr("transform", "translate(#{d3.event.translate}) scale(#{d3.event.scale})")
16
+
17
+ @svg.call(@zoom).on("dblclick.zoom", null)
18
+
19
+ @zoomTo: (t, s) ->
20
+ @zoom.translate(t)
21
+ @zoom.scale(s) if s
22
+ @group.attr("transform", "translate(#{t}) scale(#{s || @zoom.scale()})")
23
+
24
+ @clear: ->
25
+ @group?.select("g").remove()
26
+
27
+ @reset: ->
28
+ @zoomTo [@canvasPadding, @canvasPadding]
29
+
30
+ constructor: (@graph, padding=10) ->
31
+ @padding = padding
32
+
33
+ @setupEntities()
34
+ @draw()
35
+
36
+ repaint: ->
37
+ @container.attr("transform", "translate(0,1)")
38
+ @container.attr("transform", "translate(0,0)")
39
+
40
+ fit: (width, height) ->
41
+ @zoomValue = [@constructor.zoom.translate(), @constructor.zoom.scale()]
42
+
43
+ width = (width-@constructor.canvasPadding*2) / @width()
44
+ height = (height-@constructor.canvasPadding*2) / @height()
45
+ ratio = [width, height].min()
46
+
47
+ @constructor.zoomTo([@constructor.canvasPadding,@constructor.canvasPadding], ratio)
48
+
49
+ unfit: ->
50
+ @constructor.zoomTo @zoomValue...
51
+
52
+ zoomNode: (width, height, name) ->
53
+ node = @container.select("#node-#{name}")
54
+ return if node.empty()
55
+
56
+ padding = @constructor.canvasPadding
57
+ width = width-padding*2
58
+ height = height-padding*2
59
+
60
+ zoom = @constructor.zoom.scale()
61
+ bbox = node[0][0].getBBox()
62
+
63
+ if width < bbox.width*zoom || height < bbox.height*zoom
64
+ zoomTo = zoom = [width/bbox.width, height/bbox.height].min()
65
+
66
+ x = (-node.attr("x").toNumber()*zoom+width/2-bbox.width*zoom/2)+padding
67
+ y = (-node.attr("y").toNumber()*zoom+height/2-bbox.height*zoom/2)+padding
68
+
69
+ @constructor.zoomTo [x, y], zoomTo
70
+
71
+ setupEntities: ->
72
+ @container = @constructor.group.append("g")
73
+
74
+ # `nodes` is center positioned for easy layout later
75
+ @nodes = @container.selectAll("g .node")
76
+ .data(@graph.nodes)
77
+ .enter()
78
+ .append("g")
79
+ .attr("class", "node")
80
+ .attr("id", (d) -> "node-" + d.label)
81
+
82
+ @edges = @container.selectAll("g .edge")
83
+ .data(@graph.edges)
84
+ .enter()
85
+ .append("g")
86
+ .attr("class", "edge")
87
+
88
+ @edges
89
+ .append("path")
90
+ .attr("marker-end", "url(#arrowhead)")
91
+
92
+ @assignHtmlLabels(@nodes)
93
+ @assignTextLabels(@edges, 1)
94
+
95
+ assignTextLabels: (items, padding) ->
96
+ groups = items.append("g").attr("class", "label")
97
+ rects = groups.append("rect")
98
+ labels = groups.append("text")
99
+
100
+ labels.attr("text-anchor", "left")
101
+ .append("tspan")
102
+ .attr("dy", "1em")
103
+ .text((d) -> d.data)
104
+
105
+ @setupEntitiesSizes(rects, labels, padding)
106
+
107
+ assignHtmlLabels: (items, padding) ->
108
+ groups = items.append("g").attr("class", "label")
109
+ rects = groups.append("rect")
110
+ labels = groups.append("foreignObject")
111
+ texts = labels.append("xhtml:div").style
112
+ "float": "left"
113
+ "white-space": "nowrap"
114
+
115
+ texts.html((d) -> d.data).each (d) ->
116
+ if @clientWidth > 900
117
+ d3.select(@).style
118
+ "white-space": "normal"
119
+ "width": '900px'
120
+
121
+ texts
122
+ .each (d) ->
123
+ d.width = @clientWidth
124
+ d.height = @clientHeight
125
+ d.nodePadding = 0
126
+
127
+ labels
128
+ .attr("width", (d) -> d.width)
129
+ .attr("height", (d) -> d.height)
130
+
131
+ @setupEntitiesSizes(rects, labels, padding)
132
+
133
+ setupEntitiesSizes: (rects, labels, padding) ->
134
+ padding = @padding unless padding
135
+
136
+ # We need width and height for layout.
137
+ labels.each (d) ->
138
+ bbox = @getBBox()
139
+ d.bbox = bbox
140
+ d.width = bbox.width + 2 * padding
141
+ d.height = bbox.height + 2 * padding
142
+
143
+ rects
144
+ .attr("width", (d) -> d.width)
145
+ .attr("height", (d) -> d.height)
146
+
147
+ labels
148
+ .attr("x", (d) -> padding)
149
+ .attr("y", (d) -> padding)
150
+
151
+ width: -> @container[0][0].getBBox().width
152
+ height: -> @container[0][0].getBBox().height
153
+
154
+ draw: ->
155
+ dagre.layout().nodeSep(50).edgeSep(50).rankSep(50)
156
+ .nodes(@graph.nodes)
157
+ .edges(@graph.edges)
158
+ .run()
159
+
160
+ # Positioning nodes
161
+ @nodes.attr "transform", (d, i) =>
162
+ x = d.dagre.x - d.width/2
163
+ y = d.dagre.y - d.height/2
164
+
165
+ @nodes[0][i].setAttribute 'x', x
166
+ @nodes[0][i].setAttribute 'y', y
167
+
168
+ "translate(#{x},#{y})"
169
+
170
+ # Ensure that we have at least two points between source and target
171
+ @edges.each (d) ->
172
+ points = d.dagre.points
173
+ unless points.length
174
+ s = d.source.dagre
175
+ t = d.target.dagre
176
+ points.push
177
+ x: (s.x + t.x) / 2
178
+ y: (s.y + t.y) / 2
179
+
180
+ if points.length is 1
181
+ points.push
182
+ x: points[0].x
183
+ y: points[0].y
184
+
185
+ # Setting correct path coordinates
186
+ @edges.selectAll("path").attr "d", (e) ->
187
+ points = e.dagre.points.slice(0)
188
+ source = dagre.util.intersectRect(e.source.dagre, points[0])
189
+ target = dagre.util.intersectRect(e.target.dagre, points[points.length - 1])
190
+
191
+ points.unshift source
192
+ points.push target
193
+
194
+ d3.svg.line()
195
+ .x((d) -> d.x)
196
+ .y((d) -> d.y)
197
+ .interpolate("linear")(points)
198
+
199
+ # Positioning edges labels
200
+ @edges.selectAll("g.label")
201
+ .attr("transform", (d) ->
202
+ points = d.dagre.points;
203
+ x = (points[0].x + points[1].x) / 2;
204
+ y = (points[0].y + points[1].y) / 2;
205
+ return "translate(" + (-d.bbox.width / 2 + x) + "," + (-d.bbox.height / 2 + y) + ")";
206
+ )
@@ -0,0 +1,26 @@
1
+ #
2
+ # Maps internal JSON representation to a flat
3
+ # graph structure consumable by `Drawer`
4
+ #
5
+ class @Graph
6
+ constructor: (@input) ->
7
+
8
+ @edges = []
9
+ blockNodes = Object.extended()
10
+
11
+ @input.blocks.each (i, b) =>
12
+ blockNodes[b.name] =
13
+ edges: b.references()
14
+ label: b.name
15
+ data: b.title(@input.previousState?.blocks[b.name])
16
+
17
+ blockNodes.each (i, n) =>
18
+ edges = []
19
+
20
+ n.edges.each (x) =>
21
+ edges.add {source: n, target: blockNodes[x], data: blockNodes[x].label}
22
+ @edges.add edges.last()
23
+
24
+ n.edges = edges
25
+
26
+ @nodes = blockNodes.values()
@@ -0,0 +1,134 @@
1
+ #
2
+ # Internal representation of input JSON
3
+ #
4
+ class @Input
5
+ @normalize: (data) ->
6
+ data.map (x) -> new Input(x)
7
+
8
+ constructor: (@source) ->
9
+ @source.events.each (x) -> x.event = x.event.camelize(false)
10
+
11
+ # Scan for effective events
12
+ @events = []
13
+ reducer = []
14
+ transforms = Object.extended()
15
+
16
+ @source.events.each (x, i) =>
17
+ switch x.event
18
+ when 'addInstruction'
19
+ reducer.add x.name
20
+ @events.add i
21
+ when 'removeInstruction'
22
+ reducer.exclude x.name
23
+ @events.add i
24
+ when 'updateInstruction'
25
+ @events.add i if reducer.any(x.name)
26
+ when 'type', 'transformStart'
27
+ else
28
+ @events.add i
29
+
30
+ if x.event == 'transformStart'
31
+ id = @events.length-1
32
+ transforms[id] = {id: id, label: x.name}
33
+
34
+ @transforms = transforms.values()
35
+ @transforms = @transforms.filter (x, i) =>
36
+ x.length = (@transforms[i+1]?.id || @events.length) - x.id
37
+ x.id < @events.length-1
38
+
39
+ @reset()
40
+
41
+ reset: ->
42
+ @types = Object.extended()
43
+ @blocks = Object.extended()
44
+ @instructions = Object.extended()
45
+ @blocksMap = new Map 'blocks'
46
+ @instructionsMap = new Map 'instructions'
47
+
48
+ @function = new FunctionNode(@source.name, @source.present)
49
+ @cursor = 0
50
+
51
+ # run first step at initialization
52
+ i = -1; @run(i) while (i+=1) <= (@events[1] || @source.events.length-1)
53
+
54
+ rewind: (to) ->
55
+ return if to == @cursor
56
+
57
+ if to < @cursor
58
+ delete @previousState
59
+ @reset()
60
+ else
61
+ @previousState = new InputState(@)
62
+
63
+ @reset()
64
+ @increment(to)
65
+
66
+ increment: (to) ->
67
+ to = @events.length-1 unless to?
68
+ stop = [to, @events.length-1].min()
69
+ stop = 0 if stop < 0
70
+
71
+ i = @events[@cursor]; @run(i) while (i+=1) <= @events[stop]
72
+ @cursor = stop
73
+
74
+ run: (step) ->
75
+ event = @source.events[step]
76
+
77
+ if event.event == 'type'
78
+ @types[event.id] = new TypeNode(event.kind, event.name, event.parameters)
79
+ else
80
+ @[event.event]?(event)
81
+ console.log "UNKNOWN EVENT: #{event.event}" unless @[event.event]?
82
+
83
+ type: (id) ->
84
+ return undefined unless id?
85
+
86
+ if type = @types[id]
87
+ return type
88
+ else
89
+ @reset()
90
+ throw "Type #{id} not found in #{@types.keys().join(',')}"
91
+
92
+ setReturnType: (event) ->
93
+ @function.setReturnType @type(event.return_type)
94
+
95
+ setArguments: (event) ->
96
+ @function.setArguments(event.arguments.map (x) =>
97
+ new ArgumentNode(x.name, @type(x.type)))
98
+
99
+ addBasicBlock: (event) ->
100
+ @blocksMap.add event.name, (id) =>
101
+ @blocks[id] = new BlockNode(event.name)
102
+
103
+ removeBasicBlock: (event) ->
104
+ @blocksMap.remove event.name, (id) =>
105
+ delete @blocks[id]
106
+
107
+ renameBasicBlock: (event) ->
108
+ @blocksMap.rename event.name, event.new_name, (id) =>
109
+ @blocks[id].setName(event.new_name)
110
+
111
+ updateInstruction: (event) ->
112
+ id = @instructionsMap.add event.name, (id) =>
113
+ @instructions[id] = new InstructionNode
114
+
115
+ if Object.isArray(event.operands)
116
+ operands = event.operands.map (x) =>
117
+ new OperandNode x.kind, @type(x.type), x.name, x.value
118
+ else
119
+ operands = []
120
+ Object.each event.operands, (key, x) =>
121
+ operands.push [key, new OperandNode(x.kind, @type(x.type), x.name, x.value)]
122
+
123
+ @instructions[id].update event.opcode, event.name, event.parameters, operands, @type(event.type)
124
+
125
+ addInstruction: (event) ->
126
+ @instructionsMap.locate event.name, (i) =>
127
+ @blocksMap.locate event.basic_block, (b) =>
128
+ @instructions[i].link @blocks[b], event.index
129
+
130
+ removeInstruction: (event) ->
131
+ @instructionsMap.locate event.name, (i) =>
132
+ @instructions[i].unlink()
133
+
134
+ transformStart: (event) ->
@@ -0,0 +1,8 @@
1
+ class @InputState
2
+ constructor: (input) ->
3
+ @blocks = Object.extended()
4
+ @cursor = input.cursor
5
+
6
+ input.blocksMap.each (bname, id) =>
7
+ @blocks[bname] = input.blocks[id].instructions.map (x) ->
8
+ {title: x.title(), name: x.name}
@@ -0,0 +1,45 @@
1
+ #
2
+ # Failsafe Map container
3
+ #
4
+ class @Map
5
+ constructor: (@title) ->
6
+ @data = []
7
+ @map = Object.extended()
8
+
9
+ each: (block) -> @map.each block
10
+
11
+ add: (name, block) ->
12
+ id = @data.findIndex(name)
13
+
14
+ if id < 0
15
+ @data.add name
16
+ id = @data.length-1
17
+ block(id) if block
18
+ @map[name] = id
19
+
20
+ id
21
+
22
+ find: (name) ->
23
+ id = @map[name]
24
+
25
+ if id < 0
26
+ error = "Map '#{@title}': '#{name}' element not found"
27
+ throw error
28
+
29
+ id
30
+
31
+ remove: (name, block) ->
32
+ id = @find(name)
33
+ @data.splice id, 1
34
+ block(id) if block
35
+ delete @map[name]
36
+
37
+ rename: (name, newName, block) ->
38
+ id = @find(name)
39
+ @data[id] = newName
40
+ block(id) if block
41
+ delete @map[name]
42
+ @map[newName] = id
43
+
44
+ locate: (name, block) ->
45
+ block(@find(name))
@@ -0,0 +1,7 @@
1
+ class @ArgumentNode
2
+ constructor: (@name, @type) ->
3
+
4
+ title: ->
5
+ JST['nodes/argument']
6
+ type: @type.title()
7
+ name: @name
@@ -0,0 +1,76 @@
1
+ class @BlockNode
2
+ constructor: (@name, @instructions) ->
3
+ @instructions ||= []
4
+
5
+ title: (previousState) ->
6
+ JST['nodes/block']
7
+ name: @name
8
+ instructions: @titleizeInstructions(previousState)
9
+
10
+ titleizeInstructions: (previousState) ->
11
+ unless previousState
12
+ result = ""
13
+ result += JST['nodes/diff/unchanged_line'] line: x.title() for x in @instructions
14
+ result
15
+ else
16
+ result = ""
17
+ removed = Object.extended()
18
+ added = Object.extended()
19
+ changed = Object.extended()
20
+ unchanged = Object.extended()
21
+
22
+ for cs, i in @instructions
23
+ previous = previousState.find (ps) -> ps.name == cs.name
24
+ currentTitle = cs.title()
25
+
26
+ if previous && previous.title == currentTitle
27
+ unchanged[i] = previous.title
28
+ previous.newPosition = i
29
+ else if previous
30
+ changed[i] = [previous.title, currentTitle]
31
+ previous.newPosition = i
32
+ else
33
+ added[i] = currentTitle
34
+
35
+ for ps, i in previousState
36
+ if !ps.newPosition?
37
+ position = 0
38
+
39
+ while --i >= 0
40
+ if previousState[i].newPosition?
41
+ position = previousState[i].newPosition+1
42
+ break
43
+
44
+ removed[position] ||= []
45
+ removed[position].push ps.title
46
+
47
+ [@instructions.length, previousState.length].max().times (i) ->
48
+ if removed[i]
49
+ result += JST['nodes/diff/removed_line'](line: l) for l in removed[i]
50
+
51
+ result += JST['nodes/diff/added_line'](line: added[i]) if added[i]
52
+ result += JST['nodes/diff/changed_line'](before: changed[i][0], after:changed[i][1]) if changed[i]
53
+ result += JST['nodes/diff/unchanged_line'](line: unchanged[i]) if unchanged[i]
54
+
55
+ result
56
+
57
+ previous: ->
58
+ try
59
+ previous = @input.previous.blocks[@input.previous.blocksMap.find(@name)]
60
+ catch error
61
+ console.log error
62
+
63
+ previous
64
+
65
+ setName: (name) ->
66
+ @name = name
67
+
68
+ addInstruction: (instruction, index) ->
69
+ @instructions.splice index, 0, instruction
70
+
71
+ removeInstruction: (instruction) ->
72
+ index = @instructions.findIndex(instruction)
73
+ @instructions.splice index, 1 unless index < 0
74
+
75
+ references: ->
76
+ @instructions.last()?.operands.findAll((x) -> x.kind == "basic_block").map((x) -> x.name) || []