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 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,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ class YRuby
4
+ module Insns
5
+ class Base
6
+ LEN = 1
7
+
8
+ def self.call(vm)
9
+ raise NotImplementedError
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ class YRuby
4
+ module Insns
5
+ class Branchunless < Base
6
+ LEN = 2
7
+
8
+ def self.call(vm, dst)
9
+ val = vm.topn(1)
10
+ vm.pop
11
+
12
+ vm.add_pc(dst) unless val
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ class YRuby
4
+ module Insns
5
+ class Definemethod < Base
6
+ LEN = 3
7
+
8
+ def self.call(vm, mid, iseq)
9
+ vm.define_method(mid, iseq)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ class YRuby
4
+ module Insns
5
+ class Dup < Base
6
+ def self.call(vm)
7
+ val = vm.topn(1)
8
+ vm.push(val)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ class YRuby
4
+ module Insns
5
+ class Getlocal < Base
6
+ LEN = 2
7
+
8
+ def self.call(vm, idx)
9
+ val = vm.env_read(-idx)
10
+ vm.push(val)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ class YRuby
4
+ module Insns
5
+ class Jump < Base
6
+ LEN = 2
7
+
8
+ def self.call(vm, dst)
9
+ vm.add_pc(dst)
10
+ end
11
+ end
12
+ end
13
+ 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
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ class YRuby
4
+ module Insns
5
+ class OptDiv < Base
6
+ def self.call(vm)
7
+ recv = vm.topn(2)
8
+ arg = vm.topn(1)
9
+ vm.pop
10
+ vm.pop
11
+ vm.push(recv / arg)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ class YRuby
4
+ module Insns
5
+ class OptMinus < Base
6
+ def self.call(vm)
7
+ recv = vm.topn(2)
8
+ arg = vm.topn(1)
9
+ vm.pop
10
+ vm.pop
11
+ vm.push(recv - arg)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ class YRuby
4
+ module Insns
5
+ class OptMult < Base
6
+ def self.call(vm)
7
+ recv = vm.topn(2)
8
+ arg = vm.topn(1)
9
+ vm.pop
10
+ vm.pop
11
+ vm.push(recv * arg)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ class YRuby
4
+ module Insns
5
+ class OptPlus < Base
6
+ def self.call(vm)
7
+ recv = vm.topn(2)
8
+ arg = vm.topn(1)
9
+ vm.pop
10
+ vm.pop
11
+ vm.push(recv + arg)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ class YRuby
4
+ module Insns
5
+ class OptSendWithoutBlock < Base
6
+ LEN = 2
7
+
8
+ def self.call(vm, cd)
9
+ vm.sendish(cd)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class YRuby
4
+ module Insns
5
+ class Putnil < Base
6
+ def self.call(vm)
7
+ vm.push(nil)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ class YRuby
4
+ module Insns
5
+ class Putobject < Base
6
+ LEN = 2
7
+
8
+ def self.call(vm, value)
9
+ vm.push(value)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class YRuby
4
+ module Insns
5
+ class Putself < Base
6
+ def self.call(vm)
7
+ vm.push(vm.cfp.self_value)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ class YRuby
4
+ module Insns
5
+ class Setlocal < Base
6
+ LEN = 2
7
+
8
+ def self.call(vm, idx)
9
+ val = vm.pop
10
+ vm.env_write(-idx, val)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -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
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'prism'
4
+
5
+ class YRuby
6
+ class Parser
7
+ def parse(source)
8
+ result = Prism.parse(source)
9
+
10
+ result
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ class YRuby
4
+ class RClass
5
+ def initialize
6
+ @m_tbl = {}
7
+ end
8
+
9
+ def add_method_iseq(mid, iseq)
10
+ @m_tbl[mid] = iseq
11
+ end
12
+
13
+ def search_method(mid)
14
+ @m_tbl[mid]
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class YRuby
4
+ class RObject
5
+ attr_reader :klass
6
+
7
+ def initialize(klass)
8
+ @klass = klass
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class YRuby
4
+ VERSION = "0.1.0"
5
+ end
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: []