Compare commits
56 Commits
diy-render
...
new-react
Author | SHA1 | Date | |
---|---|---|---|
6312b0962e | |||
79383d89b1 | |||
933b64c499 | |||
62e5761a40 | |||
07f7945917 | |||
756ab31a61 | |||
557c455167 | |||
7e34ae003e | |||
e460e0041b | |||
87cec0f4bc | |||
1ae4baf3f7 | |||
228b1b5832 | |||
add84432b4 | |||
6ce51c38db | |||
6ba3c54690 | |||
3464e50b2f | |||
515e1b106b | |||
b693f55a29 | |||
2f66bd9f08 | |||
93ac945dd2 | |||
1526656dfe | |||
959cb628fa | |||
5d42da9d03 | |||
f5ece67233 | |||
246bb5ee4f | |||
0cdd5fdf13 | |||
8183dfd6b1 | |||
950c77b127 | |||
a896702880 | |||
53d576ef90 | |||
1f19a28906 | |||
03c0041255 | |||
736bff02a5 | |||
2645f54d5b | |||
aa2303ddaa | |||
f4dfbb5845 | |||
398d364c02 | |||
3d96d59371 | |||
a0721a0cde | |||
d021816726 | |||
e8d9000a74 | |||
db7fa423bb | |||
bd882b7d2e | |||
5c2f423f58 | |||
19a0b7f9fe | |||
4305ddaf1d | |||
b5b0630060 | |||
c22a4a95d3 | |||
69414a11f9 | |||
40d97dcf87 | |||
acd66f66f3 | |||
f2af076c8e | |||
1efce561d7 | |||
1d005862ac | |||
e51d6cb3fa | |||
d47d8a9e2a |
19
Gemfile
19
Gemfile
@ -1,24 +1,17 @@
|
||||
source 'https://rubygems.org'
|
||||
|
||||
gem "opal" , :github => "opal/opal"
|
||||
gem 'opal-browser'
|
||||
|
||||
#gem "salama" , "0.2" , :path => "../salama"
|
||||
gem 'opal-react', :path => '../react.rb'
|
||||
gem 'opal-jquery'
|
||||
gem 'react-source'
|
||||
|
||||
gem "parslet" , github: "salama/parslet"
|
||||
gem "salama" , github: "salama/salama"
|
||||
gem "salama-reader" , github: "salama/salama-reader"
|
||||
gem "salama-arm" , github: "salama/salama-arm"
|
||||
gem "salama-object-file" , github: "salama/salama-object-file"
|
||||
gem "sass"
|
||||
gem "susy"
|
||||
|
||||
group :test do
|
||||
# Testing dependencies
|
||||
gem 'rspec', '~> 3.2.0'
|
||||
gem 'opal-rspec', '~> 0.4.2'
|
||||
gem 'capybara', '~> 2.4.2'
|
||||
gem 'selenium-webdriver', '~> 2.43.0'
|
||||
gem 'chromedriver2-helper', '~> 0.0.8'
|
||||
gem 'poltergeist', '~> 1.5.0'
|
||||
group :development do
|
||||
gem "minitest"
|
||||
gem "rubygems-tasks"
|
||||
end
|
||||
|
105
Gemfile.lock
105
Gemfile.lock
@ -1,16 +1,6 @@
|
||||
GIT
|
||||
remote: git://github.com/opal/opal.git
|
||||
revision: c604685071f8560d3357044683a99b80b210a99b
|
||||
specs:
|
||||
opal (0.9.0.dev)
|
||||
hike (~> 1.2)
|
||||
sourcemap (~> 0.1.0)
|
||||
sprockets (~> 3.1)
|
||||
tilt (>= 1.4)
|
||||
|
||||
GIT
|
||||
remote: git://github.com/salama/parslet.git
|
||||
revision: b8bf8db20a242eb6bd8fec88027e3b2ae19276e8
|
||||
revision: beeb9b441a9ade1504f7f0e848d805e36a02c544
|
||||
specs:
|
||||
parslet (1.7.0)
|
||||
|
||||
@ -35,99 +25,58 @@ GIT
|
||||
|
||||
GIT
|
||||
remote: git://github.com/salama/salama.git
|
||||
revision: df95b8b54de103523743cbfd5c46d87af84c6b09
|
||||
revision: 3fb08acf3f83aa403b095aa60be1702419d8a66d
|
||||
specs:
|
||||
salama (0.2.0)
|
||||
salama-object-file (~> 0.2)
|
||||
salama-reader (~> 0.2)
|
||||
|
||||
PATH
|
||||
remote: ../react.rb
|
||||
specs:
|
||||
opal-react (0.2.2)
|
||||
opal
|
||||
opal-activesupport
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
capybara (2.4.4)
|
||||
mime-types (>= 1.16)
|
||||
nokogiri (>= 1.3.3)
|
||||
rack (>= 1.0.0)
|
||||
rack-test (>= 0.5.4)
|
||||
xpath (~> 2.0)
|
||||
childprocess (0.5.6)
|
||||
ffi (~> 1.0, >= 1.0.11)
|
||||
chromedriver2-helper (0.0.8)
|
||||
nokogiri
|
||||
cliver (0.3.2)
|
||||
diff-lcs (1.2.5)
|
||||
ffi (1.9.10)
|
||||
hike (1.2.3)
|
||||
mime-types (2.6.1)
|
||||
mini_portile (0.6.2)
|
||||
multi_json (1.11.2)
|
||||
nokogiri (1.6.6.2)
|
||||
mini_portile (~> 0.6.0)
|
||||
opal-browser (0.2.0)
|
||||
opal
|
||||
paggio
|
||||
opal-rspec (0.4.3)
|
||||
opal (>= 0.7.0, < 0.9)
|
||||
paggio (0.2.4)
|
||||
poltergeist (1.5.1)
|
||||
capybara (~> 2.1)
|
||||
cliver (~> 0.3.1)
|
||||
multi_json (~> 1.0)
|
||||
websocket-driver (>= 0.2.0)
|
||||
minitest (5.7.0)
|
||||
opal (0.8.0)
|
||||
hike (~> 1.2)
|
||||
sourcemap (~> 0.1.0)
|
||||
sprockets (~> 3.1)
|
||||
tilt (>= 1.4)
|
||||
opal-activesupport (0.1.0)
|
||||
opal (>= 0.5.0, < 1.0.0)
|
||||
opal-jquery (0.4.0)
|
||||
opal (>= 0.7.0, < 0.9.0)
|
||||
rack (1.6.4)
|
||||
rack-test (0.6.3)
|
||||
rack (>= 1.0)
|
||||
rspec (3.2.0)
|
||||
rspec-core (~> 3.2.0)
|
||||
rspec-expectations (~> 3.2.0)
|
||||
rspec-mocks (~> 3.2.0)
|
||||
rspec-core (3.2.3)
|
||||
rspec-support (~> 3.2.0)
|
||||
rspec-expectations (3.2.1)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.2.0)
|
||||
rspec-mocks (3.2.1)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.2.0)
|
||||
rspec-support (3.2.2)
|
||||
rubyzip (1.1.7)
|
||||
react-source (0.13.3)
|
||||
rubygems-tasks (0.2.4)
|
||||
sass (3.4.16)
|
||||
selenium-webdriver (2.43.0)
|
||||
childprocess (~> 0.5)
|
||||
multi_json (~> 1.0)
|
||||
rubyzip (~> 1.0)
|
||||
websocket (~> 1.0)
|
||||
sourcemap (0.1.1)
|
||||
sprockets (3.3.2)
|
||||
sprockets (3.2.0)
|
||||
rack (~> 1.0)
|
||||
susy (2.2.5)
|
||||
sass (>= 3.3.0, < 3.5)
|
||||
tilt (2.0.1)
|
||||
websocket (1.2.2)
|
||||
websocket-driver (0.5.4)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.2)
|
||||
xpath (2.0.0)
|
||||
nokogiri (~> 1.3)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
capybara (~> 2.4.2)
|
||||
chromedriver2-helper (~> 0.0.8)
|
||||
opal!
|
||||
opal-browser
|
||||
opal-rspec (~> 0.4.2)
|
||||
minitest
|
||||
opal-jquery
|
||||
opal-react!
|
||||
parslet!
|
||||
poltergeist (~> 1.5.0)
|
||||
rspec (~> 3.2.0)
|
||||
react-source
|
||||
rubygems-tasks
|
||||
salama!
|
||||
salama-arm!
|
||||
salama-object-file!
|
||||
salama-reader!
|
||||
sass
|
||||
selenium-webdriver (~> 2.43.0)
|
||||
susy
|
||||
|
||||
BUNDLED WITH
|
||||
|
21
README.md
21
README.md
@ -1,9 +1,22 @@
|
||||
### statement of intent
|
||||
|
||||
just starting and not quite sure, but here is the direction
|
||||
|
||||
# Debugger
|
||||
|
||||
After some tryouts it ended up being an Opal application. That is ruby as javascript in the browser.
|
||||
|
||||

