yruby 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +199 -0
- data/lib/yruby/compile.rb +131 -0
- data/lib/yruby/core.rb +19 -0
- data/lib/yruby/insnhelper.rb +110 -0
- data/lib/yruby/insns/base.rb +13 -0
- data/lib/yruby/insns/branchunless.rb +16 -0
- data/lib/yruby/insns/definemethod.rb +13 -0
- data/lib/yruby/insns/dup.rb +12 -0
- data/lib/yruby/insns/getlocal.rb +14 -0
- data/lib/yruby/insns/jump.rb +13 -0
- data/lib/yruby/insns/leave.rb +22 -0
- data/lib/yruby/insns/opt_div.rb +15 -0
- data/lib/yruby/insns/opt_minus.rb +15 -0
- data/lib/yruby/insns/opt_mult.rb +15 -0
- data/lib/yruby/insns/opt_plus.rb +15 -0
- data/lib/yruby/insns/opt_send_without_block.rb +13 -0
- data/lib/yruby/insns/putnil.rb +11 -0
- data/lib/yruby/insns/putobject.rb +13 -0
- data/lib/yruby/insns/putself.rb +11 -0
- data/lib/yruby/insns/setlocal.rb +14 -0
- data/lib/yruby/insns.rb +18 -0
- data/lib/yruby/iseq.rb +86 -0
- data/lib/yruby/parser.rb +13 -0
- data/lib/yruby/rclass.rb +17 -0
- data/lib/yruby/robject.rb +11 -0
- data/lib/yruby/version.rb +5 -0
- data/lib/yruby.rb +50 -0
- metadata +109 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: b7ea42800360e25a4e07044414522e6db405ac3a49c638de709682a37e179e4a
|
|
4
|
+
data.tar.gz: '06669269c7f3bdddd63984276ef5e0efd88cbcd704e47881e79936c2496ab937'
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 3f301b9319a04cf8fb011d170b35c791e980a4b613e8a67a32d1469c50aef0b78d1f18570ef1af98d03cb584bfb9ddd424bfc7bb25cc101fddaf7e89a744b016
|
|
7
|
+
data.tar.gz: 4b97d1b57a82adc478e482b27e18c9ba4dd4d6bb80f8105efd0a8bb717075f7f16cf4917b7298c5aeb78f2a69c833478b8f901abfa830c76b7e888aebb1e8d59
|
data/README.md
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# YRuby
|
|
2
|
+
|
|
3
|
+
YRuby is a Ruby virtual machine implementation based on CRuby's YARV (Yet Another Ruby VM) architecture. It parses Ruby source code with the [Prism](https://github.com/ruby/prism) gem and executes it through a stack-based bytecode interpreter.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add this line to your application's Gemfile:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem 'yruby'
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or install it directly:
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
$ gem install yruby
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Requirements
|
|
20
|
+
|
|
21
|
+
- Ruby >= 3.3.0
|
|
22
|
+
- [prism](https://rubygems.org/gems/prism) ~> 1.0
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
### Basic Usage
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
require 'yruby'
|
|
30
|
+
|
|
31
|
+
vm = YRuby.new
|
|
32
|
+
|
|
33
|
+
vm.exec("1 + 2") # => 3
|
|
34
|
+
vm.exec("if true; 42; end") # => 42
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Each call to `exec` is independent — the VM state is reinitialized on every call.
|
|
38
|
+
|
|
39
|
+
### API
|
|
40
|
+
|
|
41
|
+
#### `YRuby.new(parser = YRuby::Parser.new) -> YRuby`
|
|
42
|
+
|
|
43
|
+
Creates a new VM instance. Uses the default Prism-based parser if none is provided.
|
|
44
|
+
|
|
45
|
+
| Parameter | Type | Description |
|
|
46
|
+
|-----------|----------------|----------------------------------------------|
|
|
47
|
+
| `parser` | `YRuby::Parser` | Parser instance to use for parsing (optional) |
|
|
48
|
+
|
|
49
|
+
#### `YRuby#exec(source) -> Object`
|
|
50
|
+
|
|
51
|
+
Parses and executes the given Ruby source string, returning the result of the last evaluated expression.
|
|
52
|
+
|
|
53
|
+
| Parameter | Type | Description |
|
|
54
|
+
|-----------|----------|------------------------------|
|
|
55
|
+
| `source` | `String` | Ruby source code to execute |
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
vm = YRuby.new
|
|
59
|
+
|
|
60
|
+
# Literals
|
|
61
|
+
vm.exec("42") # => 42
|
|
62
|
+
vm.exec("nil") # => nil
|
|
63
|
+
vm.exec("true") # => true
|
|
64
|
+
|
|
65
|
+
# Arithmetic
|
|
66
|
+
vm.exec("1 + 2") # => 3
|
|
67
|
+
vm.exec("10 - 3") # => 7
|
|
68
|
+
vm.exec("4 * 5") # => 20
|
|
69
|
+
vm.exec("10 / 2") # => 5
|
|
70
|
+
|
|
71
|
+
# Local variables
|
|
72
|
+
vm.exec(<<~RUBY) # => 11
|
|
73
|
+
a = 10
|
|
74
|
+
a + 1
|
|
75
|
+
RUBY
|
|
76
|
+
|
|
77
|
+
# Conditionals
|
|
78
|
+
vm.exec(<<~RUBY) # => 1
|
|
79
|
+
if true
|
|
80
|
+
1
|
|
81
|
+
else
|
|
82
|
+
2
|
|
83
|
+
end
|
|
84
|
+
RUBY
|
|
85
|
+
|
|
86
|
+
vm.exec(<<~RUBY) # => 2
|
|
87
|
+
if false
|
|
88
|
+
1
|
|
89
|
+
elsif true
|
|
90
|
+
2
|
|
91
|
+
end
|
|
92
|
+
RUBY
|
|
93
|
+
|
|
94
|
+
# Method definition and call
|
|
95
|
+
vm.exec(<<~RUBY) # => 3
|
|
96
|
+
def add(a, b)
|
|
97
|
+
a + b
|
|
98
|
+
end
|
|
99
|
+
add(1, 2)
|
|
100
|
+
RUBY
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
#### `YRuby::Parser#parse(source) -> Prism::ParseResult`
|
|
104
|
+
|
|
105
|
+
Parses Ruby source code and returns a Prism AST result. This is used internally by `YRuby#exec` and is rarely needed directly.
|
|
106
|
+
|
|
107
|
+
#### `YRuby::Iseq.iseq_new_main(ast) -> YRuby::Iseq`
|
|
108
|
+
|
|
109
|
+
Compiles a Prism AST into an instruction sequence for the top-level scope.
|
|
110
|
+
|
|
111
|
+
#### `YRuby::Iseq.iseq_new_method(def_node) -> YRuby::Iseq`
|
|
112
|
+
|
|
113
|
+
Compiles a method definition node into an instruction sequence.
|
|
114
|
+
|
|
115
|
+
#### `YRuby::Iseq#disasm -> String`
|
|
116
|
+
|
|
117
|
+
Returns a human-readable disassembly of the instruction sequence. Useful for debugging and learning.
|
|
118
|
+
|
|
119
|
+
```ruby
|
|
120
|
+
parser = YRuby::Parser.new
|
|
121
|
+
iseq = YRuby::Iseq.iseq_new_main(parser.parse("1 + 2"))
|
|
122
|
+
puts iseq.disasm
|
|
123
|
+
# 0000 putobject 1
|
|
124
|
+
# 0002 putobject 2
|
|
125
|
+
# 0004 opt_plus
|
|
126
|
+
# 0005 leave
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Supported Ruby Features
|
|
130
|
+
|
|
131
|
+
| Feature | Example |
|
|
132
|
+
|-------------------------------|----------------------------------------------|
|
|
133
|
+
| Integer literals | `42`, `0`, `-1` |
|
|
134
|
+
| Boolean / nil literals | `true`, `false`, `nil` |
|
|
135
|
+
| Arithmetic operators | `a + b`, `a - b`, `a * b`, `a / b` |
|
|
136
|
+
| Local variable read/write | `a = 1; a` |
|
|
137
|
+
| `if` / `else` / `elsif` | `if cond; ...; elsif ...; else; ...; end` |
|
|
138
|
+
| `if` as expression | `x = if true; 1; else; 2; end` |
|
|
139
|
+
| Method definition | `def add(a, b); a + b; end` |
|
|
140
|
+
| Method call | `add(1, 2)` |
|
|
141
|
+
| Multiple statements | `1; 2; 3` |
|
|
142
|
+
|
|
143
|
+
## Instruction Set
|
|
144
|
+
|
|
145
|
+
YRuby implements the following YARV-like instructions:
|
|
146
|
+
|
|
147
|
+
| Instruction | Operands | Description |
|
|
148
|
+
|--------------------------|-------------|-----------------------------------------------|
|
|
149
|
+
| `putobject` | `value` | Push a constant value onto the stack |
|
|
150
|
+
| `putnil` | — | Push `nil` onto the stack |
|
|
151
|
+
| `putself` | — | Push the current receiver onto the stack |
|
|
152
|
+
| `dup` | — | Duplicate the top of the stack |
|
|
153
|
+
| `getlocal` | `idx` | Push a local variable value onto the stack |
|
|
154
|
+
| `setlocal` | `idx` | Pop and store a value into a local variable |
|
|
155
|
+
| `opt_plus` | — | Pop two values and push their sum |
|
|
156
|
+
| `opt_minus` | — | Pop two values and push their difference |
|
|
157
|
+
| `opt_mult` | — | Pop two values and push their product |
|
|
158
|
+
| `opt_div` | — | Pop two values and push their quotient |
|
|
159
|
+
| `branchunless` | `offset` | Jump if the popped value is falsy |
|
|
160
|
+
| `jump` | `offset` | Unconditional jump |
|
|
161
|
+
| `definemethod` | `mid, iseq` | Register a method on the current object |
|
|
162
|
+
| `opt_send_without_block` | `cd` | Dispatch a method call |
|
|
163
|
+
| `leave` | — | Exit the current frame and return a value |
|
|
164
|
+
|
|
165
|
+
## Architecture
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
Source Code → Parser (Prism) → AST → Compile → Iseq → VM Execution → Result
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### CRuby Mapping
|
|
172
|
+
|
|
173
|
+
The internal structure mirrors CRuby's implementation:
|
|
174
|
+
|
|
175
|
+
```
|
|
176
|
+
lib/
|
|
177
|
+
├── yruby.rb # vm.c, vm_exec.c — VM core and execution loop
|
|
178
|
+
└── yruby/
|
|
179
|
+
├── core.rb # vm_core.h — ExecutionContext, ControlFrame
|
|
180
|
+
├── insnhelper.rb # vm_insnhelper.c — stack/frame/env operations
|
|
181
|
+
├── parser.rb # parse.y — Prism wrapper
|
|
182
|
+
├── compile.rb # compile.c — AST → bytecode compiler
|
|
183
|
+
├── iseq.rb # iseq.c / iseq.h — instruction sequence
|
|
184
|
+
├── rclass.rb # — class and method table
|
|
185
|
+
├── robject.rb # — object instances
|
|
186
|
+
├── insns.rb # insns.def — instruction aggregation
|
|
187
|
+
└── insns/ # insns.def — individual instruction classes
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Development
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
$ bundle install
|
|
194
|
+
$ bundle exec rake test
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## License
|
|
198
|
+
|
|
199
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class YRuby
|
|
4
|
+
class Compile
|
|
5
|
+
def iseq_compile_node(iseq, node)
|
|
6
|
+
@index_lookup_table = {}
|
|
7
|
+
insert_local_index(@index_lookup_table, node.locals)
|
|
8
|
+
iseq_set_local_table(iseq, node.locals)
|
|
9
|
+
compile_node(iseq, node)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def iseq_compile_method(iseq, def_node)
|
|
13
|
+
@index_lookup_table = {}
|
|
14
|
+
insert_local_index(@index_lookup_table, def_node.locals)
|
|
15
|
+
iseq_set_local_table(iseq, def_node.locals)
|
|
16
|
+
|
|
17
|
+
if def_node.body
|
|
18
|
+
compile_node(iseq, def_node.body)
|
|
19
|
+
else
|
|
20
|
+
iseq.emit(YRuby::Insns::Putnil)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def insert_local_index(index_lookup_table, locals)
|
|
27
|
+
locals.reverse_each.with_index do |local, index|
|
|
28
|
+
index_lookup_table[local] = index
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def iseq_set_local_table(iseq, locals)
|
|
33
|
+
iseq.local_table_size = locals.size
|
|
34
|
+
iseq.local_table = locals.dup
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def compile_node(iseq, node)
|
|
38
|
+
case node
|
|
39
|
+
when Prism::ProgramNode
|
|
40
|
+
compile_node(iseq, node.statements)
|
|
41
|
+
when Prism::StatementsNode
|
|
42
|
+
node.body.each { |stmt| compile_node(iseq, stmt) }
|
|
43
|
+
when Prism::IntegerNode
|
|
44
|
+
iseq.emit(YRuby::Insns::Putobject, node.value)
|
|
45
|
+
when Prism::NilNode
|
|
46
|
+
iseq.emit(YRuby::Insns::Putnil)
|
|
47
|
+
when Prism::TrueNode
|
|
48
|
+
iseq.emit(YRuby::Insns::Putobject, true)
|
|
49
|
+
when Prism::FalseNode
|
|
50
|
+
iseq.emit(YRuby::Insns::Putobject, false)
|
|
51
|
+
when Prism::CallNode
|
|
52
|
+
compile_call_node(iseq, node)
|
|
53
|
+
when Prism::ArgumentsNode
|
|
54
|
+
node.arguments.each { |arg| compile_node(iseq, arg) }
|
|
55
|
+
when Prism::LocalVariableWriteNode
|
|
56
|
+
compile_node(iseq, node.value)
|
|
57
|
+
iseq.emit(YRuby::Insns::Dup)
|
|
58
|
+
idx = @index_lookup_table[node.name]
|
|
59
|
+
iseq.emit(YRuby::Insns::Setlocal, idx)
|
|
60
|
+
when Prism::LocalVariableReadNode
|
|
61
|
+
idx = @index_lookup_table[node.name]
|
|
62
|
+
iseq.emit(YRuby::Insns::Getlocal, idx)
|
|
63
|
+
when Prism::IfNode
|
|
64
|
+
compile_conditional_node(iseq, node)
|
|
65
|
+
when Prism::DefNode
|
|
66
|
+
compile_def_node(iseq, node)
|
|
67
|
+
else
|
|
68
|
+
raise "Unknown node: #{node.class}"
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def compile_call_node(iseq, node)
|
|
73
|
+
if node.receiver.nil?
|
|
74
|
+
iseq.emit(YRuby::Insns::Putself)
|
|
75
|
+
argc = 0
|
|
76
|
+
if node.arguments
|
|
77
|
+
compile_node(iseq, node.arguments)
|
|
78
|
+
argc = node.arguments.arguments.size
|
|
79
|
+
end
|
|
80
|
+
cd = CallData.new(mid: node.name, argc:)
|
|
81
|
+
iseq.emit(YRuby::Insns::OptSendWithoutBlock, cd)
|
|
82
|
+
else
|
|
83
|
+
compile_node(iseq, node.receiver)
|
|
84
|
+
compile_node(iseq, node.arguments)
|
|
85
|
+
|
|
86
|
+
case node.name
|
|
87
|
+
when :+; iseq.emit(YRuby::Insns::OptPlus)
|
|
88
|
+
when :-; iseq.emit(YRuby::Insns::OptMinus)
|
|
89
|
+
when :*; iseq.emit(YRuby::Insns::OptMult)
|
|
90
|
+
when :/; iseq.emit(YRuby::Insns::OptDiv)
|
|
91
|
+
else
|
|
92
|
+
raise "Unknown operator: #{node.name}"
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def compile_def_node(iseq, node)
|
|
98
|
+
method_iseq = YRuby::Iseq.iseq_new_method(node)
|
|
99
|
+
iseq.emit(YRuby::Insns::Definemethod, node.name, method_iseq)
|
|
100
|
+
iseq.emit(YRuby::Insns::Putobject, node.name)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def compile_conditional_node(iseq, node)
|
|
104
|
+
compile_node(iseq, node.predicate)
|
|
105
|
+
branchunless_pc = iseq.size
|
|
106
|
+
iseq.emit_placeholder(YRuby::Insns::Branchunless::LEN)
|
|
107
|
+
|
|
108
|
+
# then statements
|
|
109
|
+
compile_node(iseq, node.statements)
|
|
110
|
+
|
|
111
|
+
then_end_pc = iseq.size
|
|
112
|
+
iseq.emit_placeholder(YRuby::Insns::Jump::LEN)
|
|
113
|
+
|
|
114
|
+
else_label = iseq.size
|
|
115
|
+
branchunless_offset = else_label - (branchunless_pc + YRuby::Insns::Branchunless::LEN)
|
|
116
|
+
iseq.patch_at!(branchunless_pc, YRuby::Insns::Branchunless, branchunless_offset)
|
|
117
|
+
|
|
118
|
+
# elsif / else statements
|
|
119
|
+
case node.subsequent
|
|
120
|
+
when Prism::IfNode
|
|
121
|
+
compile_conditional_node(iseq, node.subsequent)
|
|
122
|
+
when Prism::ElseNode
|
|
123
|
+
compile_node(iseq, node.subsequent.statements)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
end_label = iseq.size
|
|
127
|
+
jump_offset = end_label - (then_end_pc + YRuby::Insns::Jump::LEN)
|
|
128
|
+
iseq.patch_at!(then_end_pc, YRuby::Insns::Jump, jump_offset)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
data/lib/yruby/core.rb
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'iseq'
|
|
4
|
+
require_relative 'compile'
|
|
5
|
+
require_relative 'insns'
|
|
6
|
+
require_relative 'parser'
|
|
7
|
+
require_relative 'insnhelper'
|
|
8
|
+
|
|
9
|
+
class YRuby
|
|
10
|
+
STACK_SIZE = 128.freeze
|
|
11
|
+
|
|
12
|
+
FRAME_TYPE_TOP = :top
|
|
13
|
+
FRAME_TYPE_METHOD = :method
|
|
14
|
+
|
|
15
|
+
ControlFrame = Struct.new(:iseq, :pc, :sp, :ep, :type, :self_value, keyword_init: true)
|
|
16
|
+
ExecutionContext = Struct.new(:stack, :stack_size, :cfp, :frames, keyword_init: true)
|
|
17
|
+
|
|
18
|
+
CallData = Struct.new(:mid, :argc, keyword_init: true)
|
|
19
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'forwardable'
|
|
4
|
+
|
|
5
|
+
class YRuby
|
|
6
|
+
module InsnHelper
|
|
7
|
+
extend Forwardable
|
|
8
|
+
|
|
9
|
+
def_delegators :ec, :cfp, :cfp=, :stack, :stack=, :frames
|
|
10
|
+
|
|
11
|
+
module Macros
|
|
12
|
+
# Value Stack
|
|
13
|
+
def push(x)
|
|
14
|
+
set_sv(x)
|
|
15
|
+
inc_sp(1)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def topn(x)
|
|
19
|
+
stack[cfp.sp - x]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def pop
|
|
23
|
+
cfp.sp -= 1
|
|
24
|
+
val = stack[cfp.sp]
|
|
25
|
+
stack[cfp.sp] = nil
|
|
26
|
+
val
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# PC
|
|
30
|
+
def add_pc(x)
|
|
31
|
+
cfp.pc += x
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# environment pointer
|
|
35
|
+
def get_ep
|
|
36
|
+
cfp.ep
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# SP
|
|
40
|
+
def set_sv(x)
|
|
41
|
+
stack[cfp.sp] = x
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def inc_sp(x)
|
|
45
|
+
cfp.sp += x
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Control Frame
|
|
50
|
+
def push_frame(iseq:, type: FRAME_TYPE_TOP, self_value: nil, sp:)
|
|
51
|
+
sp = sp + iseq.local_table_size
|
|
52
|
+
ep = sp - 1
|
|
53
|
+
|
|
54
|
+
cf = ControlFrame.new(iseq:, pc: 0, sp:, ep:, type:, self_value:)
|
|
55
|
+
frames.push(cf)
|
|
56
|
+
self.cfp = cf
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def pop_frame
|
|
60
|
+
frames.pop
|
|
61
|
+
self.cfp = frames.last
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Environment Pointer
|
|
65
|
+
def env_read(index)
|
|
66
|
+
stack[get_ep + index]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def env_write(index, value)
|
|
70
|
+
stack[get_ep + index] = value
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def define_method(mid, iseq)
|
|
74
|
+
klass = cfp.self_value.klass
|
|
75
|
+
|
|
76
|
+
klass.add_method_iseq(mid, iseq)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def call_iseq_setup(recv, argc, method_iseq)
|
|
80
|
+
argv_index = cfp.sp - argc
|
|
81
|
+
recv_index = argv_index - 1
|
|
82
|
+
|
|
83
|
+
cfp.sp = recv_index
|
|
84
|
+
|
|
85
|
+
push_frame(
|
|
86
|
+
iseq: method_iseq,
|
|
87
|
+
type: FRAME_TYPE_METHOD,
|
|
88
|
+
self_value: recv,
|
|
89
|
+
sp: argv_index
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
local_only_size = method_iseq.local_table_size - method_iseq.argc
|
|
93
|
+
local_only_size.times do |i|
|
|
94
|
+
env_write(-i, nil)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def sendish(cd)
|
|
99
|
+
argc = cd.argc
|
|
100
|
+
recv = topn(argc + 1)
|
|
101
|
+
|
|
102
|
+
klass = recv.klass
|
|
103
|
+
method_iseq = klass.search_method(cd.mid)
|
|
104
|
+
|
|
105
|
+
raise "undefined method #{cd.mid}" if method_iseq.nil?
|
|
106
|
+
|
|
107
|
+
call_iseq_setup(recv, argc, method_iseq)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class YRuby
|
|
4
|
+
module Insns
|
|
5
|
+
class Leave < Base
|
|
6
|
+
def self.call(vm)
|
|
7
|
+
val = vm.topn(1)
|
|
8
|
+
vm.pop
|
|
9
|
+
|
|
10
|
+
type = vm.cfp.type
|
|
11
|
+
vm.pop_frame
|
|
12
|
+
|
|
13
|
+
case type
|
|
14
|
+
when YRuby::FRAME_TYPE_METHOD
|
|
15
|
+
vm.push(val)
|
|
16
|
+
when YRuby::FRAME_TYPE_TOP
|
|
17
|
+
throw :finish, val
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
data/lib/yruby/insns.rb
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'insns/base'
|
|
4
|
+
require_relative 'insns/putobject'
|
|
5
|
+
require_relative 'insns/putnil'
|
|
6
|
+
require_relative 'insns/leave'
|
|
7
|
+
require_relative 'insns/opt_plus'
|
|
8
|
+
require_relative 'insns/opt_minus'
|
|
9
|
+
require_relative 'insns/opt_mult'
|
|
10
|
+
require_relative 'insns/opt_div'
|
|
11
|
+
require_relative 'insns/getlocal'
|
|
12
|
+
require_relative 'insns/setlocal'
|
|
13
|
+
require_relative 'insns/dup'
|
|
14
|
+
require_relative 'insns/branchunless'
|
|
15
|
+
require_relative 'insns/jump'
|
|
16
|
+
require_relative 'insns/definemethod'
|
|
17
|
+
require_relative 'insns/putself'
|
|
18
|
+
require_relative 'insns/opt_send_without_block'
|
data/lib/yruby/iseq.rb
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class YRuby
|
|
4
|
+
class Iseq
|
|
5
|
+
class << self
|
|
6
|
+
def iseq_new_main(ast)
|
|
7
|
+
node = ast.value
|
|
8
|
+
|
|
9
|
+
iseq = new
|
|
10
|
+
|
|
11
|
+
Compile.new.iseq_compile_node(iseq, node)
|
|
12
|
+
|
|
13
|
+
iseq.emit(Insns::Leave)
|
|
14
|
+
|
|
15
|
+
iseq
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def iseq_new_method(def_node)
|
|
19
|
+
iseq = new
|
|
20
|
+
|
|
21
|
+
Compile.new.iseq_compile_method(iseq, def_node)
|
|
22
|
+
|
|
23
|
+
iseq.emit(Insns::Leave)
|
|
24
|
+
|
|
25
|
+
params = def_node.parameters
|
|
26
|
+
iseq.argc = params ? params.requireds.size : 0
|
|
27
|
+
|
|
28
|
+
iseq
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
attr_accessor :local_table, :local_table_size, :argc
|
|
33
|
+
|
|
34
|
+
def initialize
|
|
35
|
+
@iseq_encoded = []
|
|
36
|
+
@local_table = []
|
|
37
|
+
@local_table_size = 0
|
|
38
|
+
@argc = 0
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def emit(insn_class, *operands)
|
|
42
|
+
@iseq_encoded << insn_class
|
|
43
|
+
operands.each { |op| @iseq_encoded << op }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def emit_placeholder(len)
|
|
47
|
+
len.times { @iseq_encoded << nil }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def patch_at!(pc, insn_class, *operands)
|
|
51
|
+
@iseq_encoded[pc] = insn_class
|
|
52
|
+
operands.each_with_index do |op, i|
|
|
53
|
+
@iseq_encoded[pc + 1 + i] = op
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def fetch(pc)
|
|
58
|
+
@iseq_encoded[pc]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def size
|
|
62
|
+
@iseq_encoded.size
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def disasm
|
|
66
|
+
lines = []
|
|
67
|
+
lines << "== disasm: #<ISeq:<main>@<compiled>:1> =="
|
|
68
|
+
|
|
69
|
+
pc = 0
|
|
70
|
+
while pc < @iseq_encoded.size
|
|
71
|
+
insn_class = @iseq_encoded[pc]
|
|
72
|
+
len = insn_class::LEN
|
|
73
|
+
operands = @iseq_encoded[pc + 1, len - 1]
|
|
74
|
+
name = insn_class.name.split('::').last.downcase
|
|
75
|
+
if operands.empty?
|
|
76
|
+
lines << format("%04d %s", pc, name)
|
|
77
|
+
else
|
|
78
|
+
lines << format("%04d %s %s", pc, name, operands.map(&:inspect).join(', '))
|
|
79
|
+
end
|
|
80
|
+
pc += len
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
lines.join("\n")
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
data/lib/yruby/parser.rb
ADDED
data/lib/yruby/rclass.rb
ADDED
data/lib/yruby.rb
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'yruby/version'
|
|
4
|
+
require_relative 'yruby/core'
|
|
5
|
+
require_relative 'yruby/rclass'
|
|
6
|
+
require_relative 'yruby/robject'
|
|
7
|
+
|
|
8
|
+
class YRuby
|
|
9
|
+
include InsnHelper
|
|
10
|
+
include InsnHelper::Macros
|
|
11
|
+
|
|
12
|
+
attr_reader :ec
|
|
13
|
+
|
|
14
|
+
def initialize(parser = Parser.new)
|
|
15
|
+
@parser = parser
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def exec(source)
|
|
19
|
+
init
|
|
20
|
+
|
|
21
|
+
ast = @parser.parse(source)
|
|
22
|
+
|
|
23
|
+
iseq = Iseq.iseq_new_main(ast)
|
|
24
|
+
|
|
25
|
+
exec_core(iseq)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def init
|
|
31
|
+
stack = Array.new(STACK_SIZE)
|
|
32
|
+
frames = []
|
|
33
|
+
@ec = ExecutionContext.new(stack:, stack_size: STACK_SIZE, frames:)
|
|
34
|
+
@top_self = RObject.new(RClass.new)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def exec_core(iseq)
|
|
38
|
+
push_frame(iseq:, type: FRAME_TYPE_TOP, self_value: @top_self, sp: 0)
|
|
39
|
+
|
|
40
|
+
catch(:finish) do
|
|
41
|
+
loop do
|
|
42
|
+
insn_class = cfp.iseq.fetch(cfp.pc)
|
|
43
|
+
len = insn_class::LEN
|
|
44
|
+
operands = (1...len).map { |i| cfp.iseq.fetch(cfp.pc + i) }
|
|
45
|
+
add_pc(len)
|
|
46
|
+
insn_class.call(self, *operands)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: yruby
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Yuhi-Sato
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: prism
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '1.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: minitest
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '5.0'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '5.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: rake
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '13.0'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '13.0'
|
|
54
|
+
description: YRuby is a Ruby virtual machine implementation based on CRuby's YARV
|
|
55
|
+
(Yet Another Ruby VM) architecture. It parses Ruby source code with the Prism gem
|
|
56
|
+
and executes it through a stack-based bytecode interpreter.
|
|
57
|
+
executables: []
|
|
58
|
+
extensions: []
|
|
59
|
+
extra_rdoc_files: []
|
|
60
|
+
files:
|
|
61
|
+
- README.md
|
|
62
|
+
- lib/yruby.rb
|
|
63
|
+
- lib/yruby/compile.rb
|
|
64
|
+
- lib/yruby/core.rb
|
|
65
|
+
- lib/yruby/insnhelper.rb
|
|
66
|
+
- lib/yruby/insns.rb
|
|
67
|
+
- lib/yruby/insns/base.rb
|
|
68
|
+
- lib/yruby/insns/branchunless.rb
|
|
69
|
+
- lib/yruby/insns/definemethod.rb
|
|
70
|
+
- lib/yruby/insns/dup.rb
|
|
71
|
+
- lib/yruby/insns/getlocal.rb
|
|
72
|
+
- lib/yruby/insns/jump.rb
|
|
73
|
+
- lib/yruby/insns/leave.rb
|
|
74
|
+
- lib/yruby/insns/opt_div.rb
|
|
75
|
+
- lib/yruby/insns/opt_minus.rb
|
|
76
|
+
- lib/yruby/insns/opt_mult.rb
|
|
77
|
+
- lib/yruby/insns/opt_plus.rb
|
|
78
|
+
- lib/yruby/insns/opt_send_without_block.rb
|
|
79
|
+
- lib/yruby/insns/putnil.rb
|
|
80
|
+
- lib/yruby/insns/putobject.rb
|
|
81
|
+
- lib/yruby/insns/putself.rb
|
|
82
|
+
- lib/yruby/insns/setlocal.rb
|
|
83
|
+
- lib/yruby/iseq.rb
|
|
84
|
+
- lib/yruby/parser.rb
|
|
85
|
+
- lib/yruby/rclass.rb
|
|
86
|
+
- lib/yruby/robject.rb
|
|
87
|
+
- lib/yruby/version.rb
|
|
88
|
+
homepage: https://github.com/Yuhi-Sato/yruby
|
|
89
|
+
licenses:
|
|
90
|
+
- MIT
|
|
91
|
+
metadata: {}
|
|
92
|
+
rdoc_options: []
|
|
93
|
+
require_paths:
|
|
94
|
+
- lib
|
|
95
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
96
|
+
requirements:
|
|
97
|
+
- - ">="
|
|
98
|
+
- !ruby/object:Gem::Version
|
|
99
|
+
version: 3.3.0
|
|
100
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
101
|
+
requirements:
|
|
102
|
+
- - ">="
|
|
103
|
+
- !ruby/object:Gem::Version
|
|
104
|
+
version: '0'
|
|
105
|
+
requirements: []
|
|
106
|
+
rubygems_version: 3.6.7
|
|
107
|
+
specification_version: 4
|
|
108
|
+
summary: Yet Another Ruby VM - a YARV-based Ruby virtual machine implementation
|
|
109
|
+
test_files: []
|