├── .gitignore ├── .gitmodules ├── .travis.yml ├── COPYING.txt ├── Gemfile ├── Gemfile.lock ├── Guardfile ├── README.md ├── TODO.md ├── erruby ├── erruby_test ├── pipe_dot.rb └── undefined_method.rb ├── pre_compile ├── rb_src └── erruby.rb ├── rb_test ├── _load.rb ├── _require_relative.rb ├── and_dot.rb ├── array_class.rb ├── boolean_class.rb ├── class_cross_call.rb ├── class_def.rb ├── class_inherent.rb ├── class_method.rb ├── const_def.rb ├── file.rb ├── fixnum.rb ├── global_var.rb ├── hello_world.rb ├── integer_class.rb ├── ivar.rb ├── load.rb ├── method_def.rb ├── nested_block.rb ├── nested_const.rb ├── nil_class.rb ├── require_relative.rb ├── sysrb_out │ ├── .gitkeep │ ├── and_dot.out │ ├── array_class.out │ ├── boolean_class.out │ ├── class_cross_call.out │ ├── class_def.out │ ├── class_inherent.out │ ├── class_method.out │ ├── const_def.out │ ├── file.out │ ├── fixnum.out │ ├── global_var.out │ ├── hello_world.out │ ├── integer_class.out │ ├── ivar.out │ ├── load.out │ ├── method_def.out │ ├── nested_block.out │ ├── nested_const.out │ ├── nil_class.out │ ├── require_relative.out │ └── var_assign.out └── var_assign.rb ├── rebar.config ├── src ├── erb.erl ├── erruby.app.src ├── erruby.erl ├── erruby_app.erl ├── erruby_array.erl ├── erruby_boolean.erl ├── erruby_class.erl ├── erruby_debug.erl ├── erruby_fixnum.erl ├── erruby_integer.erl ├── erruby_lib │ └── erruby_file.erl ├── erruby_nil.erl ├── erruby_object.erl ├── erruby_rb.erl ├── erruby_sup.erl ├── erruby_vm.erl └── rb.hrl └── test.rb /.gitignore: -------------------------------------------------------------------------------- 1 | .eunit 2 | deps 3 | *.o 4 | *.beam 5 | *.plt 6 | .*-* 7 | erl_crash.dump 8 | ebin 9 | rel/example_project 10 | .concrete/DEV_MODE 11 | .rebar 12 | rb_test/*.out 13 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ext/ruby/spec"] 2 | path = ext/ruby/spec 3 | url = https://github.com/ruby/spec.git 4 | [submodule "ext/ruby/mspec"] 5 | path = ext/ruby/mspec 6 | url = https://github.com/ruby/mspec.git 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: erlang 2 | otp_release: 3 | - 18.0 4 | - 18.1 5 | before_script: 6 | - rvm install 2.3.1 7 | - rvm use 2.3.1 8 | - bundle install 9 | script: 10 | - rebar compile && ./test.rb 11 | 12 | -------------------------------------------------------------------------------- /COPYING.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Yu Hsiang Lin. 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. 22 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | ruby "2.3.1" 2 | source "https://rubygems.org" 3 | 4 | gem 'erlport-ast_mapping', github: "johnlinvc/erlport-ast_mapping", branch: "ruby_2_3" 5 | gem 'guard' 6 | gem 'guard-shell' 7 | gem 'guard-rebar', github: "johnlinvc/guard-rebar", branch: "feature/update_to_guard_2_13" 8 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: git://github.com/johnlinvc/erlport-ast_mapping.git 3 | revision: 6231d81b6101919dd886bc42bcf8d0c76c8cd9b3 4 | branch: ruby_2_3 5 | specs: 6 | erlport-ast_mapping (0.1.0) 7 | ast 8 | erruby_parser (~> 2.3) 9 | 10 | GIT 11 | remote: git://github.com/johnlinvc/guard-rebar.git 12 | revision: 77c6e404283477fec27d1dcb71c65d1fcdbf94f5 13 | branch: feature/update_to_guard_2_13 14 | specs: 15 | guard-rebar (1.1.1) 16 | guard (~> 2.13) 17 | guard-compat (~> 1.1) 18 | 19 | GEM 20 | remote: https://rubygems.org/ 21 | specs: 22 | ast (2.3.0) 23 | coderay (1.1.0) 24 | erruby_parser (2.3.1.2) 25 | ast (~> 2.2) 26 | ffi (1.9.10) 27 | formatador (0.2.5) 28 | guard (2.13.0) 29 | formatador (>= 0.2.4) 30 | listen (>= 2.7, <= 4.0) 31 | lumberjack (~> 1.0) 32 | nenv (~> 0.1) 33 | notiffany (~> 0.0) 34 | pry (>= 0.9.12) 35 | shellany (~> 0.0) 36 | thor (>= 0.18.1) 37 | guard-compat (1.2.1) 38 | guard-shell (0.7.1) 39 | guard (>= 2.0.0) 40 | guard-compat (~> 1.0) 41 | listen (3.0.3) 42 | rb-fsevent (>= 0.9.3) 43 | rb-inotify (>= 0.9) 44 | lumberjack (1.0.9) 45 | method_source (0.8.2) 46 | nenv (0.2.0) 47 | notiffany (0.0.8) 48 | nenv (~> 0.1) 49 | shellany (~> 0.0) 50 | pry (0.10.2) 51 | coderay (~> 1.1.0) 52 | method_source (~> 0.8.1) 53 | slop (~> 3.4) 54 | rb-fsevent (0.9.6) 55 | rb-inotify (0.9.5) 56 | ffi (>= 0.5.0) 57 | shellany (0.0.1) 58 | slop (3.6.0) 59 | thor (0.19.1) 60 | 61 | PLATFORMS 62 | ruby 63 | 64 | DEPENDENCIES 65 | erlport-ast_mapping! 66 | guard 67 | guard-rebar! 68 | guard-shell 69 | 70 | RUBY VERSION 71 | ruby 2.3.1p112 72 | 73 | BUNDLED WITH 74 | 1.12.5 75 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | # A sample Guardfile 2 | # More info at https://github.com/guard/guard#readme 3 | 4 | ## Uncomment and set this to only include directories you want to watch 5 | # directories %w(app lib config test spec features) \ 6 | # .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")} 7 | 8 | ## Note: if you are using the `directories` clause above and you are not 9 | ## watching the project directory ('.'), then you will want to move 10 | ## the Guardfile to a watched dir and symlink it back, e.g. 11 | # 12 | # $ mkdir config 13 | # $ mv Guardfile config/ 14 | # $ ln -s config/Guardfile . 15 | # 16 | # and, you'll have to watch "config/Guardfile" instead of "Guardfile" 17 | 18 | guard 'rebar-compile', all_on_start: true do 19 | watch(%r{src/.*\.erl}) 20 | watch(%r{test/.*\.erl}) 21 | end 22 | 23 | guard :shell do 24 | watch(/rb_test\/(.*)\.rb/) {|m| `./test.rb -v #{m[0]}` } 25 | end 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ErRuby - an implementation of the Ruby language on Erlang 2 | [![Build Status](https://travis-ci.org/johnlinvc/erruby.svg?branch=develop)](https://travis-ci.org/johnlinvc/erruby) 3 | ## About 4 | 5 | ErRuby is an implementation of the Ruby language using Erlang. 6 | 7 | It aims to bring some concurrency features to ruby by experimenting. 8 | 9 | It's still a work in progress. So use it at your own risk. 10 | 11 | ## Install 12 | 13 | ### Prerequisites 14 | 15 | - erlang vm 16 | - rebar2 17 | - ruby (2.3.1) 18 | 19 | To install erlang & rebar on OS X, using homebrew 20 | 21 | brew install erlang rebar 22 | 23 | ### Building 24 | 25 | After getting the source of ErRuby, get the gems for parser with bundler using: 26 | 27 | bundle install 28 | 29 | 30 | Then get the deps of erlang modules by using: 31 | 32 | rebar get-deps 33 | 34 | Last, compile ErRuby with: 35 | 36 | rebar compile 37 | 38 | 39 | Test the build result with: 40 | 41 | ./test.rb 42 | 43 | It should output `everything pass` 44 | 45 | 46 | ## Goals 47 | 48 | - Concurrent Features. 49 | - Run mspec. 50 | - GC. 51 | - Friendly installation with rvm/rbenv 52 | 53 | ## Supported features 54 | 55 | Currently it support some of the basic ruby constructs. 56 | 57 | Supported features: 58 | 59 | - `method` definition & calling. 60 | - singleton methods, class methods. 61 | - `class` and inheritance. 62 | - `block` and `yield`. 63 | - Constants. 64 | - Local variables. 65 | - Instance variables. 66 | - `load` & `require_relative`. 67 | - `Boolean` & `Integer` with basic methods. 68 | - `String` literal. 69 | - `Array` literal. 70 | 71 | Unsupported core features 72 | 73 | - class initializer, class instance variables. 74 | - `module` definition, `include`, `extend`. 75 | - variadic argument in function. 76 | - keyword argument in function. 77 | - GC. 78 | 79 | ### Class & inherentance 80 | ```ruby 81 | class Foo 82 | def to_s 83 | "foo" 84 | end 85 | end 86 | 87 | class Bar < Foo 88 | end 89 | 90 | class Alice < Bar 91 | def to_s 92 | "i'm alice" 93 | end 94 | def self.name 95 | "Alice" 96 | end 97 | end 98 | 99 | puts Foo.new.to_s # "foo" 100 | puts Bar.new.to_s # "foo" 101 | puts Alice.new.to_s # "i'm alice" 102 | puts Alice.name # "Alice" 103 | ``` 104 | 105 | ### block 106 | ```ruby 107 | def yield_with_arg(s,x) 108 | yield s,x 109 | end 110 | 111 | yield_with_arg("yield with","arg") do |ss, xx| 112 | puts ss # "yield with" 113 | puts xx # "arg" 114 | end 115 | 116 | 3.times do |i| 117 | puts i.to_s 118 | 4.times do |j| 119 | puts j.to_s 120 | end 121 | end 122 | 123 | ([1,2,3]*1000).pmap do |x| 124 | x+1 125 | end 126 | 127 | 128 | ``` 129 | 130 | 131 | ## License 132 | 133 | ErRuby is licensed to you under MIT license. See the [COPYING.txt](COPYING.txt) file for more details. 134 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | restructure to standard erlang style 2 | design the object system of ruby in erlang 3 | - everything is object, just like ruby 4 | - every object is a process. message invoke with message passing using the actor model. 5 | use otp to run the code 6 | -------------------------------------------------------------------------------- /erruby: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | pushd `dirname $0` > /dev/null 3 | export ERRUBY_PATH=`pwd` 4 | popd > /dev/null 5 | if [[ $* == *"-f"* ]] 6 | then 7 | escript "${ERRUBY_PATH}/ebin/erruby.beam" $* 8 | else 9 | erl -noshell +P 134217727 -pa ebin -s erruby main "{$*}" -s init stop 10 | fi 11 | -------------------------------------------------------------------------------- /erruby_test/pipe_dot.rb: -------------------------------------------------------------------------------- 1 | def cal 2 | 1 + 1 3 | end 4 | x = self|.cal 5 | puts(x.to_s) 6 | puts(x.to_s) 7 | puts(1|.to_s) 8 | 1|.to_s 9 | 10 | def str 11 | "hello future" 12 | end 13 | puts(self|.str) 14 | -------------------------------------------------------------------------------- /erruby_test/undefined_method.rb: -------------------------------------------------------------------------------- 1 | class Foo 2 | end 3 | f = Foo.new 4 | f.bar 5 | -------------------------------------------------------------------------------- /pre_compile: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | pushd ./deps/erlport > /dev/null 3 | make > /dev/null 4 | popd > /dev/null 5 | 6 | pushd ./deps/plists > /dev/null 7 | ./make.sh 8 | popd > /dev/null 9 | -------------------------------------------------------------------------------- /rb_src/erruby.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'rubygems' 4 | require 'bundler/setup' 5 | require 'erlport/ast_mapping' 6 | 7 | def parse(src) 8 | ErlPort::AstMapping.parse(src) 9 | end 10 | 11 | def install_encoder 12 | ErlPort::AstMapping.install_encoder 13 | end 14 | 15 | if __FILE__ == $0 16 | p parse("[1,2,3]".each_char.map(&:ord)) 17 | end 18 | -------------------------------------------------------------------------------- /rb_test/_load.rb: -------------------------------------------------------------------------------- 1 | puts "loaded" 2 | -------------------------------------------------------------------------------- /rb_test/_require_relative.rb: -------------------------------------------------------------------------------- 1 | puts "required file" 2 | -------------------------------------------------------------------------------- /rb_test/and_dot.rb: -------------------------------------------------------------------------------- 1 | puts(1&.to_s) 2 | nil&.not_exist_method 3 | -------------------------------------------------------------------------------- /rb_test/array_class.rb: -------------------------------------------------------------------------------- 1 | [true, false] 2 | [nil, nil] 3 | ["a","b","c"] 4 | [1,2].map{|x| puts(x.to_s)} 5 | puts "mul" 6 | ([1,2,3] * 3).map{|x| puts x.to_s} 7 | #a = 0 8 | #[1,2,3].map do |x| 9 | #a = a + x 10 | #end 11 | #puts a 12 | puts ["a","b","c"].at(1) 13 | puts ["a","b","c"].first 14 | puts ["a","b","c"].last 15 | 16 | puts [false].empty?.to_s 17 | puts [].empty?.to_s 18 | puts [1, 2, 3].length.to_s 19 | puts [1, 2, 3].size.to_s 20 | ary = ["a","b"] 21 | ary.concat ["c", "d"] 22 | puts ary.last 23 | ary2 = ary + ["e", "f"] 24 | puts ary2.last 25 | ary = ["a","b","c"] 26 | ary.push "d" 27 | puts ary.last 28 | ary << "e" << "f" 29 | puts ary.last 30 | array = ["b", "c"] 31 | array.unshift("a") 32 | puts array.first 33 | puts array.shift 34 | puts array.first 35 | 36 | puts [1, 2, 3, 4, 5].drop(1).length.to_s 37 | puts [1, 2, 3, 4, 5].drop(3).length.to_s 38 | puts [1, 2, 3, 4, 5].drop(0).length.to_s 39 | puts [1, 2, 3, 4, 5].drop(5).length.to_s 40 | puts [1, 2, 3, 4, 5].drop(10).length.to_s 41 | -------------------------------------------------------------------------------- /rb_test/boolean_class.rb: -------------------------------------------------------------------------------- 1 | puts (true).to_s 2 | puts (false).to_s 3 | 4 | puts (!true).to_s 5 | puts (!false).to_s 6 | 7 | puts (true == false).to_s 8 | puts (true == true).to_s 9 | puts (false == true).to_s 10 | puts (false == false).to_s 11 | 12 | puts (true & true).to_s 13 | puts (true & false).to_s 14 | puts (false & true).to_s 15 | puts (false & false).to_s 16 | 17 | puts (true ^ true).to_s 18 | puts (true ^ false).to_s 19 | puts (false ^ true).to_s 20 | puts (false ^ false).to_s 21 | 22 | puts (true | true).to_s 23 | puts (true | false).to_s 24 | puts (false | true).to_s 25 | puts (false | false).to_s 26 | 27 | puts true.to_s 28 | puts false.to_s 29 | 30 | puts true.inspect 31 | puts false.inspect 32 | -------------------------------------------------------------------------------- /rb_test/class_cross_call.rb: -------------------------------------------------------------------------------- 1 | class Foo 2 | def msg 3 | "hello" 4 | end 5 | end 6 | 7 | class Bar 8 | def say 9 | puts Foo.new.msg 10 | end 11 | end 12 | 13 | Bar.new.say 14 | -------------------------------------------------------------------------------- /rb_test/class_def.rb: -------------------------------------------------------------------------------- 1 | class Foo 2 | def message 3 | "hello world" 4 | end 5 | def hello 6 | puts message 7 | end 8 | end 9 | class Bar 10 | def message 11 | "hello bar" 12 | end 13 | def hello 14 | puts message 15 | end 16 | end 17 | f = Foo.new 18 | f.hello 19 | ff = Foo.new 20 | 21 | Bar.new.hello 22 | -------------------------------------------------------------------------------- /rb_test/class_inherent.rb: -------------------------------------------------------------------------------- 1 | class Foo 2 | def to_s 3 | "foo" 4 | end 5 | end 6 | 7 | class Bar < Foo 8 | end 9 | 10 | class Alice < Bar 11 | def to_s 12 | "i'm alice" 13 | end 14 | end 15 | 16 | puts Foo.new.to_s # "foo" 17 | puts Bar.new.to_s # "foo" 18 | puts Alice.new.to_s # "i'm alice" 19 | -------------------------------------------------------------------------------- /rb_test/class_method.rb: -------------------------------------------------------------------------------- 1 | class Foo 2 | def self.bar 3 | puts "bar" 4 | end 5 | end 6 | 7 | Foo.bar 8 | -------------------------------------------------------------------------------- /rb_test/const_def.rb: -------------------------------------------------------------------------------- 1 | HELLO_CONST = "hello const" 2 | puts HELLO_CONST 3 | -------------------------------------------------------------------------------- /rb_test/file.rb: -------------------------------------------------------------------------------- 1 | puts __FILE__ 2 | puts File.expand_path('../../../erruby/rb_test', __FILE__) 3 | -------------------------------------------------------------------------------- /rb_test/fixnum.rb: -------------------------------------------------------------------------------- 1 | puts 1.to_s 2 | puts 2.to_s 3 | puts -1.to_s 4 | puts 0.to_s 5 | puts 576460752303423488.to_s 6 | puts -576460752303423489.to_s 7 | puts (1+1).to_s 8 | puts (2-1).to_s 9 | puts (-(1)).to_s 10 | puts (3 % 2).to_s 11 | puts (4 * 2).to_s 12 | puts (3 / 2).to_s 13 | puts (4 ** 2).to_s 14 | puts (1 < 2).to_s 15 | puts (2 < 2).to_s 16 | puts (3 < 2).to_s 17 | puts (1 <= 2).to_s 18 | puts (2 <= 2).to_s 19 | puts (3 <= 2).to_s 20 | puts (1 > 2).to_s 21 | puts (2 > 2).to_s 22 | puts (3 > 2).to_s 23 | puts (1 >= 2).to_s 24 | puts (2 >= 2).to_s 25 | puts (3 >= 2).to_s 26 | puts (1 == 2).to_s 27 | puts (2 == 2).to_s 28 | puts (3 == 2).to_s 29 | puts (1 <=> 2).to_s 30 | puts (2 <=> 2).to_s 31 | puts (3 <=> 2).to_s 32 | -------------------------------------------------------------------------------- /rb_test/global_var.rb: -------------------------------------------------------------------------------- 1 | $Gvar = "hello gvar" 2 | puts $Gvar 3 | -------------------------------------------------------------------------------- /rb_test/hello_world.rb: -------------------------------------------------------------------------------- 1 | puts "hello world" 2 | -------------------------------------------------------------------------------- /rb_test/integer_class.rb: -------------------------------------------------------------------------------- 1 | puts 5.to_i.to_s 2 | puts 5.to_int.to_s 3 | puts 5.floor.to_s 4 | puts 5.ceil.to_s 5 | puts 5.truncate.to_s 6 | puts 5.numerator.to_s 7 | puts 5.ord.to_s 8 | puts 56.denominator.to_s 9 | puts 2.even?.to_s 10 | puts 3.even?.to_s 11 | puts 2.odd?.to_s 12 | puts 3.odd?.to_s 13 | puts 2.gcd(2).to_s 14 | puts 6.gcd(8).to_s 15 | puts 8.gcd(6).to_s 16 | puts 0.gcd(8).to_s 17 | puts 0.gcd(0).to_s 18 | puts 6.gcd(0).to_s 19 | puts 2.lcm(2).to_s 20 | puts 6.lcm(8).to_s 21 | puts 8.lcm(6).to_s 22 | puts 0.lcm(8).to_s 23 | puts 6.lcm(0).to_s 24 | puts 1.integer?.to_s 25 | puts 1.next.to_s 26 | puts (-1).next.to_s 27 | puts 1.succ.to_s 28 | puts (-1).succ.to_s 29 | puts "times" 30 | puts 5.times {|i| puts i.to_s}.to_s 31 | puts 0.times {|i| puts i.to_s}.to_s 32 | puts (-5).times {|i| puts i.to_s}.to_s 33 | puts "upto" 34 | puts (-2).upto(2){|i| puts i.to_s}.to_s 35 | puts (2).upto(2){|i| puts i.to_s}.to_s 36 | puts (3).upto(2){|i| puts i.to_s}.to_s 37 | puts "downto" 38 | puts (2).downto(-2){|i| puts i.to_s}.to_s 39 | puts (2).downto(2){|i| puts i.to_s}.to_s 40 | puts (5).downto(6){|i| puts i.to_s}.to_s 41 | puts "abs" 42 | puts (2).abs.to_s 43 | puts (-2).abs.to_s 44 | puts "magnitude" 45 | puts (2).magnitude.to_s 46 | puts (-2).magnitude.to_s 47 | -------------------------------------------------------------------------------- /rb_test/ivar.rb: -------------------------------------------------------------------------------- 1 | class Foo 2 | def setup 3 | @ivar = "hello" 4 | end 5 | def ivar 6 | @ivar 7 | end 8 | end 9 | foo = Foo.new 10 | foo.setup 11 | puts foo.ivar 12 | -------------------------------------------------------------------------------- /rb_test/load.rb: -------------------------------------------------------------------------------- 1 | load 'rb_test/_load.rb' 2 | load 'rb_test/_load.rb' 3 | -------------------------------------------------------------------------------- /rb_test/method_def.rb: -------------------------------------------------------------------------------- 1 | str = "not this" 2 | def hello_world_method_0 3 | "hello world no arg" 4 | end 5 | 6 | def hello_world_method_1(name) 7 | str = name 8 | str 9 | end 10 | 11 | def hello_world_method_block(n) 12 | puts n 13 | puts yield 14 | end 15 | 16 | def yield_with_arg(s,x) 17 | yield s,x 18 | end 19 | 20 | yield_with_arg("yield with","arg") do |ss, xx| 21 | puts ss # "yield with" 22 | puts xx # "arg" 23 | end 24 | 25 | puts hello_world_method_0 26 | puts hello_world_method_1("my name is erruby") 27 | hello_world_method_block("arg") { "hello block" } 28 | 29 | #hello_world_method_block(4){"hello"}.block1{"this"}.block2{"world"} 30 | -------------------------------------------------------------------------------- /rb_test/nested_block.rb: -------------------------------------------------------------------------------- 1 | 3.times do |i| 2 | puts i.to_s 3 | 4.times do |j| 4 | puts j.to_s 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /rb_test/nested_const.rb: -------------------------------------------------------------------------------- 1 | class Foo 2 | Bar = "hello nested" 3 | class Alice 4 | end 5 | end 6 | Bob = "outside bob" 7 | puts "Foo::Bar" 8 | puts Foo::Bar 9 | Foo::Alice::Bob = "inside Bob" 10 | puts Foo::Alice::Bob 11 | puts Bob 12 | -------------------------------------------------------------------------------- /rb_test/nil_class.rb: -------------------------------------------------------------------------------- 1 | nil 2 | 3 | nil & nil 4 | nil & true 5 | nil & false 6 | nil & "" 7 | 8 | nil ^ nil 9 | nil ^ true 10 | nil ^ false 11 | nil ^ "" 12 | 13 | nil | nil 14 | nil | true 15 | nil | false 16 | nil | "" 17 | 18 | nil.inspect 19 | 20 | nil.nil? 21 | 22 | nil.to_s 23 | -------------------------------------------------------------------------------- /rb_test/require_relative.rb: -------------------------------------------------------------------------------- 1 | require_relative "_require_relative" 2 | require_relative "_require_relative" 3 | -------------------------------------------------------------------------------- /rb_test/sysrb_out/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnlinvc/erruby/60df66495a01f9dda08bd3f670bfe9dc0661a168/rb_test/sysrb_out/.gitkeep -------------------------------------------------------------------------------- /rb_test/sysrb_out/and_dot.out: -------------------------------------------------------------------------------- 1 | 1 2 | -------------------------------------------------------------------------------- /rb_test/sysrb_out/array_class.out: -------------------------------------------------------------------------------- 1 | 1 2 | 2 3 | mul 4 | 1 5 | 2 6 | 3 7 | 1 8 | 2 9 | 3 10 | 1 11 | 2 12 | 3 13 | b 14 | a 15 | c 16 | false 17 | true 18 | 3 19 | 3 20 | d 21 | f 22 | d 23 | f 24 | a 25 | a 26 | b 27 | 4 28 | 2 29 | 5 30 | 0 31 | 0 32 | -------------------------------------------------------------------------------- /rb_test/sysrb_out/boolean_class.out: -------------------------------------------------------------------------------- 1 | true 2 | false 3 | false 4 | true 5 | false 6 | true 7 | false 8 | true 9 | true 10 | false 11 | false 12 | false 13 | false 14 | true 15 | true 16 | false 17 | true 18 | true 19 | true 20 | false 21 | true 22 | false 23 | true 24 | false 25 | -------------------------------------------------------------------------------- /rb_test/sysrb_out/class_cross_call.out: -------------------------------------------------------------------------------- 1 | hello 2 | -------------------------------------------------------------------------------- /rb_test/sysrb_out/class_def.out: -------------------------------------------------------------------------------- 1 | hello world 2 | hello bar 3 | -------------------------------------------------------------------------------- /rb_test/sysrb_out/class_inherent.out: -------------------------------------------------------------------------------- 1 | foo 2 | foo 3 | i'm alice 4 | -------------------------------------------------------------------------------- /rb_test/sysrb_out/class_method.out: -------------------------------------------------------------------------------- 1 | bar 2 | -------------------------------------------------------------------------------- /rb_test/sysrb_out/const_def.out: -------------------------------------------------------------------------------- 1 | hello const 2 | -------------------------------------------------------------------------------- /rb_test/sysrb_out/file.out: -------------------------------------------------------------------------------- 1 | rb_test/file.rb 2 | /Users/johnlinvc/Projs/erruby/erruby/rb_test 3 | -------------------------------------------------------------------------------- /rb_test/sysrb_out/fixnum.out: -------------------------------------------------------------------------------- 1 | 1 2 | 2 3 | -1 4 | 0 5 | 576460752303423488 6 | -576460752303423489 7 | 2 8 | 1 9 | -1 10 | 1 11 | 8 12 | 1 13 | 16 14 | true 15 | false 16 | false 17 | true 18 | true 19 | false 20 | false 21 | false 22 | true 23 | false 24 | true 25 | true 26 | false 27 | true 28 | false 29 | -1 30 | 0 31 | 1 32 | -------------------------------------------------------------------------------- /rb_test/sysrb_out/global_var.out: -------------------------------------------------------------------------------- 1 | hello gvar 2 | -------------------------------------------------------------------------------- /rb_test/sysrb_out/hello_world.out: -------------------------------------------------------------------------------- 1 | hello world 2 | -------------------------------------------------------------------------------- /rb_test/sysrb_out/integer_class.out: -------------------------------------------------------------------------------- 1 | 5 2 | 5 3 | 5 4 | 5 5 | 5 6 | 5 7 | 5 8 | 1 9 | true 10 | false 11 | false 12 | true 13 | 2 14 | 2 15 | 2 16 | 8 17 | 0 18 | 6 19 | 2 20 | 24 21 | 24 22 | 0 23 | 0 24 | true 25 | 2 26 | 0 27 | 2 28 | 0 29 | times 30 | 0 31 | 1 32 | 2 33 | 3 34 | 4 35 | 5 36 | 0 37 | -5 38 | upto 39 | -2 40 | -1 41 | 0 42 | 1 43 | 2 44 | -2 45 | 2 46 | 2 47 | 3 48 | downto 49 | 2 50 | 1 51 | 0 52 | -1 53 | -2 54 | 2 55 | 2 56 | 2 57 | 5 58 | abs 59 | 2 60 | 2 61 | magnitude 62 | 2 63 | 2 64 | -------------------------------------------------------------------------------- /rb_test/sysrb_out/ivar.out: -------------------------------------------------------------------------------- 1 | hello 2 | -------------------------------------------------------------------------------- /rb_test/sysrb_out/load.out: -------------------------------------------------------------------------------- 1 | loaded 2 | loaded 3 | -------------------------------------------------------------------------------- /rb_test/sysrb_out/method_def.out: -------------------------------------------------------------------------------- 1 | yield with 2 | arg 3 | hello world no arg 4 | my name is erruby 5 | arg 6 | hello block 7 | -------------------------------------------------------------------------------- /rb_test/sysrb_out/nested_block.out: -------------------------------------------------------------------------------- 1 | 0 2 | 0 3 | 1 4 | 2 5 | 3 6 | 1 7 | 0 8 | 1 9 | 2 10 | 3 11 | 2 12 | 0 13 | 1 14 | 2 15 | 3 16 | -------------------------------------------------------------------------------- /rb_test/sysrb_out/nested_const.out: -------------------------------------------------------------------------------- 1 | Foo::Bar 2 | hello nested 3 | inside Bob 4 | outside bob 5 | -------------------------------------------------------------------------------- /rb_test/sysrb_out/nil_class.out: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnlinvc/erruby/60df66495a01f9dda08bd3f670bfe9dc0661a168/rb_test/sysrb_out/nil_class.out -------------------------------------------------------------------------------- /rb_test/sysrb_out/require_relative.out: -------------------------------------------------------------------------------- 1 | required file 2 | -------------------------------------------------------------------------------- /rb_test/sysrb_out/var_assign.out: -------------------------------------------------------------------------------- 1 | hello world 2 | -------------------------------------------------------------------------------- /rb_test/var_assign.rb: -------------------------------------------------------------------------------- 1 | hello = "hello world" 2 | puts hello 3 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {deps, [ 2 | {erlport, ".*", {git, "https://github.com/johnlinvc/erlport.git", {branch, "erruby"}}, [raw]}, 3 | {plists, ".*", {git, "https://github.com/johnlinvc/plists.git", {branch, "master"}}, [raw]}, 4 | {getopt, "0.8.2", {git, "https://github.com/jcomellas/getopt.git", {tag, "v0.8.2"}}} 5 | ] 6 | }. 7 | {pre_hooks, [{compile, "./pre_compile"}]}. 8 | -------------------------------------------------------------------------------- /src/erb.erl: -------------------------------------------------------------------------------- 1 | -module(erb). 2 | -export([find_or_init_class/2]). 3 | 4 | find_or_init_class(Name, InitFun) -> 5 | case whereis(Name) of 6 | undefined -> InitFun(); 7 | Pid -> {ok, Pid} 8 | end. 9 | -------------------------------------------------------------------------------- /src/erruby.app.src: -------------------------------------------------------------------------------- 1 | {application, erruby, 2 | [ 3 | {description, ""}, 4 | {vsn, "1"}, 5 | {registered, []}, 6 | {applications, [ 7 | kernel, 8 | stdlib 9 | ]}, 10 | {mod, { erruby_app, []}}, 11 | {env, []} 12 | ]}. 13 | -------------------------------------------------------------------------------- /src/erruby.erl: -------------------------------------------------------------------------------- 1 | -module(erruby). 2 | -include("rb.hrl"). 3 | -export([eruby/1, start_ruby/0, stop_ruby/1, parse_ast/2, main/1]). 4 | 5 | opt_spec_list() -> 6 | [ 7 | {debug, $d, "debug", {integer, 0}, "Verbose level for debugging"}, 8 | {verbose, $v, "verbose", undefined, "print version number and enter verbose mode"}, 9 | {fast, $f, "fast", undefined, "use escript instead of erl for faster bootup"}, 10 | {help, $h, "help", undefined, "Show this help"} 11 | ]. 12 | 13 | handle_opts({debug, DebugLevel}) -> 14 | erruby_debug:set_debug_level(DebugLevel); 15 | handle_opts(help ) -> 16 | show_help(); 17 | handle_opts(verbose) -> 18 | io:format("erruby 0.1.0~n"), 19 | erruby_debug:set_debug_level(2); 20 | handle_opts(_Opts) -> 21 | ok. 22 | 23 | parse_args([ArgsAtom]) when is_atom(ArgsAtom) -> 24 | ArgsString = atom_to_list(ArgsAtom), 25 | ArgsUntokened = string:centre(ArgsString,length(ArgsString)-2), 26 | string:tokens(ArgsUntokened, " "); 27 | 28 | parse_args(Args) -> 29 | Args. 30 | 31 | main(RawArgs) -> 32 | Args = parse_args(RawArgs), 33 | add_lib_path(), 34 | erruby_debug:start_link(0), 35 | {ok, {Opts, Extra}} = getopt(Args), 36 | lists:foreach(fun handle_opts/1, Opts), 37 | [SrcFileName | RubyArgs] = Extra, 38 | try 39 | erruby_debug:debug_2("input file name ~s\n", [SrcFileName]), 40 | erruby_debug:debug_2("input args ~s\n", [RubyArgs]), 41 | eruby(SrcFileName) 42 | catch 43 | _:known_error -> 44 | erruby_debug:debug_1("known error ~n", []), 45 | erruby_debug:debug_1("~p~n",[erlang:get_stacktrace()]); 46 | _:E -> 47 | io:format("error ~p ~n", [E]), 48 | erlang:display(erlang:get_stacktrace()) 49 | end. 50 | 51 | eruby(SrcFileName) -> 52 | {ok, Binary} = file:read_file(SrcFileName), 53 | FileLines = binary:bin_to_list(Binary), 54 | Ruby = start_ruby(), 55 | Ast = parse_ast(Ruby, FileLines), 56 | stop_ruby(Ruby), 57 | erruby_vm:eval_file(Ast, SrcFileName). 58 | 59 | getopt(Args) -> 60 | getopt:parse(opt_spec_list(), Args). 61 | 62 | show_help() -> 63 | getopt:usage(opt_spec_list(), "erruby", "[programfile] [arguments]"), 64 | halt(1). 65 | 66 | 67 | install_encoder(Ruby) -> 68 | ruby:call(Ruby, erruby_rb_path() , 'install_encoder',[]). 69 | 70 | erruby_path() -> 71 | os:getenv("ERRUBY_PATH") ++ "/ebin". 72 | 73 | relative_path(Path) -> 74 | erruby_path() ++ Path. 75 | 76 | erruby_rb_path() -> 77 | list_to_atom(relative_path("/../rb_src/erruby.rb")). 78 | 79 | parse_ast(Ruby, String) -> 80 | ruby:call(Ruby, erruby_rb_path(),'parse', [String]). 81 | 82 | add_lib_path() -> 83 | code:add_path(relative_path("/../deps/erlport/ebin")), 84 | code:add_path(relative_path("/../deps/getopt/ebin")), 85 | code:add_path(relative_path("/../deps/plists/ebin")), 86 | code:add_path(erruby_path()). 87 | 88 | stop_ruby(Ruby) -> 89 | ruby:stop(Ruby). 90 | 91 | start_ruby() -> 92 | {ok, Ruby} = ruby:start(), 93 | install_encoder(Ruby), 94 | Ruby. 95 | 96 | -------------------------------------------------------------------------------- /src/erruby_app.erl: -------------------------------------------------------------------------------- 1 | -module(erruby_app). 2 | 3 | -behaviour(application). 4 | 5 | %% Application callbacks 6 | -export([start/2, stop/1]). 7 | 8 | %% =================================================================== 9 | %% Application callbacks 10 | %% =================================================================== 11 | 12 | start(_StartType, _StartArgs) -> 13 | erruby_sup:start_link(). 14 | 15 | stop(_State) -> 16 | ok. 17 | -------------------------------------------------------------------------------- /src/erruby_array.erl: -------------------------------------------------------------------------------- 1 | -module(erruby_array). 2 | -include("rb.hrl"). 3 | -export([install_array_classes/0, new_array/2, new_array/1]). 4 | -export([array_to_list/1, push/2]). 5 | 6 | %TODO find a way to define module_function 7 | install_array_classes() -> 8 | {ok, ArrayClass} = erruby_class:new_class(), 9 | 'Array' = erruby_object:def_global_const('Array', ArrayClass), 10 | erruby_object:def_method(ArrayClass, map, fun method_map/1), 11 | erruby_object:def_method(ArrayClass, pmap, fun method_pmap/1), 12 | erruby_object:def_method(ArrayClass, '*' , fun method_multiplication/2), 13 | erruby_object:def_method(ArrayClass, '+', fun method_plus/2), 14 | erruby_object:def_method(ArrayClass, 'concat', fun method_concat/2), 15 | erruby_object:def_method(ArrayClass, 'at' , fun method_at/2), 16 | erruby_object:def_method(ArrayClass, 'first' , fun method_first/1), 17 | erruby_object:def_method(ArrayClass, 'last' , fun method_last/1), 18 | erruby_object:def_method(ArrayClass, 'empty?', fun method_empty_q/1), 19 | erruby_object:def_method(ArrayClass, 'length', fun method_length/1), 20 | erruby_object:def_method(ArrayClass, 'size', fun method_length/1), 21 | erruby_object:def_method(ArrayClass, 'push' , fun method_push/2), 22 | erruby_object:def_method(ArrayClass, '<<' , fun method_push/2), 23 | erruby_object:def_method(ArrayClass, 'unshift' , fun method_unshift/2), 24 | erruby_object:def_method(ArrayClass, 'shift' , fun method_shift/1), 25 | erruby_object:def_method(ArrayClass, 'drop' , fun method_drop/2), 26 | ok. 27 | 28 | drop_elements(_List, Count) when Count =< 0 -> 29 | _List; 30 | drop_elements([Head | Tail], Count) -> 31 | drop_elements(Tail, Count - 1). 32 | 33 | method_drop(#{self := Self}=Env, IntObj) -> 34 | Int = erruby_fixnum:fix_to_int(IntObj), 35 | List = array_to_list(Self), 36 | if 37 | length(List) < Int -> 38 | new_array(Env, []); 39 | true -> 40 | ResultList = drop_elements(List, Int), 41 | new_array(Env, ResultList) 42 | end. 43 | 44 | method_map(#{self := Self}=Env) -> 45 | List = array_to_list(Self), 46 | FoldFun = fun(X, EnvAcc) -> erruby_vm:yield(EnvAcc, [X]) end, 47 | Envs = erruby_vm:scanl(FoldFun, Env, List), 48 | Results = lists:map(fun erruby_rb:ret_val/1, Envs), 49 | erruby_rb:return(Results, lists:last(Envs)). 50 | 51 | repeat_list(_List, Count) when Count =< 0 -> 52 | new_array([]); 53 | repeat_list(List, 1) -> 54 | List; 55 | repeat_list(List, Count) -> 56 | lists:append(List, repeat_list(List, Count-1)). 57 | 58 | method_multiplication(#{self := Self}=Env, IntObj) -> 59 | Int = erruby_fixnum:fix_to_int(IntObj), 60 | List = array_to_list(Self), 61 | ResultList = repeat_list(List, Int), 62 | new_array(Env, ResultList). 63 | 64 | method_plus(#{self := Self}=Env, Another) -> 65 | Elements = array_to_list(Self), 66 | AnotherElements = array_to_list(Another), 67 | NewElements = Elements ++ AnotherElements, 68 | new_array(Env, NewElements). 69 | 70 | method_concat(#{self := Self}=Env, Another) -> 71 | Elements = array_to_list(Self), 72 | AnotherElements = array_to_list(Another), 73 | NewElements = Elements ++ AnotherElements, 74 | Properties = erruby_object:get_properties(Self), 75 | NewProperties = Properties#{ elements := NewElements}, 76 | erruby_object:set_properties(Self, NewProperties), 77 | erruby_rb:return(Self, Env). 78 | 79 | method_pmap(#{self := Self}=Env) -> 80 | List = array_to_list(Self), 81 | MapFun = fun(X) -> erruby_vm:yield(Env, [X]) end, 82 | Envs = plists:map(MapFun, List, {processes, erlang:system_info(schedulers_online)}), 83 | Results = lists:map(fun erruby_rb:ret_val/1, Envs), 84 | erruby_rb:return(Results, lists:last(Envs)). 85 | 86 | method_at(#{self := Self}=Env, IntObj) -> 87 | Int = erruby_fixnum:fix_to_int(IntObj), 88 | erruby_rb:return(at(Self, Int), Env). 89 | 90 | method_first(#{self := Self}=Env) -> 91 | List = array_to_list(Self), 92 | [ Head | _Tail ] = List, 93 | erruby_rb:return(Head, Env). 94 | 95 | method_last(#{self := Self}=Env) -> 96 | List = array_to_list(Self), 97 | erruby_rb:return(lists:last(List), Env). 98 | 99 | method_empty_q(#{self := Self}=Env) -> 100 | List = array_to_list(Self), 101 | case length(List) < 1 of 102 | true -> erruby_boolean:new_true(Env); 103 | false -> erruby_boolean:new_false(Env) 104 | end. 105 | method_length(#{self := Self}=Env) -> 106 | List = array_to_list(Self), 107 | erruby_fixnum:new_fixnum(Env, length(List)). 108 | 109 | method_push(#{self := Self}=Env, Append) -> 110 | push(Self, Append), 111 | erruby_rb:return(Self, Env). 112 | 113 | method_unshift(#{self := Self}=Env, Head) -> 114 | Elements = array_to_list(Self), 115 | NewElements = [Head | Elements], 116 | Properties = erruby_object:get_properties(Self), 117 | NewProperties = Properties#{ elements := NewElements} , 118 | erruby_object:set_properties(Self, NewProperties), 119 | erruby_rb:return(Self, Env). 120 | 121 | method_shift(#{self := Self}=Env) -> 122 | Elements = array_to_list(Self), 123 | [Head | Rest] = Elements, 124 | Properties = erruby_object:get_properties(Self), 125 | NewProperties = Properties#{ elements := Rest} , 126 | erruby_object:set_properties(Self, NewProperties), 127 | erruby_rb:return(Head, Env). 128 | 129 | %TODO maybe use pid to find class 130 | new_array(Env, Elements) -> 131 | erruby_rb:return(new_array(Elements), Env). 132 | 133 | new_array(Elements) -> 134 | ArrayClass = erruby_object:find_global_const('Array'), 135 | Properties = #{elements => Elements}, 136 | {ok, Array} = erruby_object:new_object(ArrayClass, Properties), 137 | Array. 138 | 139 | %% @doc the Index is 0-based, not the 1-based of usual erlang 140 | at(Array, Index) -> 141 | Properties = erruby_object:get_properties(Array), 142 | #{ elements := Elements} = Properties, 143 | lists:nth(Index+1, Elements). 144 | 145 | push(Array, Elem) -> 146 | Elements = array_to_list(Array), 147 | NewElements = Elements ++ [Elem], 148 | Properties = erruby_object:get_properties(Array), 149 | NewProperties = Properties#{ elements := NewElements} , 150 | erruby_object:set_properties(Array, NewProperties). 151 | 152 | array_to_list(Array) -> 153 | Properties = erruby_object:get_properties(Array), 154 | #{ elements := Elements} = Properties, 155 | Elements. 156 | -------------------------------------------------------------------------------- /src/erruby_boolean.erl: -------------------------------------------------------------------------------- 1 | -module(erruby_boolean). 2 | -export([install_boolean_classes/0,new_true/1,new_false/1,true_instance/0,false_instance/0]). 3 | %TODO register the True & False class in Const 4 | 5 | install_boolean_classes() -> 6 | {ok, TrueClass} = erruby_class:new_class(), 7 | {ok, FalseClass} = erruby_class:new_class(), 8 | 'TrueClass' = erruby_object:def_global_const('TrueClass', TrueClass), 9 | 'FalseClass' = erruby_object:def_global_const('FalseClass', FalseClass), 10 | install_method(TrueClass, FalseClass, '!', fun method_not/1), 11 | install_method(TrueClass, FalseClass, '&', fun method_and/2), 12 | install_method(TrueClass, FalseClass, '^', fun method_xor/2), 13 | install_method(TrueClass, FalseClass, '|', fun method_or/2), 14 | erruby_object:def_method(TrueClass, to_s, fun method_true_to_s/1), 15 | erruby_object:def_method(FalseClass, to_s, fun method_false_to_s/1), 16 | erruby_object:def_method(TrueClass, inspect, fun method_true_to_s/1), 17 | erruby_object:def_method(FalseClass, inspect, fun method_false_to_s/1), 18 | erruby_object:new_object_with_pid_symbol(erruby_boolean_true, TrueClass), 19 | erruby_object:new_object_with_pid_symbol(erruby_boolean_false, FalseClass), 20 | ok. 21 | 22 | install_method(TC, FC, Name, Func) -> 23 | erruby_object:def_method(TC, Name, Func), 24 | erruby_object:def_method(FC, Name, Func). 25 | 26 | new_true(Env) -> erruby_rb:return(true_instance(), Env). 27 | new_false(Env) -> erruby_rb:return(false_instance(), Env). 28 | 29 | true_instance() -> 30 | whereis(erruby_boolean_true). 31 | 32 | false_instance() -> 33 | whereis(erruby_boolean_false). 34 | 35 | method_not(#{self := Self} = Env) -> 36 | True = true_instance(), 37 | False = false_instance(), 38 | RetVal = case Self of 39 | True -> False; 40 | False -> True 41 | end, 42 | erruby_rb:return(RetVal, Env). 43 | 44 | method_and(#{self := Self} = Env, Object) -> 45 | Another = object_to_boolean(Object), 46 | RetVal = and_op(Self, Another), 47 | erruby_rb:return(RetVal, Env). 48 | 49 | object_to_boolean(Object) -> 50 | NilObject = erruby_nil:nil_instance(), 51 | False = false_instance(), 52 | case Object of 53 | NilObject -> false_instance(); 54 | False -> false_instance(); 55 | _ -> true_instance() 56 | end. 57 | 58 | 59 | and_op(B1,B2) -> 60 | True = true_instance(), 61 | False = false_instance(), 62 | case B1 of 63 | True -> B2; 64 | False -> False 65 | end. 66 | 67 | or_op(B1,B2) -> 68 | True = true_instance(), 69 | False = false_instance(), 70 | case B1 of 71 | True -> True; 72 | False -> B2 73 | end. 74 | 75 | not_op(Boolean) -> 76 | True = true_instance(), 77 | False = false_instance(), 78 | case Boolean of 79 | True -> False; 80 | False -> True 81 | end. 82 | 83 | method_or(#{self := Self} = Env, Object) -> 84 | Another = object_to_boolean(Object), 85 | RetVal = or_op(Self, Another), 86 | erruby_rb:return(RetVal, Env). 87 | 88 | method_xor(#{self := Self} = Env, Object) -> 89 | Another = object_to_boolean(Object), 90 | NotAandB = and_op(not_op(Self),Another), 91 | AandNotB = and_op(Self, not_op(Another)), 92 | RetVal = or_op(NotAandB, AandNotB), 93 | erruby_rb:return(RetVal, Env). 94 | 95 | method_true_to_s(Env) -> 96 | erruby_vm:new_string("true", Env). 97 | 98 | method_false_to_s(Env) -> 99 | erruby_vm:new_string("false",Env). 100 | -------------------------------------------------------------------------------- /src/erruby_class.erl: -------------------------------------------------------------------------------- 1 | -module(erruby_class). 2 | -export([new_class/0, new_named_class/1, new_class/1, install_class_class_methods/0, init_class_class/0, class_name/1]). 3 | 4 | %TODO return self when calling method_class on self 5 | %TODO add name parameter 6 | %TODO should call initialize method when new 7 | 8 | new_class() -> 9 | new_named_class(undefined). 10 | 11 | new_named_class(Name) -> 12 | Properties = #{name => Name}, 13 | erruby_object:start_link(class_class(), Properties). 14 | 15 | class_name(Self) -> 16 | Properties = erruby_object:get_properties(Self), 17 | #{name := Name} = Properties, 18 | Name. 19 | 20 | new_class(SuperClass) -> 21 | Properties = #{superclass => SuperClass}, 22 | erruby_object:start_link(class_class(), Properties). 23 | 24 | install_class_class_methods() -> 25 | erruby_object:def_method(class_class(), 'new', fun method_new/1), 26 | ok. 27 | 28 | %FIXME new a real class 29 | method_new(#{self := Klass}=Env) -> 30 | {ok, NewObject} = erruby_object:start_link(Klass), 31 | erruby_rb:return(NewObject, Env). 32 | 33 | init_class_class() -> 34 | erb:find_or_init_class(erruby_class_class, fun init_class_class_internal/0). 35 | 36 | init_class_class_internal() -> 37 | Properties = #{superclass => erruby_object:object_class()}, 38 | {ok, Pid} = erruby_object:new_object_with_pid_symbol(erruby_class_class, erruby_object:object_class()), 39 | ok = install_class_class_methods(), 40 | {ok, Pid}. 41 | 42 | class_class() -> 43 | whereis(erruby_class_class). 44 | 45 | -------------------------------------------------------------------------------- /src/erruby_debug.erl: -------------------------------------------------------------------------------- 1 | -module(erruby_debug). 2 | -export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, handle_info/2]). 3 | -export([start_link/1, debug/3, debug_1/2,debug_2/2, debug_tmp/2, set_debug_level/1]). 4 | -export([print_env/1]). 5 | 6 | init([DebugLevel]) -> 7 | {ok, #{debug_level => DebugLevel}}. 8 | 9 | terminate(_Arg, _State) -> {ok, dead}. 10 | 11 | code_change(_OldVsn, State, _Extra) -> {ok, State}. 12 | 13 | debug(Format, Args, Level) -> 14 | FullFormat = lists:concat(["debug level ", Level, ": ", Format]), 15 | gen_server:call(erruby_debug_pid, #{level => Level, format => FullFormat, args => Args}). 16 | 17 | debug_1(Format, Args) -> 18 | debug(Format, Args, 1). 19 | 20 | debug_2(Format, Args) -> 21 | debug(Format, Args, 2). 22 | 23 | debug_tmp(Format, Args) -> 24 | io:format(Format, Args). 25 | 26 | set_debug_level(Level) -> 27 | gen_server:cast(erruby_debug_pid, #{new_level => Level}). 28 | 29 | start_link(DebugLevel) -> gen_server:start_link({local, erruby_debug_pid} ,?MODULE, [DebugLevel], []). 30 | 31 | handle_info(Info, State) -> 32 | io:format("Got unkwon info:~n~p~n", [Info]), 33 | {ok, State}. 34 | 35 | handle_call(#{level := Level, format := Format, args := Args}, _From, #{debug_level := DebugLevel} = State) when DebugLevel >= Level -> 36 | io:format(Format, Args), 37 | {reply, ok, State}; 38 | 39 | handle_call(#{level := Level}, _From, #{debug_level := DebugLevel} = State) when DebugLevel < Level -> 40 | {reply, ok, State}; 41 | 42 | handle_call(_Req, _From, State) -> 43 | io:format("handle unknow call ~p ~p ~p ~n",[_Req, _From, State]), 44 | NewState = State, 45 | {reply, done, NewState}. 46 | 47 | handle_cast(#{new_level := NewLevel}, State) -> 48 | {noreply, State#{ debug_level => NewLevel } }; 49 | 50 | handle_cast(_Req, State) -> 51 | io:format("handle unknown cast ~p ~p ~n",[_Req, State]), 52 | NewState = State, 53 | {noreply, NewState}. 54 | 55 | print_env(Env) -> 56 | debug_1("Env: ~p ~n",[Env]). 57 | -------------------------------------------------------------------------------- /src/erruby_fixnum.erl: -------------------------------------------------------------------------------- 1 | -module(erruby_fixnum). 2 | -export([install_fixnum_class/0, new_fixnum/2, fix_to_int/1]). 3 | 4 | %% 5 | %% @TODO inherent from integer & numeric 6 | install_fixnum_class() -> 7 | IntegerClass = erruby_object:find_global_const('Integer'), 8 | {ok, FixnumClass} = erruby_class:new_class(IntegerClass), 9 | 'Fixnum' = erruby_object:def_global_const('Fixnum', FixnumClass), 10 | erruby_object:def_method(FixnumClass, to_s, fun method_to_s/1), 11 | erruby_object:def_method(FixnumClass, '-@', fun method_neg/1), 12 | erruby_object:def_method(FixnumClass, '+', fun method_add/2), 13 | erruby_object:def_method(FixnumClass, '-', fun method_minus/2), 14 | erruby_object:def_method(FixnumClass, '*', fun method_multiplication/2), 15 | erruby_object:def_method(FixnumClass, '**', fun method_power/2), 16 | erruby_object:def_method(FixnumClass, '<', fun method_less/2), 17 | erruby_object:def_method(FixnumClass, '>', fun method_greater/2), 18 | erruby_object:def_method(FixnumClass, '<=', fun method_less_equal/2), 19 | erruby_object:def_method(FixnumClass, '>=', fun method_greater_equal/2), 20 | erruby_object:def_method(FixnumClass, '==', fun method_equal/2), 21 | erruby_object:def_method(FixnumClass, '<=>', fun method_cmp/2), 22 | erruby_object:def_method(FixnumClass, '/', fun method_division/2), 23 | erruby_object:def_method(FixnumClass, '%', fun method_module/2), 24 | ok. 25 | 26 | fixnum_class() -> 27 | erruby_object:find_global_const('Fixnum'). 28 | 29 | new_fixnum(Env, N) -> 30 | {ok, Obj} = erruby_object:new_object(fixnum_class(), #{val => N}), 31 | erruby_rb:return(Obj, Env). 32 | 33 | fix_to_int(Fixnum) -> 34 | get_val(Fixnum). 35 | 36 | get_val(Fixnum) -> 37 | #{val := Val} = erruby_object:get_properties(Fixnum), 38 | Val. 39 | 40 | binary_op(#{self := Self}=Env, AnotherFixnum, Fun) -> 41 | Val = Fun(get_val(Self), get_val(AnotherFixnum)), 42 | new_fixnum(Env, Val). 43 | 44 | %% @TODO use new_string instead 45 | method_to_s(#{self := Self}=Env) -> 46 | Val = get_val(Self), 47 | erruby_rb:return(integer_to_list(Val), Env). 48 | 49 | %% TODO handle case where the other is not Fixnum 50 | method_add(Env, AnotherFixnum) -> 51 | binary_op(Env, AnotherFixnum, fun (A,B) -> A+B end). 52 | 53 | method_minus(Env, AnotherFixnum) -> 54 | binary_op(Env, AnotherFixnum, fun (A,B) -> A-B end). 55 | 56 | method_multiplication(Env, AnotherFixnum) -> 57 | binary_op(Env, AnotherFixnum, fun (A,B) -> A*B end). 58 | 59 | method_division(Env, AnotherFixnum) -> 60 | binary_op(Env, AnotherFixnum, fun (A,B) -> trunc(A/B) end). 61 | 62 | method_power(Env, AnotherFixnum) -> 63 | binary_op(Env, AnotherFixnum, fun (A,B) -> trunc(math:pow(A,B)) end). 64 | 65 | method_neg(#{self := Self}=Env) -> 66 | Val = - get_val(Self), 67 | new_fixnum(Env, Val). 68 | 69 | method_module(Env, AnotherFixnum) -> 70 | binary_op(Env, AnotherFixnum, fun (A,B) -> A rem B end). 71 | 72 | binary_cmp(#{self := Self}=Env, AnotherFixnum, Fun) -> 73 | Val = get_val(Self), 74 | AnotherVal = get_val(AnotherFixnum), 75 | case Fun(Val,AnotherVal) of 76 | true -> erruby_boolean:new_true(Env); 77 | false -> erruby_boolean:new_false(Env) 78 | end. 79 | 80 | %%TODO move these to the comparator module 81 | method_less(Env, AnotherFixnum) -> 82 | binary_cmp(Env, AnotherFixnum, fun (X,Y) -> X < Y end). 83 | 84 | method_less_equal(Env, AnotherFixnum) -> 85 | binary_cmp(Env, AnotherFixnum, fun (X,Y) -> X =< Y end). 86 | 87 | method_greater(Env, AnotherFixnum) -> 88 | binary_cmp(Env, AnotherFixnum, fun (X,Y) -> X > Y end). 89 | 90 | method_greater_equal(Env, AnotherFixnum) -> 91 | binary_cmp(Env, AnotherFixnum, fun (X,Y) -> X >= Y end). 92 | 93 | method_equal(Env, AnotherFixnum) -> 94 | binary_cmp(Env, AnotherFixnum, fun (X,Y) -> X =:= Y end). 95 | 96 | 97 | cmp_helper(X,Y) when X < Y -> 98 | -1; 99 | cmp_helper(X,Y) when X =:= Y -> 100 | 0; 101 | cmp_helper(X,Y) when X > Y -> 102 | 1. 103 | 104 | method_cmp(Env, AnotherFixnum) -> 105 | binary_op(Env, AnotherFixnum, fun cmp_helper/2). 106 | 107 | -------------------------------------------------------------------------------- /src/erruby_integer.erl: -------------------------------------------------------------------------------- 1 | -module(erruby_integer). 2 | -export([install_integer_class/0]). 3 | 4 | install_integer_class() -> 5 | {ok, IntegerClass} = erruby_class:new_class(), 6 | 'Integer' = erruby_object:def_global_const('Integer', IntegerClass), 7 | erruby_object:def_method(IntegerClass, to_i, fun method_to_i/1), 8 | erruby_object:def_method(IntegerClass, to_int, fun method_to_i/1), 9 | erruby_object:def_method(IntegerClass, floor, fun method_to_i/1), 10 | erruby_object:def_method(IntegerClass, ceil, fun method_to_i/1), 11 | erruby_object:def_method(IntegerClass, truncate, fun method_to_i/1), 12 | erruby_object:def_method(IntegerClass, numerator, fun method_to_i/1), 13 | erruby_object:def_method(IntegerClass, ord, fun method_to_i/1), 14 | erruby_object:def_method(IntegerClass, denominator, fun method_denominator/1), 15 | erruby_object:def_method(IntegerClass, 'even?', fun method_even_q/1), 16 | erruby_object:def_method(IntegerClass, 'odd?', fun method_odd_q/1), 17 | erruby_object:def_method(IntegerClass, 'gcd', fun method_gcd/2), 18 | erruby_object:def_method(IntegerClass, 'lcm', fun method_lcm/2), 19 | erruby_object:def_method(IntegerClass, 'integer?', fun method_integer_q/1), 20 | erruby_object:def_method(IntegerClass, 'succ', fun method_succ/1), 21 | erruby_object:def_method(IntegerClass, 'next', fun method_succ/1), 22 | erruby_object:def_method(IntegerClass, 'times', fun method_times/1), 23 | erruby_object:def_method(IntegerClass, 'upto', fun method_upto/2), 24 | erruby_object:def_method(IntegerClass, 'downto', fun method_downto/2), 25 | erruby_object:def_method(IntegerClass, 'abs', fun method_abs/1), 26 | erruby_object:def_method(IntegerClass, 'magnitude', fun method_abs/1), 27 | ok. 28 | 29 | method_to_i(#{self := Self}=Env) -> erruby_rb:return(Self, Env). 30 | 31 | method_denominator(Env) -> 32 | erruby_fixnum:new_fixnum(Env, 1). 33 | 34 | %%TODO handle Bignum 35 | method_even_q(#{self := Self}=Env) -> 36 | Int = erruby_fixnum:fix_to_int(Self), 37 | case Int rem 2 of 38 | 0 -> erruby_boolean:new_true(Env); 39 | 1 -> erruby_boolean:new_false(Env) 40 | end. 41 | method_odd_q(#{self := Self}=Env) -> 42 | Int = erruby_fixnum:fix_to_int(Self), 43 | case Int rem 2 of 44 | 1 -> erruby_boolean:new_true(Env); 45 | 0 -> erruby_boolean:new_false(Env) 46 | end. 47 | 48 | %%TODO use binary gcd algo instead 49 | %%TODO move this to rational 50 | gcd(X,Y) when X < Y -> gcd(Y,X); 51 | gcd(X,Y) when Y =:= 0 -> X; 52 | gcd(X,Y) -> gcd(Y, X rem Y). 53 | 54 | method_gcd(#{self := Self}=Env, AnotherInt) -> 55 | X = abs(erruby_fixnum:fix_to_int(Self)), 56 | Y = abs(erruby_fixnum:fix_to_int(AnotherInt)), 57 | case min(X,Y) of 58 | 0 -> erruby_fixnum:new_fixnum(Env, max(X,Y)); 59 | _ -> erruby_fixnum:new_fixnum(Env, gcd(X,Y)) 60 | end. 61 | 62 | lcm(0,_Y) -> 0; 63 | lcm(_X,0) -> 0; 64 | lcm(X,Y) -> trunc(X / gcd(X,Y) * Y). 65 | 66 | method_lcm(#{self := Self}=Env, AnotherInt) -> 67 | X = abs(erruby_fixnum:fix_to_int(Self)), 68 | Y = abs(erruby_fixnum:fix_to_int(AnotherInt)), 69 | case min(X,Y) of 70 | 0 -> erruby_fixnum:new_fixnum(Env, 0); 71 | _ -> erruby_fixnum:new_fixnum(Env, lcm(X,Y)) 72 | end. 73 | 74 | method_integer_q(Env) -> 75 | erruby_boolean:new_true(Env). 76 | 77 | method_succ(#{self := Self}=Env) -> 78 | erruby_fixnum:new_fixnum(Env, erruby_fixnum:fix_to_int(Self)+1). 79 | 80 | 81 | yield_in_range(#{self := Self} = Env,Range) -> 82 | FoldFun = fun (X, EnvAcc) -> 83 | IntEnv = erruby_fixnum:new_fixnum(EnvAcc, X), 84 | FixInt = erruby_rb:ret_val(IntEnv), 85 | erruby_vm:yield(IntEnv, [FixInt]) end, 86 | LastEnv = lists:foldl(FoldFun, Env, Range), 87 | erruby_rb:return(Self, LastEnv). 88 | 89 | times_range(X) when X =< 0 -> []; 90 | times_range(X) -> lists:seq(0, X-1). 91 | 92 | %%TODO handle empty block case 93 | %%TODO handle Bigdecimal 94 | method_times(#{self := Self}=Env) -> 95 | Int = erruby_fixnum:fix_to_int(Self), 96 | Range = times_range(Int), 97 | yield_in_range(Env,Range). 98 | 99 | upto_range(Start,End) when Start > End -> []; 100 | upto_range(Start,End) -> lists:seq(Start,End). 101 | 102 | method_upto(#{self := Self}=Env, AnotherInteger) -> 103 | Int = erruby_fixnum:fix_to_int(Self), 104 | AnotherInt = erruby_fixnum:fix_to_int(AnotherInteger), 105 | Range = upto_range(Int,AnotherInt), 106 | yield_in_range(Env,Range). 107 | 108 | downto_range(Start,End) -> lists:reverse(upto_range(End,Start)). 109 | 110 | method_downto(#{self := Self}=Env, AnotherInteger) -> 111 | Int = erruby_fixnum:fix_to_int(Self), 112 | AnotherInt = erruby_fixnum:fix_to_int(AnotherInteger), 113 | Range = downto_range(Int,AnotherInt), 114 | yield_in_range(Env,Range). 115 | 116 | method_abs(#{self := Self}=Env) -> 117 | erruby_fixnum:new_fixnum(Env, abs(erruby_fixnum:fix_to_int(Self))). 118 | -------------------------------------------------------------------------------- /src/erruby_lib/erruby_file.erl: -------------------------------------------------------------------------------- 1 | -module(erruby_file). 2 | -include("../rb.hrl"). 3 | -export([install_file_classes/0]). 4 | 5 | install_file_classes() -> 6 | {ok, FileClass} = erruby_class:new_class(), 7 | 'File' = erruby_object:def_global_const('File', FileClass), 8 | erruby_object:def_singleton_method(FileClass, 'expand_path', fun method_expand_path/3), 9 | ok. 10 | 11 | method_expand_path(Env, Filename, RelativeDirOrFileName) -> 12 | {ok, Cwd} = file:get_cwd(), 13 | DirOrFileName = filename:absname_join(Cwd, RelativeDirOrFileName), 14 | ExpanedPath = filename:absname_join(DirOrFileName, Filename), 15 | FlattenedPath = flatten_path(ExpanedPath), 16 | erruby_vm:new_string(FlattenedPath, Env). 17 | 18 | flatten_path(Path)-> 19 | Components = filename:split(Path), 20 | FlattenedComponents = flatten_path_components(Components,[]), 21 | filename:join(FlattenedComponents). 22 | 23 | flatten_path_components([], Acc) -> lists:reverse(Acc); 24 | flatten_path_components([".."|T], [])-> 25 | flatten_path_components(T, []); 26 | flatten_path_components([".."|T], ["/"])-> 27 | flatten_path_components(T, ["/"]); 28 | flatten_path_components([".."|T], [_H|Acc])-> 29 | flatten_path_components(T, Acc); 30 | flatten_path_components([H|T], Acc)-> 31 | flatten_path_components(T, [H|Acc]). 32 | -------------------------------------------------------------------------------- /src/erruby_nil.erl: -------------------------------------------------------------------------------- 1 | -module(erruby_nil). 2 | -export([new_nil/1, install_nil_class/0, nil_instance/0]). 3 | 4 | install_nil_class() -> 5 | {ok, NilClass} = erruby_class:new_class(), 6 | 'NilClass' = erruby_object:def_global_const('NilClass', NilClass), 7 | erruby_object:def_method(NilClass, '&', fun method_and/2), 8 | erruby_object:def_method(NilClass, '^', fun method_xor/2), 9 | erruby_object:def_method(NilClass, '|', fun method_xor/2), 10 | erruby_object:def_method(NilClass, inspect, fun method_inspect/1), 11 | erruby_object:def_method(NilClass, 'nil?', fun 'method_nil_q'/1), 12 | erruby_object:def_method(NilClass, 'to_s', fun 'method_to_s'/1), 13 | erruby_object:new_object_with_pid_symbol(erruby_nil, NilClass), 14 | ok. 15 | 16 | new_nil(Env) -> 17 | erruby_rb:return(nil_instance(), Env). 18 | 19 | nil_instance() -> whereis(erruby_nil). 20 | 21 | method_and(Env, _Obj) -> erruby_boolean:new_false(Env). 22 | 23 | method_xor(Env, Obj) -> 24 | Nil = nil_instance(), 25 | False = erruby_boolean:false_instance(), 26 | case Obj of 27 | Nil -> erruby_boolean:new_false(Env); 28 | False -> erruby_boolean:new_false(Env); 29 | _ -> erruby_boolean:new_true(Env) 30 | end. 31 | 32 | method_inspect(Env) -> erruby_vm:new_string("nil",Env). 33 | 34 | method_nil_q(Env) -> erruby_boolean:new_true(Env). 35 | 36 | method_to_s(Env) -> erruby_vm:new_string("",Env). 37 | -------------------------------------------------------------------------------- /src/erruby_object.erl: -------------------------------------------------------------------------------- 1 | -module(erruby_object). 2 | -include("rb.hrl"). 3 | -behavior(gen_server). 4 | -export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, handle_info/2]). 5 | %for vm 6 | -export([def_method/4, find_instance_method/2, def_global_const/2, find_global_const/1, def_const/3, find_const/2, init_object_class/0,object_class/0]). 7 | -export([def_singleton_method/4, def_singleton_method/3]). 8 | -export([def_global_var/2, find_global_var/1]). 9 | %for other buildtin class 10 | -export([def_method/3, new_object_with_pid_symbol/2, new_object/2]). 11 | -export([def_ivar/3, find_ivar/2]). 12 | -export([init_main_object/0, main_object/0]). 13 | -export([start_link/2, start_link/1]). 14 | -export([get_properties/1, set_properties/2]). 15 | -export([get_class/1]). 16 | 17 | init([#{class := Class, properties := Properties}]) -> 18 | DefaultState = default_state(), 19 | StateWithClass = add_class_to_state(DefaultState, Class), 20 | {ok, add_property_to_state(StateWithClass, Properties)}; 21 | 22 | init([#{class := Class}]) -> 23 | DefaultState = default_state(), 24 | {ok, add_class_to_state(DefaultState, Class)}; 25 | 26 | init([]) -> 27 | {ok, default_state()}. 28 | 29 | add_class_to_state(State, Class) -> 30 | State#{class => Class}. 31 | 32 | add_property_to_state(State, Properties) -> 33 | State#{properties => Properties}. 34 | 35 | %TODO in method_class return defalut object_class if no class is present 36 | default_state() -> 37 | Methods = #{}, 38 | IVars = #{}, 39 | Consts = #{}, 40 | #{self => self(), 41 | methods => Methods, 42 | ivars => IVars, 43 | properties => #{}, 44 | consts => Consts}. 45 | 46 | 47 | %TODO unify these? 48 | start_link(Class) -> 49 | gen_server:start_link(?MODULE, [#{class => Class }], []). 50 | 51 | start_link(Class, Properties) -> 52 | gen_server:start_link(?MODULE, [#{class => Class, properties => Properties}], []). 53 | 54 | terminate(_Arg, _State) -> 55 | {ok, dead}. 56 | 57 | code_change(_OldVsn, State, _Extra) -> {ok, State}. 58 | 59 | get_class(Self) -> 60 | gen_server:call(Self, #{type => get_class}). 61 | 62 | find_instance_method(Self, Name) -> 63 | SingletonMethod = find_instance_method_in_singleton_class(Self, Name), 64 | case SingletonMethod of 65 | {ok, Method} -> Method; 66 | {not_found, _} -> 67 | find_instance_method_in_class(Self, Name) 68 | end. 69 | 70 | find_instance_method_in_singleton_class(Self, Name) -> 71 | SingletonClass = singleton_class(Self), 72 | case SingletonClass of 73 | not_found -> {not_found, Name}; 74 | _ -> 75 | Result = gen_server:call(SingletonClass, #{type => find_method, name => Name}), 76 | case Result of 77 | not_found -> {not_found, Name}; 78 | _ -> {ok, Result} 79 | end 80 | end. 81 | 82 | find_instance_method_in_class(Self, Name) -> 83 | %erruby_debug:debug_tmp("finding instance method ~p in ~p",[ Name, Self]), 84 | Klass = get_class(Self), 85 | Result = gen_server:call(Klass, #{type => find_method, name => Name}), 86 | case Result of 87 | not_found -> {not_found, Name}; 88 | _ -> Result 89 | end. 90 | 91 | find_method(Self, Name) -> 92 | gen_server:call(Self, #{type => find_method, name => Name}). 93 | 94 | self_or_object_class(Self) -> 95 | MainObject = main_object(), 96 | case Self of 97 | MainObject -> object_class(); 98 | _ -> Self 99 | end. 100 | 101 | 102 | singleton_class(Self) -> 103 | Properties = get_properties(Self), 104 | maps:get(singleton_class, Properties, not_found). 105 | 106 | get_or_create_singleton_class(Self) -> 107 | SingletonClass = singleton_class(Self), 108 | case SingletonClass of 109 | not_found -> 110 | {ok, NewSingletonClass} = erruby_class:new_named_class("singleton class"), 111 | Properties = get_properties(Self), 112 | NewProperties = Properties#{ singleton_class => NewSingletonClass }, 113 | set_properties(Self, NewProperties), 114 | NewSingletonClass; 115 | _ -> 116 | SingletonClass 117 | end. 118 | 119 | 120 | def_method(Self, Name, Args, Body) -> 121 | Receiver = self_or_object_class(Self), 122 | gen_server:call(Receiver, #{type => def_method, name => Name, args => Args, body => Body}). 123 | 124 | def_method(Self,Name,Func) when is_function(Func) -> 125 | Receiver = self_or_object_class(Self), 126 | gen_server:call(Receiver, #{type => def_method, name => Name, func => Func}). 127 | 128 | def_singleton_method(Self, Name, Args, Body) -> 129 | Receiver = get_or_create_singleton_class(Self), 130 | gen_server:call(Receiver, #{type => def_method, name => Name, args => Args, body => Body}). 131 | 132 | def_singleton_method(Self,Name,Func) when is_function(Func) -> 133 | Receiver = get_or_create_singleton_class(Self), 134 | gen_server:call(Receiver, #{type => def_method, name => Name, func => Func}). 135 | 136 | %TODO call def_const instead 137 | def_global_const(Name, Value) -> 138 | gen_server:call(object_class(), #{type => def_const, name => Name, value => Value}). 139 | 140 | find_global_const(Name) -> 141 | find_const(object_class(), Name). 142 | 143 | 144 | %TODO define on basic object instead 145 | %TODO ability to use custom getter/setter 146 | def_global_var(Name, Value) -> 147 | Msg = #{type => def_global_var, name => Name, value => Value}, 148 | gen_server:call(object_class(), Msg). 149 | 150 | find_global_var(Name) -> 151 | gen_server:call(object_class(), #{type => find_global_var, name => Name}). 152 | 153 | def_const(Self, Name, Value) -> 154 | Receiver = self_or_object_class(Self), 155 | gen_server:call(Receiver, #{type => def_const, name => Name, value => Value}). 156 | 157 | find_const(Self, Name) -> 158 | erruby_debug:debug_2("finding on ~p for const:~p~n",[Self, Name]), 159 | gen_server:call(Self, #{type => find_const, name => Name}). 160 | 161 | def_ivar(Self, Name, Value)-> 162 | gen_server:call(Self, #{type => def_ivar, name => Name, value => Value}). 163 | 164 | find_ivar(Self, Name) -> 165 | erruby_debug:debug_2("finding on ~p for ivar:~p~n",[Self, Name]), 166 | gen_server:call(Self, #{type => find_ivar, name => Name}). 167 | 168 | get_properties(Self) -> 169 | gen_server:call(Self, #{type => get_properties}). 170 | 171 | set_properties(Self, Properties) -> 172 | gen_server:call(Self, #{type => set_properties, properties => Properties}). 173 | 174 | 175 | handle_info(Info, State) -> 176 | io:format("Got unkwon info:~n~p~n", [Info]), 177 | {ok, State}. 178 | 179 | handle_call(#{ type := def_method , name := Name, body := Body, args := Args}=_Msg, _From, #{methods := Methods} =State) -> 180 | NewMethods = Methods#{ Name => #{ args => Args, body => Body, argc => length(Args) } }, 181 | NewState = State#{ methods := NewMethods}, 182 | {reply, Name, NewState}; 183 | 184 | handle_call(#{ type := def_method, name := Name, func := Func}=_Msg, _From, #{methods := Methods} = State) -> 185 | NewMethods = Methods#{ Name => Func }, 186 | NewState = State#{ methods := NewMethods}, 187 | {reply, Name, NewState}; 188 | 189 | handle_call(#{ type := find_method, name := Name }, _From, #{methods := Methods} = State) -> 190 | erruby_debug:debug_2("finding method:~p~n in State:~p~n",[Name, State]), 191 | case maps:is_key(Name,Methods) of 192 | true -> 193 | #{Name := Method} = Methods, 194 | {reply, Method, State}; 195 | false -> 196 | %TODO use error classes 197 | %io:format("Method ~p not found~n",[Name]), 198 | erruby_debug:debug_2("finding in ancestors:~p~n",[ancestors(State)]), 199 | Method = find_method_in_ancestors(ancestors(State), Name), 200 | {reply, Method, State} 201 | end; 202 | 203 | handle_call(#{ type := get_properties }, _From, #{properties := Properties}=State) -> 204 | {reply, Properties, State}; 205 | 206 | handle_call(#{ type := set_properties, properties := Properties }, _From, State) -> 207 | NewState = State#{ properties := Properties}, 208 | {reply, NewState, NewState}; 209 | 210 | handle_call(#{ type := def_ivar, name := Name, value := Value }, _From, #{ivars := IVars}=State) -> 211 | NewIvars = IVars#{Name => Value}, 212 | NewState = State#{ivars := NewIvars}, 213 | {reply, Name, NewState}; 214 | 215 | handle_call(#{ type := find_ivar, name := Name }, _From, #{ivars := IVars}=State) -> 216 | erruby_debug:debug_2("finding ivar:~p~nin State:~p~n",[Name, State]), 217 | Value = maps:get(Name, IVars, erruby_nil:nil_instance()), 218 | {reply, Value, State}; 219 | 220 | handle_call(#{ type := def_const, name := Name, value := Value }, _From, #{consts := Consts}=State) -> 221 | NewConsts = Consts#{Name => Value}, 222 | NewState = State#{consts := NewConsts}, 223 | {reply, Name, NewState}; 224 | 225 | handle_call(#{ type := find_const, name := Name }, _From, #{consts := Consts}=State) -> 226 | erruby_debug:debug_2("finding const:~p~nin State:~p~n",[Name, State]), 227 | Value = maps:get(Name, Consts, not_found), 228 | {reply, Value, State}; 229 | 230 | handle_call(#{ type := def_global_var, name := Name, value := Value}, _From, 231 | #{properties := #{global_var_tbl := GVarTbl} } = State) -> 232 | NewGVarTbl = GVarTbl#{ Name => Value}, 233 | #{properties := Properties} = State, 234 | NewProperties = Properties#{ global_var_tbl := NewGVarTbl }, 235 | NewState = State#{ properties := NewProperties}, 236 | {reply, Name, NewState}; 237 | 238 | handle_call(#{ type := find_global_var, name := Name}, _From, 239 | #{properties := #{global_var_tbl := GVarTbl} } = State) -> 240 | Value = maps:get(Name, GVarTbl, not_found), 241 | {reply, Value, State}; 242 | 243 | 244 | handle_call(#{ type := get_class}, _From, State) -> 245 | Value = maps:get(class, State, object_class()), 246 | {reply, Value, State}; 247 | 248 | 249 | handle_call(_Req, _From, State) -> 250 | io:format("handle unknow call ~p ~n ~p ~n ~p ~n",[_Req, _From, State]), 251 | NewState = State, 252 | {reply, done, NewState}. 253 | 254 | handle_cast(_Req, State) -> 255 | io:format("handle unknown cast ~p ~p ~n",[_Req, State]), 256 | NewState = State, 257 | {reply, done, NewState}. 258 | 259 | %TODO support va args 260 | method_puts(Env, String) -> 261 | io:format("~s~n", [String]), 262 | erruby_nil:new_nil(Env). 263 | 264 | append_rb_extension(FileName) -> 265 | case filename:extension(FileName) of 266 | [] -> string:concat(FileName, ".rb"); 267 | _ -> FileName 268 | end. 269 | 270 | %TODO extract to Kernal 271 | %TODO raise error if file not found 272 | method_require_relative(Env, FileName) -> 273 | RelativeFileName = relativeFileName(Env, FileName), 274 | RelativeFileNameWithExt = append_rb_extension(RelativeFileName), 275 | LoadedFeatures = find_global_var("$LOADED_FEATURES"), 276 | LoadedFeaturesList = erruby_array:array_to_list(LoadedFeatures), 277 | Contains = lists:member( RelativeFileNameWithExt, LoadedFeaturesList), 278 | case Contains of 279 | true -> erruby_boolean:new_false(Env); 280 | _ -> 281 | load_file(Env, RelativeFileNameWithExt), 282 | erruby_array:push(LoadedFeatures, RelativeFileNameWithExt), 283 | erruby_boolean:new_true(Env) 284 | end. 285 | 286 | relativeFileName(Env, FileName) -> 287 | SrcFile = erruby_vm:file_name(Env), 288 | SrcDir = filename:dirname(SrcFile), 289 | filename:join([SrcDir, FileName]). 290 | 291 | load_file(Env, RelativeFileNameWithExt) -> 292 | try 293 | erruby:eruby(RelativeFileNameWithExt), 294 | erruby_boolean:new_true(Env) 295 | catch 296 | _:_E -> 297 | erruby_debug:debug_2("cant require_relative file ~p~n", [RelativeFileNameWithExt]), 298 | erruby_boolean:new_false(Env) 299 | end. 300 | 301 | %TODO raise error if file not found 302 | % @TODO find a better way to get filename 303 | method_load(Env, FileName)-> 304 | Pwd = os:getenv("PWD"), 305 | RelativeFileNameWithExt = filename:join([Pwd, FileName]), 306 | load_file(Env, RelativeFileNameWithExt). 307 | 308 | method_self(#{self := Self}=Env) -> 309 | erruby_rb:return(Self, Env). 310 | 311 | method_inspect(#{self := Self}=Env) -> 312 | S = io_lib:format("#",[Self]), 313 | erruby_vm:new_string(S,Env). 314 | 315 | method_to_s(#{self := Self}=Env) -> 316 | S = io_lib:format("~p",[Self]), 317 | erruby_vm:new_string(S,Env). 318 | 319 | %TODO support property? 320 | new_object_with_pid_symbol(Symbol, Class) -> 321 | gen_server:start_link({local, Symbol}, ?MODULE, [#{class => Class}], []). 322 | 323 | new_object(Class, Payload) when is_map(Payload) -> 324 | start_link(Class, Payload). 325 | 326 | init_object_class() -> 327 | erb:find_or_init_class(erruby_object_class, fun init_object_class_internal/0). 328 | 329 | init_object_class_internal() -> 330 | {ok, Pid} = gen_server:start_link({local, erruby_object_class}, ?MODULE, [],[]), 331 | install_object_class_methods(), 332 | 'Object' = def_const(Pid, 'Object', Pid), 333 | set_properties(object_class(), #{global_var_tbl => #{}}), 334 | def_global_var("$LOADED_FEATURES", erruby_array:new_array([])), 335 | {ok, Pid}. 336 | 337 | init_main_object() -> 338 | erb:find_or_init_class(erruby_main_object, fun init_main_object_internal/0). 339 | 340 | init_main_object_internal() -> 341 | new_object_with_pid_symbol(erruby_main_object, object_class()). 342 | 343 | object_class() -> 344 | whereis(erruby_object_class). 345 | 346 | main_object() -> 347 | whereis(erruby_main_object). 348 | 349 | install_object_class_methods() -> 350 | %TODO use this after inherent is done 351 | %def_method(object_class(), '==', fun method_eq/2). 352 | def_method(object_class(), 'puts', fun method_puts/2), 353 | def_method(object_class(), 'self', fun method_self/1), 354 | def_method(object_class(), 'inspect', fun method_inspect/1), 355 | def_method(object_class(), 'to_s', fun method_to_s/1), 356 | def_method(object_class(), '==', fun method_eq/2), 357 | def_method(object_class(), 'require_relative', fun method_require_relative/2), 358 | def_method(object_class(), 'load', fun method_load/2), 359 | ok. 360 | 361 | 362 | method_eq(#{self := Self}=Env, Object) -> 363 | case Object of 364 | Self -> erruby_boolean:new_true(Env); 365 | _ -> erruby_boolean:new_false(Env) 366 | end. 367 | 368 | super_class(#{properties := Properties}=_State) -> 369 | maps:get(superclass, Properties, object_class()). 370 | 371 | %TODO handle include & extend 372 | ancestors(State) -> 373 | SuperClass = super_class(State), 374 | ObjectClass = object_class(), 375 | case self() of 376 | ObjectClass -> []; 377 | _ -> [SuperClass, ObjectClass] 378 | end. 379 | 380 | find_method_in_ancestors([], _Name) -> 381 | not_found; 382 | 383 | find_method_in_ancestors(Ancestors, Name) -> 384 | [Klass | Rest] = Ancestors, 385 | Method = find_method(Klass, Name), 386 | case Method of 387 | not_found -> find_method_in_ancestors(Rest, Name); 388 | _ -> Method 389 | end. 390 | 391 | -------------------------------------------------------------------------------- /src/erruby_rb.erl: -------------------------------------------------------------------------------- 1 | %%% TODO move general runtime api to here 2 | -module(erruby_rb). 3 | -export([ret_self/1,ret_val/1,return/2]). 4 | 5 | ret_self( #{self := Self} = Env ) -> 6 | Env#{ret_val => Self}. 7 | 8 | ret_val( #{ret_val := RetVal} ) -> RetVal. 9 | return(Value, Env) -> Env#{ret_val => Value}. 10 | -------------------------------------------------------------------------------- /src/erruby_sup.erl: -------------------------------------------------------------------------------- 1 | -module(erruby_sup). 2 | 3 | -behaviour(supervisor). 4 | 5 | %% API 6 | -export([start_link/0]). 7 | 8 | %% Supervisor callbacks 9 | -export([init/1]). 10 | 11 | %% Helper macro for declaring children of supervisor 12 | -define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}). 13 | 14 | %% =================================================================== 15 | %% API functions 16 | %% =================================================================== 17 | 18 | start_link() -> 19 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 20 | 21 | %% =================================================================== 22 | %% Supervisor callbacks 23 | %% =================================================================== 24 | 25 | init([]) -> 26 | {ok, { {one_for_one, 5, 10}, []} }. 27 | 28 | -------------------------------------------------------------------------------- /src/erruby_vm.erl: -------------------------------------------------------------------------------- 1 | -module(erruby_vm). 2 | -include("rb.hrl"). 3 | -export([eval_file/2, scanl/3]). 4 | -export([new_nil/1, new_string/2]). 5 | -export([eval_method_with_exit/5, yield/2]). 6 | -export([file_name/1]). 7 | 8 | print_ast(Ast) -> 9 | erruby_debug:debug_1("Ast: ~p ~n",[Ast]). 10 | 11 | print_env(Env) -> 12 | erruby_debug:debug_1("Env: ~p ~n",[Env]). 13 | 14 | scanl(_F, Acc, []) -> 15 | [Acc]; 16 | scanl(F, Acc0, [H | T]) -> 17 | Acc = apply(F, [H, Acc0]), 18 | [Acc0 | scanl(F, Acc, T)]. 19 | 20 | eval_file(Ast, FileName) -> 21 | DefaultEnv = default_env(), 22 | Env = set_filename(DefaultEnv, FileName), 23 | eval_ast(Ast, Env). 24 | 25 | set_filename(Env, FileName) -> 26 | Env#{'FileName' => FileName}. 27 | 28 | file_name(Env) -> 29 | case maps:find('FileName', Env) of 30 | {ok, Value} -> Value; 31 | error -> 32 | file_name(find_prev_frame(Env)) 33 | end. 34 | 35 | eval_ast({ast,type,'begin',children, Children}, Env) -> 36 | erruby_debug:debug_2("eval begin~n",[]), 37 | lists:foldl(fun eval_ast/2, Env, Children); 38 | 39 | eval_ast({ast, type, self, children, []}, Env) -> 40 | #{ self := Self } = Env, 41 | erruby_rb:return(Self, Env); 42 | 43 | eval_ast({ast, type, str, children, Children}, Env) -> 44 | [SBin|_T] = Children, 45 | new_string(binary_to_list(SBin), Env); 46 | 47 | eval_ast({ast, type, nil, children, []}, Env) -> 48 | erruby_nil:new_nil(Env); 49 | 50 | eval_ast({ast, type, true, children, []}, Env) -> 51 | erruby_boolean:new_true(Env); 52 | 53 | eval_ast({ast, type, false, children, []}, Env) -> 54 | erruby_boolean:new_false(Env); 55 | 56 | eval_ast({ast, type, array, children, Args}, Env) -> 57 | {EvaledArgs, LastEnv} = eval_args(Args, Env), 58 | erruby_array:new_array(LastEnv, EvaledArgs); 59 | 60 | eval_ast({ast, type, int, children, [N]}, Env) -> 61 | erruby_fixnum:new_fixnum(Env, N); 62 | 63 | eval_ast({ast, type, '__FILE__', children, []}, #{'FileName' := Filename } = Env) -> 64 | new_string(Filename, Env); 65 | 66 | eval_ast({ast, type, csend, children, Children}, Env)-> 67 | erruby_debug:debug_1("csend~n",[]), 68 | [print_ast(Ast) || Ast <- Children], 69 | [Receiver | [Msg | Args]] = Children, 70 | ReceiverFrame = receiver_or_self(Receiver, Env), 71 | UnresolvedTarget = erruby_rb:ret_val(ReceiverFrame), 72 | Target = resolve_future(UnresolvedTarget), 73 | Nil = erruby_nil:nil_instance(), 74 | case Target of 75 | Nil -> Nil; 76 | _ -> 77 | {EvaledArgs, LastEnv} = eval_args(Args, ReceiverFrame), 78 | Method = erruby_object:find_instance_method(Target, Msg), 79 | eval_method(Target,Method, EvaledArgs, LastEnv) 80 | end; 81 | 82 | eval_ast({ast, type, psend, children, Children}, Env)-> 83 | erruby_debug:debug_1("psend~n",[]), 84 | [print_ast(Ast) || Ast <- Children], 85 | [Receiver | [Msg | Args]] = Children, 86 | ReceiverFrame = receiver_or_self(Receiver, Env), 87 | UnresolvedTarget = erruby_rb:ret_val(ReceiverFrame), 88 | Target = resolve_future(UnresolvedTarget), 89 | {EvaledArgs, LastEnv} = eval_args(Args, ReceiverFrame), 90 | Method = erruby_object:find_instance_method(Target, Msg), 91 | process_eval_method(Target,Method, EvaledArgs, LastEnv); 92 | 93 | %TODO call method using method object 94 | eval_ast({ast,type,send, children, Children}, Env) -> 95 | erruby_debug:debug_1("send~n",[]), 96 | [print_ast(Ast) || Ast <- Children], 97 | [Receiver | [Msg | Args]] = Children, 98 | ReceiverFrame = receiver_or_self(Receiver, Env), 99 | UnresolvedTarget = erruby_rb:ret_val(ReceiverFrame), 100 | Target = resolve_future(UnresolvedTarget), 101 | {EvaledArgs, LastEnv} = eval_args(Args, ReceiverFrame), 102 | Method = erruby_object:find_instance_method(Target, Msg), 103 | eval_method(Target,Method, EvaledArgs, LastEnv); 104 | 105 | eval_ast({ast, type, block, children, [Method | [Args | [Body]]]= _Children}, Env) -> 106 | Block = #{body => Body, args => Args}, 107 | BlockEnv = Env#{block => Block}, 108 | Result = eval_ast(Method, BlockEnv), 109 | Result#{block => not_exist}; 110 | 111 | eval_ast({ast, type, yield, children, Args}, Env) -> 112 | {EvaledArgs, LastEnv} = eval_args(Args, Env), 113 | yield(LastEnv, EvaledArgs); 114 | 115 | %FIXME return the value 116 | eval_ast({ast, type, lvasgn, children, Children}, Env) -> 117 | [Name, ValAst] = Children, 118 | NewEnv = eval_ast(ValAst, Env), 119 | RetVal = erruby_rb:ret_val(NewEnv), 120 | bind_lvar(Name, RetVal, NewEnv); 121 | 122 | eval_ast({ast, type, lvar, children, [Name]}, Env) -> 123 | erruby_debug:debug_1("searching lvar ~p~n in frame~p~n", [Name, Env]), 124 | #{ lvars := #{Name := Val}} = Env, 125 | erruby_rb:return(Val, Env); 126 | 127 | %FIXME return the value 128 | eval_ast({ast, type, ivasgn, children, Children}, #{self := Self}=Env) -> 129 | [Name, ValAst] = Children, 130 | NewEnv = eval_ast(ValAst, Env), 131 | RetVal = erruby_rb:ret_val(NewEnv), 132 | erruby_object:def_ivar(Self, Name, RetVal), 133 | erruby_rb:return(RetVal, Env); 134 | 135 | eval_ast({ast, type, ivar, children, [Name]}, #{self := Self}=Env) -> 136 | Val = erruby_object:find_ivar(Self, Name), 137 | erruby_rb:return(Val, Env); 138 | 139 | eval_ast({ast, type, gvasgn, children, Children}, Env) -> 140 | [Name, ValAst] = Children, 141 | NewEnv = eval_ast(ValAst, Env), 142 | RetVal = erruby_rb:ret_val(NewEnv), 143 | erruby_object:def_global_var(Name, RetVal), 144 | erruby_rb:return(Name, NewEnv); 145 | 146 | eval_ast({ast, type, gvar, children, [Name]}, Env) -> 147 | Val = erruby_object:find_global_var(Name), 148 | erruby_rb:return(Val, Env); 149 | 150 | 151 | eval_ast({ast, type, defs, children, Children}, Env) -> 152 | [ReceiverAst , Name , {ast, type, args, children, Args}, Body] = Children, 153 | ReceiverEnv = eval_ast(ReceiverAst, Env), 154 | Receiver = erruby_rb:ret_val(ReceiverEnv), 155 | erruby_object:def_singleton_method(Receiver, Name, Args, Body), 156 | new_symbol(Name, Env); 157 | 158 | 159 | eval_ast({ast, type, def, children, Children}, Env) -> 160 | [Name | [ {ast, type, args, children, Args} , Body ] ] = Children, 161 | #{ self := Self } = Env, 162 | erruby_object:def_method(Self, Name, Args, Body), 163 | new_symbol(Name, Env); 164 | 165 | %TODO figure out the Unknown field in AST 166 | %TODO impl ancestors 167 | eval_ast({ast, type, class, children, 168 | [NameAst,undefined,Body] = _Children}, #{ self := Self } = Env) -> 169 | {_,_,const,_,[_,Name]} = NameAst, 170 | NameEnv = eval_ast(NameAst,Env), 171 | ClassConst = erruby_rb:ret_val(NameEnv), 172 | Class = case ClassConst of 173 | not_found -> {ok, NewClass} = erruby_class:new_named_class(Name), 174 | erruby_object:def_const(Self, Name, NewClass), 175 | NewClass; 176 | _ -> ClassConst 177 | end, 178 | case Body of 179 | undefined -> NameEnv; 180 | _ -> 181 | NewFrame = new_frame(NameEnv, Class), 182 | ResultFrame = eval_ast(Body,NewFrame), 183 | pop_frame(ResultFrame) 184 | end; 185 | 186 | %TODO refactor with the one without SupperClass 187 | eval_ast({ast, type, class, children, 188 | [NameAst,SuperClassAst,Body] = _Children}, #{ self := Self } = Env) -> 189 | {_,_,const,_,[_,Name]} = NameAst, 190 | NameEnv = eval_ast(NameAst,Env), 191 | ClassConst = erruby_rb:ret_val(NameEnv), 192 | SuperClassEnv = eval_ast(SuperClassAst,NameEnv), 193 | SuperClassConst = erruby_rb:ret_val(SuperClassEnv), 194 | %TODO should fail when SuperClassConst is not defined 195 | Class = case ClassConst of 196 | not_found -> {ok, NewClass} = erruby_class:new_class(SuperClassConst), 197 | erruby_object:def_const(Self, Name, NewClass), 198 | NewClass; 199 | _ -> ClassConst 200 | end, 201 | case Body of 202 | undefined -> SuperClassEnv; 203 | _ -> 204 | NewFrame = new_frame(SuperClassEnv, Class), 205 | ResultFrame = eval_ast(Body,NewFrame), 206 | pop_frame(ResultFrame) 207 | end; 208 | 209 | 210 | 211 | eval_ast({ast, type, casgn, children, [ParentConstAst, Name, ValAst] }, Env) -> 212 | ParentConstEnv = parent_const_env(ParentConstAst, Env), 213 | ParentConst = erruby_rb:ret_val(ParentConstEnv), 214 | NewEnv = eval_ast(ValAst, ParentConstEnv), 215 | Val = erruby_rb:ret_val(NewEnv), 216 | erruby_object:def_const(ParentConst, Name, Val), 217 | NewEnv; 218 | 219 | %TODO throw error when not_found 220 | eval_ast({ast, type, const, children, [ParentConstAst, Name]}, Env) -> 221 | ParentConstEnv = parent_const_env(ParentConstAst, Env), 222 | ParentConst = erruby_rb:ret_val(ParentConstEnv), 223 | LocalConst = erruby_object:find_const(ParentConst, Name), 224 | Const = case LocalConst of 225 | not_found -> erruby_object:find_const(erruby_object:object_class(), Name); 226 | _ -> LocalConst 227 | end, 228 | erruby_rb:return(Const, Env); 229 | 230 | 231 | eval_ast(Ast, Env) -> 232 | erruby_debug:debug_1("Unhandled eval~n",[]), 233 | print_ast(Ast), 234 | print_env(Env). 235 | 236 | 237 | process_eval_method(Target,Method,Args,Env) -> 238 | Pid = spawn(?MODULE, eval_method_with_exit, [Target,Method,Args,Env, self()]), 239 | erruby_rb:return({future, Pid}, Env). 240 | 241 | resolve_future({future, Pid}) -> 242 | receive 243 | {future, Pid, Result} -> 244 | print_env(Result), 245 | %erruby_debug:debug_tmp("resolve_future future ~p~n", [Result]), 246 | self() ! {future, Pid, Result}, 247 | erruby_rb:ret_val(Result) 248 | end; 249 | 250 | resolve_future(Any) -> 251 | print_env(Any), 252 | %erruby_debug:debug_tmp("resolve_future Any ~p~n", [Any]), 253 | Any. 254 | 255 | eval_method_with_exit(Target,Method,Args,Env, Sender) -> 256 | try 257 | Result = eval_method(Target, Method, Args, Env), 258 | Respond = {future, self(),Result}, 259 | Sender ! Respond 260 | catch 261 | _:E -> 262 | io:format("error ~p ~n", [E]), 263 | erlang:display(erlang:get_stacktrace()) 264 | end, 265 | exit(normal). 266 | 267 | eval_method(Target,Method, UnresolvedArgs, Env) when is_function(Method) -> 268 | NewFrame = new_frame(Env,Target), 269 | Args = lists:map(fun resolve_future/1, UnresolvedArgs), 270 | MethodArgs = [NewFrame | Args], 271 | ResultFrame = apply(Method, MethodArgs), 272 | pop_frame(ResultFrame); 273 | 274 | eval_method(Target, {not_found, Name}, _, _) -> 275 | %TODO raise exception instead 276 | TargetClass = erruby_object:get_class(Target), 277 | TargetClassName = erruby_class:class_name(TargetClass), 278 | io:format("Undefined Method ~s for ~p~n", [Name, TargetClassName]), 279 | exit(known_error); 280 | 281 | eval_method(Target,#{body := Body, args := ArgNamesAst} = _Method, Args, Env) -> 282 | NewFrame = new_frame(Env,Target), 283 | ArgNames = [ArgName || {ast, type, arg, children, [ArgName]} <- ArgNamesAst], 284 | NameWithArgs = lists:zip( ArgNames, Args), 285 | NewFrameWithArgs = lists:foldl(fun ({Name, Arg}, EnvAcc) -> bind_lvar(Name, Arg, EnvAcc) end, NewFrame, NameWithArgs), 286 | pop_frame( eval_ast(Body, NewFrameWithArgs)). 287 | 288 | 289 | bind_lvar(Name, Val, #{ lvars := LVars } = Env) -> 290 | Env#{ lvars := LVars#{ Name => Val }}. 291 | 292 | receiver_or_self(undefined, Env) -> 293 | #{ self := Self } = Env, 294 | erruby_rb:return(Self, Env); 295 | receiver_or_self(Receiver, Env) -> 296 | eval_ast(Receiver,Env). 297 | 298 | 299 | new_string(String, Env) -> 300 | erruby_rb:return(String, Env). 301 | 302 | new_symbol(Symbol, Env) -> 303 | erruby_rb:return(Symbol, Env). 304 | 305 | new_frame(Env, Self) -> 306 | Env#{lvars => #{}, ret_val => not_exist, self => Self, prev_frame => Env}. 307 | 308 | new_nil(Env) -> 309 | erruby_nil:new_nil(Env). 310 | 311 | %TODO move to another place 312 | find_prev_frame(Env) -> 313 | case maps:get(prev_frame, Env, no_prev_frame) of 314 | no_prev_frame -> throw(cant_find_block); 315 | Frame -> Frame 316 | end. 317 | 318 | find_block(Env) -> 319 | case maps:get(block, Env) of 320 | not_exist -> find_block(find_prev_frame(Env)); 321 | Block -> Block 322 | end. 323 | 324 | parent_const_env(ParentConstAst, Env) -> 325 | case ParentConstAst of 326 | undefined -> erruby_rb:ret_self(Env); 327 | {ast, type, const, children, _} -> eval_ast(ParentConstAst, Env) 328 | end. 329 | 330 | eval_args(ArgAsts, Env) -> 331 | [_ |Envs] = scanl(fun eval_ast/2, Env, ArgAsts), 332 | EvaledArgs = lists:map( fun erruby_rb:ret_val/1, Envs), 333 | LastEnv = case Envs of 334 | [] -> Env; 335 | _ -> lists:last(Envs) 336 | end, 337 | {EvaledArgs, LastEnv}. 338 | 339 | 340 | yield(Env, Args)-> 341 | Block = find_block(Env), 342 | #{body := Body, args := {ast, type, args, children, ArgNamesAst}} = Block, 343 | ArgNames = [ArgName || {ast, type, arg, children, [ArgName]} <- ArgNamesAst], 344 | NameWithArgs = lists:zip( ArgNames, Args), 345 | NewFrameWithArgs = lists:foldl(fun ({Name, Arg}, EnvAcc) -> bind_lvar(Name, Arg, EnvAcc) end, Env, NameWithArgs), 346 | Result = eval_ast(Body,NewFrameWithArgs), 347 | Result. 348 | 349 | pop_frame(Frame) -> 350 | #{ret_val := RetVal, prev_frame := PrevFrame} = Frame, 351 | erruby_rb:return(RetVal, PrevFrame). 352 | 353 | default_env() -> 354 | {ok, _ObjectClass} = erruby_object:init_object_class(), 355 | {ok, _ClassClass} = erruby_class:init_class_class(), 356 | {ok, MainObject} = erruby_object:init_main_object(), 357 | init_builtin_class(), 358 | #{self => MainObject, lvars => #{}}. 359 | 360 | init_builtin_class() -> 361 | ok = erruby_nil:install_nil_class(), 362 | ok = erruby_array:install_array_classes(), 363 | ok = erruby_integer:install_integer_class(), 364 | ok = erruby_fixnum:install_fixnum_class(), 365 | ok = erruby_boolean:install_boolean_classes(), 366 | ok = erruby_file:install_file_classes(), 367 | ok. 368 | -------------------------------------------------------------------------------- /src/rb.hrl: -------------------------------------------------------------------------------- 1 | -define(RB_DEBUG_T(T),erruby_debug:debug_tmp("~s:~p~n",[??T,T])). 2 | -------------------------------------------------------------------------------- /test.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'optparse' 3 | 4 | options = {} 5 | 6 | OptionParser.new do |opts| 7 | opts.banner = "Usage: test.rb [options] test_case.rb" 8 | opts.on("-v", "--[no-]verbose", "Run verbosely") do |v| 9 | options[:verbose] = v 10 | end 11 | opts.on("-h", "--help", "Print this help") do 12 | puts(opts) 13 | exit 1 14 | end 15 | end.parse! 16 | 17 | verbose = options[:verbose] 18 | 19 | def run_single_mri_test(fn, verbose:false) 20 | basename = File.basename(fn,'.rb') 21 | outname = "rb_test/sysrb_out/#{basename}.out" 22 | puts "testing #{fn}" if verbose 23 | system("ruby #{fn} > #{outname}") 24 | system("./erruby #{fn} | diff #{outname} -") 25 | end 26 | 27 | def run_mri_tests(verbose:) 28 | fail_case = [] 29 | if ARGV[0] && File.exist?(ARGV[0]) 30 | fn = ARGV[0] 31 | basename = File.basename(fn,'.rb') 32 | test_result = run_single_mri_test(fn, verbose: verbose) 33 | fail_case << fn unless test_result 34 | else 35 | Dir.glob("rb_test/*.rb") do |fn| 36 | basename = File.basename(fn,'.rb') 37 | next if basename.start_with?("_") 38 | unless run_single_mri_test(fn, verbose: verbose) 39 | fail_case << fn 40 | end 41 | end 42 | end 43 | fail_case 44 | end 45 | 46 | fail_case = run_mri_tests(verbose: verbose) 47 | 48 | if fail_case.empty? 49 | puts "everything pass" 50 | exit 0 51 | else 52 | fail_case.each do |fn| 53 | puts "test #{fn} failed" 54 | end 55 | exit 1 56 | end 57 | --------------------------------------------------------------------------------