├── .travis.yml ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── lib └── minruby.rb ├── minruby.gemspec └── test ├── minruby_test.rb └── test_helper.rb /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: ruby 3 | rvm: 4 | - 2.3.1 5 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Yusuke Endoh 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 『Ruby で学ぶ Ruby』用補助ライブラリ 2 | 3 | ASCII.jp の Web 連載『[Ruby で学ぶ Ruby](http://ascii.jp/elem/000/001/230/1230449/)』のための補助ライブラリです。 4 | 5 | ## インストール方法 6 | 7 | コンソールで 8 | 9 | gem install minruby 10 | 11 | というコマンドを実行してください。 12 | また、あなたのプログラムの最初の行に 13 | 14 | require "minruby" 15 | 16 | という行を追加してください。 17 | 18 | ## 別のインストール方法 19 | 20 | `gem` でのインストールがうまく行かない場合は、[ライブラリのファイル](https://raw.githubusercontent.com/mame/minruby/master/lib/minruby.rb)をダウンロードしてあなたのプログラムと同じフォルダに置いてください。それから、あなたのプログラムの最初の行に 21 | 22 | require "./minruby" 23 | 24 | という行を追加してください。 25 | 26 | ## 使い方 27 | 28 | 連載記事を参照してください。第 4 回以降でこのライブラリを使用しています。 29 | 30 | ## ライセンス 31 | 32 | [MIT License](http://opensource.org/licenses/MIT) 33 | 34 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rake/testtask" 3 | 4 | Rake::TestTask.new(:test) do |t| 5 | t.libs << "test" 6 | t.libs << "lib" 7 | t.test_files = FileList['test/**/*_test.rb'] 8 | end 9 | 10 | task :default => :test 11 | -------------------------------------------------------------------------------- /lib/minruby.rb: -------------------------------------------------------------------------------- 1 | require "pp" 2 | require "ripper" 3 | 4 | class MinRubyParser 5 | def self.minruby_parse(program) 6 | MinRubyParser.new.minruby_parse(program) 7 | end 8 | 9 | def minruby_parse(program) 10 | simplify(Ripper.sexp(program)) 11 | end 12 | 13 | def simplify(exp) 14 | case exp[0] 15 | when :program, :bodystmt 16 | make_stmts(exp[1]) 17 | when :def 18 | name = exp[1][1] 19 | params = exp[2] 20 | params = params[1] if params[0] == :paren 21 | params = (params[1] || []).map {|a| a[1] } 22 | body = simplify(exp[3]) 23 | ["func_def", name, params, body] 24 | when :call 25 | recv = simplify(exp[1]) 26 | name = exp[3][1] 27 | ["method_call", recv, name, []] 28 | when :fcall 29 | name = exp[1][1] 30 | ["func_call", name] 31 | when :method_add_arg 32 | call = simplify(exp[1]) 33 | e = exp[2] 34 | e = e[1] || [] if e[0] == :arg_paren 35 | e = e[1] || [] if e[0] == :args_add_block 36 | e = e.map {|e_| simplify(e_) } 37 | call[(call[0] == "func_call" ? 2 : 3)..-1] = e 38 | call 39 | when :command 40 | name = exp[1][1] 41 | args = exp[2][1].map {|e_| simplify(e_) } 42 | ["func_call", name, *args] 43 | when :if, :elsif 44 | cond_exp = simplify(exp[1]) 45 | then_exp = make_stmts(exp[2]) 46 | if exp[3] 47 | if exp[3][0] == :elsif 48 | else_exp = simplify(exp[3]) 49 | else 50 | else_exp = make_stmts(exp[3][1]) 51 | end 52 | end 53 | ["if", cond_exp, then_exp, else_exp] 54 | when :ifop 55 | cond_exp = simplify(exp[1]) 56 | then_exp = simplify(exp[2]) 57 | else_exp = simplify(exp[3]) 58 | ["if", cond_exp, then_exp, else_exp] 59 | when :if_mod 60 | cond_exp = simplify(exp[1]) 61 | then_exp = make_stmts([exp[2]]) 62 | ["if", cond_exp, then_exp, nil] 63 | when :while 64 | cond_exp = simplify(exp[1]) 65 | body_exp = make_stmts(exp[2]) 66 | ["while", cond_exp, body_exp] 67 | when :while_mod 68 | cond_exp = simplify(exp[1]) 69 | body_exp = make_stmts(exp[2][1][1]) 70 | ["while2", cond_exp, body_exp] 71 | when :binary 72 | exp1 = simplify(exp[1]) 73 | op = exp[2] 74 | exp2 = simplify(exp[3]) 75 | [op.to_s, exp1, exp2] 76 | when :var_ref 77 | case exp[1][0] 78 | when :@kw 79 | case exp[1][1] 80 | when "nil" then ["lit", nil] 81 | when "true" then ["lit", true] 82 | when "false" then ["lit", false] 83 | else 84 | raise 85 | end 86 | when :@ident 87 | ["var_ref", exp[1][1]] 88 | when :@const 89 | ["const_ref", exp[1][1]] 90 | end 91 | when :@int 92 | ["lit", exp[1].to_i] 93 | when :unary 94 | v = simplify(exp[2]) 95 | raise if v[0] != "lit" 96 | ["lit", -v[1]] 97 | when :string_literal 98 | ["lit", exp[1][1] ? exp[1][1][1] : ""] 99 | when :symbol_literal 100 | ["lit", exp[1][1][1].to_sym] 101 | when :assign 102 | case exp[1][0] 103 | when :var_field 104 | ["var_assign", exp[1][1][1], simplify(exp[2])] 105 | when :aref_field 106 | ["ary_assign", simplify(exp[1][1]), simplify(exp[1][2][1][0]), simplify(exp[2])] 107 | else 108 | raise 109 | end 110 | when :case 111 | arg = simplify(exp[1]) 112 | when_clauses = [] 113 | exp = exp[2] 114 | while exp && exp[0] == :when 115 | pat = exp[1].map {|e_| simplify(e_) } 116 | when_clauses << [pat, make_stmts(exp[2])] 117 | exp = exp[3] 118 | end 119 | else_clause = make_stmts(exp[1]) if exp 120 | #["case", arg, when_clauses, else_clause] 121 | 122 | exp = else_clause 123 | when_clauses.reverse_each do |patterns, stmts| 124 | patterns.each do |pattern| 125 | exp = ["if", ["==", arg, pattern], stmts, exp] 126 | end 127 | end 128 | exp 129 | when :method_add_block 130 | call = simplify(exp[1]) 131 | blk_params = exp[2][1][1][1].map {|a| a[1] } 132 | blk_body = exp[2][2].map {|e_| simplify(e_) } 133 | call << blk_params << blk_body 134 | when :aref 135 | ["ary_ref", simplify(exp[1]), *exp[2][1].map {|e_| simplify(e_) }] 136 | when :array 137 | ["ary_new", *(exp[1] ? exp[1].map {|e_| simplify(e_) } : [])] 138 | when :hash 139 | kvs = ["hash_new"] 140 | if exp[1] 141 | exp[1][1].each do |e_| 142 | key = simplify(e_[1]) 143 | val = simplify(e_[2]) 144 | kvs << key << val 145 | end 146 | end 147 | kvs 148 | when :void_stmt 149 | ["lit", nil] 150 | when :paren 151 | simplify(exp[1][0]) 152 | else 153 | pp exp 154 | raise "unsupported node: #{ exp[0] }" 155 | end 156 | end 157 | 158 | def make_stmts(exps) 159 | exps = exps.map {|exp| simplify(exp) } 160 | exps.size == 1 ? exps[0] : ["stmts", *exps] 161 | end 162 | end 163 | 164 | def minruby_load() 165 | File.read(ARGV.shift) 166 | end 167 | 168 | def minruby_parse(src) 169 | MinRubyParser.minruby_parse(src) 170 | end 171 | 172 | def minruby_call(mhd, args) 173 | send(mhd, *args) 174 | end 175 | -------------------------------------------------------------------------------- /minruby.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | Gem::Specification.new do |spec| 3 | spec.name = "minruby" 4 | spec.version = "1.0.4" 5 | spec.authors = ["Yusuke Endoh"] 6 | spec.email = ["mame@ruby-lang.org"] 7 | 8 | spec.summary = %q{A helper library for "Ruby de manabu Ruby"} 9 | spec.description = %q{This library provides some helper modules to implement a toy Ruby implementation. This is created for a series of articles, "Ruby de manabu Ruby (Learning Ruby by implementing Ruby)", in ASCII.jp} 10 | spec.homepage = "http://github.com/mame/minruby/" 11 | spec.license = "MIT" 12 | 13 | spec.files = `git ls-files -z`.split("\x0").reject do |f| 14 | f.match(%r{^(test|spec|features)/}) 15 | end 16 | spec.bindir = "exe" 17 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 18 | spec.require_paths = ["lib"] 19 | 20 | spec.add_development_dependency "bundler", "~> 1.13" 21 | spec.add_development_dependency "rake", "~> 10.0" 22 | end 23 | -------------------------------------------------------------------------------- /test/minruby_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class TTest < Minitest::Test 4 | def test_parse_literals 5 | assert_equal ["lit", 1], minruby_parse("1") 6 | assert_equal ["lit", -1], minruby_parse("-1") 7 | assert_equal ["lit", "foo"], minruby_parse("\"foo\"") 8 | assert_equal ["lit", nil], minruby_parse("nil") 9 | assert_equal ["lit", true], minruby_parse("true") 10 | assert_equal ["lit", false], minruby_parse("false") 11 | end 12 | 13 | def test_parse_binary 14 | assert_equal ["+" , ["lit", 1], ["lit", 2]], minruby_parse("1 + 2") 15 | assert_equal ["-" , ["lit", 1], ["lit", 2]], minruby_parse("1 - 2") 16 | assert_equal ["*" , ["lit", 1], ["lit", 2]], minruby_parse("1 * 2") 17 | assert_equal ["/" , ["lit", 1], ["lit", 2]], minruby_parse("1 / 2") 18 | assert_equal ["%" , ["lit", 1], ["lit", 2]], minruby_parse("1 % 2") 19 | assert_equal ["<" , ["lit", 1], ["lit", 2]], minruby_parse("1 < 2") 20 | assert_equal ["<=", ["lit", 1], ["lit", 2]], minruby_parse("1 <= 2") 21 | assert_equal ["==", ["lit", 1], ["lit", 2]], minruby_parse("1 == 2") 22 | assert_equal [">=", ["lit", 1], ["lit", 2]], minruby_parse("1 >= 2") 23 | assert_equal [">" , ["lit", 1], ["lit", 2]], minruby_parse("1 > 2") 24 | end 25 | 26 | def test_parse_stmts 27 | assert_equal ["stmts", ["lit", 1], ["lit", 2], ["lit", 3]], minruby_parse("1; 2; 3") 28 | end 29 | 30 | def test_parse_var 31 | assert_equal ["stmts", ["var_assign", "x", ["lit", 1]], ["var_ref", "x"]], minruby_parse("x = 1; x") 32 | end 33 | 34 | def test_parse_func_def 35 | assert_equal ["func_def", "foo", [], ["lit", 42]], minruby_parse("def foo() 42; end") 36 | end 37 | 38 | def test_parse_func_call 39 | assert_equal ["func_call", "foo", ["lit", 1], ["lit", 42]], minruby_parse("foo(1, 42)") 40 | assert_equal ["func_call", "foo", ["lit", 1], ["lit", 42]], minruby_parse("foo 1, 42") 41 | end 42 | 43 | def test_parse_if 44 | assert_equal ["if", ["lit", true], ["lit", 1], ["lit", 2]], minruby_parse("if true; 1 else 2 end") 45 | assert_equal ["if", ["lit", true], ["lit", 1], nil], minruby_parse("if true; 1 end") 46 | assert_equal ["if", ["lit", true], ["lit", 1], nil], minruby_parse("1 if true") 47 | end 48 | 49 | def test_parse_while 50 | assert_equal ["while", ["lit", true], ["lit", 1]], minruby_parse("while true; 1 end") 51 | end 52 | 53 | def test_parse_while2 54 | assert_equal ["while2", ["lit", true], ["lit", 1]], minruby_parse("begin 1; end while true") 55 | end 56 | 57 | def test_parse_ary 58 | assert_equal ["stmts", 59 | ["var_assign", "a", ["ary_new", ["lit", 1], ["lit", 2], ["lit", 3]]], 60 | ["ary_ref", ["var_ref", "a"], ["lit", 0]], 61 | ["ary_assign", ["var_ref", "a"], ["lit", 0], ["lit", 42]] 62 | ], minruby_parse("a = [1, 2, 3]; a[0]; a[0] = 42") 63 | end 64 | 65 | def test_parse_hash 66 | assert_equal ["stmts", 67 | ["var_assign", "h", ["hash_new", ["lit", "foo"], ["lit", 42]]], 68 | ["ary_ref", ["var_ref", "h"], ["lit", "foo"]], 69 | ["ary_assign", ["var_ref", "h"], ["lit", "foo"], ["lit", nil]] 70 | ], minruby_parse("h = {\"foo\" => 42}; h[\"foo\"]; h[\"foo\"] = nil") 71 | end 72 | 73 | def test_load 74 | argv = ARGV.dup 75 | ARGV.clear 76 | ARGV << __FILE__ 77 | assert_equal File.read(__FILE__), minruby_load() 78 | ensure 79 | ARGV.clear 80 | ARGV.replace argv 81 | end 82 | 83 | def foo(x, y, z) 84 | x + y * z 85 | end 86 | 87 | def test_call 88 | assert_equal 7, minruby_call(:foo, [1, 2, 3]) 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) 2 | require 'minruby' 3 | 4 | require 'minitest/autorun' 5 | --------------------------------------------------------------------------------