miniruby 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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]