|
||||
|
||||
- On the left are the classes of the system. Next idea is to have hover info about them.
|
||||
- Next a source code view (not implemented)
|
||||
- next a view of the Virtual Instructions
|
||||
- last section, current block with current Register Instruction highlighted
|
||||
- step (next) button for single stepping
|
||||
- status: starting , running , exited
|
||||
- bottom row are the registers. If the register hold an object the variables are shown.
|
||||
(also should have hover info) , the first letter indicates the class, the number is the address
|
||||
|
||||
So lots to do, but a good start.
|
||||
|
||||
|
||||
|
||||
I don't want to use gdb anymore, and it would be easier without using the qemu setup, so:
|
||||
|
||||
- single step debugging of the register machine level (as close to arm as need be)
|
||||
|
50
app/block_view.rb
Normal file
50
app/block_view.rb
Normal file
@ -0,0 +1,50 @@
|
||||
|
||||
require "instruction_view"
|
||||
|
||||
class BlockView
|
||||
|
||||
|
||||
include React::Component
|
||||
required_param :interpreter
|
||||
|
||||
define_state :block => []
|
||||
define_state :block_name => ""
|
||||
|
||||
before_mount do
|
||||
interpreter.register_event(:instruction_changed, self)
|
||||
update_block
|
||||
end
|
||||
|
||||
def update_block
|
||||
return unless interpreter.instruction
|
||||
block_name! interpreter.block.name
|
||||
codes = interpreter.block.codes.dup
|
||||
slice = codes.index(interpreter.instruction) #- 1
|
||||
codes.shift( slice ) if slice >= 0
|
||||
codes.pop while(codes.length > 4)
|
||||
block! codes
|
||||
end
|
||||
|
||||
def instruction_changed
|
||||
update_block
|
||||
end
|
||||
|
||||
def render
|
||||
return unless block
|
||||
div.block_view do
|
||||
div do
|
||||
h4 { method_name}
|
||||
h4 {"Block: #{block_name}"}
|
||||
end
|
||||
block.each do |code|
|
||||
InstructionView :interpreter => interpreter , :instruction => code
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def method_name
|
||||
bl = interpreter.block
|
||||
return bl.method if bl.method.is_a? String
|
||||
"#{bl.method.for_class.name}.#{bl.method.name}"
|
||||
end
|
||||
end
|
16
app/class_view.rb
Normal file
16
app/class_view.rb
Normal file
@ -0,0 +1,16 @@
|
||||
class ClassView
|
||||
include React::Component
|
||||
|
||||
required_param :classes, type: {}
|
||||
|
||||
def render
|
||||
div.classes do
|
||||
h4 { "Classes" }
|
||||
classes.each do |name , clas|
|
||||
div.one_class do
|
||||
clas.name
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
37
app/debugger.rb
Normal file
37
app/debugger.rb
Normal file
@ -0,0 +1,37 @@
|
||||
|
||||
require "register_view"
|
||||
require "class_view"
|
||||
require "source_view"
|
||||
require "block_view"
|
||||
require "status_view"
|
||||
|
||||
class Debugger
|
||||
|
||||
include React::Component
|
||||
required_param :machine , :type => Virtual::Machine
|
||||
define_state :interpreter => Interpreter.new
|
||||
|
||||
before_mount do
|
||||
code = Ast::ExpressionList.new( [Ast::CallSiteExpression.new(:putstring, [] ,Ast::StringExpression.new("Hello again"))])
|
||||
Virtual::Compiler.compile( code , machine.space.get_main )
|
||||
machine.run_before "Register::CallImplementation"
|
||||
interpreter.start machine.init
|
||||
end
|
||||
def render
|
||||
div.debugger_view do
|
||||
ClassView classes: machine.space.classes
|
||||
div.file_view do
|
||||
"Future Source code view"
|
||||
end
|
||||
SourceView :interpreter => interpreter
|
||||
BlockView :interpreter => interpreter
|
||||
StatusView :interpreter => interpreter
|
||||
div.registers_view do
|
||||
interpreter.registers.each do |r , oid|
|
||||
RegisterView interpreter: interpreter , register: r
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
28
app/instruction_view.rb
Normal file
28
app/instruction_view.rb
Normal file
@ -0,0 +1,28 @@
|
||||
|
||||
|
||||
class InstructionView
|
||||
|
||||
include React::Component
|
||||
required_param :interpreter
|
||||
required_param :instruction
|
||||
|
||||
define_state :active => ""
|
||||
|
||||
before_mount do
|
||||
check_active interpreter.instruction
|
||||
end
|
||||
|
||||
def check_active i
|
||||
active! instruction == i ? "bright" : ""
|
||||
|
||||
end
|
||||
def instruction_changed old , ins
|
||||
check_active ins
|
||||
end
|
||||
|
||||
def render
|
||||
div :class => active do
|
||||
instruction.to_s if instruction
|
||||
end
|
||||
end
|
||||
end
|
16
app/main.rb
Normal file
16
app/main.rb
Normal file
@ -0,0 +1,16 @@
|
||||
require 'opal'
|
||||
require "opal/parser"
|
||||
|
||||
require "salama"
|
||||
require "interpreter"
|
||||
|
||||
require 'opal-react'
|
||||
|
||||
require "debugger"
|
||||
|
||||
require 'opal-jquery'
|
||||
|
||||
Document.ready? do # Document.ready? is a opal-jquery method.
|
||||
machine = Virtual.machine.boot
|
||||
React.render( React.create_element( Debugger , :machine => machine ), Element['#content'] )
|
||||
end
|
64
app/register_view.rb
Normal file
64
app/register_view.rb
Normal file
@ -0,0 +1,64 @@
|
||||
|
||||
class RegisterView
|
||||
|
||||
include React::Component
|
||||
required_param :interpreter
|
||||
required_param :register
|
||||
|
||||
define_state :objects_id
|
||||
define_state :fields => []
|
||||
|
||||
before_mount do
|
||||
interpreter.register_event(:register_changed, self)
|
||||
interpreter.register_event(:object_changed, self)
|
||||
register_changed( register , nil , interpreter.registers[register])
|
||||
end
|
||||
|
||||
def register_changed reg , old , value
|
||||
reg = reg.symbol unless reg.is_a? Symbol
|
||||
return unless reg == register
|
||||
objects_id! value
|
||||
calc_fields
|
||||
end
|
||||
|
||||
def object_changed reg
|
||||
reg = reg.symbol unless reg.is_a? Symbol
|
||||
return unless reg == register
|
||||
puts "Object changed in #{reg}"
|
||||
calc_fields
|
||||
end
|
||||
|
||||
def calc_fields
|
||||
#puts "My id #{objects_id} , #{objects_id.class}"
|
||||
object = Virtual.machine.objects[objects_id]
|
||||
if object and ! object.is_a?(String)
|
||||
has_fields = []
|
||||
clazz = object.class.name.split("::").last
|
||||
#puts "found #{clazz}"
|
||||
has_fields << clazz
|
||||
object.get_instance_variables.each do |variable|
|
||||
f = object.get_instance_variable(variable)
|
||||
has_fields << f
|
||||
end
|
||||
fields! has_fields
|
||||
end
|
||||
end
|
||||
|
||||
def render
|
||||
div.register_view do
|
||||
div do
|
||||
objects_id.to_s
|
||||
end
|
||||
fields.each do |attribute|
|
||||
div.col_md_12 do
|
||||
"#{marker(attribute)} - #{attribute.object_id}".span
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def marker var
|
||||
return "W" if var.is_a? String
|
||||
var.class.name.split("::").last[0]
|
||||
end
|
||||
end
|
38
app/source_view.rb
Normal file
38
app/source_view.rb
Normal file
@ -0,0 +1,38 @@
|
||||
class SourceView
|
||||
|
||||
include React::Component
|
||||
|
||||
required_param :interpreter
|
||||
|
||||
define_state :sources => []
|
||||
|
||||
before_mount do
|
||||
interpreter.register_event(:instruction_changed, self)
|
||||
instruction_changed nil , interpreter.instruction
|
||||
end
|
||||
|
||||
def instruction_changed old , ins
|
||||
text = ins ? source_text(ins.source) : "exit"
|
||||
return if sources.last == text
|
||||
sources << text
|
||||
sources.shift if sources.length > 5
|
||||
sources! sources
|
||||
end
|
||||
|
||||
def render
|
||||
div.source_view do
|
||||
h4 {"Virtual Machine Instruction"}
|
||||
sources.each do |s|
|
||||
s.br
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def source_text source
|
||||
if source.is_a? Virtual::Instruction
|
||||
return source.class.name
|
||||
else
|
||||
return "Method: #{source.name}"
|
||||
end
|
||||
end
|
||||
end
|
41
app/status_view.rb
Normal file
41
app/status_view.rb
Normal file
@ -0,0 +1,41 @@
|
||||
|
||||
class StatusView
|
||||
|
||||
include React::Component
|
||||
required_param :interpreter
|
||||
|
||||
define_state :state => "starting"
|
||||
define_state :stdout
|
||||
|
||||
before_mount do
|
||||
interpreter.register_event(:instruction_changed, self)
|
||||
end
|
||||
|
||||
def update_state
|
||||
state! interpreter.state
|
||||
stdout! interpreter.stdout
|
||||
end
|
||||
|
||||
def instruction_changed old , nex
|
||||
update_state
|
||||
end
|
||||
|
||||
def render
|
||||
div.status_view do
|
||||
div do
|
||||
button.bright { "next" }.on(:click) { interpreter.tick }
|
||||
" ".br
|
||||
end
|
||||
div do
|
||||
h4 {"Status:"}
|
||||
state.to_s.br
|
||||
end
|
||||
div do
|
||||
h4 {"Stdout:"}
|
||||
end
|
||||
div do
|
||||
interpreter.stdout.br
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
40
app/styles.scss
Normal file
40
app/styles.scss
Normal file
@ -0,0 +1,40 @@
|
||||
@import "susy";
|
||||
|
||||
$susy: (
|
||||
columns: 24 ,
|
||||
gutter-position: split ,
|
||||
);
|
||||
|
||||
.debugger-view { @include container(90%); }
|
||||
|
||||
.classes { @include span(3); }
|
||||
|
||||
.file-view {
|
||||
@include span(4);
|
||||
margin: span(1);
|
||||
}
|
||||
|
||||
.source-view { @include span(6); }
|
||||
|
||||
.block-view {
|
||||
@include span(4);
|
||||
margin-right: span(3);
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.status-view {
|
||||
@include span(2 at 22);
|
||||
}
|
||||
|
||||
.registers-view {
|
||||
@include span(20 at 3);
|
||||
}
|
||||
|
||||
.register-view {
|
||||
@include gallery(3);
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.bright {
|
||||
background-color: orange ;
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
// Place your apps css here
|
||||
@import "susy";
|
||||
|
||||
|
||||
$susy: (
|
||||
columns: 24 ,
|
||||
gutter-position: split ,
|
||||
);
|
||||
|
||||
.debugger_view { @include container(90%); }
|
||||
|
||||
.classes {
|
||||
@include span(3);
|
||||
}
|
||||
|
||||
.one_class {
|
||||
margin: 10px;
|
||||
}
|
||||
.file_view {
|
||||
@include span(4);
|
||||
margin-left: span(1);
|
||||
}
|
||||
|
||||
.source_view { @include span(6); }
|
||||
|
||||
.block_view {
|
||||
@include span(4);
|
||||
margin-right: span(2);
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.status_view {
|
||||
@include span(2 at 22);
|
||||
line-height : 1.25em;
|
||||
}
|
||||
|
||||
.registers_view {
|
||||
@include span(20 at 3);
|
||||
}
|
||||
|
||||
.register_view {
|
||||
@include gallery(4);
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.field {
|
||||
@include span(5)
|
||||
}
|
||||
|
||||
.value {
|
||||
@include span(18)
|
||||
}
|
||||
|
||||
.value_head {
|
||||
background-color: #C5FFD9;
|
||||
}
|
||||
|
||||
.act{
|
||||
background-color: #00B3FF;
|
||||
-moz-border-radius: 7px;
|
||||
-webkit-border-radius: 7px;
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
.bright {
|
||||
padding-right: 6px;
|
||||
padding-left: 6px;
|
||||
background-color: #00E3FF ;
|
||||
-moz-border-radius: 7px;
|
||||
-webkit-border-radius: 7px;
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
body {
|
||||
font-family: arial, helvetica, serif;
|
||||
}
|
||||
|
||||
#nav, #nav ul { /* all lists */
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
float : left;
|
||||
width : 10em;
|
||||
/*border around submenu goes here*/
|
||||
-moz-border-radius: 8px;
|
||||
-webkit-border-radius: 8px;
|
||||
background:#fff;
|
||||
border:1px solid #C3D46A
|
||||
}
|
||||
|
||||
#nav li { /* all list items */
|
||||
position : relative;
|
||||
float : left;
|
||||
line-height : 1.25em;
|
||||
width: 9em;
|
||||
}
|
||||
|
||||
#nav li ul { /* second-level lists */
|
||||
position : absolute;
|
||||
left: -999em;
|
||||
margin-left : 10.0em;
|
||||
margin-top : -2.7em;
|
||||
}
|
||||
|
||||
#nav li ul ul { /* third-and-above-level lists */
|
||||
left: -999em;
|
||||
}
|
||||
|
||||
#nav li a , li span {
|
||||
padding-left: 0.5em;
|
||||
width : 9.5em;
|
||||
display : block;
|
||||
color : black;
|
||||
font-weight : bold;
|
||||
text-decoration : none;
|
||||
background-color : white;
|
||||
-moz-border-radius: 7px;
|
||||
-webkit-border-radius: 7px;
|
||||
}
|
||||
|
||||
#nav li a:hover {
|
||||
color : white;
|
||||
background-color : #98CC1F;
|
||||
}
|
||||
|
||||
#nav li:hover ul ul, #nav li:hover ul ul ul , #nav li:hover ul ul ul ul , #nav li:hover ul ul ul ul ul{
|
||||
left: -999em;
|
||||
}
|
||||
|
||||
/* lists nested under hovered list items */
|
||||
#nav li:hover ul, #nav li li:hover ul, #nav li li li:hover ul, #nav li li li li:hover ul , #nav li li li li li:hover ul {
|
||||
left: auto;
|
||||
}
|
47
config.ru
47
config.ru
@ -1,31 +1,42 @@
|
||||
# config.ru
|
||||
require 'bundler'
|
||||
Bundler.require
|
||||
require 'tilt/erb'
|
||||
|
||||
require_relative "lib/parse_task"
|
||||
Opal.use_gem "salama"
|
||||
Opal.use_gem "salama-arm"
|
||||
|
||||
require "opal"
|
||||
require 'opal-browser'
|
||||
|
||||
Opal.use_gem("salama")
|
||||
Opal.use_gem("salama-arm")
|
||||
require "tilt/erb"
|
||||
require "susy"
|
||||
require "json"
|
||||
require "react/source"
|
||||
|
||||
class DebugServer < Opal::Server
|
||||
|
||||
def parse(num)
|
||||
string_input = '"Hello again".putstring()'
|
||||
parser = Parser::Salama.new
|
||||
out = parser.parse(string_input)
|
||||
parts = Parser::Transform.new.apply(out)
|
||||
parts.to_basic
|
||||
end
|
||||
|
||||
|
||||
def call(env)
|
||||
if( env["REQUEST_PATH"] == "/tasks.json")
|
||||
[200, { 'Content-Type' => 'text/json' }, [ParseTask.new.parse(1).to_json]]
|
||||
if env['PATH_INFO'].include? "/parse.json"
|
||||
parse_out = parse(1).to_s
|
||||
[200, { 'Content-Type' => 'text/json' }, [parse_out]]
|
||||
else
|
||||
super(env)
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
run DebugServer.new { |s|
|
||||
s.main = 'debugger'
|
||||
s.append_path 'lib'
|
||||
s.append_path 'assets'
|
||||
# s.source_map = true
|
||||
s.debug = !ENV["DEBUG"].nil?
|
||||
|
||||
end
|
||||
run DebugServer.new {|s|
|
||||
s.append_path 'app'
|
||||
s.append_path 'lib'
|
||||
s.append_path File.dirname(::React::Source.bundled_path_for("react-with-addons.js"))
|
||||
s.main = 'main'
|
||||
s.debug = false
|
||||
s.source_map = true
|
||||
s.index_path = "index.html.erb"
|
||||
s.sprockets.cache = Sprockets::Cache::MemoryStore.new(5000)
|
||||
}
|
||||
|
@ -1,11 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Debugger</title>
|
||||
<link rel="stylesheet" href="/assets/css/menu.css" type="text/css" charset="utf-8">
|
||||
<link rel="stylesheet" href="/assets/css/app.css" type="text/css" charset="utf-8">
|
||||
<title>Salama Debugger</title>
|
||||
<link rel="stylesheet" href="/assets/styles.css">
|
||||
<script src="https://code.jquery.com/jquery-2.1.3.min.js"></script>
|
||||
<script src="/assets/react-with-addons.js"></script>
|
||||
<%= javascript_include_tag 'main' %>
|
||||
</head>
|
||||
<body>
|
||||
<%= javascript_include_tag "debugger" %>
|
||||
<div id="content"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,14 +0,0 @@
|
||||
require_relative "element_view"
|
||||
|
||||
class ConstantView < ElementView
|
||||
|
||||
def initialize class_or_id , text = nil
|
||||
@class_or_id = class_or_id
|
||||
@text = text
|
||||
end
|
||||
|
||||
def draw
|
||||
@element = div(@class_or_id , @text)
|
||||
end
|
||||
|
||||
end
|
@ -1,81 +0,0 @@
|
||||
class ElementView
|
||||
|
||||
def initialize
|
||||
@element = nil
|
||||
end
|
||||
|
||||
#abstract function that should return the single element that is being represented
|
||||
# the element is also stored in @element
|
||||
def draw
|
||||
raise "implement me to return an Element"
|
||||
end
|
||||
|
||||
# helper function to create an element with possible classes, id and text
|
||||
# The first argument is a bit haml inspired, so "tagname.classname" is the format
|
||||
# but if tagname is ommited it will default to div
|
||||
# also several classnames may be given
|
||||
# if one of the names ends in a ! (bang) it will be assigned as the id
|
||||
# second argument is optional, but if given will be added as text (content) to the newly
|
||||
# created Element
|
||||
# return the new Element, which is not linked into the dom at that point (see << and add*)
|
||||
def div name_class = "div" , text = nil
|
||||
name , clazz = name_class.split(".")
|
||||
name = "div" if name.empty?
|
||||
element = $document.create_element(name)
|
||||
element.text = text if text
|
||||
return element unless clazz
|
||||
if( clazz.is_a? Array )
|
||||
clazz.each { |c| add_class_or_id( element , cl )}
|
||||
else
|
||||
add_class_or_id element , clazz
|
||||
end
|
||||
element
|
||||
end
|
||||
|
||||
def add_class_or_id element , class_or_id
|
||||
return element unless class_or_id
|
||||
if class_or_id[-1] == "!"
|
||||
element.id = class_or_id[0 ... -1]
|
||||
else
|
||||
element.add_class class_or_id
|
||||
end
|
||||
element
|
||||
end
|
||||
|
||||
# wrap the @element variable with the given element
|
||||
# so if wrapper == <h4/> the new @element will be <h4> old @element </h4>
|
||||
# return the new @element, which is wrapper
|
||||
def wrap_element wrapper
|
||||
@element = wrap_node_with @element , wrapper
|
||||
end
|
||||
|
||||
#wrap the given node with the wappper, so for a div wrapper and a button node
|
||||
# the result will be <div> <button>hwatever was in there</button> <div>
|
||||
def wrap_node_with node , wrapper
|
||||
node.replace_with(wrapper) if node.parent
|
||||
wrapper << node
|
||||
end
|
||||
|
||||
# add the given element to the @element, at the end
|
||||
# return the div that was passed in (use << to return the @element)
|
||||
def append_element div
|
||||
@element << div
|
||||
div
|
||||
end
|
||||
|
||||
# add the given element to the @element , at the front
|
||||
# return the div that was passed in (use >> to return the @element)
|
||||
def prepend_element div
|
||||
@element >> div
|
||||
div
|
||||
end
|
||||
|
||||
# create a new element with class and possibly text
|
||||
# add that new element to the @element
|
||||
# return the newly created element
|
||||
def add class_or_id , tex = nil
|
||||
append_element div( class_or_id , tex)
|
||||
end
|
||||
|
||||
|
||||
end
|
@ -1,56 +0,0 @@
|
||||
require_relative "element_view"
|
||||
|
||||
class ListView < ElementView
|
||||
|
||||
def initialize children
|
||||
@children = children
|
||||
@elements = []
|
||||
end
|
||||
|
||||
# create a root node acording to the tag given (default div)
|
||||
# The tag name will be passed to the div function, so class and id may be set as well (see there)
|
||||
# draw all children and keep the elements as @elements
|
||||
# return (as per base class) the single root of the collection
|
||||
def draw root = "div"
|
||||
@element = div(root)
|
||||
@elements = @children.collect do | c |
|
||||
append_element c.draw
|
||||
end
|
||||
@element
|
||||
end
|
||||
|
||||
# replace the child at index with the given one (second arg)
|
||||
# The child must be an ElementView , which will be rendered and
|
||||
# the old node will be replaces in the live dom
|
||||
def replace_at index , with
|
||||
old = @elements[index]
|
||||
@children[index] = with
|
||||
rendered = with.draw
|
||||
@elements[index] = rendered
|
||||
old.replace_with rendered
|
||||
end
|
||||
|
||||
# remove the first child and element (from view)
|
||||
def remove_first
|
||||
remove_at 0
|
||||
end
|
||||
|
||||
# remove both child and element at given position
|
||||
def remove_at index
|
||||
raise "insex out of bounds #{index} => #{@children.length}" if(index >= @children.length or index < 0)
|
||||
@children.delete_at( index )
|
||||
element = @elements.delete_at(index)
|
||||
element.remove
|
||||
end
|
||||
|
||||
# append a View instnace to the children array
|
||||
# render it and append it to the html element
|
||||
# and keep a copy in @elements
|
||||
def append_view view
|
||||
@children << view
|
||||
rendered = view.draw
|
||||
@elements << rendered # add to internal array
|
||||
@element << rendered # add to html children
|
||||
rendered
|
||||
end
|
||||
end
|
@ -1,58 +0,0 @@
|
||||
|
||||
class BlocksView < ListView
|
||||
|
||||
def initialize interpreter
|
||||
@interpreter = interpreter
|
||||
@interpreter.register_event(:instruction_changed, self)
|
||||
super([BlockView.new(@interpreter.block)])
|
||||
@method_name = method_name
|
||||
end
|
||||
|
||||
def draw
|
||||
super()
|
||||
wrap_element div("div.block_view") << div("h4" , "Method + Block " ) << div("h4.method" , @method_name)
|
||||
return @element
|
||||
end
|
||||
|
||||
def instruction_changed
|
||||
new_name = method_name
|
||||
unless new_name == @method_name
|
||||
@method_name = new_name
|
||||
@element.at_css(".method").text = method_name
|
||||
end
|
||||
return if @interpreter.block.object_id == @children.last.block.object_id
|
||||
@elements.last.at_css(".bright").remove_class("bright")
|
||||
append_view( BlockView.new(@interpreter.block) )
|
||||
remove_first if( @elements.length > 6)
|
||||
end
|
||||
|
||||
def method_name
|
||||
bl = @interpreter.block
|
||||
return "" unless bl
|
||||
return bl.method if bl.method.is_a? String
|
||||
"#{bl.method.for_class.name}.#{bl.method.name}"
|
||||
end
|
||||
end
|
||||
|
||||
class BlockView < ElementView
|
||||
|
||||
def initialize block
|
||||
@block = block
|
||||
end
|
||||
attr_reader :block
|
||||
|
||||
def draw
|
||||
@element = div("div") << div("span.bright" , block_name )
|
||||
end
|
||||
|
||||
def method_name
|
||||
return @block.method if @block.method.is_a? String
|
||||
@block.method.name
|
||||
end
|
||||
|
||||
def block_name
|
||||
return @block if @block.is_a? String
|
||||
"#{method_name}.#{@block.name}"
|
||||
end
|
||||
|
||||
end
|
@ -1,37 +0,0 @@
|
||||
|
||||
class ClassesView < ListView
|
||||
|
||||
def initialize interpreter
|
||||
@interpreter = interpreter
|
||||
classes = []
|
||||
Virtual.machine.space.classes.each do |name , claz|
|
||||
next if [:Kernel,:Module,:MetaClass,:BinaryCode].index name
|
||||
classes << claz
|
||||
end
|
||||
classes.sort! {|a,b| a.name <=> b.name }
|
||||
super( classes.collect{|c| ClassView.new(c)})
|
||||
end
|
||||
|
||||
def draw
|
||||
super()
|
||||
wrap_element div("ul.nav!")
|
||||
wrap_element( div(".classes") << div("h4" , "Classes") )
|
||||
return @element
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class ClassView < ElementView
|
||||
def initialize clazz
|
||||
@clazz = clazz
|
||||
end
|
||||
|
||||
def draw
|
||||
@element = div("li") << div( "a" , @clazz.name ) << (ul = div("ul"))
|
||||
@clazz.object_layout.object_instance_names.each do |name|
|
||||
ul << (div("li") << div("a", name ))
|
||||
end
|
||||
@element.style["z-index"] = 20
|
||||
@element
|
||||
end
|
||||
end
|
@ -1,47 +0,0 @@
|
||||
|
||||
require "opal"
|
||||
require "opal-parser"
|
||||
|
||||
|
||||
require 'browser'
|
||||
require 'native'
|
||||
require "salama"
|
||||
require "interpreter/interpreter"
|
||||
require "base/list_view"
|
||||
require_relative "classes_view"
|
||||
require_relative "status_view"
|
||||
require_relative "file_view"
|
||||
require_relative "blocks_view"
|
||||
require_relative "instruction_view"
|
||||
require_relative "registers_view"
|
||||
|
||||
class MainView < ListView
|
||||
|
||||
def initialize
|
||||
machine = Virtual.machine.boot
|
||||
|
||||
# compile_main includes the parse
|
||||
# parsing generates an ast as seen below and then compiles it.
|
||||
# machine.compile_main "2 + 5"
|
||||
|
||||
# so the code above is functionally equivalent to the one below, minus the parse
|
||||
# When the ast expression is given all works, so pretty sure it is the parse that fails
|
||||
|
||||
code = Ast::OperatorExpression.new("+", Ast::IntegerExpression.new(2),Ast::IntegerExpression.new(5))
|
||||
Virtual::Compiler.compile( code , machine.space.get_main )
|
||||
|
||||
machine.run_before "Register::CallImplementation"
|
||||
@interpreter = Interpreter::Interpreter.new
|
||||
@interpreter.start machine.init
|
||||
super( [ClassesView.new(@interpreter) ,
|
||||
FileView.new ,
|
||||
BlocksView.new(@interpreter) ,
|
||||
InstructionView.new(@interpreter) ,
|
||||
StatusView.new(@interpreter) ,
|
||||
RegistersView.new(@interpreter) ] )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
view = MainView.new()
|
||||
view.draw.append_to($document.body)
|
37
lib/eventable.rb
Normal file
37
lib/eventable.rb
Normal file
@ -0,0 +1,37 @@
|
||||
# A simple event registering/triggering module to mix into classes.
|
||||
# Events are stored in the `@events` ivar.
|
||||
module Eventable
|
||||
|
||||
# Register a handler for the given event name.
|
||||
# The event name is the method name called on the handler object
|
||||
#
|
||||
# obj.on(:foo , some_object_that_implements foo( whateverargs)
|
||||
#
|
||||
# @param [String, Symbol] name event name
|
||||
# @param [Object] object handling the event, ie implement the function name
|
||||
# @return handler
|
||||
def register_event(name, handler)
|
||||
event_table[name] << handler
|
||||
handler
|
||||
end
|
||||
|
||||
def unregister_event(name, handler)
|
||||
event_table[name].delete handler
|
||||
end
|
||||
|
||||
def event_table
|
||||
return @event_table if @event_table
|
||||
@event_table = Hash.new { |hash, key| hash[key] = [] }
|
||||
end
|
||||
|
||||
# Trigger the given event name and passes all args to each handler
|
||||
# for this event.
|
||||
#
|
||||
# obj.trigger(:foo)
|
||||
# obj.trigger(:foo, 1, 2, 3)
|
||||
#
|
||||
# @param [String, Symbol] name event name to trigger
|
||||
def trigger(name, *args)
|
||||
event_table[name].each { |handler| handler.send( name.to_sym , *args) }
|
||||
end
|
||||
end
|
@ -1,8 +0,0 @@
|
||||
class FileView < ElementView
|
||||
|
||||
|
||||
def draw
|
||||
@element = div(".file_view") << div("h4" ,"Future")
|
||||
end
|
||||
|
||||
end
|
@ -1,30 +0,0 @@
|
||||
require "base/constant_view"
|
||||
require "base/list_view"
|
||||
|
||||
class InstructionView < ListView
|
||||
|
||||
def initialize interpreter
|
||||
@interpreter = interpreter
|
||||
super([ConstantView.new( "span.bright" , "starting" )])
|
||||
@interpreter.register_event(:instruction_changed, self)
|
||||
end
|
||||
|
||||
def instruction_changed
|
||||
@element.at_css(".bright").remove_class("bright")
|
||||
instruction = append_view( ConstantView.new( "span.bright" , instruction_text ) )
|
||||
wrap_node_with instruction , div
|
||||
remove_first if( @elements.length > 6)
|
||||
end
|
||||
|
||||
def draw
|
||||
super()
|
||||
wrap_node_with @elements.first , div
|
||||
wrap_element div(".source_view") << div("h4" ,"Virtual Machine Instruction")
|
||||
@element
|
||||
end
|
||||
|
||||
def instruction_text
|
||||
return "" unless @interpreter.instruction
|
||||
@interpreter.instruction.to_s
|
||||
end
|
||||
end
|
166
lib/interpreter.rb
Normal file
166
lib/interpreter.rb
Normal file
@ -0,0 +1,166 @@
|
||||
|
||||
require "eventable"
|
||||
|
||||
class Interpreter
|
||||
# fire events for changed pc and register contents
|
||||
include Eventable
|
||||
|
||||
# current instruction or pc
|
||||
attr_reader :instruction
|
||||
|
||||
# an (arm style) link register. store the return address to return to
|
||||
attr_reader :link
|
||||
|
||||
# current executing block. since this is not a hardware simulator this is luxury
|
||||
attr_reader :block
|
||||
|
||||
# the registers, 12
|
||||
attr_reader :registers
|
||||
|
||||
# collect the output
|
||||
attr_reader :stdout
|
||||
|
||||
attr_reader :state
|
||||
|
||||
def initialize
|
||||
@state = "runnnig"
|
||||
@stdout = ""
|
||||
@registers = {}
|
||||
(0...16).each do |r|
|
||||
set_register "r#{r}".to_sym , "r#{r}:unknown"
|
||||
end
|
||||
end
|
||||
|
||||
def start bl
|
||||
set_block bl
|
||||
end
|
||||
|
||||
def set_block bl
|
||||
return if @block == bl
|
||||
raise "Error, nil block" unless bl
|
||||
old = @block
|
||||
@block = bl
|
||||
trigger(:block_changed , old , bl)
|
||||
set_instruction bl.codes.first
|
||||
end
|
||||
|
||||
def set_instruction i
|
||||
@state = "exited" unless i
|
||||
return if @instruction == i
|
||||
old = @instruction
|
||||
@instruction = i
|
||||
trigger(:instruction_changed, old , i)
|
||||
end
|
||||
|
||||
def get_register( reg )
|
||||
reg = reg.symbol if reg.is_a? Register::RegisterReference
|
||||
raise "Not a register #{reg}" unless Register::RegisterReference.look_like_reg(reg)
|
||||
@registers[reg]
|
||||
end
|
||||
|
||||
def set_register reg , val
|
||||
old = get_register( reg ) # also ensures format
|
||||
return if old === val
|
||||
reg = reg.symbol if reg.is_a? Register::RegisterReference
|
||||
@registers[reg] = val
|
||||
trigger(:register_changed, reg , old , val)
|
||||
end
|
||||
|
||||
def tick
|
||||
return unless @instruction
|
||||
name = @instruction.class.name.split("::").last
|
||||
fetch = send "execute_#{name}"
|
||||
return unless fetch
|
||||
fetch_next_intruction
|
||||
end
|
||||
|
||||
def fetch_next_intruction
|
||||
if(@instruction != @block.codes.last)
|
||||
set_instruction @block.codes[ @block.codes.index(@instruction) + 1]
|
||||
else
|
||||
next_b = @block.method.source.blocks.index(@block) + 1
|
||||
set_block @block.method.source.blocks[next_b]
|
||||
end
|
||||
end
|
||||
|
||||
def object_for reg
|
||||
id = get_register(reg)
|
||||
Virtual.machine.objects[id]
|
||||
end
|
||||
|
||||
# Instruction interpretation starts here
|
||||
def execute_Branch
|
||||
target = @instruction.block
|
||||
set_block target
|
||||
false
|
||||
end
|
||||
|
||||
def execute_LoadConstant
|
||||
to = @instruction.register
|
||||
value = @instruction.constant.object_id
|
||||
set_register( to , value )
|
||||
true
|
||||
end
|
||||
|
||||
def execute_GetSlot
|
||||
object = object_for( @instruction.array )
|
||||
value = object.internal_object_get( @instruction.index )
|
||||
value = value.object_id unless value.is_a? Integer
|
||||
set_register( @instruction.register , value )
|
||||
true
|
||||
end
|
||||
|
||||
def execute_SetSlot
|
||||
value = object_for( @instruction.register )
|
||||
object = object_for( @instruction.array )
|
||||
object.internal_object_set( @instruction.index , value )
|
||||
trigger(:object_changed, @instruction.register )
|
||||
true
|
||||
end
|
||||
|
||||
def execute_RegisterTransfer
|
||||
value = get_register @instruction.from
|
||||
set_register @instruction.to , value
|
||||
true
|
||||
end
|
||||
|
||||
def execute_FunctionCall
|
||||
@link = [@block , @instruction]
|
||||
next_block = @instruction.method.source.blocks.first
|
||||
set_block next_block
|
||||
false
|
||||
end
|
||||
|
||||
def execute_SaveReturn
|
||||
object = object_for @instruction.register
|
||||
raise "save return has nothing to save" unless @link
|
||||
trigger(:object_changed, @instruction.register )
|
||||
object.internal_object_set @instruction.index , @link
|
||||
true
|
||||
end
|
||||
|
||||
def execute_Syscall
|
||||
name = @instruction.name
|
||||
case name
|
||||
when :putstring
|
||||
str = object_for( :r1 ) # should test length, ie r2
|
||||
raise "NO string for putstring #{str}" unless str.is_a? Symbol
|
||||
@stdout += str.to_s
|
||||
when :exit
|
||||
set_instruction(nil)
|
||||
return false
|
||||
else
|
||||
raise "un-implemented syscall #{name}"
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
def execute_FunctionReturn
|
||||
object = object_for( @instruction.register )
|
||||
#wouldn't need to assign to link, but makes tsting easier
|
||||
@link = object.internal_object_get( @instruction.index )
|
||||
@block , @instruction = @link
|
||||
# we jump back to the call instruction. so it is as if the call never happened and we continue
|
||||
true
|
||||
end
|
||||
end
|
@ -1,54 +0,0 @@
|
||||
require_relative "ref_view"
|
||||
|
||||
class ObjectView < ListView
|
||||
|
||||
def initialize object_id , interpreter = nil , z = nil
|
||||
@object_id = object_id
|
||||
@z = z
|
||||
@interpreter = interpreter
|
||||
@interpreter.register_event(:object_changed, self) if interpreter
|
||||
super( content_elements )
|
||||
end
|
||||
|
||||
def draw
|
||||
@element = super(@interpreter ? "ul.nav!" : "ul")
|
||||
prepend_element div("li" , "-------------------------")
|
||||
prepend_element div( "li" ) << div("span" , class_header(@object_id) )
|
||||
return @element
|
||||
end
|
||||
|
||||
def object_changed reg , at
|
||||
puts "Object changed in #{reg}"
|
||||
for_object = @interpreter.get_register( reg )
|
||||
return unless for_object == @object_id
|
||||
puts "Object changed #{for_object} , at #{at}"
|
||||
|
||||
end
|
||||
|
||||
def class_header(id)
|
||||
object = Virtual.machine.objects[id]
|
||||
clazz = object.class.name.split("::").last
|
||||
[clazz, id].join " : "
|
||||
end
|
||||
|
||||
def content_elements
|
||||
object = Virtual.machine.objects[@object_id]
|
||||
fields = []
|
||||
if object and ! object.is_a?(String)
|
||||
fields << RefView.new( "layout" , object.get_layout.object_id , @z )
|
||||
object.get_instance_variables.each do |variable|
|
||||
f = object.get_instance_variable(variable)
|
||||
fields << RefView.new( variable , f.object_id , @z )
|
||||
end
|
||||
if( object.is_a?(Parfait::List) )
|
||||
index = 1
|
||||
object.each do | o , i|
|
||||
fields << RefView.new( index.to_s , o.object_id , @z )
|
||||
index += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
fields
|
||||
end
|
||||
|
||||
end
|
@ -1,11 +0,0 @@
|
||||
require "salama-reader"
|
||||
|
||||
class ParseTask
|
||||
def parse(num)
|
||||
string_input = '"Hello again".putstring()'
|
||||
parser = Parser::Salama.new
|
||||
out = parser.parse(string_input)
|
||||
parts = Parser::Transform.new.apply(out)
|
||||
parts.to_basic
|
||||
end
|
||||
end
|
@ -1,59 +0,0 @@
|
||||
class RefView < ListView
|
||||
|
||||
def initialize name , value , z = nil
|
||||
@name = name
|
||||
@value = value
|
||||
@z = z
|
||||
super []
|
||||
end
|
||||
|
||||
attr_reader :value
|
||||
|
||||
def value= val
|
||||
@value = val
|
||||
add_hover
|
||||
end
|
||||
|
||||
def draw
|
||||
@element = div("li") << div("a" , "#{@name} : #{marker(@value)}" )
|
||||
add_hover
|
||||
@element.style["z-index"] = @z if @z
|
||||
@element
|
||||
end
|
||||
|
||||
def add_hover
|
||||
return if is_string?
|
||||
@element.on("hover"){ hover } if is_object?(@value)
|
||||
end
|
||||
|
||||
def is_object?( )
|
||||
Virtual.machine.objects[@value] != nil
|
||||
end
|
||||
|
||||
def is_string?()
|
||||
Virtual.machine.objects[@value].is_a? String
|
||||
end
|
||||
|
||||
def is_nil?()
|
||||
Virtual.machine.objects[@value].nil?
|
||||
end
|
||||
|
||||
def hover
|
||||
puts "hovering #{@name}"
|
||||
append_view ObjectView.new(@value)
|
||||
@element.off("hover")
|
||||
end
|
||||
|
||||
def marker id
|
||||
if is_string?
|
||||
str = @value
|
||||
elsif is_nil?
|
||||
str = "nil"
|
||||
else
|
||||
var = Virtual.machine.objects[id]
|
||||
str = var.class.name.split("::").last[0,2]
|
||||
str + " : #{id.to_s}"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
@ -1,51 +0,0 @@
|
||||
require_relative "object_view"
|
||||
|
||||
class RegistersView < ListView
|
||||
|
||||
def initialize interpreter
|
||||
@interpreter = interpreter
|
||||
@interpreter.register_event(:register_changed, self)
|
||||
kids = []
|
||||
@interpreter.registers.each do |reg , val|
|
||||
kids << ValueView.new( val )
|
||||
end
|
||||
super(kids)
|
||||
end
|
||||
|
||||
def draw
|
||||
super( "div.registers_view" )
|
||||
@element.children.each_with_index do |reg, index|
|
||||
elem = div("div.register_view")
|
||||
wrap_node_with reg , elem
|
||||
end
|
||||
@element
|
||||
end
|
||||
|
||||
def register_changed reg , old , value
|
||||
reg = reg.symbol unless reg.is_a? Symbol
|
||||
index = reg.to_s[1 .. -1 ].to_i
|
||||
if( is_object? value )
|
||||
swap = ObjectView.new( value , @interpreter , 16 - index )
|
||||
else
|
||||
swap = ValueView.new value
|
||||
end
|
||||
replace_at index , swap
|
||||
# @elements[index].style["z-index"] = -index
|
||||
end
|
||||
|
||||
def is_object?( id )
|
||||
Virtual.machine.objects[id] != nil
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class ValueView < ElementView
|
||||
|
||||
def initialize value
|
||||
@value = value
|
||||
end
|
||||
|
||||
def draw
|
||||
@element = div("ul.nav!") << div("li") << div("span", @value)
|
||||
end
|
||||
end
|
@ -1,42 +0,0 @@
|
||||
class StatusView < ElementView
|
||||
|
||||
def initialize interpreter
|
||||
@interpreter = interpreter
|
||||
end
|
||||
|
||||
def draw
|
||||
@element = div(".status_view") <<
|
||||
div("h4" , "Interpreter" ) <<
|
||||
div("button.act" , "Next") <<
|
||||
div( "br") <<
|
||||
div("span.clock" , clock_text) <<
|
||||
div( "br") <<
|
||||
div("span.state" , state_text) <<
|
||||
div( "br") <<
|
||||
div( "span.link" , link_text) <<
|
||||
div( "br" , "Stdout") <<
|
||||
div("span.stdout")
|
||||
# set up event handler
|
||||
@element.at_css(".act").on("click") { self.update }
|
||||
return @element
|
||||
end
|
||||
|
||||
def update
|
||||
@interpreter.tick
|
||||
@element.at_css(".clock").text = clock_text
|
||||
@element.at_css(".link").text = link_text
|
||||
@element.at_css(".stdout").text = @interpreter.stdout
|
||||
end
|
||||
|
||||
def link_text
|
||||
"Link #{@interpreter.link}"
|
||||
end
|
||||
|
||||
def state_text
|
||||
"State #{@interpreter.state}"
|
||||
end
|
||||
|
||||
def clock_text
|
||||
"Instruction #{@interpreter.clock}"
|
||||
end
|
||||
end
|
@ -1,5 +0,0 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe 'sample http controller test', type: :http_controller do
|
||||
# Specs here
|
||||
end
|
@ -1,11 +0,0 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe 'sample integration test', type: :feature do
|
||||
# An example integration spec, this will only be run if ENV['BROWSER'] is
|
||||
# specified. Current values for ENV['BROWSER'] are 'firefox' and 'phantom'
|
||||
it 'should load the page' do
|
||||
visit '/'
|
||||
|
||||
expect(page).to have_content('Home')
|
||||
end
|
||||
end
|
@ -1,5 +0,0 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe 'sample model' do
|
||||
# Specs here
|
||||
end
|
@ -1,5 +0,0 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe 'sample task', type: :task do
|
||||
# Specs here
|
||||
end
|
@ -1,14 +0,0 @@
|
||||
# Volt sets up rspec and capybara for testing.
|
||||
require 'volt/spec/setup'
|
||||
Volt.spec_setup
|
||||
|
||||
RSpec.configure do |config|
|
||||
config.run_all_when_everything_filtered = true
|
||||
config.filter_run :focus
|
||||
|
||||
# Run specs in random order to surface order dependencies. If you find an
|
||||
# order dependency and want to debug it, you can fix the order by providing
|
||||
# the seed, which is printed after each run.
|
||||
# --seed 1234
|
||||
config.order = 'random'
|
||||
end
|
BIN
static/debugger.png
Normal file
BIN
static/debugger.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 198 KiB |
307
static/hint.css
Normal file
307
static/hint.css
Normal file
@ -0,0 +1,307 @@
|
||||
/*! Hint.css - v1.3.5 - 2015-06-16
|
||||
* http://kushagragour.in/lab/hint/
|
||||
* Copyright (c) 2015 Kushagra Gour; Licensed MIT */
|
||||
|
||||
/*-------------------------------------*\
|
||||
HINT.css - A CSS tooltip library
|
||||
\*-------------------------------------*/
|
||||
/**
|
||||
* HINT.css is a tooltip library made in pure CSS.
|
||||
*
|
||||
* Source: https://github.com/chinchang/hint.css
|
||||
* Demo: http://kushagragour.in/lab/hint/
|
||||
*
|
||||
* Release under The MIT License
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* source: hint-core.scss
|
||||
*
|
||||
* Defines the basic styling for the tooltip.
|
||||
* Each tooltip is made of 2 parts:
|
||||
* 1) body (:after)
|
||||
* 2) arrow (:before)
|
||||
*
|
||||
* Classes added:
|
||||
* 1) hint
|
||||
*/
|
||||
.hint, [data-hint] {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
/**
|
||||
* tooltip arrow
|
||||
*/
|
||||
/**
|
||||
* tooltip body
|
||||
*/ }
|
||||
.hint:before, .hint:after, [data-hint]:before, [data-hint]:after {
|
||||
position: absolute;
|
||||
-webkit-transform: translate3d(0, 0, 0);
|
||||
-moz-transform: translate3d(0, 0, 0);
|
||||
transform: translate3d(0, 0, 0);
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
z-index: 1000000;
|
||||
pointer-events: none;
|
||||
-webkit-transition: 0.3s ease;
|
||||
-moz-transition: 0.3s ease;
|
||||
transition: 0.3s ease;
|
||||
-webkit-transition-delay: 0ms;
|
||||
-moz-transition-delay: 0ms;
|
||||
transition-delay: 0ms; }
|
||||
.hint:hover:before, .hint:hover:after, .hint:focus:before, .hint:focus:after, [data-hint]:hover:before, [data-hint]:hover:after, [data-hint]:focus:before, [data-hint]:focus:after {
|
||||
visibility: visible;
|
||||
opacity: 1; }
|
||||
.hint:hover:before, .hint:hover:after, [data-hint]:hover:before, [data-hint]:hover:after {
|
||||
-webkit-transition-delay: 100ms;
|
||||
-moz-transition-delay: 100ms;
|
||||
transition-delay: 100ms; }
|
||||
.hint:before, [data-hint]:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
background: transparent;
|
||||
border: 6px solid transparent;
|
||||
z-index: 1000001; }
|
||||
.hint:after, [data-hint]:after {
|
||||
content: attr(data-hint);
|
||||
background: #383838;
|
||||
color: white;
|
||||
padding: 8px 10px;
|
||||
font-size: 12px;
|
||||
line-height: 12px;
|
||||
white-space: nowrap; }
|
||||
|
||||
/**
|
||||
* source: hint-position.scss
|
||||
*
|
||||
* Defines the positoning logic for the tooltips.
|
||||
*
|
||||
* Classes added:
|
||||
* 1) hint--top
|
||||
* 2) hint--bottom
|
||||
* 3) hint--left
|
||||
* 4) hint--right
|
||||
*/
|
||||
/**
|
||||
* set default color for tooltip arrows
|
||||
*/
|
||||
.hint--top:before {
|
||||
border-top-color: #383838; }
|
||||
|
||||
.hint--bottom:before {
|
||||
border-bottom-color: #383838; }
|
||||
|
||||
.hint--left:before {
|
||||
border-left-color: #383838; }
|
||||
|
||||
.hint--right:before {
|
||||
border-right-color: #383838; }
|
||||
|
||||
/**
|
||||
* top tooltip
|
||||
*/
|
||||
.hint--top:before {
|
||||
margin-bottom: -12px; }
|
||||
.hint--top:after {
|
||||
margin-left: -18px; }
|
||||
.hint--top:before, .hint--top:after {
|
||||
bottom: 100%;
|
||||
left: 50%; }
|
||||
.hint--top:hover:after, .hint--top:hover:before, .hint--top:focus:after, .hint--top:focus:before {
|
||||
-webkit-transform: translateY(-8px);
|
||||
-moz-transform: translateY(-8px);
|
||||
transform: translateY(-8px); }
|
||||
|
||||
/**
|
||||
* bottom tooltip
|
||||
*/
|
||||
.hint--bottom:before {
|
||||
margin-top: -12px; }
|
||||
.hint--bottom:after {
|
||||
margin-left: -18px; }
|
||||
.hint--bottom:before, .hint--bottom:after {
|
||||
top: 100%;
|
||||
left: 50%; }
|
||||
.hint--bottom:hover:after, .hint--bottom:hover:before, .hint--bottom:focus:after, .hint--bottom:focus:before {
|
||||
-webkit-transform: translateY(8px);
|
||||
-moz-transform: translateY(8px);
|
||||
transform: translateY(8px); }
|
||||
|
||||
/**
|
||||
* right tooltip
|
||||
*/
|
||||
.hint--right:before {
|
||||
margin-left: -12px;
|
||||
margin-bottom: -6px; }
|
||||
.hint--right:after {
|
||||
margin-bottom: -14px; }
|
||||
.hint--right:before, .hint--right:after {
|
||||
left: 100%;
|
||||
bottom: 50%; }
|
||||
.hint--right:hover:after, .hint--right:hover:before, .hint--right:focus:after, .hint--right:focus:before {
|
||||
-webkit-transform: translateX(8px);
|
||||
-moz-transform: translateX(8px);
|
||||
transform: translateX(8px); }
|
||||
|
||||
/**
|
||||
* left tooltip
|
||||
*/
|
||||
.hint--left:before {
|
||||
margin-right: -12px;
|
||||
margin-bottom: -6px; }
|
||||
.hint--left:after {
|
||||
margin-bottom: -14px; }
|
||||
.hint--left:before, .hint--left:after {
|
||||
right: 100%;
|
||||
bottom: 50%; }
|
||||
.hint--left:hover:after, .hint--left:hover:before, .hint--left:focus:after, .hint--left:focus:before {
|
||||
-webkit-transform: translateX(-8px);
|
||||
-moz-transform: translateX(-8px);
|
||||
transform: translateX(-8px); }
|
||||
|
||||
/**
|
||||
* source: hint-theme.scss
|
||||
*
|
||||
* Defines basic theme for tooltips.
|
||||
*
|
||||
*/
|
||||
.hint, [data-hint] {
|
||||
/**
|
||||
* tooltip body
|
||||
*/ }
|
||||
.hint:after, [data-hint]:after {
|
||||
text-shadow: 0 -1px 0px black;
|
||||
box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.3); }
|
||||
|
||||
/**
|
||||
* source: hint-color-types.scss
|
||||
*
|
||||
* Contains tooltips of various types based on color differences.
|
||||
*
|
||||
* Classes added:
|
||||
* 1) hint--error
|
||||
* 2) hint--warning
|
||||
* 3) hint--info
|
||||
* 4) hint--success
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* Error
|
||||
*/
|
||||
.hint--error:after {
|
||||
background-color: #b34e4d;
|
||||
text-shadow: 0 -1px 0px #592726; }
|
||||
.hint--error.hint--top:before {
|
||||
border-top-color: #b34e4d; }
|
||||
.hint--error.hint--bottom:before {
|
||||
border-bottom-color: #b34e4d; }
|
||||
.hint--error.hint--left:before {
|
||||
border-left-color: #b34e4d; }
|
||||
.hint--error.hint--right:before {
|
||||
border-right-color: #b34e4d; }
|
||||
|
||||
/**
|
||||
* Warning
|
||||
*/
|
||||
.hint--warning:after {
|
||||
background-color: #c09854;
|
||||
text-shadow: 0 -1px 0px #6c5328; }
|
||||
.hint--warning.hint--top:before {
|
||||
border-top-color: #c09854; }
|
||||
.hint--warning.hint--bottom:before {
|
||||
border-bottom-color: #c09854; }
|
||||
.hint--warning.hint--left:before {
|
||||
border-left-color: #c09854; }
|
||||
.hint--warning.hint--right:before {
|
||||
border-right-color: #c09854; }
|
||||
|
||||
/**
|
||||
* Info
|
||||
*/
|
||||
.hint--info:after {
|
||||
background-color: #3986ac;
|
||||
text-shadow: 0 -1px 0px #193b4d; }
|
||||
.hint--info.hint--top:before {
|
||||
border-top-color: #3986ac; }
|
||||
.hint--info.hint--bottom:before {
|
||||
border-bottom-color: #3986ac; }
|
||||
.hint--info.hint--left:before {
|
||||
border-left-color: #3986ac; }
|
||||
.hint--info.hint--right:before {
|
||||
border-right-color: #3986ac; }
|
||||
|
||||
/**
|
||||
* Success
|
||||
*/
|
||||
.hint--success:after {
|
||||
background-color: #458746;
|
||||
text-shadow: 0 -1px 0px #1a321a; }
|
||||
.hint--success.hint--top:before {
|
||||
border-top-color: #458746; }
|
||||
.hint--success.hint--bottom:before {
|
||||
border-bottom-color: #458746; }
|
||||
.hint--success.hint--left:before {
|
||||
border-left-color: #458746; }
|
||||
.hint--success.hint--right:before {
|
||||
border-right-color: #458746; }
|
||||
|
||||
/**
|
||||
* source: hint-always.scss
|
||||
*
|
||||
* Defines a persisted tooltip which shows always.
|
||||
*
|
||||
* Classes added:
|
||||
* 1) hint--always
|
||||
*
|
||||
*/
|
||||
.hint--always:after, .hint--always:before {
|
||||
opacity: 1;
|
||||
visibility: visible; }
|
||||
.hint--always.hint--top:after, .hint--always.hint--top:before {
|
||||
-webkit-transform: translateY(-8px);
|
||||
-moz-transform: translateY(-8px);
|
||||
transform: translateY(-8px); }
|
||||
.hint--always.hint--bottom:after, .hint--always.hint--bottom:before {
|
||||
-webkit-transform: translateY(8px);
|
||||
-moz-transform: translateY(8px);
|
||||
transform: translateY(8px); }
|
||||
.hint--always.hint--left:after, .hint--always.hint--left:before {
|
||||
-webkit-transform: translateX(-8px);
|
||||
-moz-transform: translateX(-8px);
|
||||
transform: translateX(-8px); }
|
||||
.hint--always.hint--right:after, .hint--always.hint--right:before {
|
||||
-webkit-transform: translateX(8px);
|
||||
-moz-transform: translateX(8px);
|
||||
transform: translateX(8px); }
|
||||
|
||||
/**
|
||||
* source: hint-rounded.scss
|
||||
*
|
||||
* Defines rounded corner tooltips.
|
||||
*
|
||||
* Classes added:
|
||||
* 1) hint--rounded
|
||||
*
|
||||
*/
|
||||
.hint--rounded:after {
|
||||
border-radius: 4px; }
|
||||
|
||||
/**
|
||||
* source: hint-effects.scss
|
||||
*
|
||||
* Defines various transition effects for the tooltips.
|
||||
*
|
||||
* Classes added:
|
||||
* 1) hint--no-animate
|
||||
* 2) hint--bounce
|
||||
*
|
||||
*/
|
||||
.hint--no-animate:before, .hint--no-animate:after {
|
||||
-webkit-transition-duration: 0ms;
|
||||
-moz-transition-duration: 0ms;
|
||||
transition-duration: 0ms; }
|
||||
|
||||
.hint--bounce:before, .hint--bounce:after {
|
||||
-webkit-transition: opacity 0.3s ease, visibility 0.3s ease, -webkit-transform 0.3s cubic-bezier(0.71, 1.7, 0.77, 1.24);
|
||||
-moz-transition: opacity 0.3s ease, visibility 0.3s ease, -moz-transform 0.3s cubic-bezier(0.71, 1.7, 0.77, 1.24);
|
||||
transition: opacity 0.3s ease, visibility 0.3s ease, transform 0.3s cubic-bezier(0.71, 1.7, 0.77, 1.24); }
|
21
test/helper.rb
Normal file
21
test/helper.rb
Normal file
@ -0,0 +1,21 @@
|
||||
require 'rubygems'
|
||||
require 'bundler'
|
||||
begin
|
||||
Bundler.setup(:default, :development)
|
||||
rescue Bundler::BundlerError => e
|
||||
$stderr.puts e.message
|
||||
$stderr.puts "Run `bundle install` to install missing gems"
|
||||
exit e.status_code
|
||||
end
|
||||
if ENV['CODECLIMATE_REPO_TOKEN']
|
||||
require "codeclimate-test-reporter"
|
||||
CodeClimate::TestReporter.start
|
||||
end
|
||||
|
||||
require "minitest/autorun"
|
||||
|
||||
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
||||
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'test'))
|
||||
|
||||
require "salama"
|
||||
require "interpreter"
|
82
test/interpreter_test.rb
Normal file
82
test/interpreter_test.rb
Normal file
@ -0,0 +1,82 @@
|
||||
require_relative "helper"
|
||||
|
||||
class InterpreterTest < MiniTest::Test
|
||||
|
||||
def setup
|
||||
Virtual.machine.boot
|
||||
code = Ast::ExpressionList.new( [Ast::CallSiteExpression.new(:putstring, [] ,Ast::StringExpression.new("Hello again"))])
|
||||
Virtual::Compiler.compile( code , Virtual.machine.space.get_main )
|
||||
Virtual.machine.run_before "Register::CallImplementation"
|
||||
@interpreter = Interpreter.new
|
||||
@interpreter.start Virtual.machine.init
|
||||
end
|
||||
|
||||
def ticks num
|
||||
last = nil
|
||||
num.times do
|
||||
last = @interpreter.instruction
|
||||
@interpreter.tick
|
||||
end
|
||||
return last
|
||||
end
|
||||
def test_branch
|
||||
was = @interpreter.block
|
||||
assert_equal Register::Branch , ticks(1).class
|
||||
assert was != @interpreter.block
|
||||
end
|
||||
def test_load
|
||||
assert_equal Register::LoadConstant , ticks(2).class
|
||||
assert_equal Parfait::Space , Virtual.machine.objects[ @interpreter.get_register(:r1)].class
|
||||
assert_equal :r1, @interpreter.instruction.array.symbol
|
||||
end
|
||||
def test_get
|
||||
assert_equal Register::GetSlot , ticks(3).class
|
||||
assert @interpreter.get_register( :r3 )
|
||||
assert @interpreter.get_register( :r3 ).is_a? Integer
|
||||
end
|
||||
def test_transfer
|
||||
transfer = ticks 5
|
||||
assert_equal Register::RegisterTransfer , transfer.class
|
||||
assert_equal @interpreter.get_register(transfer.to) , @interpreter.get_register(transfer.from)
|
||||
end
|
||||
def test_call
|
||||
assert_equal Register::FunctionCall , ticks(7).class
|
||||
assert @interpreter.link
|
||||
end
|
||||
def test_save
|
||||
done = ticks(8)
|
||||
assert_equal Register::SaveReturn , done.class
|
||||
assert @interpreter.get_register done.register.symbol
|
||||
end
|
||||
|
||||
def test_chain
|
||||
["Branch" , "LoadConstant" , "GetSlot" , "SetSlot" , "RegisterTransfer" ,
|
||||
"GetSlot" , "FunctionCall" , "SaveReturn" , "LoadConstant" , "SetSlot" ,
|
||||
"GetSlot" , "GetSlot" , "SetSlot" , "LoadConstant" , "SetSlot" ,
|
||||
"RegisterTransfer" , "GetSlot" , "FunctionCall" , "SaveReturn" , "RegisterTransfer" ,
|
||||
"Syscall" , "RegisterTransfer" , "RegisterTransfer" , "SetSlot" , "GetSlot" ,
|
||||
"GetSlot" , "RegisterTransfer" ,"GetSlot" , "GetSlot","GetSlot",
|
||||
"FunctionReturn" , "RegisterTransfer" , "Syscall" , "NilClass"].each_with_index do |name , index|
|
||||
got = ticks(1)
|
||||
assert got.class.name.index(name) , "Wrong class for #{index+1}, expect #{name} , got #{got}"
|
||||
end
|
||||
end
|
||||
|
||||
def test_putstring
|
||||
done = ticks(21)
|
||||
assert_equal Register::Syscall , done.class
|
||||
assert_equal "Hello again" , @interpreter.stdout
|
||||
end
|
||||
|
||||
def test_return
|
||||
done = ticks(31)
|
||||
assert_equal Register::FunctionReturn , done.class
|
||||
assert @interpreter.block.is_a?(Virtual::Block)
|
||||
assert @interpreter.instruction.is_a?(Register::Instruction) , "not instruction #{@interpreter.instruction}"
|
||||
end
|
||||
|
||||
def test_exit
|
||||
done = ticks(34)
|
||||
assert_equal NilClass , done.class
|
||||
end
|
||||
end
|
Reference in New Issue
Block a user