miniruby 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: c2564e7112404f30ad0a3086614cefc695da9c1df699fc7d97a0822c6433cae1
4
+ data.tar.gz: 3f0cb46867de3e19a278ce4004f1aed0cedc795aa13b13a716fe2a70dcd6bb58
5
+ SHA512:
6
+ metadata.gz: d6d74088718f4eedc19a9461129ba67fae171ae2f07bd31ef98be3ecfb22c3fa8e1f61efe32e7b831f64f550e586207fc863ce8ea083b9d2c2787b272d3dba20
7
+ data.tar.gz: 36ddb499adb601dfb3f85230d99dd3899d5342f603b17c85c256e7e71fcad4c46495f8b1a18392206d5ea2d24fcd0fba4f02587b210f93dd519eed7ca13add02
data/.rubocop.yml ADDED
@@ -0,0 +1,77 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.2
3
+
4
+ inherit_gem:
5
+ rubocop-espago: sorbet.yml
6
+
7
+ Lint/BooleanSymbol:
8
+ Enabled: false
9
+
10
+ Lint/MissingSuper:
11
+ Enabled: false
12
+
13
+ Style/NumericPredicate:
14
+ Enabled: false
15
+
16
+ Metrics/MethodLength:
17
+ Enabled: false
18
+
19
+ Metrics/AbcSize:
20
+ Enabled: false
21
+
22
+ Style/YodaExpression:
23
+ Enabled: false
24
+
25
+ Metrics/CyclomaticComplexity:
26
+ Enabled: false
27
+
28
+ Metrics/BlockLength:
29
+ Enabled: false
30
+
31
+ Metrics/ClassLength:
32
+ Enabled: false
33
+
34
+ Style/RaiseArgs:
35
+ Enabled: false
36
+
37
+ Style/ZeroLengthPredicate:
38
+ Enabled: false
39
+
40
+ Lint/DeprecatedConstants:
41
+ Enabled: false
42
+
43
+ Metrics/PerceivedComplexity:
44
+ Enabled: false
45
+
46
+ Style/InfiniteLoop:
47
+ Enabled: false
48
+
49
+ Style/WhileUntilModifier:
50
+ Enabled: false
51
+
52
+ Style/FormatStringToken:
53
+ Enabled: false
54
+
55
+ Layout/EmptyLinesAroundArguments:
56
+ Enabled: false
57
+
58
+ Naming/BlockForwarding:
59
+ Enabled: false
60
+
61
+ Sorbet/RedundantExtendTSig:
62
+ Enabled: false
63
+
64
+ Sorbet/ForbidTUnsafe:
65
+ Enabled: false
66
+
67
+ Sorbet/ForbidTypeAliasedShapes:
68
+ Enabled: false
69
+
70
+ Sorbet/ForbidTUntyped:
71
+ Enabled: false
72
+
73
+ Style/MethodCallWithoutArgsParentheses:
74
+ Enabled: false
75
+
76
+ Lint/RedundantRequireStatement:
77
+ Enabled: false
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.4.2
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2025-02-12
4
+
5
+ - Initial release
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Mateusz Drewniak
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,332 @@
1
+ # MiniRuby
2
+
3
+ This library implements an interpreter for MiniRuby, a small subset of the Ruby language 💎.
4
+ It is implemented as a Ruby gem with sorbet.
5
+
6
+ It has been built for educational purposes, to serve as a simple example of how modern interpreters work.
7
+
8
+ ## Installation
9
+
10
+ Install the gem and add to the application's Gemfile by executing:
11
+
12
+ ```sh
13
+ bundle add miniruby
14
+ ```
15
+
16
+ If bundler is not being used to manage dependencies, install the gem by executing:
17
+
18
+ ```sh
19
+ gem install miniruby
20
+ ```
21
+
22
+ ## Usage
23
+
24
+ ### Lexer
25
+
26
+ This library implements a streaming MiniRuby lexer.
27
+ You can use it by creating an instance of `MiniRuby::Lexer` passing in a string
28
+ with source code.
29
+
30
+ You can call the `next` method to receive the next token.
31
+ Once the lexing is complete a token of type `:end_of_file` gets returned.
32
+
33
+ ```rb
34
+ require 'ruby_json_parser'
35
+
36
+ lexer = MiniRuby::Lexer.new(<<~RUBY)
37
+ foo = 5
38
+ foo * 3.2
39
+ RUBY
40
+ lexer.next #=> Token(:identifier, "foo")
41
+ lexer.next #=> Token(:equal)
42
+ lexer.next #=> Token(:integer, "5")
43
+ lexer.next #=> Token(:newline)
44
+ lexer.next #=> Token(:identifier, "foo")
45
+ lexer.next #=> Token(:star)
46
+ lexer.next #=> Token(:float, "3.2")
47
+ lexer.next #=> Token(:newline)
48
+ lexer.next #=> Token(:end_of_file)
49
+ ```
50
+
51
+ There is a simplified API that lets you generate an array of all tokens.
52
+
53
+ ```rb
54
+ require 'ruby_json_parser'
55
+
56
+ MiniRuby.lex(<<~RUBY)
57
+ foo = 5
58
+ foo * 3.2
59
+ RUBY
60
+ #=> [Token(:lbrace), Token(:string, "some"), Token(:colon), Token(:lbracket), Token(:string, "json"), Token(:comma), Token(:number, "2e-29"), Token(:comma), Token(:string, "text"), Token(:rbracket), Token(:rbrace)]
61
+ ```
62
+
63
+ ### Parser
64
+
65
+ This library implements a MiniRuby parser.
66
+ You can use it by calling `MiniRuby.parse` passing in a string
67
+ with source code.
68
+
69
+ It returns `MiniRuby::Parser::Result` which contains the produced AST (Abstract Syntax Tree) and the list of encountered errors.
70
+
71
+ ```rb
72
+ require 'ruby_json_parser'
73
+
74
+ MiniRuby.parse(<<~RUBY)
75
+ a = 0
76
+ while a < 5
77
+ a = a + 2
78
+ puts(a)
79
+ end
80
+
81
+ a
82
+ RUBY
83
+ #=> <MiniRuby::Parser::Result>
84
+ # AST:
85
+ # (program
86
+ # (expr_stmt
87
+ # (assignment
88
+ # a
89
+ # 0))
90
+ # (expr_stmt
91
+ # (while
92
+ # (bin_expr
93
+ # <
94
+ # a
95
+ # 5)
96
+ # (then
97
+ # (expr_stmt
98
+ # (assignment
99
+ # a
100
+ # (bin_expr
101
+ # +
102
+ # a
103
+ # 2)))
104
+ # (expr_stmt
105
+ # (call
106
+ # puts
107
+ # a)))))
108
+ # (expr_stmt
109
+ # a))
110
+
111
+ result = MiniRuby.parse('if foo; puts("lol")')
112
+ #=> <MiniRuby::Parser::Result>
113
+ # !Errors!
114
+ # - unexpected END_OF_FILE, expected end
115
+ #
116
+ # AST:
117
+ # (program
118
+ # (expr_stmt
119
+ # (invalid Token(:end_of_file, S(P(0), P(0))))))
120
+
121
+ result.ast # get the AST
122
+ result.err? # check if there are any errors
123
+ result.errors # get the list of errors
124
+ ```
125
+
126
+ All AST nodes are implemented as classes under the `MiniRuby::AST` module.
127
+ AST nodes have an `inspect` method that presents their structure in the [S-expression](https://en.wikipedia.org/wiki/S-expression) format.
128
+ You can also use `#to_s` to convert them to a Ruby-like human readable format.
129
+
130
+ ```rb
131
+ result = MiniRuby.parse(<<~RUBY)
132
+ a = 5
133
+ if a > 2
134
+ a = -1
135
+ end
136
+
137
+ puts(a)
138
+ RUBY
139
+ ast = result.ast
140
+
141
+ puts ast.inspect # S-expression format
142
+ # (program
143
+ # (expr_stmt
144
+ # (assignment
145
+ # a
146
+ # 5))
147
+ # (expr_stmt
148
+ # (if
149
+ # (bin_expr
150
+ # >
151
+ # a
152
+ # 2)
153
+ # (then
154
+ # (expr_stmt
155
+ # (assignment
156
+ # a
157
+ # (unary_expr
158
+ # -
159
+ # 1))))))
160
+ # (expr_stmt
161
+ # (call
162
+ # puts
163
+ # a)))
164
+
165
+ puts ast.to_s # Ruby-like format
166
+ # a = 5
167
+ # if a > 2
168
+ # a = -1
169
+ # end
170
+ # puts(a)
171
+
172
+ ast.class #=> MiniRuby::AST::ProgramNode
173
+
174
+ ast.statements[0].expression.class #=> MiniRuby::AST::AssignmentExpressionNode
175
+ ast.statements[0].expression.value #=> MiniRuby::AST::IntegerLiteralNode("5")
176
+ ```
177
+
178
+ ### Bytecode Compiler
179
+
180
+ This library implements a MiniRuby bytecode compiler.
181
+ You can use it by calling `MiniRuby.compile` passing in a string
182
+ with source code.
183
+
184
+ It returns `MiniRuby::BytecodeFunction`, an executable chunk of bytecode.
185
+
186
+
187
+ ```rb
188
+ require 'ruby_json_parser'
189
+
190
+ func = MiniRuby.compile(<<~RUBY)
191
+ a = 0
192
+ while a < 5
193
+ a = a + 2
194
+ puts(a)
195
+ end
196
+
197
+ a
198
+ RUBY
199
+ # == BytecodeFunction <main> at: <main> ==
200
+ # 0000 18 01 PREP_LOCALS 1
201
+ # 0002 0F 00 LOAD_VALUE 0 (0)
202
+ # 0004 1A 01 SET_LOCAL 1
203
+ # 0006 01 POP
204
+ # 0007 12 NIL
205
+ # 0008 19 01 GET_LOCAL 1
206
+ # 0010 0F 01 LOAD_VALUE 1 (5)
207
+ # 0012 0C LESS
208
+ # 0013 16 10 JUMP_UNLESS 16
209
+ # 0015 01 POP
210
+ # 0016 19 01 GET_LOCAL 1
211
+ # 0018 0F 02 LOAD_VALUE 2 (2)
212
+ # 0020 04 ADD
213
+ # 0021 1A 01 SET_LOCAL 1
214
+ # 0023 01 POP
215
+ # 0024 1B SELF
216
+ # 0025 19 01 GET_LOCAL 1
217
+ # 0027 17 03 CALL 3 (#<MiniRuby::CallInfo:0x0000000103651ef0 @name=:puts, @arg_count=1>)
218
+ # 0029 15 18 LOOP 24
219
+ # 0031 01 POP
220
+ # 0032 19 01 GET_LOCAL 1
221
+ # 0034 13 RETURN
222
+
223
+ func.class #=> MiniRuby::BytecodeFunction
224
+ ```
225
+
226
+ You can also use the compiler directly to compile an already produced AST.
227
+
228
+ ```rb
229
+ require 'ruby_json_parser'
230
+
231
+ parse_result = MiniRuby.parse(<<~RUBY)
232
+ a = 0
233
+ while a < 5
234
+ a = a + 2
235
+ puts(a)
236
+ end
237
+
238
+ a
239
+ RUBY
240
+
241
+ func = MiniRuby::Compiler.compile_ast(parse_result.ast)
242
+ # == BytecodeFunction <main> at: <main> ==
243
+ # 0000 18 01 PREP_LOCALS 1
244
+ # 0002 0F 00 LOAD_VALUE 0 (0)
245
+ # 0004 1A 01 SET_LOCAL 1
246
+ # 0006 01 POP
247
+ # 0007 12 NIL
248
+ # 0008 19 01 GET_LOCAL 1
249
+ # 0010 0F 01 LOAD_VALUE 1 (5)
250
+ # 0012 0C LESS
251
+ # 0013 16 10 JUMP_UNLESS 16
252
+ # 0015 01 POP
253
+ # 0016 19 01 GET_LOCAL 1
254
+ # 0018 0F 02 LOAD_VALUE 2 (2)
255
+ # 0020 04 ADD
256
+ # 0021 1A 01 SET_LOCAL 1
257
+ # 0023 01 POP
258
+ # 0024 1B SELF
259
+ # 0025 19 01 GET_LOCAL 1
260
+ # 0027 17 03 CALL 3 (#<MiniRuby::CallInfo:0x0000000103651ef0 @name=:puts, @arg_count=1>)
261
+ # 0029 15 18 LOOP 24
262
+ # 0031 01 POP
263
+ # 0032 19 01 GET_LOCAL 1
264
+ # 0034 13 RETURN
265
+
266
+ func.class #=> MiniRuby::BytecodeFunction
267
+ ```
268
+
269
+ ### VM
270
+
271
+ This library implements a MiniRuby Virtual Machine.
272
+ You can use it by calling `MiniRuby.interpret` passing in a string
273
+ with source code.
274
+
275
+ It returns the last computed value in the bytecode.
276
+
277
+
278
+ ```rb
279
+ require 'ruby_json_parser'
280
+
281
+ result = MiniRuby.interpret(<<~RUBY)
282
+ a = 0
283
+ while a < 5
284
+ a = a + 2
285
+ puts(a)
286
+ end
287
+
288
+ a
289
+ RUBY
290
+ # 2
291
+ # 4
292
+ # 6
293
+
294
+ result == 6 #=> true
295
+ ```
296
+
297
+ You can also use the VM directly to interpret an already produced piece of bytecode.
298
+
299
+ ```rb
300
+ require 'ruby_json_parser'
301
+
302
+ func = MiniRuby::Compiler.compile_source(<<~RUBY)
303
+ a = 0
304
+ while a < 5
305
+ a = a + 2
306
+ puts(a)
307
+ end
308
+
309
+ a
310
+ RUBY
311
+
312
+ result = MiniRuby::VM.run(func)
313
+ # 2
314
+ # 4
315
+ # 6
316
+
317
+ result == 6 #=> true
318
+ ```
319
+
320
+ ## Development
321
+
322
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
323
+
324
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
325
+
326
+ ## Contributing
327
+
328
+ Bug reports and pull requests are welcome on GitHub at https://github.com/Verseth/miniruby.
329
+
330
+ ## License
331
+
332
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'minitest/test_task'
5
+
6
+ Minitest::TestTask.create
7
+
8
+ require 'rubocop/rake_task'
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[test rubocop]